1
0
mirror of https://github.com/phpbb/phpbb.git synced 2025-01-16 21:58:17 +01:00
php-phpbb/doctum.phar
William Desportes 8c334f0c10
[ticket/16754] Update Doctum to 5.4.1
PHPBB3-16754
2021-04-22 02:03:21 +02:00

122342 lines
3.8 MiB
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env php
<?php
/**
* Doctum phar, generated by Doctum the 2021-04-22T01:52:41+02:00
* @see https://github.com/code-lts/doctum#readme
* @version 5.4.1
* @license MIT
*/
if (! class_exists('Phar')) {
echo 'You seem to be missing the Phar extension' . PHP_EOL;
echo 'Please read: https://stackoverflow.com/a/8851170/5155484 to find a solution' . PHP_EOL;
echo 'You can also ask for help on https://github.com/code-lts/doctum/issues if you think this is bug' . PHP_EOL;
exit(1);
}
Phar::mapPhar('doctum-5.4.1.phar');
include 'phar://' . __FILE__ . '/bin/doctum-binary.php';
__HALT_COMPILER(); ?>
Ådoctum-5.4.1.pharTa:8:{s:7:"vcs.git";s:38:"https://github.com/code-lts/doctum.git";s:11:"vcs.browser";s:34:"https://github.com/code-lts/doctum";s:7:"vcs.ref";s:40:"aba1c342d817fb06725dfe8909ca601d5cb3b47e";s:7:"version";s:5:"5.4.1";s:10:"build-date";s:25:"2021-04-22T01:52:41+02:00";s:7:"license";s:3:"MIT";s:6:"vendor";s:6:"Doctum";s:4:"name";s:6:"Doctum";} locale/en.moÜɺ€`ÜU0´ locale/fr.mo ɺ€` lM€{´src/Store/StoreInterface.php€ɺ€`€ÄòÖŠ´src/Store/JsonStore.php[ɺ€`[h•¹´src/Store/ArrayStore.php« ɺ€`« û³ß!´$src/Version/GitVersionCollection.phplɺ€`lfBq´'src/Version/SingleVersionCollection.phpɺ€`™ÓÄ ´src/Version/Version.phpµɺ€`µ#JN•´!src/Version/VersionCollection.phpŽ ɺ€`Ž ó?`<60>´src/Message.phplɺ€`ltŸè´src/Project.phppXɺ€`pXù(=—´src/Indexer.php—ɺ€`—y1r´%src/Reflection/FunctionReflection.phpLɺ€`L~Ê»+´#src/Reflection/MethodReflection.phpaɺ€`a^†
G´&src/Reflection/LazyClassReflection.phpuɺ€`uø¹§µ´&src/Reflection/ParameterReflection.php}ɺ€`}<00>,YÚ´src/Reflection/Reflection.php…)ɺ€`…)0·`M´%src/Reflection/ConstantReflection.php„ɺ€`„ž} ´"src/Reflection/ClassReflection.php&9ɺ€`&9<00>n[d´%src/Reflection/PropertyReflection.phpo ɺ€`o °AŒÊ´!src/Reflection/HintReflection.phpɺ€`l«þW´%src/Console/Command/RenderCommand.phpëɺ€`ë!gÔ3´$src/Console/Command/ParseCommand.phpâɺ€`âÔ<>´src/Console/Command/Command.phpN;ɺ€`N;Ï°$³´&src/Console/Command/VersionCommand.php?ɺ€`?9ôE ´%src/Console/Command/UpdateCommand.phpqɺ€`qÃÍðb´src/Console/Application.php@ɺ€`@Êw…´ src/Tree.php
ɺ€`
¢˜™L´src/Renderer/ThemeSet.php° ɺ€`° ¤è…N´src/Renderer/TwigExtension.php"ɺ€`"û=þX´src/Renderer/Index.phpkɺ€`kVË_Ä´src/Renderer/Theme.php…ɺ€`…¦Žª´src/Renderer/Diff.phpX ɺ€`X #貇´src/Renderer/Renderer.php´)ɺ€`´)³yÑd´src/ErrorHandler.phpɺ€`H6Éî´src/Parser/ParseError.php+ɺ€`+çà]´src/Parser/ParserContext.phpLɺ€`LÖÔÐ*´$src/Parser/ClassVisitorInterface.php•ɺ€`•²ýðs´ src/Parser/Node/DocBlockNode.phpç ɺ€`ç zZ´src/Parser/CodeParser.phpAɺ€`AŸJ`È´src/Parser/DocBlockParser.phpüɺ€`üºNw)´src/Parser/NodeVisitor.phpbɺ€`b=è´src/Parser/Parser.php<68>ɺ€`<60>9ˆ´src/Parser/ClassTraverser.phpåɺ€`åâm¢j´.src/Parser/ClassVisitor/MethodClassVisitor.php(ɺ€`(Ú•ç#´0src/Parser/ClassVisitor/PropertyClassVisitor.php/ ɺ€`/ °þj¤´2src/Parser/ClassVisitor/ViewSourceClassVisitor.phpýɺ€`ý7¥q€´2src/Parser/ClassVisitor/InheritdocClassVisitor.phpN
ɺ€`N
xcV´src/Parser/Transaction.php_ɺ€`_5g|`´#src/Parser/Filter/DefaultFilter.php~ɺ€`~·LÏF´%src/Parser/Filter/FilterInterface.phpnɺ€`nª—Ž´ src/Parser/Filter/TrueFilter.phpàɺ€`àˆmø´"src/Parser/Filter/PublicFilter.phpyɺ€`ydô´/src/RemoteRepository/GitLabRemoteRepository.phpvɺ€`v¥eà´/src/RemoteRepository/GitHubRemoteRepository.php”ɺ€`”´2src/RemoteRepository/BitBucketRemoteRepository.phpÙɺ€`Ù=z´1src/RemoteRepository/AbstractRemoteRepository.php-ɺ€`-)x˜ö´src/Doctum.php÷9ɺ€`÷9ˆ A^´-src/Resources/themes/default/fonts/doctum.ttfLɺ€`LÓý”Ù´.src/Resources/themes/default/fonts/doctum.woff4 ɺ€`4 Jž½´-src/Resources/themes/default/fonts/doctum.svgfɺ€`fgi¬´/src/Resources/themes/default/fonts/doctum.woff2Èɺ€`Ȇ.´-src/Resources/themes/default/fonts/doctum.eotìɺ€`ì=CW2´2src/Resources/themes/default/fonts/doctum-font.css°ɺ€`°â ´,src/Resources/themes/default/interfaces.twigaɺ€`aô+Ž´(src/Resources/themes/default/traits.twig
ɺ€`
}5ž´)src/Resources/themes/default/manifest.ymlɺ€`« õt´'src/Resources/themes/default/index.twig½Éº€`½}ƒ÷´+src/Resources/themes/default/doctum.js.twigM*ɺ€`M*,e6´,src/Resources/themes/default/namespaces.twigóɺ€`óï8µ´+src/Resources/themes/default/doc-index.twigɺ€`õe«ä´'src/Resources/themes/default/class.twigÓ-ɺ€`Ó-äa´(src/Resources/themes/default/search.twigP ɺ€`P §ÿt©´+src/Resources/themes/default/namespace.twigù ɺ€`ù ±<>T´(src/Resources/themes/default/macros.twigIɺ€`IÚç¿X´)src/Resources/themes/default/classes.twigYɺ€`YÔ`ü‹´/src/Resources/themes/default/layout/layout.twigô ɺ€`ô ¯½;´-src/Resources/themes/default/layout/base.twigúɺ€`úžßz´,src/Resources/themes/default/opensearch.twigƒɺ€`ƒ ·ÜÒ´8src/Resources/themes/default/css/bootstrap-theme.min.css<^ɺ€`<^‰+8´2src/Resources/themes/default/css/bootstrap.min.css´Vɺ€`´VsœJ´+src/Resources/themes/default/css/doctum.css`ɺ€``E«S´.src/Resources/themes/default/css/bootstrap.cssçɺ€`盼8y´4src/Resources/themes/default/css/bootstrap-theme.cssždɺ€`ždYã4´0src/Resources/themes/default/js/bootstrap.min.jspɺ€`p&›¶Î´8src/Resources/themes/default/js/jquery-3.5.1.slim.min.js¼ɺ€`¼Ü6Î;´0src/Resources/themes/default/js/typeahead.min.jsësɺ€`ës¸ò•.´,src/Resources/themes/default/js/bootstrap.js'ɺ€`'A>Ï´bin/doctum-binary.phpɺ€`Ãrú´bin/doctum.php\ɺ€`\yð¹Ýý2vendor/nikic/php-parser/grammar/rebuildParsers.phpš ɺ€`š ˆ‰´&vendor/nikic/php-parser/grammar/php7.yßÐɺ€`ßÐŒ˜‡´(vendor/nikic/php-parser/grammar/tokens.y
ɺ€`
v<19>¨´/vendor/nikic/php-parser/grammar/tokens.template*ɺ€`*”‰_Ö´/vendor/nikic/php-parser/grammar/parser.template ɺ€` -ÉšQ´&vendor/nikic/php-parser/grammar/php5.yœÅɺ€`œÅ_(A…´.vendor/nikic/php-parser/lib/PhpParser/Node.php‡ɺ€`‡Nm´Fvendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluationException.phpVɺ€`VÇo 5´5vendor/nikic/php-parser/lib/PhpParser/JsonDecoder.php/ ɺ€`/ ?%C<>´<vendor/nikic/php-parser/lib/PhpParser/ConstExprEvaluator.php¨#ɺ€`¨#¹>~(´@vendor/nikic/php-parser/lib/PhpParser/PrettyPrinter/Standard.php9ŸÉº€`9ŸßÁÅó´1vendor/nikic/php-parser/lib/PhpParser/Builder.phpËɺ€`Ë´6vendor/nikic/php-parser/lib/PhpParser/NodeAbstract.phpɺ€`Ê<<™´Avendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Collecting.phppɺ€`p«z|<7C>´?vendor/nikic/php-parser/lib/PhpParser/ErrorHandler/Throwing.phpjɺ€`jãgH[´4vendor/nikic/php-parser/lib/PhpParser/NodeDumper.php\ɺ€`\N涴3vendor/nikic/php-parser/lib/PhpParser/Node/Expr.php…ɺ€`…ØÒË´9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Label.phpÍɺ€`ͱºC´:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Trait_.phpéɺ€`éúXvù´<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Property.phpµ ɺ€`µ ß7´:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.phpc ɺ€`c #1E´7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Do_.php-ɺ€`-<00>Â+´9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Case_.phpWɺ€`WUåÊë´<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/GroupUse.phpíɺ€`ík´>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/InlineHTML.php‰ɺ€`‰ÍYvñ´>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Expression.phpÍɺ€`͹IªË´8vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Use_.phpXɺ€`X©Ë´Fvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation.php
ɺ€`
!L±Z´:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/UseUse.phpEɺ€`E<00>®d´Dvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/PropertyProperty.phpÈɺ€`È_úùw´<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Declare_.phpqɺ€`q½Yï!´?vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassMethod.phpHɺ€`H÷s v´8vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/For_.php)ɺ€`)je´>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Interface_.phpÍɺ€`͹Ù<C2B9>+´=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Function_.phpÿ ɺ€`ÿ #Äìú´;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ElseIf_.php4ɺ€`4Üq^k´Bvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/DeclareDeclare.php<68>ɺ€`<60>*Åç#´9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Goto_.phpóɺ€`óåׂâ´;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Static_.php°ɺ€`°Í´=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/StaticVar.phpˆɺ€`ˆ/bTS´7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/If_.php%ɺ€`%i4Ò&´9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Echo_.php<68>ɺ€`<60>Iµa¿´;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Global_.php£ɺ€`£<00>exд<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUse.phpɺ€`G¯,%´:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Const_.phpµɺ€`µ×â´<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Finally_.phpšɺ€`š|´;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Return_.php¡ɺ€`¡ŽWˆè´;vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Switch_.php ɺ€` ²ùØ´=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassLike.phpm ɺ€`m [´:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Catch_.phpiɺ€`i¦_g>´<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TryCatch.php ɺ€` Õodï´9vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Else_.phpɺ€`øáRR´>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/ClassConst.php ɺ€` vÅâ ´=vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Continue_.phpÄɺ€`ÄHW
´:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/While_.php0ɺ€`0˜]u´:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Throw_.php™ɺ€`™wתƴ:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Unset_.phpɺ€`¤É¤í´<vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Foreach_.phpYɺ€`Y‰°& ´@vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/HaltCompiler.phpòɺ€`ò<46>´>vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Namespace_.php«ɺ€`«<00>9ú„´Qvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Precedence.phpEɺ€`Ezñº´Lvendor/nikic/php-parser/lib/PhpParser/Node/Stmt/TraitUseAdaptation/Alias.php,ɺ€`,9=6à´7vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Nop.php-ɺ€`-Òuç´:vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Break_.phpµɺ€`µ‚…`s´Bvendor/nikic/php-parser/lib/PhpParser/Node/Name/FullyQualified.phpœɺ€`œ«aXÏ´<vendor/nikic/php-parser/lib/PhpParser/Node/Name/Relative.php™ɺ€`™)FCs´4vendor/nikic/php-parser/lib/PhpParser/Node/Param.phpØɺ€`ؤգŒ´5vendor/nikic/php-parser/lib/PhpParser/Node/Scalar.phpbɺ€`bÎfüÀ´3vendor/nikic/php-parser/lib/PhpParser/Node/Name.php$ɺ€`$<ÉUé´8vendor/nikic/php-parser/lib/PhpParser/Node/UnionType.php¥ɺ€`¥àŽp´=vendor/nikic/php-parser/lib/PhpParser/Node/AttributeGroup.phpˆɺ€`ˆV)´5vendor/nikic/php-parser/lib/PhpParser/Node/Const_.phpÙɺ€`ÙwyÛÉ´3vendor/nikic/php-parser/lib/PhpParser/Node/Stmt.php…ɺ€`…n©´@vendor/nikic/php-parser/lib/PhpParser/Node/VarLikeIdentifier.php÷ɺ€`÷j³F×´Hvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/EncapsedStringPart.phpÑɺ€`Ñ1y÷h´@vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst.phpMɺ€`MT!Ž´=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/LNumber.phpØɺ€`ØFJl&´>vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/Encapsed.php¼ɺ€`¼¨OsL´=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/DNumber.php6ɺ€`6>P™´=vendor/nikic/php-parser/lib/PhpParser/Node/Scalar/String_.phpɺ€` ­`î´Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Trait_.phpAɺ€`Aº·d´´Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Class_.phpAɺ€`AT z´Gvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Method.phpCɺ€`C¸~Oä´Jvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Function_.phpJɺ€`J(øde´Evendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Line.php=ɺ€`=ß ìg´Dvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Dir.php:ɺ€`:!qpð´Kvendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/Namespace_.phpMɺ€`MuŒÜë´Evendor/nikic/php-parser/lib/PhpParser/Node/Scalar/MagicConst/File.php=ɺ€`=i É´?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Instanceof_.phpDɺ€`D¹E„m´>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryMinus.php…ɺ€`… šä´8vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast.php+ɺ€`+òxŸw´>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticCall.phpgɺ€`g4‡}´>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BooleanNot.phpɺ€`ZF'´<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Include_.php˜ɺ€`˜Ž9̓´=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayItem.php]ɺ€`]‰)^´Cvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Concat.phpäɺ€`ä@-tž´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mul.phpÞɺ€`ÞFH†b´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftLeft.phpêɺ€`êÑü8´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Div.phpÞɺ€`ÞK»Ö´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseOr.phpêɺ€`êÏW!´Bvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Minus.phpâɺ€`â­¯Öa´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Mod.phpÞɺ€`Þÿ´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseXor.phpìɺ€`ìFª%´Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Plus.phpàɺ€`àŒk´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Pow.phpÞɺ€`ÞÕeдGvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/BitwiseAnd.phpìɺ€`ì4œ)Ì´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/ShiftRight.phpìɺ€`ìZÏâ´Evendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp/Coalesce.phpèɺ€`èwy´;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostDec.phpyɺ€`yëukd´<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp.phpZɺ€`ZTÏZ˜´<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignOp.phpÍɺ€`ÍÑ¿‘´>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BitwiseNot.php…ɺ€`…<00>ßF´<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/FuncCall.phpUɺ€`Uhà$ ´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Isset_.phpzɺ€`zøu\д:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Print_.phpyɺ€`yرִAvendor/nikic/php-parser/lib/PhpParser/Node/Expr/PropertyFetch.phpºɺ€`º-ÊÄ´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Assign.phpɺ€`$„Í2´;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Closure.phpz
ɺ€`z
[2—´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Yield_.phpGɺ€`GâÜú­´9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Eval_.phpvɺ€`vð }´Cvendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClassConstFetch.phpÏɺ€`ϪþÜ3´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Array_.phpɺ€`J<>ÙŠ´Ivendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafePropertyFetch.phpÔɺ€`Ô±þÄñ´>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ConstFetch.php¤ɺ€`¤ÁS„C´=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/UnaryPlus.php<68>ɺ€`<60>*ñ^u´Dvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Smaller.php7ɺ€`7… Þ¯´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalXor.php?ɺ€`?cÑá´Cvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Concat.php5ɺ€`5Ò”Ú§´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanOr.php<ɺ€`<Ž!íG´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mul.php/ɺ€`/Þõ€´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftLeft.php<ɺ€`<õø¥˜´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Div.php/ɺ€`/}bz´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseOr.php;ɺ€`;´Ëÿ´Bvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Equal.php4ɺ€`4}žuz´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BooleanAnd.php>ɺ€`>Ó.Jo´Bvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Minus.php3ɺ€`3[J´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Mod.php/ɺ€`/’“ ´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseXor.php=ɺ€`= ªÇ´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Identical.php=ɺ€`=€“{´Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Plus.php1ɺ€`1Ó€Ò/´Evendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotEqual.php:ɺ€`:ð€”Ó´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalOr.php<ɺ€`<0ëtÞ´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Pow.php0ɺ€`0ÁjH¹´Kvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/GreaterOrEqual.phpFɺ€`Faé§)´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/LogicalAnd.php?ɺ€`?¿þ{δIvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/NotIdentical.phpCɺ€`C<00>ö·´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/BitwiseAnd.php=ɺ€`=ë†Ø&´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/ShiftRight.php>ɺ€`>÷à à´Evendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Coalesce.php:ɺ€`:oRœÕ´Dvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Greater.php7ɺ€`7Á¹x´Kvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/SmallerOrEqual.phpFɺ€`Fˆ´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/BinaryOp/Spaceship.php=ɺ€`=ꑹ´Gvendor/nikic/php-parser/lib/PhpParser/Node/Expr/StaticPropertyFetch.phpɺ€`v©´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Match_.php‰ɺ€`‰µÉ 5´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreInc.phpvɺ€`vLîàû´Fvendor/nikic/php-parser/lib/PhpParser/Node/Expr/NullsafeMethodCall.phpƒɺ€`ƒh½-¦´9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Error.phpìɺ€`ì´Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrayDimFetch.php8ɺ€`8_¬È´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Empty_.phpyɺ€`yz´=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/YieldFrom.php“ɺ€`“?:À´Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/ArrowFunction.phpj ɺ€`j <00>©ÜE´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Throw_.php<68>ɺ€`<60>Faªs´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PreDec.phprɺ€`rŽèÊ´>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ClosureUse.phpqɺ€`q<1C>¡÷´9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Exit_.phpñɺ€`ñ+Ã,1´<vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Variable.php<68>ɺ€`<60>† ¹´=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/ShellExec.phpªɺ€`ªÂ,
´;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/PostInc.phpyɺ€`yHuJ/´Avendor/nikic/php-parser/lib/PhpParser/Node/Expr/ErrorSuppress.php<68>ɺ€`<60> 6°Ã´9vendor/nikic/php-parser/lib/PhpParser/Node/Expr/List_.phpÑɺ€`ÑÒF*ž´:vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Clone_.phpvɺ€`vÇÃÉ[´;vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Ternary.phpÙɺ€`Ù6Åõ÷´>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Bool_.phpÑɺ€`Ñ à»´=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Int_.phpÏɺ€`Ïuhö ´?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Double.php}ɺ€`}ûÇpn´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Object_.phpÕɺ€`Õ÷ížã´?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Array_.phpÓɺ€`Óg,Gd´@vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/String_.phpÕɺ€`Õ Ù¹´?vendor/nikic/php-parser/lib/PhpParser/Node/Expr/Cast/Unset_.phpÓɺ€`ÓÙ2û‰´=vendor/nikic/php-parser/lib/PhpParser/Node/Expr/AssignRef.php3ɺ€`3Ÿs©i´8vendor/nikic/php-parser/lib/PhpParser/Node/Expr/New_.phpµɺ€`µüSR¦´>vendor/nikic/php-parser/lib/PhpParser/Node/Expr/MethodCall.phplɺ€`lxÀøF´7vendor/nikic/php-parser/lib/PhpParser/Node/MatchArm.php<68>ɺ€`<60>/„m´9vendor/nikic/php-parser/lib/PhpParser/Node/Identifier.phpéɺ€`é£!<21>$´;vendor/nikic/php-parser/lib/PhpParser/Node/NullableType.phpçɺ€`çž”ÛØ´8vendor/nikic/php-parser/lib/PhpParser/Node/Attribute.php(ɺ€`(OiÙ´;vendor/nikic/php-parser/lib/PhpParser/Node/FunctionLike.phpüɺ€`üS5$D´2vendor/nikic/php-parser/lib/PhpParser/Node/Arg.phpÿɺ€`ÿõ¦¼ð´;vendor/nikic/php-parser/lib/PhpParser/Internal/DiffElem.php,ɺ€`,ðjÌF´9vendor/nikic/php-parser/lib/PhpParser/Internal/Differ.php:ɺ€`:Ú_¨´>vendor/nikic/php-parser/lib/PhpParser/Internal/TokenStream.phpK#ɺ€`K#Y•ß³´Lvendor/nikic/php-parser/lib/PhpParser/Internal/PrintableNewAnonClassNode.php7ɺ€`7 ´/vendor/nikic/php-parser/lib/PhpParser/Lexer.phpiXɺ€`iXͦe´7vendor/nikic/php-parser/lib/PhpParser/ParserFactory.phpNɺ€`Nÿupš´5vendor/nikic/php-parser/lib/PhpParser/NodeVisitor.phpêɺ€`êZM¹Í´8vendor/nikic/php-parser/lib/PhpParser/Builder/Trait_.phpöɺ€`ö±T@B´:vendor/nikic/php-parser/lib/PhpParser/Builder/Property.php¥ ɺ€`¥ §Ié´8vendor/nikic/php-parser/lib/PhpParser/Builder/Class_.phpN ɺ€`N FÛ´8vendor/nikic/php-parser/lib/PhpParser/Builder/Method.phpÚ Éº€`Ú Db„á´6vendor/nikic/php-parser/lib/PhpParser/Builder/Use_.php¿ɺ€`¿Ë£Ù´7vendor/nikic/php-parser/lib/PhpParser/Builder/Param.php» ɺ€`» }§?È´Dvendor/nikic/php-parser/lib/PhpParser/Builder/TraitUseAdaptation.phpXɺ€`X¨¯zâ´=vendor/nikic/php-parser/lib/PhpParser/Builder/Declaration.phpØɺ€`Ø­<>ÔF´<vendor/nikic/php-parser/lib/PhpParser/Builder/Interface_.phpÍɺ€`Í¥„!´;vendor/nikic/php-parser/lib/PhpParser/Builder/Function_.php<68>ɺ€`<60>3`Êm´:vendor/nikic/php-parser/lib/PhpParser/Builder/TraitUse.php%ɺ€`%êjB´<vendor/nikic/php-parser/lib/PhpParser/Builder/Namespace_.phpÿɺ€`ÿûbÀ!´>vendor/nikic/php-parser/lib/PhpParser/Builder/FunctionLike.phpHɺ€`H•ó›;´5vendor/nikic/php-parser/lib/PhpParser/Comment/Doc.phpgɺ€`gþóÑÑ´4vendor/nikic/php-parser/lib/PhpParser/NodeFinder.php” ɺ€`” 8MH´?vendor/nikic/php-parser/lib/PhpParser/PrettyPrinterAbstract.phpêɺ€`ê´Kvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NodeConnectingVisitor.phppɺ€`p½Õ1¼´Dvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/CloningVisitor.phpöɺ€`ö­zÂa´Dvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FindingVisitor.phpbɺ€`bhúÅ´Ivendor/nikic/php-parser/lib/PhpParser/NodeVisitor/FirstFindingVisitor.phpÒɺ€`Òz¥JB´Bvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/NameResolver.phpÉ#ɺ€`É#Ý<ý¼´Mvendor/nikic/php-parser/lib/PhpParser/NodeVisitor/ParentConnectingVisitor.phpaɺ€`a¬¢´7vendor/nikic/php-parser/lib/PhpParser/NodeTraverser.php`)ɺ€``)<-Av´@vendor/nikic/php-parser/lib/PhpParser/NodeTraverserInterface.phpuɺ€`uÅ´8vendor/nikic/php-parser/lib/PhpParser/BuilderHelpers.phpº#ɺ€`º#åŠëK´0vendor/nikic/php-parser/lib/PhpParser/Parser.phptɺ€`tTíﺴ1vendor/nikic/php-parser/lib/PhpParser/Comment.phpzɺ€`z]5Þ+´/vendor/nikic/php-parser/lib/PhpParser/Error.php²ɺ€`²#ú¦€´6vendor/nikic/php-parser/lib/PhpParser/ErrorHandler.php&ɺ€`&ßèE´7vendor/nikic/php-parser/lib/PhpParser/Parser/Tokens.phpxɺ€`xáƒñ¤´5vendor/nikic/php-parser/lib/PhpParser/Parser/Php5.phpe|ɺ€`e|#Z &´5vendor/nikic/php-parser/lib/PhpParser/Parser/Php7.php\‰ɺ€`\‰ Ä?J´9vendor/nikic/php-parser/lib/PhpParser/Parser/Multiple.phptɺ€`tOˆ´8vendor/nikic/php-parser/lib/PhpParser/BuilderFactory.php²%ɺ€`²%s,å´Pvendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/MatchTokenEmulator.php¥ɺ€`¥ÞÄŽ´]vendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NumericLiteralSeparatorEmulator.phpFɺ€`FZ´Kvendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/TokenEmulator.phpgɺ€`gc´Mvendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/ReverseEmulator.phpÇɺ€`ǯÃy´Ovendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/AttributeEmulator.phpñɺ€`ñ<00><>£Ü´Wvendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/CoaleseEqualTokenEmulator.php#ɺ€`#E<>Js´Mvendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FnTokenEmulator.phpɺ€`Éœ
´Wvendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/FlexibleDocStringEmulator.phpW ɺ€`W .——n´Mvendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/KeywordEmulator.php„ɺ€`„2ëŽW´Svendor/nikic/php-parser/lib/PhpParser/Lexer/TokenEmulator/NullsafeTokenEmulator.php¥ɺ€`¥;.6$´9vendor/nikic/php-parser/lib/PhpParser/Lexer/Emulative.php³!ɺ€`³!“tì´5vendor/nikic/php-parser/lib/PhpParser/NameContext.phpÃ&ɺ€`Ã&º­´8vendor/nikic/php-parser/lib/PhpParser/ParserAbstract.phpVŸÉº€`VŸZf´=vendor/nikic/php-parser/lib/PhpParser/NodeVisitorAbstract.php¶ɺ€`¶ßÄîX´vendor/nikic/php-parser/LICENSEðɺ€`ð¥ä”*´vendor/composer/installed.phpwɺ€`w%Óñ.´%vendor/composer/InstalledVersions.phpg)ɺ€`g)ï™"c´#vendor/composer/autoload_static.php5ɺ€`5<52>´'vendor/composer/autoload_namespaces.phpÑɺ€`Ñ¢uð!´vendor/composer/installed.jsonOɺ€`OR¾i´"vendor/composer/platform_check.phpžɺ€`žô*aC´!vendor/composer/autoload_psr4.php)ɺ€`)4Õ"g´!vendor/composer/autoload_real.phpõ ɺ€`õ ¬ÿÿ´%vendor/composer/autoload_classmap.php*ɺ€`*ÜÅi­´vendor/composer/LICENSE.ɺ€`. Õ´"vendor/composer/autoload_files.php­ɺ€`­K¿>/´vendor/composer/ClassLoader.php8ɺ€`8l=ú-´'vendor/symfony/polyfill-php73/Php73.phpbɺ€`bJš<´+vendor/symfony/polyfill-php73/bootstrap.phpóɺ€`ó€Ò|´%vendor/symfony/polyfill-php73/LICENSE)ɺ€`)´`e0´?vendor/symfony/polyfill-php73/Resources/stubs/JsonException.phpɺ€`<F´/vendor/symfony/console/Logger/ConsoleLogger.phpÐɺ€`Ði<>л´2vendor/symfony/console/Question/ChoiceQuestion.php¶ɺ€`¶¦¼vr´8vendor/symfony/console/Question/ConfirmationQuestion.phpɺ€`?uôÈ´,vendor/symfony/console/Question/Question.php±ɺ€`±î´3vendor/symfony/console/SingleCommandApplication.php¬ɺ€`¬õ””¹´(vendor/symfony/console/ConsoleEvents.php~ɺ€`~~´2vendor/symfony/console/Event/ConsoleErrorEvent.phpóɺ€`óªÄa”´3vendor/symfony/console/Event/ConsoleSignalEvent.php<68>ɺ€`<60>b×´-vendor/symfony/console/Event/ConsoleEvent.phpÄɺ€`Äöz±)´4vendor/symfony/console/Event/ConsoleCommandEvent.phpÇɺ€`Ç<13>´6vendor/symfony/console/Event/ConsoleTerminateEvent.php"ɺ€`"†q…œ´ vendor/symfony/console/Color.phpåɺ€`ååPnÙ´.vendor/symfony/console/Input/InputArgument.phpc ɺ€`c ñ
k´+vendor/symfony/console/Input/ArrayInput.php®ɺ€`®ë#øœ´*vendor/symfony/console/Input/ArgvInput.php€.ɺ€`€.ˆR´&vendor/symfony/console/Input/Input.phpWɺ€`W%ôl´4vendor/symfony/console/Input/InputAwareInterface.php:ɺ€`:± '´´,vendor/symfony/console/Input/StringInput.phpõɺ€`õJ‰E<E280B0>´,vendor/symfony/console/Input/InputOption.php¡ɺ€`¡w}´9vendor/symfony/console/Input/StreamableInputInterface.phpiɺ€`iõ™í´0vendor/symfony/console/Input/InputDefinition.phpK*ɺ€`K*¾éàA´/vendor/symfony/console/Input/InputInterface.php<68>ɺ€`<60>K ¿´=vendor/symfony/console/Formatter/NullOutputFormatterStyle.php¢ɺ€`¢¥øØ-´8vendor/symfony/console/Formatter/NullOutputFormatter.phpŽɺ€`Ž€·²@´Fvendor/symfony/console/Formatter/WrappableOutputFormatterInterface.php­ɺ€`­}z•ï´=vendor/symfony/console/Formatter/OutputFormatterInterface.php>ɺ€`>¢ãYÀ´4vendor/symfony/console/Formatter/OutputFormatter.phpñɺ€`ñÓ à´9vendor/symfony/console/Formatter/OutputFormatterStyle.phpP ɺ€`P pbd´Bvendor/symfony/console/Formatter/OutputFormatterStyleInterface.phpZɺ€`ZsÊÄð´>vendor/symfony/console/Formatter/OutputFormatterStyleStack.phpý ɺ€`ý ;2ü´=vendor/symfony/console/CommandLoader/FactoryCommandLoader.phpEɺ€`E=H¾·´?vendor/symfony/console/CommandLoader/CommandLoaderInterface.phpjɺ€`jw£´?vendor/symfony/console/CommandLoader/ContainerCommandLoader.phpâɺ€`⇅0w´3vendor/symfony/console/Tester/ApplicationTester.phpɺ€`©¸C´-vendor/symfony/console/Tester/TesterTrait.phpoɺ€`o•¿´/vendor/symfony/console/Tester/CommandTester.phpg ɺ€`g xí3 ´Dvendor/symfony/console/DependencyInjection/AddConsoleCommandPass.phpªɺ€`ª<00>®÷D´0vendor/symfony/console/Command/LockableTrait.phpžɺ€`žÜ«Ç’´*vendor/symfony/console/Command/Command.phpóJɺ€`óJÈ$ôV´=vendor/symfony/console/Command/SignalableCommandInterface.phpªɺ€`ªGeæM´.vendor/symfony/console/Command/ListCommand.php½ɺ€`½äÈFs´.vendor/symfony/console/Command/HelpCommand.phpI ɺ€`I  œã´0vendor/symfony/console/Output/BufferedOutput.phpUɺ€`UBE$Þ´1vendor/symfony/console/Output/OutputInterface.phpÔ Éº€`Ô èýöL´5vendor/symfony/console/Output/TrimmedBufferOutput.phpQɺ€`QÙ¬”.´/vendor/symfony/console/Output/ConsoleOutput.phpòɺ€`òC·¹X´6vendor/symfony/console/Output/ConsoleSectionOutput.phpFɺ€`FH :´8vendor/symfony/console/Output/ConsoleOutputInterface.php ɺ€` ´,vendor/symfony/console/Output/NullOutput.php ɺ€` í¶Ð´.vendor/symfony/console/Output/StreamOutput.phpÔ Éº€`Ô BŽM´(vendor/symfony/console/Output/Output.phpLɺ€`L­ÛJ´&vendor/symfony/console/Application.phpx§Éº€`x§Þ´-vendor/symfony/console/Style/SymfonyStyle.phpª8ɺ€`ª8$¾­È´/vendor/symfony/console/Style/StyleInterface.phpN
ɺ€`N
Ǽ£1´,vendor/symfony/console/Style/OutputStyle.phpþ ɺ€`þ ¤‘·ÿ´8vendor/symfony/console/SignalRegistry/SignalRegistry.php0ɺ€`0<00>6p[´0vendor/symfony/console/Helper/TableCellStyle.php²ɺ€`²Xõ´'vendor/symfony/console/Helper/Table.php1nɺ€`1n‡Ìô}´+vendor/symfony/console/Helper/HelperSet.phpÜɺ€`Ü O!´0vendor/symfony/console/Helper/TableSeparator.phpɺ€`&Šª ´2vendor/symfony/console/Helper/InputAwareHelper.phpëɺ€`ë¼<>´(vendor/symfony/console/Helper/Dumper.phpèɺ€`è+'šf´/vendor/symfony/console/Helper/ProcessHelper.phpÞɺ€`ÞJ^˜´2vendor/symfony/console/Helper/DescriptorHelper.phpX ɺ€`X ƒÂW¬´-vendor/symfony/console/Helper/ProgressBar.phpÌFɺ€`ÌF»W38´,vendor/symfony/console/Helper/TableStyle.phpé0ɺ€`é0@/®´+vendor/symfony/console/Helper/TableRows.phpUɺ€`U$Öÿ´1vendor/symfony/console/Helper/FormatterHelper.php… ɺ€`… ºÆr­´1vendor/symfony/console/Helper/HelperInterface.phppɺ€`pàñn´0vendor/symfony/console/Helper/QuestionHelper.php0Hɺ€`0H(A­´(vendor/symfony/console/Helper/Helper.php<68>ɺ€`<60>º´6vendor/symfony/console/Helper/DebugFormatterHelper.phpG ɺ€`G ­©´7vendor/symfony/console/Helper/SymfonyQuestionHelper.phpÓ Éº€`Ó Œ*ä-´3vendor/symfony/console/Helper/ProgressIndicator.phpßɺ€`ßôÏ´+vendor/symfony/console/Helper/TableCell.phpɺ€`Í.´6vendor/symfony/console/EventListener/ErrorListener.php ɺ€` \q%;´0vendor/symfony/console/Descriptor/Descriptor.php/ ɺ€`/ MgÞ´´4vendor/symfony/console/Descriptor/JsonDescriptor.php3ɺ€`3~V¼´<vendor/symfony/console/Descriptor/ApplicationDescription.php¿ɺ€`¿Wf(°´9vendor/symfony/console/Descriptor/DescriptorInterface.phpƒɺ€`ƒPÚZö´4vendor/symfony/console/Descriptor/TextDescriptor.php•0ɺ€`•0ì.¯´8vendor/symfony/console/Descriptor/MarkdownDescriptor.phpâɺ€`â9¾ð´3vendor/symfony/console/Descriptor/XmlDescriptor.phpñ"ɺ€`ñ"!¾â`´?vendor/symfony/console/Exception/NamespaceNotFoundException.phpÚɺ€`ÚBL»H´7vendor/symfony/console/Exception/ExceptionInterface.php©ɺ€`©âló¸´:vendor/symfony/console/Exception/MissingInputException.phpîɺ€`îQÊg;´3vendor/symfony/console/Exception/LogicException.phpªɺ€`ªSML<4D>´=vendor/symfony/console/Exception/CommandNotFoundException.phpßɺ€`ß<00>RÐô´;vendor/symfony/console/Exception/InvalidOptionException.phpüɺ€`üí“;´5vendor/symfony/console/Exception/RuntimeException.php®ɺ€`®¶*b´=vendor/symfony/console/Exception/InvalidArgumentException.php¾ɺ€`¾îu i´!vendor/symfony/console/Cursor.php)ɺ€`)i¶Ö.´vendor/symfony/console/LICENSE)ɺ€`)“ ­÷´#vendor/symfony/console/Terminal.php‡ɺ€`‡Zш´4vendor/symfony/console/Resources/bin/hiddeninput.exe$ɺ€`$<>¥v´vendor/symfony/finder/Glob.php$ɺ€`$àt¯Ú´:vendor/symfony/finder/Iterator/SizeRangeFilterIterator.phpƒɺ€`ƒŠ=Ì´Avendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php» ɺ€`» »ñza´5vendor/symfony/finder/Iterator/PathFilterIterator.php²ɺ€`²2…´=vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php® ɺ€`® ¸<>½g´=vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.phpQɺ€`Q|@u´9vendor/symfony/finder/Iterator/FileTypeFilterIterator.phpQɺ€`Q+ù¢f´:vendor/symfony/finder/Iterator/DateRangeFilterIterator.php¦ɺ€`¦ƒ­ò)´<vendor/symfony/finder/Iterator/FilecontentFilterIterator.php­ɺ€`­œæ™,´/vendor/symfony/finder/Iterator/LazyIterator.php ɺ€` ²Ù‘´3vendor/symfony/finder/Iterator/SortableIterator.phpÀɺ€`À]¼·H´7vendor/symfony/finder/Iterator/CustomFilterIterator.phpÙɺ€`Ùd)´9vendor/symfony/finder/Iterator/FilenameFilterIterator.phpœɺ€`œ¾£4 ´;vendor/symfony/finder/Iterator/DepthRangeFilterIterator.phpÕɺ€`Õ¸À–Õ´ vendor/symfony/finder/Finder.phpYɺ€`Y[ªA´5vendor/symfony/finder/Comparator/NumberComparator.php
ɺ€`
<>¡´/vendor/symfony/finder/Comparator/Comparator.php}ɺ€`}uÁ§¡´3vendor/symfony/finder/Comparator/DateComparator.phpɺ€` |´%vendor/symfony/finder/SplFileInfo.phpɺ€`¹b¥´#vendor/symfony/finder/Gitignore.phpúɺ€`ú¤\úÉ´>vendor/symfony/finder/Exception/DirectoryNotFoundException.php£ɺ€`£ßRI´9vendor/symfony/finder/Exception/AccessDeniedException.php«ɺ€`«ÊcWÞ´vendor/symfony/finder/LICENSE)ɺ€`)“ ­÷´-vendor/symfony/polyfill-mbstring/Mbstring.phpYqɺ€`YqÃ=Ws´.vendor/symfony/polyfill-mbstring/bootstrap.php.ɺ€`.6Œ·Õ´0vendor/symfony/polyfill-mbstring/bootstrap80.phpB"ɺ€`B"cP!“´(vendor/symfony/polyfill-mbstring/LICENSE)ɺ€`)“\ª´@vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.phpÙ_ɺ€`Ù_Z¶ŸŠ´Fvendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php9ɺ€`9>|zK´@vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.phpò`ɺ€`ò`ŠSéý´+vendor/symfony/polyfill-php80/bootstrap.phpÒɺ€`Òõ(ï`´'vendor/symfony/polyfill-php80/Php80.php2 ɺ€`2 M D´%vendor/symfony/polyfill-php80/LICENSE$ɺ€`$LO!
´Evendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php3ɺ€`3y4%´<vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php*ɺ€`*¥…ñ*´<vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php¥Éº€`¥þ9´;vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php ɺ€` &Úƒò´'vendor/symfony/polyfill-ctype/Ctype.php}ɺ€`}\Šè°´+vendor/symfony/polyfill-ctype/bootstrap.php@ɺ€`@j×Q9´-vendor/symfony/polyfill-ctype/bootstrap80.phprɺ€`rßF)ƒ´%vendor/symfony/polyfill-ctype/LICENSE)ɺ€`)´`e0´?vendor/symfony/service-contracts/ServiceSubscriberInterface.phpÉɺ€`ÉáB§6´=vendor/symfony/service-contracts/ServiceProviderInterface.phpºɺ€`º­mX´7vendor/symfony/service-contracts/Attribute/Required.phpɺ€`e;Z´3vendor/symfony/service-contracts/ResetInterface.phpçɺ€`çµvú´<vendor/symfony/service-contracts/Test/ServiceLocatorTest.php[ ɺ€`[ q¾)6´;vendor/symfony/service-contracts/ServiceSubscriberTrait.phpGɺ€`GÉóØ´8vendor/symfony/service-contracts/ServiceLocatorTrait.php¼ɺ€`¼za ´(vendor/symfony/service-contracts/LICENSE)ɺ€`)Ǩ7´/vendor/symfony/process/Pipes/PipesInterface.phpØɺ€`اí´*vendor/symfony/process/Pipes/UnixPipes.php?ɺ€`?L…ð´-vendor/symfony/process/Pipes/WindowsPipes.phpɺ€`½—Ü–´.vendor/symfony/process/Pipes/AbstractPipes.phpŠɺ€`ŠÌYcA´%vendor/symfony/process/PhpProcess.phpž ɺ€`ž âÚR´&vendor/symfony/process/InputStream.php ɺ€` Í$fô.vendor/symfony/process/PhpExecutableFinder.php
ɺ€`
ˆX´7vendor/symfony/process/Exception/ExceptionInterface.phpÂɺ€`ÂÑçÝ+´=vendor/symfony/process/Exception/ProcessSignaledException.php±ɺ€`±¾°äò´3vendor/symfony/process/Exception/LogicException.phpÒɺ€`Ò°¨W´;vendor/symfony/process/Exception/ProcessFailedException.php<68>ɺ€`<60>´5´=vendor/symfony/process/Exception/ProcessTimedOutException.php<68>ɺ€`<60>{0˜´5vendor/symfony/process/Exception/RuntimeException.phpáɺ€`á>H´=vendor/symfony/process/Exception/InvalidArgumentException.phpðɺ€`ðË…<C38B>ë´vendor/symfony/process/LICENSE)ɺ€`)“ ­÷´'vendor/symfony/process/ProcessUtils.phpMɺ€`M|¡%»´"vendor/symfony/process/Process.phpåÏɺ€`åÏ´£oÓ´+vendor/symfony/process/ExecutableFinder.php
ɺ€`
km°:´1vendor/symfony/deprecation-contracts/function.phpðɺ€`ðrgòá´,vendor/symfony/deprecation-contracts/LICENSE)ɺ€`)8½´2vendor/symfony/polyfill-intl-grapheme/Grapheme.php«#ɺ€`«#woŠ´3vendor/symfony/polyfill-intl-grapheme/bootstrap.phpåɺ€`åúâ´5vendor/symfony/polyfill-intl-grapheme/bootstrap80.phpm
ɺ€`m
Z¾ö´-vendor/symfony/polyfill-intl-grapheme/LICENSE)ɺ€`)“\ª´/vendor/symfony/string/AbstractUnicodeString.phpõfɺ€`õf0]¼"´4vendor/symfony/string/Inflector/EnglishInflector.php²:ɺ€`²:? ¥”´6vendor/symfony/string/Inflector/InflectorInterface.php‰ɺ€`‰7¾6<C2BE>´3vendor/symfony/string/Inflector/FrenchInflector.php<68>ɺ€`<60>é i…´)vendor/symfony/string/CodePointString.php{ɺ€`{n^t´(vendor/symfony/string/AbstractString.phpÅGɺ€`ÅGü"ñg´'vendor/symfony/string/UnicodeString.phpC2ɺ€`C2j8<6A>¥´$vendor/symfony/string/ByteString.phpÐ;ɺ€`Ð;ßM-Ö´$vendor/symfony/string/LazyString.php)ɺ€`)þI!´6vendor/symfony/string/Exception/ExceptionInterface.phpQɺ€`Q$‘áÕ´4vendor/symfony/string/Exception/RuntimeException.phppɺ€`pâ0Ê´<vendor/symfony/string/Exception/InvalidArgumentException.php€ɺ€`€e¯¯<C2AF>´vendor/symfony/string/LICENSE)ɺ€`)Ïm,c´.vendor/symfony/string/Slugger/AsciiSlugger.php·ɺ€`·™œ0=´2vendor/symfony/string/Slugger/SluggerInterface.phpËɺ€`Ë"ÌO¹´-vendor/symfony/string/Resources/functions.php]ɺ€`]ÂÛ<7F>´<vendor/symfony/string/Resources/data/wcswidth_table_wide.php™/ɺ€`™/€½cð´<vendor/symfony/string/Resources/data/wcswidth_table_zero.phpK7ɺ€`K7Ï”¯•´'vendor/symfony/yaml/Tag/TaggedValue.phpÅɺ€`ÅnÃß%´vendor/symfony/yaml/Yaml.php ɺ€` ijá½´+vendor/symfony/yaml/Command/LintCommand.php¶ ɺ€`¶ \y®©´vendor/symfony/yaml/Dumper.php¿ɺ€`¿
€ct´!vendor/symfony/yaml/Unescaper.php)ɺ€`)WY&X´vendor/symfony/yaml/Parser.php?Éɺ€`?Éö®n´vendor/symfony/yaml/Inline.phpɺ€`Þ%æT´4vendor/symfony/yaml/Exception/ExceptionInterface.phpÎɺ€`ÎB9´/vendor/symfony/yaml/Exception/DumpException.phpÇɺ€`ÇŒ´0vendor/symfony/yaml/Exception/ParseException.php© ɺ€`© :—ü´2vendor/symfony/yaml/Exception/RuntimeException.phpåɺ€`åô_q¦´vendor/symfony/yaml/LICENSE)ɺ€`)“ ­÷´vendor/symfony/yaml/Escaper.phpùɺ€`ùøNâ´(vendor/symfony/filesystem/Filesystem.phpÔoɺ€`Ôo➂δ3vendor/symfony/filesystem/Exception/IOException.php¼ɺ€`¼H´:vendor/symfony/filesystem/Exception/ExceptionInterface.phpÐɺ€`Ð nßj´=vendor/symfony/filesystem/Exception/FileNotFoundException.phpÉɺ€`É ´´<vendor/symfony/filesystem/Exception/IOExceptionInterface.php¦ɺ€`¦¨™i”´@vendor/symfony/filesystem/Exception/InvalidArgumentException.phpÏɺ€`ÏÐ*“Š´!vendor/symfony/filesystem/LICENSE)ɺ€`)“ ­÷´5vendor/symfony/polyfill-intl-normalizer/bootstrap.phpÕɺ€`Õ#p« ´7vendor/symfony/polyfill-intl-normalizer/bootstrap80.phpÏɺ€`Ïì,È´/vendor/symfony/polyfill-intl-normalizer/LICENSE)ɺ€`)“\ª´6vendor/symfony/polyfill-intl-normalizer/Normalizer.phpc%ɺ€`c%r7xã´Fvendor/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php”ɺ€`”ŽÏÇ%´Tvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalDecomposition.php{˜Éº€`{˜²jeº´Xvendor/symfony/polyfill-intl-normalizer/Resources/unidata/compatibilityDecomposition.phpoɺ€`ocã,½´Rvendor/symfony/polyfill-intl-normalizer/Resources/unidata/canonicalComposition.phpãDɺ€`ãD'×´Lvendor/symfony/polyfill-intl-normalizer/Resources/unidata/combiningClass.phpD5ɺ€`D5<00>€ý ´vendor/autoload.php²Éº€`²€è×´4vendor/wdes/php-i18n-l10n/src/plugins/BasePlugin.php: ɺ€`: Øì¦>´2vendor/wdes/php-i18n-l10n/src/plugins/MoReader.php?/ɺ€`?/Ý®½,´*vendor/wdes/php-i18n-l10n/src/Launcher.php ɺ€` ¦€;´5vendor/wdes/php-i18n-l10n/src/Twig/Extension/I18n.phpJɺ€`JŸ<>žÌ´2vendor/wdes/php-i18n-l10n/src/Twig/MemoryCache.php ɺ€` ê±a¾´6vendor/wdes/php-i18n-l10n/src/Twig/TranslationNode.phpŠɺ€`ŠÜå{_´2vendor/wdes/php-i18n-l10n/src/Twig/TokenParser.php¾ɺ€`¾ÑÖÍ´!vendor/wdes/php-i18n-l10n/LICENSEUAɺ€`UAi0´<vendor/phpdocumentor/type-resolver/src/PseudoTypes/True_.phpoɺ€`o½)¢x´=vendor/phpdocumentor/type-resolver/src/PseudoTypes/False_.phpsɺ€`s"WA´/vendor/phpdocumentor/type-resolver/src/Type.phpÕɺ€`Õ28)´5vendor/phpdocumentor/type-resolver/src/PseudoType.phpnɺ€`nÒŸ½´8vendor/phpdocumentor/type-resolver/src/FqsenResolver.phpùɺ€`ùG6xþ´8vendor/phpdocumentor/type-resolver/src/Types/Integer.phpbɺ€`bÅôûÍ´5vendor/phpdocumentor/type-resolver/src/Types/This.phpKɺ€`Kå=Æ´;vendor/phpdocumentor/type-resolver/src/Types/Collection.php<68>ɺ€`<60>9:´;vendor/phpdocumentor/type-resolver/src/Types/Expression.php-ɺ€`-tÝV@´:vendor/phpdocumentor/type-resolver/src/Types/Resource_.phpqɺ€`qÐ9¡Š´=vendor/phpdocumentor/type-resolver/src/Types/AbstractList.php|ɺ€`|¢ž„è´7vendor/phpdocumentor/type-resolver/src/Types/Scalar.php¦ɺ€`¦6Á´:vendor/phpdocumentor/type-resolver/src/Types/Iterable_.php:ɺ€`:"PB®´8vendor/phpdocumentor/type-resolver/src/Types/Context.phpô ɺ€`ô >À@´8vendor/phpdocumentor/type-resolver/src/Types/Boolean.php`ɺ€``~Te|´=vendor/phpdocumentor/type-resolver/src/Types/Intersection.phpɺ€`z®<>´?vendor/phpdocumentor/type-resolver/src/Types/AggregatedType.phpÐ
ɺ€`Ð
`!çÞ´8vendor/phpdocumentor/type-resolver/src/Types/Static_.php÷ɺ€`÷›æÁ´7vendor/phpdocumentor/type-resolver/src/Types/Float_.php_ɺ€`_üg´<vendor/phpdocumentor/type-resolver/src/Types/ClassString.php”ɺ€`”üUx´8vendor/phpdocumentor/type-resolver/src/Types/Object_.phpɺ€`ª^²´7vendor/phpdocumentor/type-resolver/src/Types/Array_.phpÔɺ€`ÔÇØ]Ö´?vendor/phpdocumentor/type-resolver/src/Types/ContextFactory.phpj:ɺ€`j:Â-bM´8vendor/phpdocumentor/type-resolver/src/Types/String_.phpkɺ€`k/<…s´6vendor/phpdocumentor/type-resolver/src/Types/Self_.php¾ɺ€`¾ñ‡Ù¬´8vendor/phpdocumentor/type-resolver/src/Types/Parent_.phpÖɺ€`ÖƒŽ¥ ´9vendor/phpdocumentor/type-resolver/src/Types/Compound.phpùɺ€`ù´ ݲ´9vendor/phpdocumentor/type-resolver/src/Types/Nullable.phpGɺ€`G"B1e´:vendor/phpdocumentor/type-resolver/src/Types/Callable_.phpmɺ€`m$&ð´7vendor/phpdocumentor/type-resolver/src/Types/Mixed_.phprɺ€`r‡ŸÆ´6vendor/phpdocumentor/type-resolver/src/Types/Null_.phpjɺ€`j?“¸*´6vendor/phpdocumentor/type-resolver/src/Types/Void_.phpùɺ€`ù¹´7vendor/phpdocumentor/type-resolver/src/TypeResolver.php7Hɺ€`7HrTPÒ´*vendor/phpdocumentor/type-resolver/LICENSE8ɺ€`8á‰Ê´=vendor/phpdocumentor/reflection-common/src/ProjectFactory.phpXɺ€`XÝ´6vendor/phpdocumentor/reflection-common/src/Project.phpúɺ€`ú079´4vendor/phpdocumentor/reflection-common/src/Fqsen.phpåɺ€`å^LÕ´7vendor/phpdocumentor/reflection-common/src/Location.phpŽɺ€`ŽpȘ´6vendor/phpdocumentor/reflection-common/src/Element.phpɺ€`ÜkDk´3vendor/phpdocumentor/reflection-common/src/File.phpšɺ€`š.¾Žo´.vendor/phpdocumentor/reflection-common/LICENSE9ɺ€`9*2È<32>´Ivendor/phpdocumentor/reflection-docblock/src/DocBlockFactoryInterface.phpwɺ€`w§÷«`´9vendor/phpdocumentor/reflection-docblock/src/DocBlock.php{ɺ€`{/¾qH´@vendor/phpdocumentor/reflection-docblock/src/DocBlockFactory.phpE%ɺ€`E%הƴ6vendor/phpdocumentor/reflection-docblock/src/Utils.php2 ɺ€`2 Ñg<C391>ˆ´Hvendor/phpdocumentor/reflection-docblock/src/Exception/PcreException.php“ɺ€`“4ZU\´Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/DescriptionFactory.php.ɺ€`.:èûå´=vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tag.php<68>ɺ€`<60>1Ñä´Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/TagFactory.php{ɺ€`{´I4é´Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Author.phpÌ Éº€`Ì m±qˆ´Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyWrite.php ɺ€` …OO)´Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Link.php‡ɺ€`‡Òtú´Ivendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Deprecated.php5 ɺ€`5 ¯<>#´Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Covers.php}
ɺ€`}
¿tð@´Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Since.phps
ɺ€`s
¬—«e´Gvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Property.phpɺ€`Ï8³„´Bvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/See.phpæ ɺ€`æ GÝP´Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Method.php\ ɺ€`\ ¹âJê´Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Source.php¦ ɺ€`¦ ªBK´Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Param.phpµɺ€`µÜĦ´]vendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/PassthroughFormatter.php£ɺ€`£ Ûe.´Wvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter/AlignFormatter.php£ɺ€`£±xí<78>´Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Uses.phpT
ɺ€`T
ðš–)´Kvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/PropertyRead.phpɺ€`?i´Ivendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/InvalidTag.phpÂɺ€`Â<00>ះ´Svendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Factory/StaticMethod.phpɺ€`þP,´Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Example.php ɺ€` ñÝç´Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Version.phpÈ
ɺ€`È
ñN~8´Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Return_.php-ɺ€`-€."í´Hvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Formatter.phpɺ€`v=,+´Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Throws.phpɺ€`ÓoVU´Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/BaseTag.php{ɺ€`{´Rvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Reference.phpÍɺ€`Ížô.´Nvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Fqsen.php ɺ€` ð Áv´Lvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Reference/Url.phpÁɺ€`Á·ÊÀ´Jvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/TagWithType.php©ɺ€`©5ºçû´Fvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Generic.php¨ ɺ€`¨ â[Ï´Cvendor/phpdocumentor/reflection-docblock/src/DocBlock/Tags/Var_.phpæ ɺ€`æ !žN2´Evendor/phpdocumentor/reflection-docblock/src/DocBlock/Description.phpРɺ€`Ð «Oà´Gvendor/phpdocumentor/reflection-docblock/src/DocBlock/ExampleFinder.phpfɺ€`f•äLvendor/phpdocumentor/reflection-docblock/src/DocBlock/StandardTagFactory.phpS1ɺ€`S1Žs´Dvendor/phpdocumentor/reflection-docblock/src/DocBlock/Serializer.phpSɺ€`S6Rþ´0vendor/phpdocumentor/reflection-docblock/LICENSE8ɺ€`8á‰Ê´&vendor/webmozart/assert/src/Assert.phpúÛɺ€`úÛBÅ-Ž´8vendor/webmozart/assert/src/InvalidArgumentException.php[ɺ€`[ê¹*i´%vendor/webmozart/assert/src/Mixin.phpÙɺ€`ÙN±ìÖ´vendor/webmozart/assert/LICENSE<ɺ€`<tØ}õ´<vendor/phpmyadmin/twig-i18n-extension/src/Node/TransNode.php($ɺ€`($ß?ô´´;vendor/phpmyadmin/twig-i18n-extension/src/I18nExtension.phpɺ€`q'0Í´Jvendor/phpmyadmin/twig-i18n-extension/src/TokenParser/TransTokenParser.php ɺ€` É`é´-vendor/phpmyadmin/twig-i18n-extension/LICENSEYɺ€`Y Í;Y´5vendor/code-lts/cli-tools/src/AnsiEscapeSequences.phpRɺ€`Rþ[¡´.vendor/code-lts/cli-tools/src/OutputFormat.php\ɺ€`\)l­´4vendor/code-lts/cli-tools/src/ErrorsConsoleStyle.phpCɺ€`CצJ´0vendor/code-lts/cli-tools/src/AnalysisResult.php_ɺ€`_C/Œù´-vendor/code-lts/cli-tools/src/OutputStyle.php=ɺ€`=:³J§´1vendor/code-lts/cli-tools/src/File/FileReader.phpÃɺ€`Ãœ´9vendor/code-lts/cli-tools/src/File/RelativePathHelper.phpYɺ€`YõS<C3B5>ü´?vendor/code-lts/cli-tools/src/File/SimpleRelativePathHelper.phpFɺ€`F5îæo´Avendor/code-lts/cli-tools/src/File/CouldNotWriteFileException.php ɺ€`  ;U´>vendor/code-lts/cli-tools/src/File/FuzzyRelativePathHelper.phpÇɺ€`ÇÜ´@vendor/code-lts/cli-tools/src/File/CouldNotReadFileException.phpɺ€`¾Ä¥O´=vendor/code-lts/cli-tools/src/File/NullRelativePathHelper.phpœɺ€`œR´<vendor/code-lts/cli-tools/src/File/PathNotFoundException.phpPɺ€`P\ų´1vendor/code-lts/cli-tools/src/File/FileWriter.php¯ɺ€`¯·-á“´'vendor/code-lts/cli-tools/src/Error.php<68>ɺ€`<60>í”fO´6vendor/code-lts/cli-tools/src/Symfony/SymfonyStyle.phpq ɺ€`q ¶ã1\´7vendor/code-lts/cli-tools/src/Symfony/SymfonyOutput.phpQ
ɺ€`Q
~z^´Bvendor/code-lts/cli-tools/src/ErrorFormatter/RawErrorFormatter.phpX ɺ€`X ¤*.´Fvendor/code-lts/cli-tools/src/ErrorFormatter/RawTextErrorFormatter.phpBɺ€`B'¤•_´Evendor/code-lts/cli-tools/src/ErrorFormatter/GithubErrorFormatter.phplɺ€`lm2´Ivendor/code-lts/cli-tools/src/ErrorFormatter/CheckstyleErrorFormatter.php ɺ€` ¡”å>´Dvendor/code-lts/cli-tools/src/ErrorFormatter/TableErrorFormatter.php>ɺ€`>ˆ^Ê´Cvendor/code-lts/cli-tools/src/ErrorFormatter/JsonErrorFormatter.phpD ɺ€`D ¼D`Ä´Gvendor/code-lts/cli-tools/src/ErrorFormatter/TeamcityErrorFormatter.phpŽɺ€`Ž=Ãe´?vendor/code-lts/cli-tools/src/ErrorFormatter/ErrorFormatter.php¯ɺ€`¯ã10´Dvendor/code-lts/cli-tools/src/ErrorFormatter/JunitErrorFormatter.php¦ɺ€`¦§©äT´Evendor/code-lts/cli-tools/src/ErrorFormatter/GitlabErrorFormatter.phpNɺ€`Nè·;´'vendor/code-lts/cli-tools/src/Utils.phpɺ€`ý—´Dvendor/code-lts/cli-tools/src/Exceptions/FormatNotFoundException.php7ɺ€`7¾ª’´(vendor/code-lts/cli-tools/src/Output.php`ɺ€``+5Ér´!vendor/code-lts/cli-tools/LICENSEUAɺ€`UAi0´7vendor/psr/container/src/NotFoundExceptionInterface.phpžÉº€`ž><3E>æ´8vendor/psr/container/src/ContainerExceptionInterface.phpɺ€`B¯xº´/vendor/psr/container/src/ContainerInterface.phpɺ€`jåè.´vendor/psr/container/LICENSEyɺ€`yÊO“p´5vendor/ondram/ci-detector/src/CiDetectorInterface.php<ɺ€`<b¸Û´.vendor/ondram/ci-detector/src/TrinaryLogic.php^ɺ€`^Ÿwii´%vendor/ondram/ci-detector/src/Env.php²ɺ€`²^5š ´Bvendor/ondram/ci-detector/src/Exception/CiNotDetectedException.phpƒÉº€`ƒ^t(´,vendor/ondram/ci-detector/src/Ci/Jenkins.phpÙɺ€`Ù<00>IbÔ´+vendor/ondram/ci-detector/src/Ci/Bamboo.phpkɺ€`klZ0£´-vendor/ondram/ci-detector/src/Ci/AppVeyor.phpPɺ€`P ´-vendor/ondram/ci-detector/src/Ci/Codeship.phpɺ€`©%ª´1vendor/ondram/ci-detector/src/Ci/AwsCodeBuild.phpòɺ€`òAçL´7vendor/ondram/ci-detector/src/Ci/BitbucketPipelines.php]ɺ€`]„Áï&´3vendor/ondram/ci-detector/src/Ci/AzurePipelines.phpÓɺ€`Ó!a.´.vendor/ondram/ci-detector/src/Ci/SourceHut.phpåɺ€`å%쫶´,vendor/ondram/ci-detector/src/Ci/Wercker.php0ɺ€`0ʘR ´/vendor/ondram/ci-detector/src/Ci/AbstractCi.php¦ɺ€`¦<_ï´-vendor/ondram/ci-detector/src/Ci/TeamCity.php´ɺ€`´z!<21>a´*vendor/ondram/ci-detector/src/Ci/Drone.phpWɺ€`WåÛÏZ´+vendor/ondram/ci-detector/src/Ci/GitLab.phpfɺ€`fálPW´+vendor/ondram/ci-detector/src/Ci/Circle.php·ɺ€`·Œî*´*vendor/ondram/ci-detector/src/Ci/Buddy.phpLɺ€`Lfg_ý´+vendor/ondram/ci-detector/src/Ci/Travis.php°ɺ€`°K5íL´0vendor/ondram/ci-detector/src/Ci/CiInterface.php$ ɺ€`$ —Tyû´2vendor/ondram/ci-detector/src/Ci/Continuousphp.phpkɺ€`k. 5´2vendor/ondram/ci-detector/src/Ci/GitHubActions.php‰ɺ€`‰ì—¹ö´,vendor/ondram/ci-detector/src/CiDetector.php} ɺ€`} Ç`ž"´%vendor/erusev/parsedown/Parsedown.php•¦Éº€`•¦7º~Ž´#vendor/erusev/parsedown/LICENSE.txtIɺ€`Ië@B´6vendor/twig/twig/src/FileExtensionEscapingStrategy.phpžɺ€`ž—¤D´!vendor/twig/twig/src/TwigTest.phpûɺ€`ûP€º´+vendor/twig/twig/src/Loader/ArrayLoader.php4ɺ€`4È ïØ´0vendor/twig/twig/src/Loader/FilesystemLoader.php<68>ɺ€`<60>€êpÅ´/vendor/twig/twig/src/Loader/LoaderInterface.phpWɺ€`WKXq´+vendor/twig/twig/src/Loader/ChainLoader.php ɺ€` èh«´)vendor/twig/twig/src/ExpressionParser.php™ɺ€`™1‡Å÷´vendor/twig/twig/src/Markup.phpLɺ€`L>†û ´vendor/twig/twig/src/Source.phpÿɺ€`ÿ„m“´!vendor/twig/twig/src/Compiler.php2ɺ€`27^˜´!vendor/twig/twig/src/Template.php2ɺ€`2šT´(vendor/twig/twig/src/TemplateWrapper.php…
ɺ€`…
­FÚ\´%vendor/twig/twig/src/TwigFunction.php- ɺ€`- Öã—´1vendor/twig/twig/src/Util/TemplateDirIterator.phpþɺ€`þsr+y´2vendor/twig/twig/src/Util/DeprecationCollector.phpɺ€`¤¿9´vendor/twig/twig/src/Token.phpQɺ€`Ql7i´$vendor/twig/twig/src/TokenStream.phpË Éº€`Ë ^ !Y´<vendor/twig/twig/src/Extension/RuntimeExtensionInterface.phpFɺ€`FûÄ‚Ý´4vendor/twig/twig/src/Extension/ProfilerExtension.phpɺ€`OÚ,\´5vendor/twig/twig/src/Extension/ExtensionInterface.phpóɺ€`ódGo´1vendor/twig/twig/src/Extension/DebugExtension.phpÝɺ€`݈Åo´4vendor/twig/twig/src/Extension/AbstractExtension.php¹ɺ€`¹½ÀÉ ´8vendor/twig/twig/src/Extension/StringLoaderExtension.phpɺ€`âÁ+Å´3vendor/twig/twig/src/Extension/StagingExtension.php¥ ɺ€`¥ ](Ûë´3vendor/twig/twig/src/Extension/EscaperExtension.php8ɺ€`8†Cö„´3vendor/twig/twig/src/Extension/SandboxExtension.phpr ɺ€`r ]D´5vendor/twig/twig/src/Extension/OptimizerExtension.phpKɺ€`Kí†[¹´3vendor/twig/twig/src/Extension/GlobalsInterface.phpGɺ€`GXáAd´0vendor/twig/twig/src/Extension/CoreExtension.php½Îɺ€`½Î ¹´)vendor/twig/twig/src/Profiler/Profile.php*ɺ€`*µÆ'¢´7vendor/twig/twig/src/Profiler/Node/EnterProfileNode.phpÝɺ€`Ý´7vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php ɺ€` fjдAvendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php# ɺ€`# ¾•ô´3vendor/twig/twig/src/Profiler/Dumper/BaseDumper.phpÀɺ€`ÀƒäÇ©´3vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php¢ɺ€`¢ìГ•´8vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.phpÅɺ€`Å…@Tð´3vendor/twig/twig/src/Profiler/Dumper/TextDumper.php€ɺ€`€T5ó´(vendor/twig/twig/src/Node/ImportNode.phpÄɺ€`ÄBWk§´$vendor/twig/twig/src/Node/IfNode.phpvɺ€`vQܾf´"vendor/twig/twig/src/Node/Node.phpæɺ€`æ ó~´0vendor/twig/twig/src/Node/BlockReferenceNode.php?ɺ€`?‡mÞÈ´'vendor/twig/twig/src/Node/BlockNode.php
ɺ€`
ŸÈØT´2vendor/twig/twig/src/Node/NodeCaptureInterface.php€ɺ€`€¤‚õ´'vendor/twig/twig/src/Node/MacroNode.php¡ ɺ€`¡ “ä<E2809C>´%vendor/twig/twig/src/Node/ForNode.php!ɺ€`!Xññ”´(vendor/twig/twig/src/Node/ModuleNode.phpÔ9ɺ€`Ô9Q´'vendor/twig/twig/src/Node/FlushNode.phpŠɺ€`ŠßV⦴,vendor/twig/twig/src/Node/AutoEscapeNode.phpWɺ€`W#=ix´/vendor/twig/twig/src/Node/CheckToStringNode.phpäɺ€`äßžö³´'vendor/twig/twig/src/Node/EmbedNode.phpɺ€`ƒã;ž´$vendor/twig/twig/src/Node/DoNode.php!ɺ€`!}:ì´'vendor/twig/twig/src/Node/PrintNode.phpvɺ€`v¥Êäp´)vendor/twig/twig/src/Node/SandboxNode.phpɺ€`,ׯ3´&vendor/twig/twig/src/Node/TextNode.php ɺ€` Îx§<78>´3vendor/twig/twig/src/Node/CheckSecurityCallNode.phpNɺ€`N59Îx´@vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php+ɺ€`+Õšn1´9vendor/twig/twig/src/Node/Expression/FilterExpression.phpɺ€`ãK4¹´:vendor/twig/twig/src/Node/Expression/GetAttrExpression.phpä
ɺ€`ä
æpUg´7vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php¯ɺ€`¯i°šw´7vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php¯ɺ€`¯ƒ´<vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.phpɺ€`bì´7vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php¯ɺ€`¯þ庴;vendor/twig/twig/src/Node/Expression/VariadicExpression.phpºɺ€`º |?j´Avendor/twig/twig/src/Node/Expression/BlockReferenceExpression.phpÿɺ€`ÿ$_øB´;vendor/twig/twig/src/Node/Expression/TempNameExpression.phpxɺ€`x!zì¡´>vendor/twig/twig/src/Node/Expression/ConditionalExpression.phpšɺ€`šüA”»´=vendor/twig/twig/src/Node/Expression/MethodCallExpression.phpôɺ€`ô¦àÅ´7vendor/twig/twig/src/Node/Expression/NameExpression.php™ ɺ€`™ ´;vendor/twig/twig/src/Node/Expression/ConstantExpression.phpGɺ€`G@GžB´9vendor/twig/twig/src/Node/Expression/ParentExpression.php„ɺ€`„<00>à<EFBFBD>´8vendor/twig/twig/src/Node/Expression/ArrayExpression.phpÓɺ€`ÓŒ?ßµ´6vendor/twig/twig/src/Node/Expression/Test/EvenTest.php§ɺ€`§meWδ9vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php
ɺ€`
³vÂc´=vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.phpɺ€`!5T©´:vendor/twig/twig/src/Node/Expression/Test/ConstantTest.phpɺ€`ÜÓ1´6vendor/twig/twig/src/Node/Expression/Test/NullTest.php•ɺ€`•÷þ`V´5vendor/twig/twig/src/Node/Expression/Test/OddTest.php¤ɺ€`¤=kwI´8vendor/twig/twig/src/Node/Expression/Test/SameasTest.phpðɺ€`ð{Loô?vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php.ɺ€`.f )´;vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.phpºɺ€`º´l!´8vendor/twig/twig/src/Node/Expression/Binary/InBinary.php²ɺ€`²Å+§´@vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.phpŒɺ€`ŒBÖuœ´@vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php¹ɺ€`¹ឦ‡´<vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.phpµɺ€`µ?ádž´;vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php+ɺ€`+œó>´>vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.phpMɺ€`M*»I´8vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php²ɺ€`²l—þ¤´9vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php²ɺ€`²\ÊaÛ´>vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php—ɺ€`—¿Š²G´9vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php²ɺ€`²b¿@´?vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php¤ɺ€`¤·³´;vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.phpŸɺ€`Ÿ¨r“ô´9vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php²ɺ€`²Ûk&A´?vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php¸ɺ€`¸üÁ´=vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php,ɺ€`,ím>´9vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php²ɺ€`²ÿªáø´9vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php³ɺ€`³i
û1´Bvendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php1ɺ€`1ó¦Ê´>vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.phpàɺ€`àlÄÁ&´@vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php¹ɺ€`¹;ŸÛ”´=vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php±ɺ€`±±Ôc1´:vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php*ɺ€`*W¸éŽ´;vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php¬ɺ€`¬Lfز´>vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php.ɺ€`.Dw´9vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php²ɺ€`²)»´;vendor/twig/twig/src/Node/Expression/AbstractExpression.phpÄɺ€`ÄZT´;vendor/twig/twig/src/Node/Expression/FunctionExpression.php\ɺ€`\ß¾øS´=vendor/twig/twig/src/Node/Expression/AssignNameExpression.phpɺ€`!Áùç´?vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php6ɺ€`6гye´7vendor/twig/twig/src/Node/Expression/TestExpression.php]ɺ€`]c ¼´7vendor/twig/twig/src/Node/Expression/CallExpression.php¶0ɺ€`¶0ÊÈ®´=vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.phpÿɺ€`ÿg”l´4vendor/twig/twig/src/Node/Expression/InlinePrint.php¥ɺ€`¥¸¦&õ´,vendor/twig/twig/src/Node/DeprecatedNode.phpiɺ€`iŽìá´%vendor/twig/twig/src/Node/SetNode.php+ ɺ€`+ ·u´&vendor/twig/twig/src/Node/BodyNode.phpWɺ€`Wäàám´&vendor/twig/twig/src/Node/WithNode.php5ɺ€`5Âa0Ž´/vendor/twig/twig/src/Node/CheckSecurityNode.php$ ɺ€`$ s ´)vendor/twig/twig/src/Node/IncludeNode.phpE ɺ€`E ÿÙq%´)vendor/twig/twig/src/Node/ForLoopNode.php}ɺ€`}©J54´1vendor/twig/twig/src/Node/NodeOutputInterface.phpkɺ€`k£Á3ã´vendor/twig/twig/src/Lexer.php«Kɺ€`«K´%vendor/twig/twig/src/ExtensionSet.php31ɺ€`31‡6ß´$vendor/twig/twig/src/Error/Error.phpÁɺ€`ÁÆGë´+vendor/twig/twig/src/Error/RuntimeError.php<68>ɺ€`<60>¤¾B´*vendor/twig/twig/src/Error/SyntaxError.phpžɺ€`žáéöÑ´*vendor/twig/twig/src/Error/LoaderError.phpƒɺ€`ƒÏ ]6´/vendor/twig/twig/src/Sandbox/SecurityPolicy.phpªɺ€`ª~Ä8´>vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.phpÃɺ€`ÃçaÒy´8vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.phpñɺ€`ñø¬!Ï´>vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.phpdɺ€`dB4ìÒ´@vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.phptɺ€`tÜü4´.vendor/twig/twig/src/Sandbox/SecurityError.php™ɺ€`™Þ ´;vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php§ɺ€`§àenf´@vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.phpÏɺ€`Ïì%ö´$vendor/twig/twig/src/Environment.php]ɺ€`]Öïyñ´<vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.phpCɺ€`Cƒ<>~´7vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php ɺ€` *B;«´9vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.phpªɺ€`ªñž+´9vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.phpõɺ€`õûcÛ´7vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.phpTɺ€`TuH´8vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.phpɺ€`M?LÞ´?vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php´ɺ€`´³&vendor/twig/twig/src/NodeTraverser.phpɺ€`ÉŒA¦´vendor/twig/twig/src/Parser.php‰,ɺ€`‰,¼¸„E´1vendor/twig/twig/src/Test/IntegrationTestCase.phpØ"ɺ€`Ø"`ö`ù´*vendor/twig/twig/src/Test/NodeTestCase.phpŸɺ€`Ÿµ<>ox´4vendor/twig/twig/src/TokenParser/WithTokenParser.phpɺ€`¹G¿´3vendor/twig/twig/src/TokenParser/UseTokenParser.php¸ɺ€`¸‰HÖÜ´8vendor/twig/twig/src/TokenParser/AbstractTokenParser.php=ɺ€`=Gå3;´5vendor/twig/twig/src/TokenParser/EmbedTokenParser.phpàɺ€`à<00>Êa´2vendor/twig/twig/src/TokenParser/DoTokenParser.php ɺ€` î´7vendor/twig/twig/src/TokenParser/SandboxTokenParser.phpɺ€`Z¬E¨´:vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php.ɺ€`.Ið{´3vendor/twig/twig/src/TokenParser/ForTokenParser.phpm ɺ€`m E‰
[´:vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.phpÉɺ€`Éd™´4vendor/twig/twig/src/TokenParser/FromTokenParser.phpɺ€`á쀴5vendor/twig/twig/src/TokenParser/BlockTokenParser.php¯ ɺ€`¯ ´7vendor/twig/twig/src/TokenParser/ExtendsTokenParser.phpRɺ€`RV~¥2´2vendor/twig/twig/src/TokenParser/IfTokenParser.phpÇ Éº€`Ç ·4ƒ´5vendor/twig/twig/src/TokenParser/MacroTokenParser.phpeɺ€`eƒ—ú‹´5vendor/twig/twig/src/TokenParser/FlushTokenParser.phpÄɺ€`ÄÒ×´3vendor/twig/twig/src/TokenParser/SetTokenParser.php'ɺ€`'¾r¡x´9vendor/twig/twig/src/TokenParser/TokenParserInterface.phpkɺ€`k4kL´5vendor/twig/twig/src/TokenParser/ApplyTokenParser.phpÔɺ€`ÔÕRt9´7vendor/twig/twig/src/TokenParser/IncludeTokenParser.php\ɺ€`\·ÍÃ'´6vendor/twig/twig/src/TokenParser/ImportTokenParser.php˜ɺ€`˜º ‡*´=vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.phpkɺ€`k<00>´;vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.phpMɺ€`MˆÒ±4´=vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php«ɺ€`«ÎC0´-vendor/twig/twig/src/Cache/CacheInterface.php<68>ɺ€`<60>*å¦ô´(vendor/twig/twig/src/Cache/NullCache.php²ɺ€`²ªÁåñ´.vendor/twig/twig/src/Cache/FilesystemCache.php· ɺ€`· Ö÷8´#vendor/twig/twig/src/TwigFilter.php7 ɺ€`7 ´ÈGý´vendor/twig/twig/LICENSEéɺ€`é»Q×Ö´LICENSENɺ€`N¥<>R´Þ$,8¢9Project-Id-Version: 1.0.0
Report-Msgid-Bugs-To: williamdes@wdes.fr
PO-Revision-Date: 2020-07-13 09:34+0200
Last-Translator: Blob Blob <williamdes@wdes.fr>
Language-Team: English <https://github.com/code-lts/doctum/>
Language: en
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Plural-Forms: nplurals=2; plural=(n != 1);
X-Generator: Poedit 2.2.4
X-Poedit-SourceCharset: UTF-8
Þ(\5œp q}ƒ ¡«
³H¾ 
")< D
NYh
~
 ©½ÌüÕÒäê ñ
ý
 & ,6=B`H©¼Ãß
çò
ûU\ e
ozƒ œ ¦
°»Î æ òÿ
, 7
O 0Z 
œ
¢
©
¸
Ç
Î
Ú
é
ñ
û
   "  #'!
( % &$All ClassesClassClass in namespace %sClassesConstantsDetailsExceptionsGenerated by %sDoctum, a API Documentation generator and fork of Sami%s.IndexInterfaceInterfacesMethodMethod in class %sMethodsNamespaceNamespacesNo descriptionNo results were foundParametersPropertiesProperty in class %sReturn ValueSearchSearch ResultsSee alsoThis page allows you to search through the API documentation for
specific terms. Enter your search words into the box below and click
"submit". The search will be performed on namespaces, classes, interfaces,
traits, functions, and methods.Toggle navigationTraitTraitsView sourceat line %sclassdeprecatedfrom&nbsp;%sin %sinterfacesubmittodotraitProject-Id-Version: 1.0.0
Report-Msgid-Bugs-To: williamdes@wdes.fr
PO-Revision-Date: 2020-07-18 13:40+0200
Last-Translator: William Desportes <williamdes@wdes.fr>
Language-Team: French <https://github.com/code-lts/doctum/>
Language: fr
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
X-Generator: Poedit 2.2.4
Toutes les classesClasseClasse dans le namespace %sClassesConstantesDétailsExceptionsGénéré par %sDoctum, un générateur de documentation d'API, successeur de Sami%s.SommaireInterfaceInterfacesMéthodeMéthode de la classe %sMéthodesNamespaceNamespacesPas de descriptionAucun résultat trouvéParamètresPropriétésPropriété de la classe %sValeur de retourRechercherRésultats de rechercheVoir aussiCette page vous permet de rechercher dans la documentation de l'API des termes spécifiques.
Entrez vos mots clefs de recherche dans la case ci-dessous et cliquez sur "Valider".
La recherche sera effectuée sur les espaces de noms, les classes, les interfaces, les traits, les fonctions et les méthodes.Afficher le menuTraitTraitsVoir la sourceà la ligne %sclassedépréciédepuis&nbsp;%sdans %sinterfaceValidertodotrait<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Store;
use Doctum\Project;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\FunctionReflection;
interface StoreInterface
{
public function readClass(Project $project, $name);
public function writeClass(Project $project, ClassReflection $class);
public function removeClass(Project $project, $name);
public function readFunction(Project $project, string $name): FunctionReflection;
public function writeFunction(Project $project, FunctionReflection $Function): void;
public function removeFunction(Project $project, string $name): void;
/**
* Read the storage and return it
*
* @return array<int|string,ClassReflection|FunctionReflection>
*/
public function readProject(Project $project);
/**
* Empty the storage
*
* @return void
*/
public function flushProject(Project $project);
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Store;
use Doctum\Project;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\FunctionReflection;
use Exception;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
class JsonStore implements StoreInterface
{
private const JSON_PRETTY_PRINT = 128;
/**
* @return ClassReflection A ClassReflection instance
*
* @throws \InvalidArgumentException if the class does not exist in the store
*/
public function readClass(Project $project, $name): ClassReflection
{
$fileName = $this->getFilename('class', $project, $name);
if (!file_exists($fileName)) {
throw new \InvalidArgumentException(sprintf('File "%s" for class "%s" does not exist.', $fileName, $name));
}
return ClassReflection::fromArray($project, $this->readJsonFile($fileName));
}
protected function readJsonFile(string $filePath): array
{
$contents = file_get_contents($filePath);
if ($contents === false) {
throw new Exception(
sprintf('Unable to read the class: %s', $filePath)
);
}
$contents = json_decode($contents, true);
if ($contents === false) {
throw new Exception(
sprintf('Unable to JSON decode the class from: %s', $filePath)
);
}
return $contents;
}
public function removeClass(Project $project, $name)
{
if (!file_exists($this->getFilename('class', $project, $name))) {
throw new \RuntimeException(sprintf('Unable to remove the "%s" class.', $name));
}
unlink($this->getFilename('class', $project, $name));
}
public function writeClass(Project $project, ClassReflection $class)
{
file_put_contents($this->getFilename('class', $project, $class->getName()), json_encode($class->toArray(), self::JSON_PRETTY_PRINT));
}
/**
* @return FunctionReflection A FunctionReflection instance
*
* @throws \InvalidArgumentException if the function does not exist in the store
*/
public function readFunction(Project $project, string $name): FunctionReflection
{
$fileName = $this->getFilename('function', $project, $name);
if (!file_exists($fileName)) {
throw new \InvalidArgumentException(sprintf('File "%s" for function "%s" does not exist.', $fileName, $name));
}
return FunctionReflection::fromArray($project, $this->readJsonFile($fileName));
}
public function removeFunction(Project $project, string $name): void
{
if (!file_exists($this->getFilename('function', $project, $name))) {
throw new \RuntimeException(sprintf('Unable to remove the "%s" function.', $name));
}
unlink($this->getFilename('function', $project, $name));
}
public function writeFunction(Project $project, FunctionReflection $function): void
{
file_put_contents($this->getFilename('function', $project, $function->getName()), json_encode($function->toArray(), self::JSON_PRETTY_PRINT));
}
public function readProject(Project $project)
{
$classesOrFunctions = [];
$files = Finder::create()->name('c_*.json')->files()->in($this->getStoreDir($project));
foreach ($files as $file) {
$contents = file_get_contents($file->getPathname());
if ($contents === false) {
continue;
}
$data = json_decode($contents, true);
if ($data === false || $data === null) {
continue;
}
$classesOrFunctions[] = ClassReflection::fromArray($project, $data);
}
$files = Finder::create()->name('f_*.json')->files()->in($this->getStoreDir($project));
foreach ($files as $file) {
$contents = file_get_contents($file->getPathname());
if ($contents === false) {
continue;
}
$data = json_decode($contents, true);
if ($data === false || $data === null) {
continue;
}
$classesOrFunctions[] = FunctionReflection::fromArray($project, $data);
}
return $classesOrFunctions;
}
public function flushProject(Project $project)
{
$filesystem = new Filesystem();
$filesystem->remove($this->getStoreDir($project));
}
protected function getFilename(string $type, Project $project, string $name): string
{
$dir = $this->getStoreDir($project);
return $dir . '/' . $type[0] . '_' . md5($name) . '.json';
}
protected function getStoreDir(Project $project): string
{
$dir = $project->getCacheDir() . '/store';
if (!is_dir($dir)) {
$filesystem = new Filesystem();
$filesystem->mkdir($dir);
}
return $dir;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Store;
use Doctum\Project;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\FunctionReflection;
/**
* Stores classes in-memory.
*
* Mainly useful for unit tests.
*/
class ArrayStore implements StoreInterface
{
/**
* @var array<string,ClassReflection>
*/
private $classes = [];
/**
* @var array<string,FunctionReflection>
*/
private $functions = [];
public function setClasses($classes)
{
foreach ($classes as $class) {
$this->classes[$class->getName()] = $class;
}
}
public function readClass(Project $project, $name)
{
if (!isset($this->classes[$name])) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $name));
}
return $this->classes[$name];
}
public function removeClass(Project $project, $name)
{
if (!isset($this->classes[$name])) {
throw new \InvalidArgumentException(sprintf('Class "%s" does not exist.', $name));
}
unset($this->classes[$name]);
}
public function writeClass(Project $project, ClassReflection $class)
{
$this->classes[$class->getName()] = $class;
}
public function readFunction(Project $project, string $name): FunctionReflection
{
if (!isset($this->functions[$name])) {
throw new \InvalidArgumentException(sprintf('Function "%s" does not exist.', $name));
}
return $this->functions[$name];
}
public function removeFunction(Project $project, string $name): void
{
if (!isset($this->functions[$name])) {
throw new \InvalidArgumentException(sprintf('Function "%s" does not exist.', $name));
}
unset($this->functions[$name]);
}
public function writeFunction(Project $project, FunctionReflection $function): void
{
$this->functions[$function->getName()] = $function;
}
public function readProject(Project $project)
{
return array_merge($this->classes, $this->functions);
}
public function flushProject(Project $project)
{
$this->classes = [];
$this->functions = [];
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Version;
use Symfony\Component\Finder\Glob;
use Symfony\Component\Process\Process;
class GitVersionCollection extends VersionCollection
{
/**
* @var \Closure
*/
protected $sorter;
/**
* @var \Closure
*/
protected $filter;
/**
* @var string
*/
protected $repo;
/**
* @var string
*/
protected $gitPath;
public function __construct(string $repo)
{
$this->repo = $repo;
$this->filter = static function (string $version): bool {
foreach (['PR', 'RC', 'BETA', 'ALPHA'] as $str) {
if (strpos($version, $str) !== false) {
return false;
}
}
return true;
};
$this->sorter = static function (string $a, string $b): int {
return version_compare($a, $b, '>') === true ? 1 : 0;
};
$this->gitPath = 'git';
}
/**
* @phpstan-return void
*/
protected function switchVersion(Version $version)
{
$process = new Process(['git', 'status', '--porcelain', '--untracked-files=no'], $this->repo);
$process->run();
if (!$process->isSuccessful() || trim($process->getOutput())) {
throw new \RuntimeException(sprintf('Unable to switch to version "%s" as the repository is not clean.', $version));
}
$this->execute(['checkout', '-qf', (string) $version]);
}
public function setGitPath(string $path): void
{
$this->gitPath = $path;
}
public function setFilter(\Closure $filter): void
{
$this->filter = $filter;
}
public function setSorter(\Closure $sorter): void
{
$this->sorter = $sorter;
}
/**
* @param \Closure|null|string[] $filter An array_filter callback that will be used for filtering versions or an array of globs
*/
public function addFromTags($filter = null): self
{
$tags = array_filter(explode("\n", $this->execute(['tag'])));
$versions = array_filter($tags, $this->filter);
if (null !== $filter) {
if (!$filter instanceof \Closure) {
$regexes = [];
foreach ((array) $filter as $f) {
$regexes[] = Glob::toRegex($f);
}
$filter = static function ($version) use ($regexes) {
foreach ($regexes as $regex) {
if (preg_match($regex, $version)) {
return true;
}
}
return false;
};
}
$versions = array_filter($versions, $filter);
}
usort($versions, $this->sorter);
foreach ($versions as $version) {
$version = new Version($version);
$version->setFrozen(true);
$this->add($version);
}
return $this;
}
/**
* @param string[] $arguments
*/
protected function execute(array $arguments): string
{
array_unshift($arguments, $this->gitPath);
$process = new Process($arguments, $this->repo);
$process->run();
if (!$process->isSuccessful()) {
throw new \RuntimeException(sprintf('Unable to run the command (%s).', $process->getErrorOutput()));
}
return $process->getOutput();
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Version;
class SingleVersionCollection extends VersionCollection
{
public function add($version, $longname = null)
{
$countable = is_array($this->versions) || $this->versions instanceof \Countable;
if ($countable && count($this->versions)) {
throw new \LogicException('A SingleVersionCollection can only contain one Version');
}
parent::add($version, $longname);
return $this;
}
protected function switchVersion(Version $version)
{
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Version;
class Version
{
/**
* @var bool
*/
protected $isFrozen;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $longname;
public function __construct(string $name, ?string $longname = null)
{
$this->name = $name;
$this->longname = null === $longname ? $name : $longname;
$this->isFrozen = false;
}
public function __toString()
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
public function getLongName(): ?string
{
return $this->longname;
}
public function setFrozen(bool $isFrozen): void
{
$this->isFrozen = $isFrozen;
}
public function isFrozen(): bool
{
return $this->isFrozen;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Version;
use Doctum\Project;
/**
* @implements \Iterator<Version>
*/
abstract class VersionCollection implements \Iterator, \Countable
{
/**
* @var Version[]|mixed
*/
protected $versions = [];
/**
* @var int
*/
protected $indice;
/**
* @var Project
*/
protected $project;
/**
* @phpstan-param array|Version $versions
*/
public function __construct($versions)
{
$this->add($versions);
}
/**
* @phpstan-return void
*/
abstract protected function switchVersion(Version $version);
/**
* @phpstan-return mixed
*/
public static function create()
{
$r = new \ReflectionClass(get_called_class());
return $r->newInstanceArgs(func_get_args());
}
public function setProject(Project $project): void
{
$this->project = $project;
}
/**
* @param string[]|string|Version $version
* @param string $longname
* @phpstan-return self
*/
public function add($version, $longname = null)
{
if (is_array($version)) {
foreach ($version as $v) {
$this->add($v);
}
} else {
if (!$version instanceof Version) {
$version = new Version($version, $longname);
}
$this->versions[] = $version;
}
return $this;
}
/**
* @return Version[]
*/
public function getVersions(): array
{
return $this->versions;
}
public function key(): int
{
return $this->indice;
}
public function current(): Version
{
return $this->versions[$this->indice];
}
public function next(): void
{
++$this->indice;
}
public function rewind(): void
{
$this->indice = 0;
}
public function valid(): bool
{
if ($this->indice < count($this->versions)) {
$this->switchVersion($this->current());
return true;
}
return false;
}
public function count(): int
{
return count($this->versions);
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Message
{
public const PARSE_ERROR = 1;
public const PARSE_CLASS = 2;
public const PARSE_VERSION_FINISHED = 3;
public const RENDER_PROGRESS = 4;
public const RENDER_VERSION_FINISHED = 5;
public const SWITCH_VERSION = 6;
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum;
use Doctum\Parser\Parser;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\FunctionReflection;
use Doctum\Reflection\LazyClassReflection;
use Doctum\Renderer\Renderer;
use Doctum\RemoteRepository\AbstractRemoteRepository;
use Doctum\Store\StoreInterface;
use Doctum\Version\SingleVersionCollection;
use Doctum\Version\Version;
use Doctum\Version\VersionCollection;
use Symfony\Component\Filesystem\Filesystem;
/**
* Project represents an API project.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Project
{
protected $versions;
protected $store;
/** @var Parser */
protected $parser;
/** @var Renderer */
protected $renderer;
/** @var ClassReflection[] */
protected $classes;
/** @var array<string,array<string,FunctionReflection[]>> */
protected $functions;
protected $namespaceClasses;
protected $namespaceInterfaces;
protected $namespaceExceptions;
protected $namespaces;
protected $config;
/** @var string|null */
protected $version;
protected $filesystem;
protected $interfaces;
/** @var string */
protected $sourceDir;
public function __construct(StoreInterface $store, ?VersionCollection $versions = null, array $config = [])
{
if (null === $versions) {
$versions = new SingleVersionCollection(new Version(Doctum::$defaultVersionName));
}
$this->versions = $versions;
$this->store = $store;
$this->config = array_merge(
[
'build_dir' => sys_get_temp_dir() . 'doctum/build',
'cache_dir' => sys_get_temp_dir() . 'doctum/cache',
'include_parent_data' => true,
'theme' => 'default',
],
$config
);
$this->filesystem = new Filesystem();
if (count($this->versions) > 1) {
foreach (['build_dir', 'cache_dir'] as $dir) {
if (false === strpos($this->config[$dir], '%version%')) {
throw new \LogicException(
sprintf(
'The "%s" setting must have the "%%version%%" placeholder'
. ' as the project has more than one version.',
$dir
)
);
}
}
}
$this->initialize();
}
public function setRenderer(Renderer $renderer): void
{
$this->renderer = $renderer;
}
public function setParser(Parser $parser): void
{
$this->parser = $parser;
}
public function setSourceDir(string $sourceDir): void
{
$this->sourceDir = $sourceDir;
}
public function getSourceDir(): string
{
return $this->replaceVars($this->sourceDir);
}
public function getConfig($name, $default = null)
{
return $this->config[$name] ?? $default;
}
public function getVersion(): ?string
{
return $this->version;
}
public function getVersions(): array
{
return $this->versions->getVersions();
}
public function update($callback = null, $force = false): void
{
foreach ($this->versions as $version) {
$this->switchVersion($version, $callback, $force);
$this->parseVersion($version, null, $callback, $force);
$this->renderVersion($version, null, $callback, $force);
}
}
public function parse($callback = null, $force = false): void
{
$previous = null;
foreach ($this->versions as $version) {
$this->switchVersion($version, $callback, $force);
$this->parseVersion($version, $previous, $callback, $force);
$previous = $this->getCacheDir();
}
}
public function render($callback = null, $force = false): void
{
$previous = null;
foreach ($this->versions as $version) {
// here, we don't want to flush the parse cache, as we are rendering
$this->switchVersion($version, $callback, false);
$this->renderVersion($version, $previous, $callback, $force);
$previous = $this->getBuildDir();
}
}
public function switchVersion(Version $version, $callback = null, $force = false): void
{
if (null !== $callback) {
call_user_func($callback, Message::SWITCH_VERSION, $version);
}
$this->version = $version->getName();
$this->initialize();
if (!$force) {
$this->read();
}
}
public function hasNamespaces(): bool
{
// if there is only one namespace and this is the global one, it means that there is no namespace in the project
return [''] != array_keys($this->namespaces);
}
public function hasNamespace(string $namespace): bool
{
return array_key_exists($namespace, $this->namespaces);
}
public function getNamespaces(): array
{
ksort($this->namespaces);
return array_keys($this->namespaces);
}
/**
* @return array<string,FunctionReflection[]>
*/
public function getNamespaceFunctions($namespace): array
{
if (! isset($this->functions[$namespace])) {
return [];
}
ksort($this->functions[$namespace]);
return $this->functions[$namespace];
}
public function getNamespaceAllClasses($namespace)
{
$classes = array_merge(
$this->getNamespaceExceptions($namespace),
$this->getNamespaceInterfaces($namespace),
$this->getNamespaceClasses($namespace)
);
ksort($classes);
return $classes;
}
public function getNamespaceExceptions($namespace)
{
if (!isset($this->namespaceExceptions[$namespace])) {
return [];
}
ksort($this->namespaceExceptions[$namespace]);
return $this->namespaceExceptions[$namespace];
}
public function getNamespaceClasses($namespace)
{
if (!isset($this->namespaceClasses[$namespace])) {
return [];
}
ksort($this->namespaceClasses[$namespace]);
return $this->namespaceClasses[$namespace];
}
public function getNamespaceInterfaces($namespace)
{
if (!isset($this->namespaceInterfaces[$namespace])) {
return [];
}
ksort($this->namespaceInterfaces[$namespace]);
return $this->namespaceInterfaces[$namespace];
}
public function getNamespaceSubNamespaces($parent): array
{
$prefix = strlen($parent) ? ($parent . '\\') : '';
$len = strlen($prefix);
$namespaces = [];
foreach ($this->namespaces as $sub) {
$prefixMatch = substr($sub, 0, $len) === $prefix;
if ($prefixMatch && strpos(substr($sub, $len), '\\') === false) {
$namespaces[] = $sub;
}
}
return $namespaces;
}
public function addFunction(FunctionReflection $fun): void
{
$this->functions[$fun->getNamespace()][$fun->getName()] = $fun;
$fun->setProject($this);
}
public function addClass(ClassReflection $class): void
{
$this->classes[$class->getName()] = $class;
$class->setProject($this);
if ($class->isProjectClass()) {
$this->updateCache($class);
}
}
public function removeClass(ClassReflection $class): void
{
unset($this->classes[$class->getName()]);
unset($this->interfaces[$class->getName()]);
unset($this->namespaceClasses[$class->getNamespace()][$class->getName()]);
unset($this->namespaceInterfaces[$class->getNamespace()][$class->getName()]);
unset($this->namespaceExceptions[$class->getNamespace()][$class->getName()]);
}
public function getProjectInterfaces(): array
{
$interfaces = [];
foreach ($this->interfaces as $interface) {
if ($interface->isProjectClass()) {
$interfaces[$interface->getName()] = $interface;
}
}
ksort($interfaces);
return $interfaces;
}
public function getProjectClasses(): array
{
$classes = [];
foreach ($this->classes as $name => $class) {
if ($class->isProjectClass()) {
$classes[$name] = $class;
}
}
ksort($classes);
return $classes;
}
public function getClass(string $name): ClassReflection
{
$name = ltrim($name, '\\');
if (isset($this->classes[$name])) {
return $this->classes[$name];
}
$class = new LazyClassReflection($name);
$this->addClass($class);
return $class;
}
/**
* this must only be used in LazyClassReflection to get the right values
*/
public function loadClass(string $name): ?ClassReflection
{
$name = ltrim($name, '\\');
if ($this->getClass($name) instanceof LazyClassReflection) {
try {
$this->addClass($this->store->readClass($this, $name));
} catch (\InvalidArgumentException $e) {
// probably a PHP built-in class
return null;
}
}
return $this->classes[$name];
}
public function initialize()
{
$this->namespaces = [];
$this->interfaces = [];
$this->classes = [];
$this->namespaceClasses = [];
$this->namespaceInterfaces = [];
$this->namespaceExceptions = [];
}
public function read(): void
{
$this->initialize();
foreach ($this->store->readProject($this) as $classOrFun) {
if ($classOrFun instanceof FunctionReflection) {
$this->addFunction($classOrFun);
} elseif ($classOrFun instanceof ClassReflection) {
$this->addClass($classOrFun);
}
}
}
public function getBuildDir()
{
return $this->prepareDir($this->config['build_dir']);
}
public function getCacheDir()
{
return $this->prepareDir($this->config['cache_dir']);
}
public function flushDir(string $dir): void
{
$this->filesystem->remove($dir);
$this->filesystem->mkdir($dir);
file_put_contents($dir . '/DOCTUM_VERSION', Doctum::VERSION);
file_put_contents($dir . '/PROJECT_VERSION', $this->version);
}
public function seedCache(string $previous, string $current): void
{
$this->filesystem->remove($current);
$this->filesystem->mirror($previous, $current);
$this->read();
}
/**
* @internal Will be removed without notice someday
*/
public static function isPhpTypeHint(string $hint): bool
{
// TODO: improve
return in_array(
strtolower($hint),
[
'',
'scalar',
'object',
'boolean',
'bool',
'true',
'false',
'int',
'integer',
'array',
'string',
'mixed',
'void',
'null',
'resource',
'double',
'float',
'callable',
'$this'
]
);
}
/**
* @var array<string,true>
* @internal Will be removed without notice someday
*/
public static $phpInternalClasses = [
'stdclass' => true,
'exception' => true,
'errorexception' => true,
'error' => true,
'parseerror' => true,
'typeerror' => true,
'arithmeticerror' => true,
'divisionbyzeroerror' => true,
'closure' => true,
'generator' => true,
'closedgeneratorexception' => true,
'datetime' => true,
'datetimeimmutable' => true,
'datetimezone' => true,
'dateinterval' => true,
'dateperiod' => true,
'libxmlerror' => true,
'sqlite3' => true,
'sqlite3stmt' => true,
'sqlite3result' => true,
'domexception' => true,
'domstringlist' => true,
'domnamelist' => true,
'domimplementationlist' => true,
'domimplementationsource' => true,
'domimplementation' => true,
'domnode' => true,
'domnamespacenode' => true,
'domdocumentfragment' => true,
'domdocument' => true,
'domnodelist' => true,
'domnamednodemap' => true,
'domcharacterdata' => true,
'domattr' => true,
'domelement' => true,
'domtext' => true,
'domcomment' => true,
'domtypeinfo' => true,
'domuserdatahandler' => true,
'domdomerror' => true,
'domerrorhandler' => true,
'domlocator' => true,
'domconfiguration' => true,
'domcdatasection' => true,
'domdocumenttype' => true,
'domnotation' => true,
'domentity' => true,
'domentityreference' => true,
'domprocessinginstruction' => true,
'domstringextend' => true,
'domxpath' => true,
'finfo' => true,
'logicexception' => true,
'badfunctioncallexception' => true,
'badmethodcallexception' => true,
'domainexception' => true,
'invalidargumentexception' => true,
'lengthexception' => true,
'outofrangeexception' => true,
'runtimeexception' => true,
'outofboundsexception' => true,
'overflowexception' => true,
'rangeexception' => true,
'underflowexception' => true,
'unexpectedvalueexception' => true,
'recursiveiteratoriterator' => true,
'iteratoriterator' => true,
'filteriterator' => true,
'recursivefilteriterator' => true,
'callbackfilteriterator' => true,
'recursivecallbackfilteriterator' => true,
'parentiterator' => true,
'limititerator' => true,
'cachingiterator' => true,
'recursivecachingiterator' => true,
'norewinditerator' => true,
'appenditerator' => true,
'infiniteiterator' => true,
'regexiterator' => true,
'recursiveregexiterator' => true,
'emptyiterator' => true,
'recursivetreeiterator' => true,
'arrayobject' => true,
'arrayiterator' => true,
'recursivearrayiterator' => true,
'splfileinfo' => true,
'directoryiterator' => true,
'filesystemiterator' => true,
'recursivedirectoryiterator' => true,
'globiterator' => true,
'splfileobject' => true,
'spltempfileobject' => true,
'spldoublylinkedlist' => true,
'splqueue' => true,
'splstack' => true,
'splheap' => true,
'splminheap' => true,
'splmaxheap' => true,
'splpriorityqueue' => true,
'splfixedarray' => true,
'splobjectstorage' => true,
'multipleiterator' => true,
'pdoexception' => true,
'pdo' => true,
'pdostatement' => true,
'pdorow' => true,
'sessionhandler' => true,
'reflectionexception' => true,
'reflection' => true,
'reflectionfunctionabstract' => true,
'reflectionfunction' => true,
'reflectiongenerator' => true,
'reflectionparameter' => true,
'reflectiontype' => true,
'reflectionmethod' => true,
'reflectionclass' => true,
'reflectionobject' => true,
'reflectionproperty' => true,
'reflectionextension' => true,
'reflectionzendextension' => true,
'__php_incomplete_class' => true,
'php_user_filter' => true,
'directory' => true,
'assertionerror' => true,
'simplexmlelement' => true,
'simplexmliterator' => true,
'pharexception' => true,
'phar' => true,
'phardata' => true,
'pharfileinfo' => true,
'xmlreader' => true,
'xmlwriter' => true,
'collator' => true,
'numberformatter' => true,
'normalizer' => true,
'locale' => true,
'messageformatter' => true,
'intldateformatter' => true,
'resourcebundle' => true,
'transliterator' => true,
'intltimezone' => true,
'intlcalendar' => true,
'intlgregoriancalendar' => true,
'spoofchecker' => true,
'intlexception' => true,
'intliterator' => true,
'intlbreakiterator' => true,
'intlrulebasedbreakiterator' => true,
'intlcodepointbreakiterator' => true,
'intlpartsiterator' => true,
'uconverter' => true,
'intlchar' => true,
'traversable' => true,
'iteratoraggregate' => true,
'iterator' => true,
'arrayaccess' => true,
'serializable' => true,
'throwable' => true,
'datetimeinterface' => true,
'jsonserializable' => true,
'recursiveiterator' => true,
'outeriterator' => true,
'countable' => true,
'seekableiterator' => true,
'splobserver' => true,
'splsubject' => true,
'sessionhandlerinterface' => true,
'sessionidinterface' => true,
'sessionupdatetimestamphandlerinterface' => true,
'reflector' => true,
'stringable' => true,// PHP >= 8.0
'weakreference' => true,// PHP >= 7.4.0
];
protected function updateCache(ClassReflection $class)
{
$name = $class->getName();
$this->namespaces[$class->getNamespace()] = $class->getNamespace();
// add sub-namespaces
$namespace = $class->getNamespace() ?? '';
while ($namespace = substr($namespace, 0, (int) strrpos($namespace, '\\'))) {
$this->namespaces[$namespace] = $namespace;
}
if ($class->isException()) {
$this->namespaceExceptions[$class->getNamespace()][$name] = $class;
} elseif ($class->isInterface()) {
$this->namespaceInterfaces[$class->getNamespace()][$name] = $class;
$this->interfaces[$name] = $class;
} else {
$this->namespaceClasses[$class->getNamespace()][$name] = $class;
}
}
protected function prepareDir($dir)
{
static $prepared = [];
$dir = $this->replaceVars($dir);
if (isset($prepared[$dir])) {
return $dir;
}
$prepared[$dir] = true;
if (!is_dir($dir)) {
$this->flushDir($dir);
return $dir;
}
$doctumVersion = null;
if (file_exists($dir . '/DOCTUM_VERSION')) {
$doctumVersion = file_get_contents($dir . '/DOCTUM_VERSION');
}
if (Doctum::VERSION !== $doctumVersion) {
$this->flushDir($dir);
}
return $dir;
}
protected function replaceVars(string $pattern): string
{
return str_replace('%version%', (string) $this->version, $pattern);
}
protected function parseVersion(Version $version, $previous, $callback = null, $force = false): void
{
if (null === $this->parser) {
throw new \LogicException('You must set a parser.');
}
if ($version->isFrozen() && count($this->classes) > 0) {
return;
}
if ($force) {
$this->store->flushProject($this);
}
if ($previous && 0 === count($this->classes)) {
$this->seedCache($previous, $this->getCacheDir());
}
$transaction = $this->parser->parse($this, $callback);
if (null !== $callback) {
call_user_func($callback, Message::PARSE_VERSION_FINISHED, $transaction);
}
}
protected function renderVersion(Version $version, $previous, $callback = null, $force = false): void
{
if (null === $this->renderer) {
throw new \LogicException('You must set a renderer.');
}
$frozen = $version->isFrozen() && $this->renderer->isRendered($this) && $this->version === file_get_contents($this->getBuildDir() . '/PROJECT_VERSION');
if ($force && !$frozen) {
$this->flushDir($this->getBuildDir());
}
if ($previous && !$this->renderer->isRendered($this)) {
$this->seedCache($previous, $this->getBuildDir());
}
$diff = $this->renderer->render($this, $callback, $force);
if (null !== $callback) {
call_user_func($callback, Message::RENDER_VERSION_FINISHED, $diff);
}
}
public function getSourceRoot()
{
$root = $this->getConfig('source_url');
if (! $root) {
return;
}
if (strpos($root, 'github') !== false) {
return $root . '/tree/' . $this->version;
}
}
public function getViewSourceUrl($relativePath, $line)
{
$remoteRepository = $this->getConfig('remote_repository');
if ($remoteRepository instanceof AbstractRemoteRepository) {
return $remoteRepository->getFileUrl($this->version, $relativePath, $line);
}
return '';
}
public function getBaseUrl(): ?string
{
$url = $this->getConfig('base_url');
return $url === null ? null : rtrim($url, '/');
}
public function hasFooterLink(): bool
{
return $this->getConfig('footer_link') !== null && is_array($this->getConfig('footer_link'));
}
/**
* @return array<string,string>
* @phpstan-return array{href: string, rel: string, target: string, before_text: string, link_text: string, after_text: string}
*/
public function getFooterLink(): array
{
$link = $this->getConfig('footer_link');
return [
'href' => $link['href'] ?? '',
'target' => $link['target'] ?? '',
'rel' => $link['rel'] ?? '',
'before_text' => $link['before_text'] ?? '',
'link_text' => $link['link_text'] ?? '',
'after_text' => $link['after_text'] ?? '',
];
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum;
class Indexer
{
private const TYPE_CLASS = 1;
private const TYPE_METHOD = 2;
private const TYPE_NAMESPACE = 3;
public function getIndex(Project $project)
{
$index = [
'searchIndex' => [],
'info' => [],
];
foreach ($project->getNamespaces() as $namespace) {
$index['searchIndex'][] = $this->getSearchString($namespace);
$index['info'][] = [self::TYPE_NAMESPACE, $namespace];
}
foreach ($project->getProjectClasses() as $class) {
$index['searchIndex'][] = $this->getSearchString((string) $class);
$index['info'][] = [self::TYPE_CLASS, $class];
}
foreach ($project->getProjectClasses() as $class) {
foreach ($class->getMethods() as $method) {
$index['searchIndex'][] = $this->getSearchString((string) $method);
$index['info'][] = [self::TYPE_METHOD, $method];
}
}
return $index;
}
protected function getSearchString($string)
{
return strtolower(preg_replace('/\s+/', '', $string));
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
use Doctum\Project;
class FunctionReflection extends Reflection
{
protected $namespace;
/** @var array<string,ParameterReflection> */
protected $parameters = [];
protected $byRef;
protected $project;
protected $relativeFilePath;
protected $exceptions = [];
public function __toString()
{
return $this->namespace . '\\' . $this->name;
}
public function setByRef($boolean)
{
$this->byRef = $boolean;
}
public function isByRef()
{
return $this->byRef;
}
/**
* @return Project
*/
public function getProject()
{
return $this->project;
}
public function setProject(Project $project)
{
$this->project = $project;
}
public function setNamespace($namespace)
{
$this->namespace = ltrim($namespace, '\\');
}
public function getNamespace()
{
return $this->namespace;
}
public function getClass()
{
return null;
}
public function setClass(ClassReflection $class)
{
return; // No op
}
/**
* @overrides
*/
public function getHint()
{
if (! is_array($this->hint)) {
return [];
}
$hints = [];
$project = $this->getProject();
foreach ($this->hint as $hint) {
$hints[] = new HintReflection(Project::isPhpTypeHint($hint[0]) ? $hint[0] : $project->getClass($hint[0]), $hint[1]);
}
return $hints;
}
/**
* @return void
*/
public function addParameter(ParameterReflection $parameter)
{
$this->parameters[$parameter->getName()] = $parameter;
$parameter->setFunction($this);
}
/**
* @return array<string,ParameterReflection>
*/
public function getParameters()
{
return $this->parameters;
}
public function getParameter($name)
{
if (ctype_digit((string) $name)) {
$tmp = array_values($this->parameters);
return $tmp[$name] ?? null;
}
return $this->parameters[$name] ?? null;
}
/**
* Can be any iterator (so that we can lazy-load the parameters)
* @param array<string,ParameterReflection> $parameters
* @return void
*/
public function setParameters($parameters)
{
$this->parameters = $parameters;
}
public function setExceptions($exceptions)
{
$this->exceptions = $exceptions;
}
public function getExceptions()
{
$exceptions = [];
foreach ($this->exceptions as $exception) {
$exception[0] = $this->getProject()->getClass($exception[0]);
$exceptions[] = $exception;
}
return $exceptions;
}
public function getRawExceptions()
{
return $this->exceptions;
}
public function setRelativeFilePath($relativeFilePath)
{
$this->relativeFilePath = $relativeFilePath;
}
public function getRelativeFilePath()
{
return $this->relativeFilePath;
}
public function getSourcePath()
{
if (null === $this->relativeFilePath) {
return '';
}
return $this->project->getViewSourceUrl($this->relativeFilePath, $this->line);
}
public function toArray()
{
return [
'namespace' => $this->namespace,
'name' => $this->name,
'line' => $this->line,
'short_desc' => $this->shortDesc,
'long_desc' => $this->longDesc,
'hint' => $this->hint,
'hint_desc' => $this->hintDesc,
'tags' => $this->tags,
'modifiers' => $this->modifiers,
'is_by_ref' => $this->byRef,
'exceptions' => $this->exceptions,
'errors' => $this->errors,
'parameters' => array_map(
static function ($parameter) {
return $parameter->toArray();
},
$this->parameters
),
];
}
/**
* @return self
*/
public static function fromArray(Project $project, array $array)
{
$method = new self($array['name'], $array['line']);
$method->shortDesc = $array['short_desc'];
$method->longDesc = $array['long_desc'];
$method->hint = $array['hint'];
$method->hintDesc = $array['hint_desc'];
$method->tags = $array['tags'];
$method->modifiers = $array['modifiers'];
$method->byRef = $array['is_by_ref'];
$method->exceptions = $array['exceptions'];
$method->errors = $array['errors'];
$method->namespace = $array['namespace'] ?? '';// New in 5.4.0
foreach ($array['parameters'] as $parameter) {
$method->addParameter(ParameterReflection::fromArray($project, $parameter));
}
return $method;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
use Doctum\Project;
class MethodReflection extends Reflection
{
/** @var ClassReflection */
protected $class;
protected $parameters = [];
protected $byRef;
protected $exceptions = [];
public function __toString()
{
return $this->class . '::' . $this->name;
}
public function setByRef($boolean)
{
$this->byRef = $boolean;
}
public function isByRef()
{
return $this->byRef;
}
/**
* {@inheritDoc}
*/
public function setModifiers(int $modifiers): void
{
// if no modifiers, method is public
if (0 === ($modifiers & self::VISIBILITY_MODIFER_MASK)) {
$modifiers |= self::MODIFIER_PUBLIC;
}
$this->modifiers = $modifiers;
}
/**
* @return ClassReflection|null
*/
public function getClass()
{
return $this->class;
}
public function setClass(ClassReflection $class)
{
$this->class = $class;
}
public function addParameter(ParameterReflection $parameter)
{
$this->parameters[$parameter->getName()] = $parameter;
$parameter->setMethod($this);
}
public function getParameters()
{
return $this->parameters;
}
public function getParameter($name)
{
if (ctype_digit((string) $name)) {
$tmp = array_values($this->parameters);
return $tmp[$name] ?? null;
}
return $this->parameters[$name] ?? null;
}
/*
* Can be any iterator (so that we can lazy-load the parameters)
*/
public function setParameters($parameters)
{
$this->parameters = $parameters;
}
public function setExceptions($exceptions)
{
$this->exceptions = $exceptions;
}
public function getExceptions()
{
$exceptions = [];
foreach ($this->exceptions as $exception) {
$exception[0] = $this->class->getProject()->getClass($exception[0]);
$exceptions[] = $exception;
}
return $exceptions;
}
public function getRawExceptions()
{
return $this->exceptions;
}
public function getSourcePath()
{
return $this->class->getSourcePath($this->line);
}
public function toArray()
{
return [
'name' => $this->name,
'line' => $this->line,
'short_desc' => $this->shortDesc,
'long_desc' => $this->longDesc,
'hint' => $this->hint,
'hint_desc' => $this->hintDesc,
'tags' => $this->tags,
'see' => $this->see,
'modifiers' => $this->modifiers,
'is_by_ref' => $this->byRef,
'exceptions' => $this->exceptions,
'errors' => $this->errors,
'parameters' => array_map(
static function ($parameter) {
return $parameter->toArray();
},
$this->parameters
),
];
}
/**
* @return self
*/
public static function fromArray(Project $project, array $array)
{
$method = new self($array['name'], $array['line']);
$method->shortDesc = $array['short_desc'];
$method->longDesc = $array['long_desc'];
$method->hint = $array['hint'];
$method->hintDesc = $array['hint_desc'];
$method->tags = $array['tags'];
$method->modifiers = $array['modifiers'];
$method->byRef = $array['is_by_ref'];
$method->exceptions = $array['exceptions'];
$method->errors = $array['errors'];
$method->see = $array['see'] ?? [];// New in 5.4.0
foreach ($array['parameters'] as $parameter) {
$method->addParameter(ParameterReflection::fromArray($project, $parameter));
}
return $method;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
class LazyClassReflection extends ClassReflection
{
protected $loaded = false;
public function __construct($name)
{
parent::__construct($name, -1);
}
public function isProjectClass(): bool
{
if (false === $this->loaded) {
$this->load();
}
return parent::isProjectClass();
}
public function getShortDesc()
{
if (false === $this->loaded) {
$this->load();
}
return parent::getShortDesc();
}
public function setShortDesc($shortDesc)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function getLongDesc()
{
if (false === $this->loaded) {
$this->load();
}
return parent::getLongDesc();
}
public function setLongDesc($longDesc)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function getHint()
{
if (false === $this->loaded) {
$this->load();
}
return parent::getHint();
}
public function setHint($hint)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function isAbstract(): bool
{
if (false === $this->loaded) {
$this->load();
}
return parent::isAbstract();
}
public function isFinal(): bool
{
if (false === $this->loaded) {
$this->load();
}
return parent::isFinal();
}
public function getFile()
{
if (false === $this->loaded) {
$this->load();
}
return parent::getFile();
}
public function setFile($file)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
/**
* {@inheritDoc}
*/
public function setModifiers(int $modifiers): void
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function addProperty(PropertyReflection $property)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function getProperties($deep = false)
{
if (false === $this->loaded) {
$this->load();
}
return parent::getProperties($deep);
}
public function setProperties($properties)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function addMethod(MethodReflection $method)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function getParentMethod($name)
{
if (false === $this->loaded) {
$this->load();
}
return parent::getParentMethod($name);
}
public function getMethods($deep = false)
{
if (false === $this->loaded) {
$this->load();
}
return parent::getMethods($deep);
}
public function setMethods($methods)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function addInterface($interface)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function getInterfaces($deep = false)
{
if (false === $this->loaded) {
$this->load();
}
return parent::getInterfaces($deep);
}
public function setParent($parent)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function getParent($deep = false)
{
if (false === $this->loaded) {
$this->load();
}
return parent::getParent($deep);
}
public function setInterface($boolean)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
public function isInterface()
{
if (false === $this->loaded) {
$this->load();
}
return parent::isInterface();
}
public function isException()
{
if (false === $this->loaded) {
$this->load();
}
return parent::isException();
}
public function getAliases()
{
if (false === $this->loaded) {
$this->load();
}
return parent::getAliases();
}
public function setAliases($aliases)
{
throw new \LogicException('A LazyClassReflection instance is read-only.');
}
protected function load()
{
$class = $this->project->loadClass($this->name);
if (null === $class) {
$this->projectClass = false;
} else {
foreach (array_keys(get_class_vars(ClassReflection::class)) as $property) {
$this->$property = $class->$property;
}
}
$this->loaded = true;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
use Doctum\Project;
class ParameterReflection extends Reflection
{
/** @var FunctionReflection */
protected $function;
/** @var MethodReflection */
protected $method;
protected $byRef;
protected $default;
protected $variadic;
public function __toString()
{
return $this->method . '#' . $this->name;
}
/**
* @return ClassReflection|null
*/
public function getClass()
{
return $this->method->getClass();
}
public function setByRef($boolean)
{
$this->byRef = $boolean;
}
public function isByRef()
{
return $this->byRef;
}
public function setDefault($default)
{
$this->default = $default;
}
public function getDefault()
{
return $this->default;
}
public function setVariadic($variadic)
{
$this->variadic = $variadic;
}
public function getVariadic()
{
return $this->variadic;
}
/**
* @return MethodReflection|null
*/
public function getMethod()
{
return $this->method;
}
public function setMethod(MethodReflection $method)
{
$this->method = $method;
}
/**
* @return FunctionReflection|null
*/
public function getFunction()
{
return $this->function;
}
public function setFunction(FunctionReflection $function)
{
$this->function = $function;
}
/**
* @overrides
*/
public function getHint()
{
if (! is_array($this->hint)) {
return [];
}
$hints = [];
if (isset($this->function)) {
/** @var FunctionReflection $function */
$function = $this->getFunction();
$project = $function->getProject();
} else {
/** @var ClassReflection $class */
$class = $this->getClass();
$project = $class->getProject();
}
foreach ($this->hint as $hint) {
$hints[] = new HintReflection(Project::isPhpTypeHint($hint[0]) ? $hint[0] : $project->getClass($hint[0]), $hint[1]);
}
return $hints;
}
public function toArray()
{
return [
'name' => $this->name,
'line' => $this->line,
'short_desc' => $this->shortDesc,
'long_desc' => $this->longDesc,
'hint' => $this->hint,
'tags' => $this->tags,
'modifiers' => $this->modifiers,
'default' => $this->default,
'variadic' => $this->variadic,
'is_by_ref' => $this->byRef,
'is_read_only' => $this->isReadOnly(),
];
}
/**
* @return self
*/
public static function fromArray(Project $project, array $array)
{
$parameter = new self($array['name'], $array['line']);
$parameter->shortDesc = $array['short_desc'];
$parameter->longDesc = $array['long_desc'];
$parameter->hint = $array['hint'];
$parameter->tags = $array['tags'];
$parameter->modifiers = $array['modifiers'];
$parameter->default = $array['default'];
$parameter->variadic = $array['variadic'];
$parameter->byRef = $array['is_by_ref'];
$parameter->isReadOnly = $array['is_read_only'] ?? false;// New in 5.4.0
return $parameter;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
use Doctum\Project;
abstract class Reflection
{
// Constants from: vendor/nikic/php-parser/lib/PhpParser/Node/Stmt/Class_.php
public const MODIFIER_PUBLIC = 1;
public const MODIFIER_PROTECTED = 2;
public const MODIFIER_PRIVATE = 4;
public const MODIFIER_STATIC = 8;
public const MODIFIER_ABSTRACT = 16;
public const MODIFIER_FINAL = 32;
protected const VISIBILITY_MODIFER_MASK = 7; // 1 | 2 | 4
/** @var string */
protected $name;
/** @var int */
protected $line;
/** @var string */
protected $shortDesc;
/** @var string */
protected $longDesc;
/** @var string|null|array<int,mixed> */
protected $hint;
/** @var string */
protected $hintDesc;
/** @var array<string,array> */
protected $tags;
/** @var string|null */
protected $docComment;
/** @var array<int,array<int,string|false>> */
protected $see = [];
/** @var string[] */
protected $errors = [];
/** @var bool */
protected $isReadOnly = false;
/** @var int */
protected $modifiers;
public function __construct(string $name, $line)
{
$this->name = $name;
$this->line = $line;
$this->tags = [];
}
/**
* @return self|null
*/
abstract public function getClass();
/**
* Set the modifier flags
*
* @phpstan-param self::MODIFIER_*|int $modifiers
*/
public function setModifiers(int $modifiers): void
{
$this->modifiers = $modifiers;
}
/**
* Set the modifier from phpdoc tags
*
*/
public function setModifiersFromTags(): void
{
$hasFinalTag = count($this->getTags('final')) > 0;
$hasProtectedTag = count($this->getTags('protected')) > 0;
$hasPrivateTag = count($this->getTags('private')) > 0;
$hasPublicTag = count($this->getTags('public')) > 0;
$hasStaticTag = count($this->getTags('static')) > 0;
$accessTags = $this->getTags('access');
$hasAccessTag = count($accessTags) > 0;
$flags = $this->modifiers ?? 0;
if ($hasAccessTag) {
$accessTag = strtolower(trim(implode('', $accessTags[0])));
if ($accessTag === 'protected') {
$hasProtectedTag = true;
} elseif ($accessTag === 'private') {
$hasPrivateTag = true;
} elseif ($accessTag === 'public') {
$hasPublicTag = true;
}
}
if ($hasFinalTag) {
$flags |= self::MODIFIER_FINAL;
}
if ($hasProtectedTag) {
$flags |= self::MODIFIER_PROTECTED;
}
if ($hasPrivateTag) {
$flags |= self::MODIFIER_PRIVATE;
}
if ($hasPublicTag) {
$flags |= self::MODIFIER_PUBLIC;
}
if ($hasStaticTag) {
$flags |= self::MODIFIER_STATIC;
}
$this->setModifiers($flags);
}
public function isPublic(): bool
{
return self::MODIFIER_PUBLIC === (self::MODIFIER_PUBLIC & $this->modifiers);
}
public function isProtected(): bool
{
return self::MODIFIER_PROTECTED === (self::MODIFIER_PROTECTED & $this->modifiers);
}
public function isPrivate(): bool
{
return self::MODIFIER_PRIVATE === (self::MODIFIER_PRIVATE & $this->modifiers);
}
public function isStatic(): bool
{
return self::MODIFIER_STATIC === (self::MODIFIER_STATIC & $this->modifiers);
}
public function isAbstract(): bool
{
return self::MODIFIER_ABSTRACT === (self::MODIFIER_ABSTRACT & $this->modifiers);
}
public function isFinal(): bool
{
return self::MODIFIER_FINAL === (self::MODIFIER_FINAL & $this->modifiers);
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): void
{
$this->name = $name;
}
/**
* @return int
*/
public function getLine()
{
return $this->line;
}
/**
* @param int $line
*/
public function setLine($line): void
{
$this->line = $line;
}
/**
* @return string
*/
public function getShortDesc()
{
return $this->shortDesc;
}
/**
* @param string $shortDesc
* @return void
*/
public function setShortDesc($shortDesc)
{
$this->shortDesc = $shortDesc;
}
/**
* @return string
*/
public function getLongDesc()
{
return $this->longDesc;
}
/**
* @param string $longDesc
* @return void
*/
public function setLongDesc($longDesc)
{
$this->longDesc = $longDesc;
}
/**
* @return HintReflection[]
*/
public function getHint()
{
if (! is_array($this->hint)) {
return [];
}
$hints = [];
/** @var FunctionReflection $class Not sure this is the right type */
$class = $this->getClass();
$project = $class->getProject();
foreach ($this->hint as $hint) {
$hints[] = new HintReflection(Project::isPhpTypeHint($hint[0]) ? $hint[0] : $project->getClass($hint[0]), $hint[1]);
}
return $hints;
}
/**
* @return string
* @example string|int
*/
public function getHintAsString()
{
$str = [];
foreach ($this->getHint() as $hint) {
$str[] = ($hint->isClass() ? $hint->getName()->getShortName() : $hint->getName()) . ($hint->isArray() ? '[]' : '');
}
return implode('|', $str);
}
public function hasHint(): bool
{
return $this->hint ? true : false;
}
/**
* @param string|array|null $hint
* @return void
*/
public function setHint($hint)
{
$this->hint = $hint;
}
/**
* @return string|array|null
*/
public function getRawHint()
{
return $this->hint;
}
/**
* @return void
*/
public function setHintDesc($desc)
{
$this->hintDesc = $desc;
}
/**
* @return string
*/
public function getHintDesc()
{
return $this->hintDesc;
}
/**
* @param array<string,array> $tags
* @return void
*/
public function setTags(array $tags): void
{
$this->tags = $tags;
}
/**
* @return array<string,array>
*/
public function getTags($name)
{
return $this->tags[$name] ?? [];
}
/**
* @return array<string,array>
*/
public function getDeprecated()
{
return $this->getTags('deprecated');
}
/**
* @return array<string,array>
*/
public function getTodo()
{
return $this->getTags('todo');
}
public function isDeprecated(): bool
{
return ! empty($this->getDeprecated());
}
public function isInternal(): bool
{
return ! empty($this->getTags('internal'));
}
public function hasExamples(): bool
{
return ! empty($this->getExamples());
}
/**
* @return array[]
*/
public function getExamples(): array
{
return $this->getTags('example');
}
public function hasSince(): bool
{
return $this->getSince() !== null;
}
public function getSince(): ?string
{
/** @var array[] $sinceTags */
$sinceTags = $this->getTags('since');
return count($sinceTags) > 0 ? implode(' ', $sinceTags[0]) : null;
}
/**
* Get the internal tags
*
* @return array<string,array>
*/
public function getInternal(): array
{
return $this->getTags('internal');
}
public function setReadOnly(bool $isReadOnly): void
{
$this->isReadOnly = $isReadOnly;
}
public function isReadOnly(): bool
{
return $this->isReadOnly;
}
/**
* not serialized as it is only useful when parsing
* @param string|null $comment
* @return void
*/
public function setDocComment($comment)
{
$this->docComment = $comment;
}
/**
* @return string|null
*/
public function getDocComment()
{
return $this->docComment;
}
/**
* @return array[]
*/
public function getSee()
{
$see = [];
/** @var FunctionReflection $class Not sure this is the right type */
$class = $this->getClass();
$project = $class->getProject();
foreach ($this->see as $seeElem) {
if ($seeElem[3]) {
$seeElem = $this->prepareMethodSee($seeElem);
} elseif ($seeElem[2]) {
$seeElem[2] = Project::isPhpTypeHint($seeElem[2]) ? $seeElem[2] : $project->getClass($seeElem[2]);
}
$see[] = $seeElem;
}
return $see;
}
/**
* @param array<int,array<int,string|false>> $see
* @return void
*/
public function setSee(array $see)
{
$this->see = $see;
}
/**
* @param array<int,string|false> $seeElem
* @return array<int,MethodReflection|ClassReflection|false>
*/
private function prepareMethodSee(array $seeElem): array
{
/** @var FunctionReflection $class Not sure this is the right type */
$class = $this->getClass();
$project = $class->getProject();
$method = null;
if ($seeElem[2] !== false) {
$class = $project->getClass($seeElem[2]);
$method = $class->getMethod($seeElem[3]);
}
if ($method) {
$seeElem[2] = false;
$seeElem[3] = $method;
} else {
$seeElem[2] = false;
$seeElem[3] = false;
}
return $seeElem;
}
/**
* @return string[]
*/
public function getErrors(): array
{
return $this->errors;
}
/**
* @param string[] $errors
*/
public function setErrors(array $errors): void
{
$this->errors = $errors;
}
/**
* @param array<string,mixed> $array
* @return self
*/
abstract public static function fromArray(Project $project, array $array);
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
use Doctum\Project;
class ConstantReflection extends Reflection
{
protected $class;
public function __toString()
{
return $this->class . '::' . $this->name;
}
public function getClass()
{
return $this->class;
}
public function setClass(ClassReflection $class)
{
$this->class = $class;
}
public function toArray()
{
return [
'name' => $this->name,
'line' => $this->line,
'short_desc' => $this->shortDesc,
'long_desc' => $this->longDesc,
'modifiers' => $this->modifiers,
'tags' => $this->tags,
];
}
/**
* @return self
*/
public static function fromArray(Project $project, array $array)
{
$constant = new self($array['name'], $array['line']);
$constant->shortDesc = $array['short_desc'];
$constant->longDesc = $array['long_desc'];
$constant->modifiers = $array['modifiers'] ?? 0;// New in 5.4.0
$constant->tags = $array['tags'] ?? [];// New in 5.4.0
return $constant;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
use Doctum\Project;
class ClassReflection extends Reflection
{
private const CATEGORY_CLASS = 1;
private const CATEGORY_INTERFACE = 2;
private const CATEGORY_TRAIT = 3;
/** @var Project */
protected $project;
protected $hash;
protected $namespace;
/** @var PropertyReflection[] */
protected $properties = [];
/** @var MethodReflection[] */
protected $methods = [];
protected $interfaces = [];
protected $constants = [];
protected $traits = [];
protected $parent;
protected $file;
protected $relativeFilePath;
protected $category = self::CATEGORY_CLASS;
protected $projectClass = true;
protected $aliases = [];
protected $fromCache = false;
public function __toString()
{
return $this->name;
}
public function getClass()
{
return $this;
}
public function getCategoryId(): int
{
return $this->category;
}
public function isProjectClass(): bool
{
return $this->projectClass;
}
public function isPhpClass(): bool
{
return isset(Project::$phpInternalClasses[strtolower($this->name)]);
}
public function setName(string $name): void
{
parent::setName(ltrim($name, '\\'));
}
public function getShortName()
{
$pos = strrpos($this->name, '\\');
if ($pos !== false) {
return substr($this->name, $pos + 1);
}
return $this->name;
}
public function getHash()
{
return $this->hash;
}
public function setHash($hash)
{
$this->hash = $hash;
}
public function getFile()
{
return $this->file;
}
public function setFile($file)
{
$this->file = $file;
}
public function setRelativeFilePath($relativeFilePath)
{
$this->relativeFilePath = $relativeFilePath;
}
public function getRelativeFilePath()
{
return $this->relativeFilePath;
}
public function getSourcePath($line = null)
{
if (null === $this->relativeFilePath) {
return '';
}
return $this->project->getViewSourceUrl($this->relativeFilePath, $line);
}
/**
* @return Project
*/
public function getProject()
{
return $this->project;
}
/**
* @return void
*/
public function setProject(Project $project)
{
$this->project = $project;
}
/**
* @return void
*/
public function setNamespace(string $namespace): void
{
$this->namespace = ltrim($namespace, '\\');
}
public function getNamespace()
{
return $this->namespace;
}
public function addProperty(PropertyReflection $property)
{
$this->properties[$property->getName()] = $property;
$property->setClass($this);
}
public function getProperties($deep = false)
{
if (false === $deep) {
return $this->properties;
}
$properties = [];
if ($this->getParent()) {
foreach ($this->getParent()->getProperties(true) as $name => $property) {
$properties[$name] = $property;
}
}
foreach ($this->getTraits(true) as $trait) {
foreach ($trait->getProperties(true) as $name => $property) {
$properties[$name] = $property;
}
}
foreach ($this->properties as $name => $property) {
$properties[$name] = $property;
}
return $properties;
}
/**
* Can be any iterator (so that we can lazy-load the properties)
*
* @param PropertyReflection[] $properties
* @return void
*/
public function setProperties($properties)
{
$this->properties = $properties;
}
/**
* @return void
*/
public function addConstant(ConstantReflection $constant)
{
$this->constants[$constant->getName()] = $constant;
$constant->setClass($this);
}
public function getConstants($deep = false)
{
if (false === $deep) {
return $this->constants;
}
$constants = [];
if ($this->getParent()) {
foreach ($this->getParent()->getConstants(true) as $name => $constant) {
$constants[$name] = $constant;
}
}
foreach ($this->constants as $name => $constant) {
$constants[$name] = $constant;
}
return $constants;
}
public function setConstants($constants)
{
$this->constants = $constants;
}
/**
* @return void
*/
public function addMethod(MethodReflection $method)
{
$this->methods[$method->getName()] = $method;
$method->setClass($this);
}
/**
* @return MethodReflection|false False if not found
*/
public function getMethod($name)
{
return $this->methods[$name] ?? false;
}
public function getParentMethod($name)
{
if ($this->getParent()) {
foreach ($this->getParent()->getMethods(true) as $n => $method) {
if ($name == $n) {
return $method;
}
}
}
foreach ($this->getInterfaces(true) as $interface) {
foreach ($interface->getMethods(true) as $n => $method) {
if ($name == $n) {
return $method;
}
}
}
}
public function getMethods($deep = false)
{
if (false === $deep) {
return $this->methods;
}
$methods = [];
if ($this->isInterface()) {
foreach ($this->getInterfaces(true) as $interface) {
foreach ($interface->getMethods(true) as $name => $method) {
$methods[$name] = $method;
}
}
}
if ($this->getParent()) {
foreach ($this->getParent()->getMethods(true) as $name => $method) {
$methods[$name] = $method;
}
}
foreach ($this->getTraits(true) as $trait) {
foreach ($trait->getMethods(true) as $name => $method) {
$methods[$name] = $method;
}
}
foreach ($this->methods as $name => $method) {
$methods[$name] = $method;
}
return $methods;
}
/**
* @return void
*/
public function setMethods($methods)
{
$this->methods = $methods;
}
/**
* @param \PhpParser\Node\Stmt\Interface_ $interface
* @return void
*/
public function addInterface($interface)
{
$this->interfaces[$interface] = $interface;
}
/**
* @return ClassReflection[]
*/
public function getInterfaces($deep = false)
{
$interfaces = [];
foreach ($this->interfaces as $interface) {
$interfaces[] = $this->project->getClass($interface);
}
if (false === $deep) {
return $interfaces;
}
$allInterfaces = $interfaces;
foreach ($interfaces as $interface) {
$allInterfaces = array_merge($allInterfaces, $interface->getInterfaces(true));
}
if ($parent = $this->getParent()) {
$allInterfaces = array_merge($allInterfaces, $parent->getInterfaces(true));
}
return $allInterfaces;
}
/**
* @param \PhpParser\Node\Stmt\Trait_ $trait
* @return void
*/
public function addTrait($trait)
{
$this->traits[$trait] = $trait;
}
/**
* @return ClassReflection[]
*/
public function getTraits($deep = false)
{
$traits = [];
foreach ($this->traits as $trait) {
$traits[] = $this->project->getClass($trait);
}
if (false === $deep) {
return $traits;
}
$allTraits = $traits;
foreach ($traits as $trait) {
$allTraits = array_merge($allTraits, $trait->getTraits(true));
}
if ($parent = $this->getParent()) {
$allTraits = array_merge($allTraits, $parent->getTraits(true));
}
return $allTraits;
}
/**
* @return void
*/
public function setTraits($traits)
{
$this->traits = $traits;
}
/**
* @return void
*/
public function setParent($parent)
{
$this->parent = $parent;
}
public function getParent($deep = false)
{
if (!$this->parent) {
return $deep ? [] : null;
}
$parent = $this->project->getClass($this->parent);
if (false === $deep) {
return $parent;
}
return array_merge([$parent], $parent->getParent(true));
}
/**
* @return void
*/
public function setInterface($boolean)
{
$this->category = $boolean ? self::CATEGORY_INTERFACE : self::CATEGORY_CLASS;
}
/**
* @return bool
*/
public function isInterface()
{
return self::CATEGORY_INTERFACE === $this->category;
}
/**
* @return void
*/
public function setTrait($boolean)
{
$this->category = $boolean ? self::CATEGORY_TRAIT : self::CATEGORY_CLASS;
}
/**
* @return bool
*/
public function isTrait()
{
return self::CATEGORY_TRAIT === $this->category;
}
/**
* @return void
*/
public function setCategory($category)
{
$this->category = $category;
}
public function hasMixins(): bool
{
return ! empty($this->getTags('mixin'));
}
/**
* @return array<int,array<string,ClassReflection>>
*/
public function getMixins(): array
{
$mixins = [];
foreach ($this->getTags('mixin') as $mixin) {
$mixins[] = [
'class' => new ClassReflection($mixin[0], -1),
];
}
return $mixins;
}
/**
* @return bool
*/
public function isException()
{
$parent = $this;
while ($parent = $parent->getParent()) {
if ('Exception' == $parent->getName()) {
return true;
}
}
return false;
}
public function getAliases()
{
return $this->aliases;
}
/**
* @return void
*/
public function setAliases($aliases)
{
$this->aliases = $aliases;
}
public function isFromCache()
{
return $this->fromCache;
}
/**
* @return void
*/
public function notFromCache()
{
$this->fromCache = false;
}
/**
* @return array<string,mixed>
*/
public function toArray()
{
return [
'name' => $this->name,
'line' => $this->line,
'short_desc' => $this->shortDesc,
'long_desc' => $this->longDesc,
'hint' => $this->hint,
'tags' => $this->tags,
'namespace' => $this->namespace,
'file' => $this->file,
'relative_file' => $this->relativeFilePath,
'hash' => $this->hash,
'parent' => $this->parent,
'modifiers' => $this->modifiers,
'is_trait' => $this->isTrait(),
'is_interface' => $this->isInterface(),
'is_read_only' => $this->isReadOnly(),
'aliases' => $this->aliases,
'errors' => $this->errors,
'interfaces' => $this->interfaces,
'traits' => $this->traits,
'properties' => array_map(
static function ($property) {
return $property->toArray();
},
$this->properties
),
'methods' => array_map(
static function ($method) {
return $method->toArray();
},
$this->methods
),
'constants' => array_map(
static function ($constant) {
return $constant->toArray();
},
$this->constants
),
];
}
/**
* @return self
*/
public static function fromArray(Project $project, array $array)
{
$class = new self($array['name'], $array['line']);
$class->shortDesc = $array['short_desc'];
$class->longDesc = $array['long_desc'];
$class->hint = $array['hint'];
$class->tags = $array['tags'];
$class->namespace = $array['namespace'];
$class->hash = $array['hash'];
$class->file = $array['file'];
$class->relativeFilePath = $array['relative_file'];
$class->modifiers = $array['modifiers'];
$class->fromCache = true;
if (isset($array['is_read_only'])) {// New in 5.4.0
$class->setReadOnly($array['is_read_only']);
}
if ($array['is_interface']) {
$class->setInterface(true);
}
if ($array['is_trait']) {
$class->setTrait(true);
}
$class->aliases = $array['aliases'];
$class->errors = $array['errors'];
$class->parent = $array['parent'];
$class->interfaces = $array['interfaces'];
$class->constants = $array['constants'];
$class->traits = $array['traits'];
$class->setProject($project);
foreach ($array['methods'] as $method) {
$class->addMethod(MethodReflection::fromArray($project, $method));
}
foreach ($array['properties'] as $property) {
$class->addProperty(PropertyReflection::fromArray($project, $property));
}
foreach ($array['constants'] as $constant) {
$class->addConstant(ConstantReflection::fromArray($project, $constant));
}
return $class;
}
/**
* @return void
*/
public function sortInterfaces($sort)
{
if (is_callable($sort)) {
uksort($this->interfaces, $sort);
} else {
ksort($this->interfaces);
}
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
use Doctum\Project;
class PropertyReflection extends Reflection
{
protected $class;
protected $default;
/** @var bool */
protected $isWriteOnly = false;
public function __toString()
{
return $this->class . '::$' . $this->name;
}
/**
* {@inheritDoc}
*/
public function setModifiers(int $modifiers): void
{
// if no modifiers, property is public
if (0 === ($modifiers & self::VISIBILITY_MODIFER_MASK)) {
$modifiers |= self::MODIFIER_PUBLIC;
}
$this->modifiers = $modifiers;
}
public function setWriteOnly(bool $isWriteOnly): void
{
$this->isWriteOnly = $isWriteOnly;
}
public function isWriteOnly(): bool
{
return $this->isWriteOnly;
}
public function setDefault($default): void
{
$this->default = $default;
}
public function getDefault()
{
return $this->default;
}
public function getClass()
{
return $this->class;
}
public function setClass(ClassReflection $class): void
{
$this->class = $class;
}
/**
* @return array<string,mixed>
*/
public function toArray()
{
return [
'name' => $this->name,
'line' => $this->line,
'short_desc' => $this->shortDesc,
'long_desc' => $this->longDesc,
'hint' => $this->hint,
'hint_desc' => $this->hintDesc,
'tags' => $this->tags,
'modifiers' => $this->modifiers,
'default' => $this->default,
'errors' => $this->errors,
'is_read_only' => $this->isReadOnly(),
'is_write_only' => $this->isWriteOnly(),
];
}
/**
* @return self
*/
public static function fromArray(Project $project, array $array)
{
$property = new self($array['name'], $array['line']);
$property->shortDesc = $array['short_desc'];
$property->longDesc = $array['long_desc'];
$property->hint = $array['hint'];
$property->hintDesc = $array['hint_desc'];
$property->tags = $array['tags'];
$property->modifiers = $array['modifiers'];
$property->default = $array['default'];
$property->errors = $array['errors'];
if (isset($array['is_read_only'])) {// New in 5.4.0
$property->setReadOnly($array['is_read_only']);
}
if (isset($array['is_write_only'])) {// New in 5.4.0
$property->setWriteOnly($array['is_write_only']);
}
return $property;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Reflection;
class HintReflection
{
protected $name;
protected $array;
public function __construct($name, $array)
{
$this->name = $name;
$this->array = $array;
}
public function __toString()
{
// We're casting name to string, as it can be eg. `ClassReflection` object.
return (string) $this->name;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function isClass()
{
return $this->name instanceof ClassReflection;
}
public function isArray()
{
return $this->array;
}
public function setArray($boolean)
{
$this->array = (bool) $boolean;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Console\Command;
use Doctum\Doctum;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class RenderCommand extends Command
{
/**
* @see Command
* @phpstan-return void
*/
protected function configure()
{
parent::configure();
$this->addForceOption();
$this->addOutputFormatOption();
$this->addNoProgressOption();
$this->addIgnoreParseErrors();
$defaultVersionName = Doctum::$defaultVersionName;
$this
->setName('render')
->setDescription('Renders a project')
->setHelp(
<<<EOF
The <info>%command.name%</info> command renders a project as a static set of HTML files:
<info>php %command.full_name% render config/doctum.php</info>
The <comment>--force</comment> option forces a rebuild (it disables the
incremental rendering algorithm):
<info>php %command.full_name% render config/doctum.php --force</info>
The <comment>--version</comment> option overrides the version specified
in the configuration:
<info>php %command.full_name% render config/doctum.php --version=$defaultVersionName</info>
EOF
);
}
/**
* @see Command
*
* @throws \InvalidArgumentException When the target directory does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
return $this->render($this->doctum->getProject());
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Console\Command;
use Doctum\Doctum;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ParseCommand extends Command
{
/**
* @see Command
* @phpstan-return void
*/
protected function configure()
{
parent::configure();
$this->addForceOption();
$this->addOutputFormatOption();
$this->addNoProgressOption();
$this->addIgnoreParseErrors();
$defaultVersionName = Doctum::$defaultVersionName;
$this
->setName('parse')
->setDescription('Parses a project')
->setHelp(
<<<EOF
The <info>%command.name%</info> command parses a project and generates a database
with API information:
<info>php %command.full_name% config/symfony.php</info>
The <comment>--force</comment> option forces a rebuild (it disables the
incremental parsing algorithm):
<info>php %command.full_name% config/symfony.php --force</info>
The <comment>--version</comment> option overrides the version specified
in the configuration:
<info>php %command.full_name% config/symfony.php --version=$defaultVersionName</info>
EOF
);
}
/**
* @see Command
*
* @throws \InvalidArgumentException When the target directory does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
return $this->parse($this->doctum->getProject());
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Console\Command;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\AnsiEscapeSequences;
use CodeLts\CliTools\ErrorsConsoleStyle;
use CodeLts\CliTools\Exceptions\FormatNotFoundException;
use CodeLts\CliTools\OutputFormat;
use CodeLts\CliTools\Symfony\SymfonyOutput;
use CodeLts\CliTools\Symfony\SymfonyStyle;
use Doctum\Message;
use Doctum\Parser\Transaction;
use Doctum\Project;
use Doctum\Renderer\Diff;
use Doctum\Doctum;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
abstract class Command extends BaseCommand
{
private const PARSE_ERROR = 64;
/**
* @var Doctum
*/
protected $doctum;
/**
* @var string
*/
protected $version;
/**
* @var bool
*/
protected $started;
/**
* @var bool
*/
protected $progressStarted = false;
/**
* @var array<string,Diff>
*/
protected $diffs = [];
/**
* @var array<string,Transaction>
*/
protected $transactions = [];
/**
* @var \Doctum\Parser\ParseError[]
*/
protected $errors = [];
/**
* @var InputInterface
*/
protected $input;
/**
* @var SymfonyOutput
*/
protected $output;
/**
* @var SymfonyOutput
*/
protected $errorOutput;
/**
* @var string|null
*/
protected $sourceRootDirectory = null;
/**
* @see Command
* @phpstan-return void
*/
protected function configure()
{
$this->getDefinition()->addArgument(new InputArgument('config', InputArgument::REQUIRED, 'The configuration file'));
$this->getDefinition()->addOption(new InputOption('only-version', '', InputOption::VALUE_REQUIRED, 'The version to build'));
}
protected function addForceOption(): void
{
$this->getDefinition()->addOption(new InputOption('force', '', InputOption::VALUE_NONE, 'Forces to rebuild from scratch', null));
}
protected function addOutputFormatOption(): void
{
$this->getDefinition()->addOption(
new InputOption(
'output-format',
'',
InputOption::VALUE_REQUIRED,
'The format to display errors',
OutputFormat::OUTPUT_FORMAT_RAW_TEXT
)
);
}
protected function addNoProgressOption(): void
{
$this->getDefinition()->addOption(new InputOption('no-progress', '', InputOption::VALUE_NONE, 'Do not display the progress bar', null));
}
protected function addIgnoreParseErrors(): void
{
$this->getDefinition()->addOption(
new InputOption('ignore-parse-errors', '', InputOption::VALUE_NONE, 'Ignores parse errors and exits 0', null)
);
}
/**
* @phpstan-return void
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$stdErr = $output;
if ($output instanceof ConsoleOutputInterface) {
$stdErr = $output->getErrorOutput();
}
$errorConsoleStyle = new ErrorsConsoleStyle($this->input, $output);
$this->output = new SymfonyOutput($output, new SymfonyStyle($errorConsoleStyle));
$this->errorOutput = new SymfonyOutput($stdErr, new SymfonyStyle($errorConsoleStyle));
/** @var string $config */
$config = $input->getArgument('config');
$filesystem = new Filesystem();
if ($config && !$filesystem->isAbsolutePath($config)) {
$config = getcwd() . '/' . $config;
}
if (!is_file($config)) {
throw new \InvalidArgumentException(sprintf('Configuration file "%s" does not exist.', $config));
}
$this->doctum = $this->loadDoctum($config);
if (!$this->doctum instanceof Doctum) {
throw new \RuntimeException(sprintf('Configuration file "%s" must return a Doctum instance.', $config));
}
if ($input->getOption('only-version')) {
/** @var string $onlyVersionOption */
$onlyVersionOption = $input->getOption('only-version');
$this->doctum->setVersion((string) $onlyVersionOption);
}
}
public function update(Project $project): int
{
if (! $this->checkOptionsValues()) {
return 1;
}
$this->sourceRootDirectory = $project->getSourceDir();
$this->output->writeFormatted(
$this->output->isDecorated() ? '<bg=cyan;fg=white> Updating project </>' : 'Updating project'
);
$project->update([$this, 'messageCallback'], $this->input->getOption('force'));
$this->displayParseSummary();
$this->displayRenderSummary();
return $this->getExitCode();
}
public function parse(Project $project): int
{
if (! $this->checkOptionsValues()) {
return 1;
}
$project->parse([$this, 'messageCallback'], $this->input->getOption('force'));
$this->sourceRootDirectory = $project->getSourceDir();
$this->output->writeFormatted(
$this->output->isDecorated() ? '<bg=cyan;fg=white> Parsing project </>' : 'Parsing project'
);
$this->displayParseSummary();
return $this->getExitCode();
}
public function render(Project $project): int
{
if (! $this->checkOptionsValues()) {
return 1;
}
$project->render([$this, 'messageCallback'], $this->input->getOption('force'));
$this->sourceRootDirectory = $project->getSourceDir();
$this->output->writeFormatted(
$this->output->isDecorated() ? '<bg=cyan;fg=white> Rendering project </>' : 'Rendering project'
);
$this->displayRenderSummary();
return $this->getExitCode();
}
private function checkOptionsValues(): bool
{
try {
OutputFormat::checkOutputFormatIsValid($this->getOutputFormat());
return true;
} catch (FormatNotFoundException $e) {
$this->output->getStyle()->error($e->getMessage());
return false;
}
}
private function getOutputFormat(): string
{
/** @var string $outputFormat */
$outputFormat = $this->input->getOption('output-format');
return (string) $outputFormat;
}
private function getExitCode(): int
{
if ($this->input->getOption('ignore-parse-errors')) {
return 0;
}
if (count($this->errors) > 0) {
return self::PARSE_ERROR;
}
return 0;
}
/**
* @param mixed $data
*/
public function messageCallback(int $message, $data): void
{
switch ($message) {
case Message::PARSE_CLASS:
[$step, $steps, $class] = $data;
$this->displayParseProgress($class);
$this->makeProgress($step, $steps);
break;
case Message::PARSE_ERROR:
$this->errors = array_merge($this->errors, $data);
break;
case Message::SWITCH_VERSION:
$this->version = $data;
$this->errors = [];
$this->started = false;
$this->displayNewVersion();
break;
case Message::PARSE_VERSION_FINISHED:
$this->transactions[(string) $this->version] = $data;
$this->displayParseEnd($data);
$this->endProgress();
$this->started = false;
break;
case Message::RENDER_VERSION_FINISHED:
$this->diffs[(string) $this->version] = $data;
$this->displayRenderEnd($data);
$this->endProgress();
$this->started = false;
break;
case Message::RENDER_PROGRESS:
[$section, $message, $step, $steps] = $data;
$this->displayRenderProgress($section, $message);
$this->makeProgress($step, $steps);
break;
}
}
protected function makeProgress(int $step, int $steps): void
{
if ($this->progressStarted === false) {
$this->output->getStyle()->progressStart($steps);
$this->progressStarted = true;
}
$this->output->getStyle()->progressAdvance(1);
}
protected function endProgress(): void
{
if ($this->progressStarted === false) {
return;
}
$this->progressStarted = false;
$this->output->getStyle()->progressFinish();
}
/**
* @param \Doctum\Reflection\ClassReflection $class
*/
public function displayParseProgress($class): void
{
if (! $this->started) {
$this->started = true;
}
if ($this->progressStarted === false) {
// This avoids to have a "Parsing" stuck before the "Parsing" in progress
$this->output->writeRaw("\n");
return;
}
if ($this->output->isDecorated()) {
$this->output->writeRaw(AnsiEscapeSequences::MOVE_CURSOR_UP_2);
}
$errorsPluralText = (1 === count($this->errors) ? '' : 's');
$this->output->writeFormatted(
$this->output->isDecorated() ? sprintf(
' Parsing %s' . AnsiEscapeSequences::ERASE_TO_LINE_END . "\n %s"
. AnsiEscapeSequences::ERASE_TO_LINE_END . "\n",
count($this->errors) ? ' <fg=red>' . count($this->errors) . ' error' . $errorsPluralText . '</>' : '',
$class->getName()
) : sprintf(
'Parsing %s %s' . "\n",
$class->getName(),
count($this->errors) ? 'total: ' . count($this->errors) . ' error' . $errorsPluralText : ''
)
);
}
public function displayRenderProgress(string $section, string $message): void
{
if (! $this->started) {
$this->started = true;
}
if ($this->progressStarted === false) {
// This avoids to have a "Rendering" stuck before the "Rendering" in progress
$this->output->writeRaw("\n");
return;
}
if ($this->output->isDecorated()) {
$this->output->writeRaw(AnsiEscapeSequences::MOVE_CURSOR_UP_2);
}
$this->output->writeFormatted(
$this->output->isDecorated() ? sprintf(
' Rendering '
. AnsiEscapeSequences::ERASE_TO_LINE_END
. "\n <info>%s</info> %s" . AnsiEscapeSequences::ERASE_TO_LINE_END . "\n",
$section,
$message
) : sprintf(
'Rendering %s %s' . "\n",
$section,
$message
)
);
}
public function displayParseEnd(Transaction $transaction): void
{
if (!$this->started) {
return;
}
$this->output->writeFormatted(
$this->output->isDecorated() ? AnsiEscapeSequences::MOVE_CURSOR_UP_2 . '<info> Parsing done</info>'
. AnsiEscapeSequences::ERASE_TO_LINE_END . "\n"
. AnsiEscapeSequences::ERASE_TO_LINE_END . "\n" . AnsiEscapeSequences::MOVE_CURSOR_UP_1 : 'Parsing done' . "\n"
);
if ($this->output->isVerbose() && count($this->errors) > 0) {
$this->output->writeLineFormatted('');
$analysisResult = new AnalysisResult(
$this->errors,
[],
[],
[]
);
OutputFormat::displayUserChoiceFormat(
$this->getOutputFormat(),
$analysisResult,
$this->sourceRootDirectory,
$this->errorOutput
);
$this->output->writeLineFormatted('');
}
}
public function displayRenderEnd(Diff $diff): void
{
if (!$this->started) {
return;
}
$this->output->writeFormatted(
$this->output->isDecorated() ? AnsiEscapeSequences::MOVE_CURSOR_UP_2
. '<info> Rendering done</info>'
. AnsiEscapeSequences::ERASE_TO_LINE_END . "\n"
. AnsiEscapeSequences::ERASE_TO_LINE_END . "\n"
. AnsiEscapeSequences::MOVE_CURSOR_UP_1 : 'Rendering done' . "\n"
);
$this->output->writeLineFormatted('');
}
public function displayParseSummary(): void
{
if (count($this->transactions) <= 0) {
return;
}
$this->output->writeLineFormatted('');// Display a line break after the title
$this->output->writeLineFormatted(
'<bg=cyan;fg=white> Version </> <bg=cyan;fg=white> Updated C </> <bg=cyan;fg=white> Removed C </>'
);
foreach ($this->transactions as $version => $transaction) {
$this->output->writeLineFormatted(
sprintf(
'%9s %11d %11d',
$version,
count($transaction->getModifiedClasses()),
count($transaction->getRemovedClasses())
)
);
}
$this->output->writeLineFormatted('');
}
public function displayRenderSummary(): void
{
if (count($this->diffs) <= 0) {
return;
}
$this->output->writeLineFormatted('');// Display a line break after the title
$this->output->writeLineFormatted(
'<bg=cyan;fg=white> Version </> <bg=cyan;fg=white> Updated C </>'
. ' <bg=cyan;fg=white> Updated N </> <bg=cyan;fg=white> Removed C </>'
. ' <bg=cyan;fg=white> Removed N </>'
);
foreach ($this->diffs as $version => $diff) {
$this->output->writeLineFormatted(
sprintf(
'%9s %11d %11d %11d %11d',
$version,
count($diff->getModifiedClasses()),
count($diff->getModifiedNamespaces()),
count($diff->getRemovedClasses()),
count($diff->getRemovedNamespaces())
)
);
}
$this->output->writeLineFormatted('');
}
public function displayNewVersion(): void
{
$this->output->getStyle()->section(sprintf("\n<fg=cyan>Version %s</>", $this->version));
}
/**
* @return Doctum
*/
private function loadDoctum(string $config)
{
return require $config;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Console\Command;
use Doctum\Doctum;
use Phar;
use Symfony\Component\Console\Command\Command as BaseCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class VersionCommand extends BaseCommand
{
/**
* @phpstan-return void
*/
protected function configure()
{
parent::configure();
$this
->setName('version')
->setDescription('Know everything about this version')
->setHelp(
<<<EOF
The <info>%command.name%</info> command gives you access to version data:
<info>php %command.full_name%</info>
To print everything in the JSON format:
<info>php %command.full_name% --json</info>
To print everything in a text format:
<info>php %command.full_name% --text</info>
EOF
);
$this->getDefinition()->addOption(
new InputOption('json', null, InputOption::VALUE_NONE, 'Show the data in a JSON format')
);
$this->getDefinition()->addOption(
new InputOption('text', null, InputOption::VALUE_NONE, 'Show the data in a text format for humans')
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$data = [
'version' => Doctum::VERSION,
'major' => Doctum::VERSION_MAJOR,
'minor' => Doctum::VERSION_MINOR,
'patch' => Doctum::VERSION_PATCH,
'is_dev_version' => Doctum::IS_DEV,
'license' => 'MIT',
'phar_metadata' => null,
];
if (class_exists(Phar::class)) {
$pharPath = Phar::running(false);
if ($pharPath !== '') {
$phar_self = new Phar($pharPath);
$metadata = $phar_self->getMetadata();
$data['phar_metadata'] = [
'vcs.git' => $metadata['vcs.git'],
'vcs.browser' => $metadata['vcs.browser'],
'vcs.ref' => $metadata['vcs.ref'],
'build-date' => $metadata['build-date'],
];
}
}
if ($input->getOption('json')) {
$jsonData = json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
if ($jsonData === false) {
return 1;
}
$output->writeln($jsonData);
return 0;
}
if ($input->getOption('text')) {
/** @var bool $isDev */
$isDev = $data['is_dev_version'];
$output->writeln('Version: ' . $data['version']);
$output->writeln('Version-major: ' . $data['major']);
$output->writeln('Version-minor: ' . $data['minor']);
$output->writeln('Version-patch: ' . $data['patch']);
$output->writeln('Version-is-dev: ' . ($isDev ? 'yes' : 'no'));
$output->writeln('License: ' . $data['license']);
$isPhar = $data['phar_metadata'] !== null;
$output->writeln('Phar-detected: ' . ($isPhar ? 'yes' : 'no'));
if ($isPhar) {
/** @var array<string,string> $meta */
$meta = $data['phar_metadata'];
$output->writeln('Phar-Vcs-Git: ' . $meta['vcs.git']);
$output->writeln('Phar-Vcs-Browser: ' . $meta['vcs.browser']);
$output->writeln('Phar-Vcs-Ref: ' . $meta['vcs.ref']);
$output->writeln('Phar-Build-Date: ' . $meta['build-date']);
}
return 0;
}
$output->writeln($data['version']);
return 0;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Console\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class UpdateCommand extends Command
{
/**
* @see Command
* @phpstan-return void
*/
protected function configure()
{
parent::configure();
$this->addForceOption();
$this->addOutputFormatOption();
$this->addNoProgressOption();
$this->addIgnoreParseErrors();
$this
->setName('update')
->setDescription('Parses then renders a project')
->setHelp(
<<<EOF
The <info>%command.name%</info> command parses and renders a project:
<info>php %command.full_name% config/symfony.php</info>
This command is the same as running the parse command followed
by the render command.
EOF
);
}
/**
* @see Command
*
* @throws \InvalidArgumentException When the target directory does not exist
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
return $this->update($this->doctum->getProject());
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Console;
use Doctum\Console\Command\ParseCommand;
use Doctum\Console\Command\RenderCommand;
use Doctum\Console\Command\UpdateCommand;
use Doctum\Console\Command\VersionCommand;
use Doctum\ErrorHandler;
use Doctum\Doctum;
use Symfony\Component\Console\Application as BaseApplication;
class Application extends BaseApplication
{
public function __construct()
{
error_reporting(-1);
ErrorHandler::register();
parent::__construct('Doctum', Doctum::VERSION);
$this->add(new VersionCommand());
$this->add(new UpdateCommand());
$this->add(new ParseCommand());
$this->add(new RenderCommand());
}
public function getLongVersion()
{
return parent::getLongVersion() . ' by <comment>Fabien Potencier and William Desportes</comment>';
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum;
use Wdes\phpI18nL10n\Launcher;
class Tree
{
public static function getGlobalNamespacePageName(): string
{
return Launcher::gettext('[Global_Namespace]');
}
public static function getGlobalNamespaceName(): string
{
return Launcher::gettext('[Global Namespace]');
}
/**
* @return array[]
*/
public function getTree(Project $project)
{
$namespaces = [];
$ns = $project->getNamespaces();
foreach ($ns as $namespace) {
if (false !== $pos = strpos($namespace, '\\')) {
$namespaces[substr($namespace, 0, $pos)][] = $namespace;
} else {
$namespaces[$namespace][] = $namespace;
}
}
return $this->generateClassTreeLevel($project, 1, $namespaces, []);
}
/**
* @param int $level
* @param array $namespaces
* @param \Doctum\Reflection\ClassReflection[] $classes
* @return array[]
*/
protected function generateClassTreeLevel(Project $project, $level, array $namespaces, array $classes)
{
++$level;
$tree = [];
foreach ($namespaces as $namespace => $subnamespaces) {
// classes
$cl = $project->getNamespaceAllClasses($namespace);
// subnamespaces
$ns = [];
foreach ($subnamespaces as $subnamespace) {
$parts = explode('\\', $subnamespace);
if (!isset($parts[$level - 1])) {
continue;
}
$ns[implode('\\', array_slice($parts, 0, $level))][] = $subnamespace;
}
$parts = explode('\\', $namespace);
$url = '';
$url = $parts[count($parts) - 1]
&& $project->hasNamespace($namespace)
&& (count($subnamespaces) || count($cl)) ? $namespace : '';
$short = $parts[count($parts) - 1] ? $parts[count($parts) - 1] : self::getGlobalNamespaceName();
$tree[] = [$short, $url, $this->generateClassTreeLevel($project, $level, $ns, $cl)];
}
foreach ($classes as $class) {
$short = $class->getShortName();
$tree[] = [$short, $class, []];
}
return $tree;
}
}
<?php
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types = 1);
namespace Doctum\Renderer;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Yaml\Yaml;
class ThemeSet
{
protected $themes;
public function __construct(array $dirs)
{
$this->discover($dirs);
}
public function getTheme($name)
{
if (!isset($this->themes[$name])) {
throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist.', $name));
}
return $this->themes[$name];
}
protected function discover(array $dirs)
{
$this->themes = [];
$parents = [];
$themes = Finder::create()->name('manifest.yml')->files()->in($dirs)->getIterator();
foreach ($themes as $manifest) {
$manifest = $manifest->getPathname();
if (! file_exists($manifest)) {
// This should not exist
throw new \InvalidArgumentException(sprintf('Theme manifest "%s" is not a file.', $manifest));
}
$text = file_get_contents($manifest);
if ($text === false) {
throw new \Exception(sprintf('Could not read "%s" file.', $manifest));
}
$config = Yaml::parse($text);
if (!isset($config['name'])) {
throw new \InvalidArgumentException(sprintf('Theme manifest in "%s" must have a "name" entry.', $manifest));
}
$theme = new Theme($config['name'], dirname($manifest));
$this->themes[$config['name']] = $theme;
if (isset($config['parent'])) {
$parents[$config['name']] = $config['parent'];
}
foreach (['static', 'global', 'namespace', 'class'] as $type) {
if (isset($config[$type])) {
$theme->setTemplates($type, $config[$type]);
}
}
}
// populate parent
foreach ($parents as $name => $parent) {
if (!isset($this->themes[$parent])) {
throw new \LogicException(sprintf('Theme "%s" inherits from an unknown "%s" theme.', $name, $parent));
}
$this->themes[$name]->setParent($this->themes[$parent]);
}
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Renderer;
use Doctum\Reflection\Reflection;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\FunctionReflection;
use Doctum\Reflection\MethodReflection;
use Doctum\Reflection\PropertyReflection;
use Doctum\Tree;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Twig\TwigFilter;
use Parsedown;
class TwigExtension extends AbstractExtension
{
/** @var Parsedown */
protected $markdown;
protected $project;
/** @var int|null */
protected $currentDepth = null;
/**
* Returns a list of filters to add to the existing list.
*
* @return TwigFilter[] An array of filters
*/
public function getFilters()
{
return [
new TwigFilter('desc', [$this, 'parseDesc'], ['needs_context' => true, 'is_safe' => ['html']]),
new TwigFilter('snippet', [$this, 'getSnippet']),
];
}
/**
* Returns a list of functions to add to the existing list.
*
* @return TwigFunction[] An array of functions
*/
public function getFunctions()
{
return [
new TwigFunction('global_namespace_name', [Tree::class, 'getGlobalNamespaceName']),
new TwigFunction('function_path', [$this, 'pathForFunction'], ['needs_context' => true, 'is_safe' => ['html']]),
new TwigFunction('namespace_path', [$this, 'pathForNamespace'], ['needs_context' => true, 'is_safe' => ['html']]),
new TwigFunction('class_path', [$this, 'pathForClass'], ['needs_context' => true, 'is_safe' => ['html']]),
new TwigFunction('method_path', [$this, 'pathForMethod'], ['needs_context' => true, 'is_safe' => ['html']]),
new TwigFunction('property_path', [$this, 'pathForProperty'], ['needs_context' => true, 'is_safe' => ['html']]),
new TwigFunction('path', [$this, 'pathForStaticFile'], ['needs_context' => true]),
new TwigFunction(
'abbr_class',
static function ($class, bool $absolute = false) {
return self::abbrClass($class, $absolute);
},
['is_safe' => ['html']]
),
];
}
public function setCurrentDepth(int $depth): void
{
$this->currentDepth = $depth;
}
public function pathForFunction(array $context, string $function): string
{
return $this->relativeUri($this->currentDepth) . '#function_' . str_replace('\\', '', $function);
}
public function pathForClass(array $context, string $class): string
{
return $this->relativeUri($this->currentDepth) . str_replace('\\', '/', $class) . '.html';
}
public function pathForNamespace(array $context, string $namespace): string
{
if ($namespace === '') {
$namespace = Tree::getGlobalNamespacePageName();
}
return $this->relativeUri($this->currentDepth) . str_replace('\\', '/', $namespace) . '.html';
}
public function pathForMethod(array $context, MethodReflection $method)
{
/** @var Reflection $class */
$class = $method->getClass();
return $this->relativeUri($this->currentDepth) . str_replace('\\', '/', $class->getName()) . '.html#method_' . $method->getName();
}
public function pathForProperty(array $context, PropertyReflection $property)
{
/** @var Reflection $class */
$class = $property->getClass();
return $this->relativeUri($this->currentDepth) . str_replace('\\', '/', $class->getName()) . '.html#property_' . $property->getName();
}
public function pathForStaticFile(array $context, string $file): string
{
return $this->relativeUri($this->currentDepth) . $file;
}
/**
* Generate the abbreviation of a class
*
* @param ClassReflection|string $class The class
*/
public static function abbrClass($class, bool $absolute = false): string
{
if ($class instanceof ClassReflection) {
$short = $class->getShortName();
$class = $class->getName();
if ($short === $class && !$absolute) {
return htmlspecialchars($class, ENT_QUOTES);
}
} else {
$parts = explode('\\', $class, ENT_QUOTES);
if (count($parts) === 1 && !$absolute) {
return htmlspecialchars($class);
}
$short = array_pop($parts);
}
return sprintf('<abbr title="%s">%s</abbr>', htmlentities($class, ENT_QUOTES), htmlspecialchars($short));
}
public function parseDesc(array $context, ?string $desc, Reflection $classOrFunctionRefl)
{
if ($desc === null || $desc === '') {
return $desc;
}
if (null === $this->markdown) {
$this->markdown = new Parsedown();
}
$desc = str_replace(['<code>', '</code>'], ['```', '```'], $desc);
$desc = (string) preg_replace_callback(
'/@see ([^ ]+)/', // Match until a whitespace is found
function ($match) use (&$classOrFunctionRefl): string {
return $this->transformContentsIntoLinks($match[1], $classOrFunctionRefl);
},
$desc
);
$desc = (string) preg_replace_callback(
'/\{@link (?!\})(?<contents>[^\r\n\t\f]+)\}/',
function (array $match) use (&$classOrFunctionRefl): string {
$data = rtrim($match['contents'], '}');
return $this->transformContentsIntoLinks($data, $classOrFunctionRefl);
},
$desc
);
return $this->markdown->text($desc);
}
public function transformContentsIntoLinks(string $data, Reflection $classOrFunctionRefl): string
{
$isClassReflection = $classOrFunctionRefl instanceof ClassReflection;
$isFunctionReflection = $classOrFunctionRefl instanceof FunctionReflection;
if (! $isClassReflection && ! $isFunctionReflection) {
return $data;
}
/** @var ClassReflection|FunctionReflection $class */
$class = $classOrFunctionRefl;
// Example: Foo::bar_function_on_foo_class
$classMethod = explode('::', trim($data, " \t\n\r"), 2);
// Found "bar_function_on_foo_class", from example: bar_function_on_foo_class
if (count($classMethod) === 1 && $class instanceof ClassReflection) {
// In this case we resolve a link to a method name in the current class
$method = $class->getMethod($classMethod[0]);
if ($method !== false) {
$short = $this->pathForMethod([], $method);
return '[' . $data . '](' . $short . ')';
}
}
/** @var \Doctum\Project|null $project Original one is not realistic */
$project = $class->getProject();
if ($project === null) {
// This should never happen
return $data;
}
$cr = $project->getClass($classMethod[0]);
if ($cr->isPhpClass()) {
$className = $cr->getName();
return '[' . $className . '](https://www.php.net/' . $className . ')';
}
if (! $cr->isProjectClass()) {
return $data;
}
// Found "bar_function_on_foo_class", from example: Foo::bar_function_on_foo_class
if (count($classMethod) === 2) {
// In this case we have a function name to resolve on the previously found class
$method = $cr->getMethod($classMethod[1]);
if ($method !== false) {
$short = $this->pathForMethod([], $method);
return '[' . $data . '](' . $short . ')';
}
}
// Final case, we link the found class
$short = $this->pathForClass([], $cr->getName());
return '[' . $data . '](' . $short . ')';
}
/**
* Seems not to be used
*
* @return string
*/
public function getSnippet(string $string)
{
if (preg_match('/^(.{50,}?)\s.*/m', $string, $matches)) {
$string = $matches[1];
}
return str_replace(["\n", "\r"], '', strip_tags($string));
}
protected function relativeUri(?int $value): string
{
if (!$value) {
return '';
}
return rtrim(str_repeat('../', $value), '/') . '/';
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Renderer;
use Doctum\Project;
class Index implements \Serializable
{
protected $classes;
protected $versions;
protected $namespaces;
public function __construct(?Project $project = null)
{
$this->classes = [];
if (null !== $project) {
foreach ($project->getProjectClasses() as $class) {
$this->classes[$class->getName()] = $class->getHash();
}
}
$this->versions = [];
if (null !== $project) {
foreach ($project->getVersions() as $version) {
$this->versions[] = (string) $version;
}
}
$this->namespaces = [];
if (null !== $project) {
$this->namespaces = $project->getNamespaces();
}
}
public function getVersions()
{
return $this->versions;
}
public function getClasses()
{
return $this->classes;
}
public function getNamespaces()
{
return $this->namespaces;
}
public function getHash($class)
{
return $this->classes[$class] ?? false;
}
public function serialize()
{
return serialize([$this->classes, $this->versions, $this->namespaces]);
}
public function unserialize($data)
{
[$this->classes, $this->versions, $this->namespaces] = unserialize($data);
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Renderer;
class Theme
{
protected $name;
protected $dir;
protected $parent;
protected $templates;
public function __construct($name, $dir)
{
$this->name = $name;
$this->dir = $dir;
}
public function getTemplateDirs()
{
$dirs = [];
if ($this->parent) {
$dirs = $this->parent->getTemplateDirs();
}
array_unshift($dirs, $this->dir);
return $dirs;
}
public function setParent(Theme $parent)
{
$this->parent = $parent;
}
public function getParent()
{
return $this->parent;
}
public function getName()
{
return $this->name;
}
public function getTemplates($type)
{
$templates = [];
if ($this->parent) {
$templates = $this->parent->getTemplates($type);
}
if (!isset($this->templates[$type])) {
return $templates;
}
return array_replace($templates, $this->templates[$type]);
}
public function setTemplates($type, $templates)
{
$this->templates[$type] = $templates;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Renderer;
use Doctum\Project;
class Diff
{
/** @var Project */
protected $project;
/** @var Index */
protected $current;
/** @var string[] */
protected $versions;
/** @var string */
protected $filename;
/** @var bool */
protected $alreadyRendered;
protected $previousNamespaces;
protected $currentNamespaces;
/** @var Index */
private $previous;
public function __construct(Project $project, string $filename)
{
$this->project = $project;
$this->current = new Index($project);
$this->filename = $filename;
if (file_exists($filename)) {
$this->alreadyRendered = true;
$previous = $this->readSerializedFile($filename);
if (null === $previous) {
$this->alreadyRendered = false;
$this->previous = new Index();
} else {
$this->previous = $previous;
}
} else {
$this->alreadyRendered = false;
$this->previous = new Index();
}
$this->previousNamespaces = $this->previous->getNamespaces();
$this->currentNamespaces = $this->current->getNamespaces();
}
protected function readSerializedFile(string $filename): ?Index
{
$contents = file_get_contents($filename);
if ($contents === false) {
return null;
}
$contents = @unserialize($contents);
return $contents === false ? null : $contents;
}
/**
* @return bool
*/
public function isEmpty()
{
return !$this->areVersionsModified() && (0 == count($this->getModifiedClasses()) + count($this->getRemovedClasses()));
}
/**
* @return void
*/
public function save()
{
file_put_contents($this->filename, serialize($this->current));
}
/**
* @return bool
*/
public function isAlreadyRendered()
{
return $this->alreadyRendered;
}
/**
* @return bool
*/
public function areVersionsModified()
{
$versions = [];
foreach ($this->project->getVersions() as $version) {
$versions[] = (string) $version;
}
return $versions != $this->previous->getVersions();
}
public function getModifiedNamespaces()
{
return array_diff($this->currentNamespaces, $this->previousNamespaces);
}
public function getRemovedNamespaces()
{
return array_diff($this->previousNamespaces, $this->currentNamespaces);
}
/**
* @return \Doctum\Reflection\ClassReflection[]
*/
public function getModifiedClasses()
{
$classes = [];
foreach ($this->current->getClasses() as $class => $hash) {
if ($hash !== $this->previous->getHash($class)) {
$classes[] = $this->project->getClass($class);
}
}
return $classes;
}
public function getRemovedClasses()
{
return array_diff(array_keys($this->previous->getClasses()), array_keys($this->current->getClasses()));
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Renderer;
use Doctum\Indexer;
use Doctum\Message;
use Doctum\Project;
use Doctum\Reflection\ClassReflection;
use Doctum\Tree;
use Symfony\Component\Filesystem\Filesystem;
class Renderer
{
protected $twig;
protected $templates;
protected $filesystem;
protected $themes;
protected $theme;
/** @var int */
protected $steps;
/** @var int */
protected $step;
protected $tree;
protected $indexer;
protected $cachedTree;
public function __construct(\Twig\Environment $twig, ThemeSet $themes, Tree $tree, Indexer $indexer)
{
$this->twig = $twig;
$this->themes = $themes;
$this->tree = $tree;
$this->cachedTree = [];
$this->indexer = $indexer;
$this->filesystem = new Filesystem();
}
public function isRendered(Project $project)
{
return $this->getDiff($project)->isAlreadyRendered();
}
public function render(Project $project, $callback = null, $force = false)
{
$cacheDir = $project->getCacheDir() . '/twig';
$this->twig->setCache($cacheDir);
if ($force) {
$project->flushDir($cacheDir);
}
$diff = $this->getDiff($project);
if ($diff->isEmpty()) {
return $diff;
}
$this->steps = count($diff->getModifiedClasses()) + count($diff->getModifiedNamespaces()) + count($this->getTheme($project)->getTemplates('global')) + 1;
$this->step = 0;
$this->theme = $this->getTheme($project);
$dirs = $this->theme->getTemplateDirs();
// add parent directory to be able to extends the same template as the current one but in the parent theme
foreach ($dirs as $dir) {
$dirs[] = dirname($dir);
}
$this->twig->getLoader()->setPaths(array_unique($dirs));
$this->twig->addGlobal('has_namespaces', $project->hasNamespaces());
$this->twig->addGlobal('project', $project);
$this->renderStaticTemplates($project, $callback);
$this->renderGlobalTemplates($project, $callback);
$this->renderNamespaceTemplates($diff->getModifiedNamespaces(), $project, $callback);
$this->renderClassTemplates($diff->getModifiedClasses(), $project, $callback);
// cleanup
foreach ($diff->getRemovedClasses() as $class) {
foreach ($this->theme->getTemplates('class') as $target) {
$this->filesystem->remove(sprintf($target, str_replace('\\', '/', $class)));
}
}
$diff->save();
return $diff;
}
protected function renderStaticTemplates(Project $project, $callback = null)
{
if (null !== $callback) {
call_user_func($callback, Message::RENDER_PROGRESS, ['Static', 'Rendering files', $this->step, $this->steps]);
}
$dirs = $this->theme->getTemplateDirs();
foreach ($this->theme->getTemplates('static') as $template => $target) {
foreach (array_reverse($dirs) as $dir) {
if (file_exists($dir . '/' . $template)) {
$this->filesystem->copy($dir . '/' . $template, $project->getBuildDir() . '/' . $target);
continue 2;
}
}
}
}
protected function renderGlobalTemplates(Project $project, $callback = null)
{
$variables = [
'namespaces' => $project->getNamespaces(),
'interfaces' => $project->getProjectInterfaces(),
'classes' => $project->getProjectClasses(),
'items' => $this->getIndex($project),
'index' => $this->indexer->getIndex($project),
'tree' => $this->getTree($project),
];
foreach ($this->theme->getTemplates('global') as $template => $target) {
if (null !== $callback) {
call_user_func($callback, Message::RENDER_PROGRESS, ['Global', $target, $this->step, $this->steps]);
}
$this->save($project, $target, $template, $variables);
}
}
protected function renderNamespaceTemplates(array $namespaces, Project $project, $callback = null)
{
foreach ($namespaces as $namespace) {
$namespaceDisplayName = $namespace;
$namespaceName = $namespace;
if ($namespace === '') {
$namespaceDisplayName = Tree::getGlobalNamespaceName();
$namespaceName = Tree::getGlobalNamespacePageName();
}
if (null !== $callback) {
call_user_func($callback, Message::RENDER_PROGRESS, ['Namespace', $namespaceDisplayName, $this->step, $this->steps]);
}
$variables = [
'namespace' => $namespace,
'subnamespaces' => $project->getNamespaceSubNamespaces($namespace),
'functions' => $project->getNamespaceFunctions($namespace),
'classes' => $project->getNamespaceClasses($namespace),
'interfaces' => $project->getNamespaceInterfaces($namespace),
'exceptions' => $project->getNamespaceExceptions($namespace),
'tree' => $this->getTree($project),
];
foreach ($this->theme->getTemplates('namespace') as $template => $target) {
$this->save($project, sprintf($target, str_replace('\\', '/', $namespaceName)), $template, $variables);
}
}
}
/**
* @param Callable|null $callback
* @return array<string,mixed>
*/
protected function getVariablesFromClassReflection(ClassReflection $class, Project $project, $callback = null): array
{
if (null !== $callback) {
call_user_func($callback, Message::RENDER_PROGRESS, ['Class', $class->getName(), $this->step, $this->steps]);
}
$properties = $class->getProperties($project->getConfig('include_parent_data'));
$sortProperties = $project->getConfig('sort_class_properties');
if ($sortProperties) {
if (is_callable($sortProperties)) {
uksort($properties, $sortProperties);
} else {
ksort($properties);
}
}
$methods = $class->getMethods($project->getConfig('include_parent_data'));
$sortMethods = $project->getConfig('sort_class_methods');
if ($sortMethods) {
if (is_callable($sortMethods)) {
uksort($methods, $sortMethods);
} else {
ksort($methods);
}
}
$constants = $class->getConstants($project->getConfig('include_parent_data'));
$sortConstants = $project->getConfig('sort_class_constants');
if ($sortConstants) {
if (is_callable($sortConstants)) {
uksort($constants, $sortConstants);
} else {
ksort($constants);
}
}
$traits = $class->getTraits($project->getConfig('include_parent_data'));
$sortTraits = $project->getConfig('sort_class_traits');
if ($sortTraits) {
if (is_callable($sortTraits)) {
uksort($traits, $sortTraits);
} else {
ksort($traits);
}
}
$sortInterfaces = $project->getConfig('sort_class_interfaces');
if ($sortInterfaces) {
$class->sortInterfaces($sortInterfaces);
}
return [
'class' => $class,
'properties' => $properties,
'methods' => $methods,
'constants' => $constants,
'traits' => $traits,
'tree' => $this->getTree($project),
];
}
/**
* @param ClassReflection[] $classes
* @param Project $project
* @param Callable|null $callback
* @return void
*/
protected function renderClassTemplates(array $classes, Project $project, $callback = null)
{
foreach ($classes as $class) {
$variables = $this->getVariablesFromClassReflection($class, $project, $callback);
foreach ($this->theme->getTemplates('class') as $template => $target) {
$this->save($project, sprintf($target, str_replace('\\', '/', $class->getName())), $template, $variables);
}
}
}
protected function save(Project $project, $uri, $template, $variables)
{
$depth = substr_count($uri, '/');
/** @var TwigExtension $twigExtension */
$twigExtension = $this->twig->getExtension(TwigExtension::class);
$twigExtension->setCurrentDepth($depth);
$this->twig->addGlobal('root_path', str_repeat('../', $depth));
$file = $project->getBuildDir() . '/' . $uri;
if (!is_dir($dir = dirname($file))) {
$this->filesystem->mkdir($dir);
}
file_put_contents($file, $this->twig->render($template, $variables));
}
protected function getIndex(Project $project)
{
$items = [];
foreach ($project->getProjectClasses() as $class) {
$letter = strtoupper(substr($class->getShortName(), 0, 1));
$items[$letter][] = ['class', $class];
foreach ($class->getProperties() as $property) {
$letter = strtoupper(substr($property->getName(), 0, 1));
$items[$letter][] = ['property', $property];
}
foreach ($class->getMethods() as $method) {
$letter = strtoupper(substr($method->getName(), 0, 1));
$items[$letter][] = ['method', $method];
}
}
ksort($items);
return $items;
}
protected function getDiff(Project $project)
{
return new Diff($project, $project->getBuildDir() . '/renderer.index');
}
protected function getTheme(Project $project)
{
return $this->themes->getTheme($project->getConfig('theme'));
}
/**
* Get tree for the given project.
*
* @param Project $project
*
* @return array
*/
private function getTree(Project $project)
{
$key = $project->getBuildDir();
if (!isset($this->cachedTree[$key])) {
$this->cachedTree[$key] = $this->tree->getTree($project);
}
return $this->cachedTree[$key];
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum;
final class ErrorHandler
{
private $levels = [
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
E_STRICT => 'Runtime Notice',
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
];
/**
* Registers the error handler.
*/
public static function register(): void
{
set_error_handler([new static(), 'handle']);
}
/**
* @throws \ErrorException When error_reporting returns error
*/
public function handle($level, $message, $file = 'unknown', $line = 0, $context = []): bool
{
if (error_reporting() & $level) {
throw new \ErrorException(sprintf('%s: %s in %s line %d', $this->levels[$level] ?? $level, $message, $file, $line));
}
return false;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use CodeLts\CliTools\Error;
final class ParseError extends Error
{
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use Doctum\Parser\Filter\FilterInterface;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\FunctionReflection;
class ParserContext
{
protected $filter;
protected $docBlockParser;
/** @var \PhpParser\PrettyPrinter\Standard */
protected $prettyPrinter;
/** @var ParseError[] */
protected $errors = [];
/** @var string|null */
protected $namespace;
protected $aliases;
protected $class;
/** @var string|null */
protected $file;
/** @var string|null */
protected $hash;
protected $classes;
/** @var array<string,FunctionReflection> */
protected $functions;
public function __construct(FilterInterface $filter, DocBlockParser $docBlockParser, $prettyPrinter)
{
$this->filter = $filter;
$this->docBlockParser = $docBlockParser;
$this->prettyPrinter = $prettyPrinter;
$this->functions = [];
}
public function getFilter()
{
return $this->filter;
}
/**
* @return DocBlockParser
*/
public function getDocBlockParser()
{
return $this->docBlockParser;
}
/**
* @return \PhpParser\PrettyPrinter\Standard
*/
public function getPrettyPrinter()
{
return $this->prettyPrinter;
}
public function addAlias(?string $alias, string $name): void
{
$this->aliases[$alias] = $name;
}
public function getAliases()
{
return $this->aliases;
}
public function enterFile($file, $hash): void
{
$this->file = $file;
$this->hash = $hash;
$this->errors = [];
$this->classes = [];
}
public function leaveFile()
{
$this->hash = null;
$this->file = null;
$this->errors = [];
return $this->classes;
}
/**
* @return string|null
*/
public function getHash()
{
return $this->hash;
}
/**
* @return string|null
*/
public function getFile()
{
return $this->file;
}
public function addErrors(string $name, int $line, array $errors): void
{
foreach ($errors as $error) {
$this->addError($name, $line, $error);
}
}
public function addError(?string $name, int $line, string $error): void
{
$this->errors[] = new ParseError(
sprintf('%s on "%s"', $error, $name),
$this->file,
$line
);
}
/**
* @return ParseError[]
*/
public function getErrors(): array
{
return $this->errors;
}
public function addFunction(FunctionReflection $fun): void
{
$this->functions[$this->namespace . '\\' . $fun->getName()] = $fun;
}
/**
* @return array<string,FunctionReflection>
*/
public function getFunctions()
{
return $this->functions;
}
public function enterClass(ClassReflection $class): void
{
$this->class = $class;
}
public function leaveClass(): void
{
if (null === $this->class) {
return;
}
$this->classes[] = $this->class;
$this->class = null;
}
public function getClass()
{
return $this->class;
}
public function enterNamespace(string $namespace): void
{
$this->namespace = $namespace;
$this->aliases = [];
}
public function leaveNamespace(): void
{
$this->namespace = null;
$this->aliases = [];
}
public function getNamespace(): ?string
{
return $this->namespace;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use Doctum\Reflection\ClassReflection;
interface ClassVisitorInterface
{
public function visit(ClassReflection $class);
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\Node;
class DocBlockNode
{
/** @var string */
protected $shortDesc;
/** @var string */
protected $longDesc;
/** @var array<string,array> */
protected $tags = [];
/** @var string[] */
protected $errors = [];
/**
* Add a tag
*
* @param string $key
* @param array[] $value
*/
public function addTag($key, $value): void
{
$this->tags[$key][] = $value;
}
/**
* @return array<string,array>
*/
public function getTags(): array
{
return $this->tags;
}
/**
* @return array<string,array>
*/
public function getOtherTags(): array
{
$tags = $this->tags;
unset($tags['param'], $tags['return'], $tags['var'], $tags['throws']);
foreach ($tags as $name => $values) {
foreach ($values as $i => $value) {
// For 'see' tag we try to maintain backwards compatibility
// by returning only a part of the value.
if ($name === 'see') {
$value = $value[0];
}
$tags[$name][$i] = is_string($value) ? explode(' ', $value) : $value;
}
}
return $tags;
}
/**
* @param string $key
* @return mixed[]
*/
public function getTag($key): array
{
return $this->tags[$key] ?? [];
}
/**
* @return string
*/
public function getShortDesc()
{
return $this->shortDesc;
}
/**
* @return string
*/
public function getLongDesc()
{
return $this->longDesc;
}
/**
* @param string $shortDesc
*/
public function setShortDesc($shortDesc): void
{
$this->shortDesc = $shortDesc;
}
/**
* @param string $longDesc
*/
public function setLongDesc($longDesc): void
{
$this->longDesc = $longDesc;
}
public function getDesc(): string
{
return $this->shortDesc . "\n\n" . $this->longDesc;
}
public function addError(string $error): void
{
$this->errors[] = $error;
}
/**
* @return string[]
*/
public function getErrors(): array
{
return $this->errors;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use PhpParser\Error;
use PhpParser\NodeTraverser;
use PhpParser\Parser as PhpParser;
class CodeParser
{
/** @var PhpParser */
protected $parser;
/** @var NodeTraverser */
protected $traverser;
/** @var ParserContext */
protected $context;
public function __construct(ParserContext $context, PhpParser $parser, NodeTraverser $traverser)
{
$this->context = $context;
$this->parser = $parser;
$this->traverser = $traverser;
// with big fluent interfaces it can happen that PHP-Parser's Traverser
// exceeds the 100 recursions limit; we set it to 10000 to be sure.
ini_set('xdebug.max_nesting_level', '10000');
}
public function getContext(): ParserContext
{
return $this->context;
}
public function parse(string $code): void
{
try {
$this->traverser->traverse($this->parser->parse($code) ?? []);
} catch (Error $e) {
$this->context->addError($this->context->getFile(), 0, $e->getMessage());
}
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use Doctum\Parser\Node\DocBlockNode;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\PropertyRead;
use phpDocumentor\Reflection\DocBlock\Tags\Property;
use phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use phpDocumentor\Reflection\DocBlock\Tags\See;
use phpDocumentor\Reflection\DocBlock\Tags\Throws;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\Types\Context;
class DocBlockParser
{
public function parse(?string $comment, ParserContext $context): DocBlockNode
{
$docBlock = null;
$errorMessage = '';
$result = new DocBlockNode();
if ($comment === null) {
return $result;
}
try {
$factory = DocBlockFactory::createInstance();
$docBlockContext = new Context(
$context->getNamespace() ?? '',
$context->getAliases() ?: []
);
$docBlock = $factory->create($comment, $docBlockContext);
} catch (\Exception $e) {
$errorMessage = $e->getMessage();
}
if ($errorMessage !== '') {
$result->addError($errorMessage);
return $result;
}
if ($docBlock === null) {
$errorMessage = 'Unable to build the doc block factory, this should not happen. Please report it !';
$result->addError($errorMessage);
return $result;
}
$result->setShortDesc($docBlock->getSummary());
$result->setLongDesc($docBlock->getDescription()->__toString());
foreach ($docBlock->getTags() as $tag) {
$result->addTag($tag->getName(), $this->parseTag($tag));
}
return $result;
}
protected function parseTag(DocBlock\Tag $tag)
{
$class = get_class($tag);
switch ($class) {
case Var_::class:
case Return_::class:
/** @var \phpDocumentor\Reflection\DocBlock\Tags\Return_ $tag */
$type = $tag->getType();
return [
$this->parseHint($type !== null ? explode('|', $type->__toString()) : []),
$tag->getDescription() !== null ? $tag->getDescription()->__toString() : '',
];
case Property::class:
case PropertyRead::class:
case PropertyWrite::class:
case Param::class:
/** @var \phpDocumentor\Reflection\DocBlock\Tags\Param $tag */
$type = $tag->getType();
return [
$this->parseHint($type !== null ? explode('|', $type->__toString()) : []),
ltrim($tag->getVariableName() ?? '', '$'),
$tag->getDescription() !== null ? $tag->getDescription()->__toString() : '',
];
case Throws::class:
/** @var \phpDocumentor\Reflection\DocBlock\Tags\Throws $tag */
$type = $tag->getType();
return [
$type !== null ? $type->__toString() : '',
$tag->getDescription() !== null ? $tag->getDescription()->__toString() : '',
];
case See::class:
// For backwards compatibility, in first cell we store content.
// In second - only a referer for further parsing.
// In docblock node we handle this in getOtherTags() method.
/** @var \phpDocumentor\Reflection\DocBlock\Tags\See $tag */
return [
$tag->__toString(),
$tag->getReference()->__toString(),
$tag->getDescription() !== null ? $tag->getDescription()->__toString() : '',
];
case InvalidTag::class:
/** @var \phpDocumentor\Reflection\DocBlock\Tags\InvalidTag $tag */
return $tag->__toString();
default:
return $tag->__toString();
}
}
protected function parseHint(array $rawHints): array
{
$hints = [];
foreach ($rawHints as $hint) {
if ('[]' == substr($hint, -2)) {
$hints[] = [substr($hint, 0, -2), true];
} else {
$hints[] = [$hint, false];
}
}
return $hints;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use PhpParser\Node as AbstractNode;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\NodeVisitorAbstract;
use PhpParser\Node\Stmt\ClassConst as ClassConstNode;
use PhpParser\Node\Stmt\ClassMethod as ClassMethodNode;
use PhpParser\Node\Stmt\Class_ as ClassNode;
use PhpParser\Node\Stmt\ClassLike as ClassLikeNode;
use PhpParser\Node\Stmt\Interface_ as InterfaceNode;
use PhpParser\Node\Stmt\Namespace_ as NamespaceNode;
use PhpParser\Node\Stmt\Function_ as FunctionNode;
use PhpParser\Node\Stmt\Property as PropertyNode;
use PhpParser\Node\Stmt\TraitUse as TraitUseNode;
use PhpParser\Node\Stmt\Trait_ as TraitNode;
use PhpParser\Node\Stmt\Use_ as UseNode;
use PhpParser\Node\NullableType;
use PhpParser\Node\Expr\Error as ExprError;
use PhpParser\Node\Expr as NodeExpr;
use Doctum\Project;
use Doctum\Reflection\Reflection;
use Doctum\Reflection\FunctionReflection;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\ConstantReflection;
use Doctum\Reflection\MethodReflection;
use Doctum\Reflection\ParameterReflection;
use Doctum\Reflection\PropertyReflection;
use Doctum\Parser\Node\DocBlockNode;
use PhpParser\Node\Stmt\PropertyProperty;
use PhpParser\Node\UnionType;
class NodeVisitor extends NodeVisitorAbstract
{
protected $context;
public function __construct(ParserContext $context)
{
$this->context = $context;
}
public function enterNode(AbstractNode $node)
{
if ($node instanceof NamespaceNode) {
$this->context->enterNamespace($node->name === null ? '' : $node->name->__toString());
} elseif ($node instanceof UseNode) {
$this->addAliases($node);
} elseif ($node instanceof InterfaceNode) {
$this->addInterface($node);
} elseif ($node instanceof ClassNode) {
$this->addClass($node);
} elseif ($node instanceof TraitNode) {
$this->addTrait($node);
} elseif ($node instanceof FunctionNode) {
$this->addFunction($node, $this->context->getNamespace());
} elseif ($this->context->getClass() && $node instanceof TraitUseNode) {
$this->addTraitUse($node);
} elseif ($this->context->getClass() && $node instanceof PropertyNode) {
$this->addProperty($node);
} elseif ($this->context->getClass() && $node instanceof ClassMethodNode) {
$this->addMethod($node);
} elseif ($this->context->getClass() && $node instanceof ClassConstNode) {
$this->addConstant($node);
}
return null;
}
public function leaveNode(AbstractNode $node)
{
if ($node instanceof NamespaceNode) {
$this->context->leaveNamespace();
} elseif ($node instanceof ClassNode || $node instanceof InterfaceNode || $node instanceof TraitNode) {
$this->context->leaveClass();
}
return null;
}
protected function addAliases(UseNode $node)
{
foreach ($node->uses as $use) {
$alias = $use->getAlias()->toString();
$fullName = $use->name->__toString();
$this->context->addAlias($alias, $fullName);
}
}
protected function addFunction(FunctionNode $node, ?string $namespace = null)
{
$function = new FunctionReflection($node->name->__toString(), $node->getLine());
$function->setNamespace($namespace !== null ? $namespace : '');
$function->setByRef((string) $node->byRef);
foreach ($node->params as $param) {
if ($param->var instanceof ExprError) {
$errors = [
'The expression had an error, please report this to Doctum for a better handling of this error',
];
$this->context->addErrors($function->__toString(), $node->getLine(), $errors);
continue;
}
if ($param->var->name instanceof NodeExpr) {
$errors = [
'This was unexpected, please report this to Doctum for a better handling of this error',
];
$this->context->addErrors($function->__toString(), $node->getLine(), $errors);
continue;
}
$parameter = new ParameterReflection(
$param->var->name,
$param->getLine()
);
$parameter->setModifiers($param->flags);
$parameter->setByRef($param->byRef);
if ($param->default) {
$parameter->setDefault($this->context->getPrettyPrinter()->prettyPrintExpr($param->default));
}
$parameter->setVariadic($param->variadic);
$type = $param->type;
$typeStr = $this->typeToString($type);
if (null !== $typeStr) {
$typeArr = [[$typeStr, false]];
if ($param->type instanceof NullableType) {
$typeArr[] = ['null', false];
}
$parameter->setHint($this->resolveHint($typeArr));
}
$function->addParameter($parameter);
}
$docComment = $node->getDocComment();
$docComment = $docComment === null ? null : $docComment->__toString();
$comment = $this->context->getDocBlockParser()->parse($docComment, $this->context, $function);
$function->setDocComment($docComment);
$function->setShortDesc($comment->getShortDesc());
$function->setLongDesc($comment->getLongDesc());
$function->setSee($this->resolveSee($comment->getTag('see')));
if (!$errors = $comment->getErrors()) {
$errors = $this->updateMethodParametersFromTags($function, $comment->getTag('param'));
$this->addTagFromCommentToMethod('return', $comment, $function, $errors);
$function->setExceptions($comment->getTag('throws'));
$function->setTags($comment->getOtherTags());
}
$function->setModifiersFromTags();
$function->setErrors($errors);
$returnType = $node->getReturnType();
$returnTypeStr = $this->typeToString($returnType);
if (null !== $returnTypeStr) {
$returnTypeArr = [[$returnTypeStr, false]];
if ($returnType instanceof NullableType) {
$returnTypeArr[] = ['null', false];
}
$function->setHint($this->resolveHint($returnTypeArr));
}
$this->context->addFunction($function);
if ($errors) {
$this->context->addErrors((string) $function, $node->getLine(), $errors);
}
}
/**
* @param \PhpParser\Node\Identifier|\PhpParser\Node\Name|NullableType|UnionType|null $type Type declaration
*/
protected function typeToString($type): ?string
{
$typeString = null;
if ($type !== null && ! ($type instanceof NullableType || $type instanceof UnionType)) {
$typeString = $type->__toString();
} elseif ($type instanceof NullableType) {
$typeString = $type->type->__toString();
} elseif ($type instanceof UnionType) {
$typeString = [];
foreach ($type->types as $type) {
$typeString[] = $type->__toString();
}
$typeString = implode('|', $typeString);
}
if ($typeString === null) {
return null;
}
if ($type instanceof FullyQualified && 0 !== strpos($typeString, '\\')) {
$typeString = '\\' . $typeString;
}
return $typeString;
}
protected function addInterface(InterfaceNode $node)
{
$class = $this->addClassOrInterface($node);
$class->setInterface(true);
foreach ($node->extends as $interface) {
$class->addInterface((string) $interface);
}
}
protected function addClass(ClassNode $node)
{
// Skip anonymous classes
if ($node->isAnonymous()) {
return;
}
$class = $this->addClassOrInterface($node);
foreach ($node->implements as $interface) {
$class->addInterface((string) $interface);
}
if ($node->extends) {
$class->setParent((string) $node->extends);
}
}
protected function addTrait(TraitNode $node)
{
$class = $this->addClassOrInterface($node);
$class->setTrait(true);
}
protected function addClassOrInterface(ClassLikeNode $node)
{
$class = new ClassReflection((string) $node->namespacedName, $node->getLine());
return $this->addClassOrInterfaceForReflection($class, $node);
}
protected function addClassOrInterfaceForReflection(ClassReflection $class, ClassLikeNode $node): ClassReflection
{
if ($node instanceof ClassNode) {
$class->setModifiers($node->flags);
}
$class->setNamespace($this->context->getNamespace() ?? '');
$class->setAliases($this->context->getAliases());
$class->setHash($this->context->getHash());
$class->setFile($this->context->getFile());
$docComment = $node->getDocComment();
$docComment = $docComment === null ? null : $docComment->__toString();
$comment = $this->context->getDocBlockParser()->parse($docComment, $this->context, $class);
$class->setDocComment($docComment);
$class->setShortDesc($comment->getShortDesc());
$class->setLongDesc($comment->getLongDesc());
$class->setSee($this->resolveSee($comment->getTag('see')));
if ($errors = $comment->getErrors()) {
$class->setErrors($errors);
} else {
$otherTags = $comment->getOtherTags();
if (isset($otherTags['readonly'])) {
$class->setReadOnly(true);
}
$class->setTags($otherTags);
}
if ($this->context->getFilter()->acceptClass($class)) {
if ($errors) {
$this->context->addErrors((string) $class, $node->getLine(), $errors);
}
$this->context->enterClass($class);
}
$class->setModifiersFromTags();
return $class;
}
protected function addMethod(ClassMethodNode $node)
{
$method = new MethodReflection($node->name->__toString(), $node->getLine());
$method->setModifiers($node->flags);
$method->setByRef((string) $node->byRef);
foreach ($node->params as $param) {
if ($param->var instanceof ExprError) {
$errors = [
'The expression had an error, please report this to Doctum for a better handling of this error',
];
$this->context->addErrors($method->__toString(), $node->getLine(), $errors);
continue;
}
if ($param->var->name instanceof NodeExpr) {
$errors = [
'This was unexpected, please report this to Doctum for a better handling of this error',
];
$this->context->addErrors($method->__toString(), $node->getLine(), $errors);
continue;
}
$parameter = new ParameterReflection($param->var->name, $param->getLine());
$parameter->setModifiers($param->flags);
$parameter->setByRef($param->byRef);
if ($param->default) {
$parameter->setDefault($this->context->getPrettyPrinter()->prettyPrintExpr($param->default));
}
$parameter->setVariadic($param->variadic);
$type = $param->type;
$typeStr = $this->typeToString($type);
if (null !== $typeStr) {
$typeArr = [[$typeStr, false]];
if ($param->type instanceof NullableType) {
$typeArr[] = ['null', false];
}
$parameter->setHint($this->resolveHint($typeArr));
}
$method->addParameter($parameter);
}
$docComment = $node->getDocComment();
$docComment = $docComment === null ? null : $docComment->__toString();
$comment = $this->context->getDocBlockParser()->parse(
$docComment,
$this->context,
$method
);
$method->setDocComment($docComment);
$method->setShortDesc($comment->getShortDesc());
$method->setLongDesc($comment->getLongDesc());
$method->setSee($this->resolveSee($comment->getTag('see')));
if (!$errors = $comment->getErrors()) {
$errors = $this->updateMethodParametersFromTags($method, $comment->getTag('param'));
$this->addTagFromCommentToMethod('return', $comment, $method, $errors);
$method->setExceptions($comment->getTag('throws'));
$method->setTags($comment->getOtherTags());
}
$method->setModifiersFromTags();
$method->setErrors($errors);
$returnType = $node->getReturnType();
$returnTypeStr = $this->typeToString($returnType);
if (null !== $returnTypeStr) {
$returnTypeArr = [[$returnTypeStr, false]];
if ($returnType instanceof NullableType) {
$returnTypeArr[] = ['null', false];
}
$method->setHint($this->resolveHint($returnTypeArr));
}
if ($this->context->getFilter()->acceptMethod($method)) {
$this->context->getClass()->addMethod($method);
if ($errors) {
$this->context->addErrors((string) $method, $node->getLine(), $errors);
}
}
}
protected function addTagFromCommentToMethod(
string $tagName,
DocBlockNode $comment,
Reflection $methodOrFunctionOrProperty,
array &$errors
): void {
$tagsThatShouldHaveOnlyOne = ['return', 'var'];
$tag = $comment->getTag($tagName);
if (is_array($tag)) {
if (in_array($tagName, $tagsThatShouldHaveOnlyOne, true) && count($tag) > 1) {
$errors[] = sprintf(
'Too much @%s tags on "%s" at @%s found: %d @%s tags',
$tagName,
$methodOrFunctionOrProperty->getName(),
$tagName,
count($tag),
$tagName
);
}
$firstTagFound = $tag[0] ?? null;
if ($firstTagFound !== null) {
if (is_array($firstTagFound)) {
$hint = $firstTagFound[0];
$hintDescription = $firstTagFound[1] ?? null;
$methodOrFunctionOrProperty->setHint(is_array($hint) ? $this->resolveHint($hint) : $hint);
if ($hintDescription !== null) {
if (is_string($hintDescription)) {
$methodOrFunctionOrProperty->setHintDesc($hintDescription);
} else {
$errors[] = sprintf(
'The hint description on "%s" at @%s is invalid: "%s"',
$methodOrFunctionOrProperty->getName(),
$tagName,
$hintDescription
);
}
}
} else {
$errors[] = sprintf(
'The hint on "%s" at @%s is invalid: "%s"',
$methodOrFunctionOrProperty->getName(),
$tagName,
$firstTagFound
);
}
}
}
}
protected function addProperty(PropertyNode $node)
{
foreach ($node->props as $prop) {
[$property, $errors] = $this->getPropertyReflectionFromParserProperty($node, $prop);
if ($this->context->getFilter()->acceptProperty($property)) {
$this->context->getClass()->addProperty($property);
if ($errors) {
$this->context->addErrors((string) $property, $prop->getLine(), $errors);
}
}
}
}
/**
* @return array<int,PropertyReflection|string[]>
* @phpstan-return array{PropertyReflection,string[]}
*/
protected function getPropertyReflectionFromParserProperty(PropertyNode $node, PropertyProperty $prop): array
{
$property = new PropertyReflection($prop->name->toString(), $prop->getLine());
$property->setModifiers($node->flags);
$property->setDefault($prop->default);
$docComment = $node->getDocComment();
$docComment = $docComment === null ? null : $docComment->__toString();
$comment = $this->context->getDocBlockParser()->parse($docComment, $this->context, $property);
$property->setDocComment($docComment);
$property->setShortDesc($comment->getShortDesc());
$property->setLongDesc($comment->getLongDesc());
$property->setSee($this->resolveSee($comment->getTag('see')));
if ($errors = $comment->getErrors()) {
$property->setErrors($errors);
} else {
$this->addTagFromCommentToMethod('var', $comment, $property, $errors);
$otherTags = $comment->getOtherTags();
if (isset($otherTags['readonly'])) {
$property->setReadOnly(true);
}
$property->setTags($otherTags);
}
$property->setModifiersFromTags();
return [$property, $errors];
}
protected function addTraitUse(TraitUseNode $node)
{
foreach ($node->traits as $trait) {
$this->context->getClass()->addTrait((string) $trait);
}
}
protected function addConstant(ClassConstNode $node)
{
foreach ($node->consts as $const) {
$constant = new ConstantReflection($const->name->toString(), $const->getLine());
$docComment = $node->getDocComment();
$docComment = $docComment === null ? null : $docComment->__toString();
$comment = $this->context->getDocBlockParser()->parse($docComment, $this->context, $constant);
$constant->setDocComment($docComment);
$constant->setShortDesc($comment->getShortDesc());
$constant->setLongDesc($comment->getLongDesc());
$constant->setTags($comment->getOtherTags());
$constant->setModifiers($node->flags);
$constant->setModifiersFromTags();
$this->context->getClass()->addConstant($constant);
}
}
/**
* @param array[] $tags
* @phpstan-param array{array{string,bool}|empty-array|string,string,string} $tags
*
* @return array|null
* @phpstan-return non-empty-array{non-empty-array{string,bool},string,string}|null
*/
private function findParameterInTags(array $tags, string $tagName): ?array
{
foreach ($tags as $tag) {
if (! is_array($tag)) {
continue;
}
if (count($tag) < 2) {
continue;
}
if ($tag[1] === $tagName) {
return $tag;
}
}
return null;
}
/**
* @param array[] $tags All the tags
*
* @return string[] The invalid tags
*/
private function getInvalidTags(array $tags): array
{
$invalidTags = [];
foreach ($tags as $tag) {
if (! is_array($tag)) {
$invalidTags[] = $tag;
}
}
return $invalidTags;
}
/**
* @param FunctionReflection|MethodReflection $method
* @param array[] $tags
* @return string[]
*/
protected function updateMethodParametersFromTags(Reflection $method, array $tags): array
{
$errors = [];
// bypass if there is no @param tags defined (@param tags are optional)
if (!count($tags)) {
return $errors;
}
/** @var Reflection[] $parameters */
$parameters = $method->getParameters();
foreach ($parameters as $parameter) {
$tag = $this->findParameterInTags($tags, $parameter->getName());
if (! $parameter->hasHint() && $tag === null) {
$errors[] = sprintf(
'The "%s" parameter of the method "%s" is missing a @param tag',
$parameter->getName(),
$method->getName()
);
continue;
}
if ($tag !== null) {
$parameter->setShortDesc($tag[2]);
if (! $parameter->hasHint()) {
$parameter->setHint($this->resolveHint($tag[0]));
}
}
}
if (count($tags) > count($parameters)) {
$errors[] = sprintf(
'The method "%s" has "%d" @param tags but only "%d" where expected.',
$method->getName(),
count($tags),
count($method->getParameters())
);
}
$invalidTags = $this->getInvalidTags($tags);
if (count($invalidTags) > 0) {
$errors[] = sprintf(
'The method "%s" has "%d" invalid @param tags.',
$method->getName(),
count($invalidTags)
);
foreach ($invalidTags as $invalidTag) {
$errors[] = sprintf(
'Invalid @param tag on "%s": "%s"',
$method->getName(),
$invalidTag
);
}
}
return $errors;
}
protected function resolveHint(array $hints): array
{
foreach ($hints as $i => $hint) {
$hints[$i] = [$this->resolveAlias($hint[0]), $hint[1]];
}
return $hints;
}
protected function resolveAlias($alias)
{
// not a class
if (Project::isPhpTypeHint($alias)) {
return $alias;
}
// FQCN
if ('\\' == substr($alias, 0, 1)) {
return $alias;
}
$class = $this->context->getClass();
// A class MIGHT or MIGHT NOT be present in context.
// It is not present in cases, where eg. `@see` tag refers to non existing class/method.
// We may want to run class related checks only, if class is actually present.
if ($class) {
// special aliases
if ('self' === $alias || 'static' === $alias || '\$this' === $alias) {
return $class->getName();
}
// an alias defined by a use statement
$aliases = $class->getAliases();
if (isset($aliases[$alias])) {
return $aliases[$alias];
}
// a class in the current class namespace
return $class->getNamespace() . '\\' . $alias;
}
return $alias;
}
/**
* @return array<int,array<int,string|false>>
*/
protected function resolveSee(array $see): array
{
$return = [];
foreach ($see as $seeEntry) {
// Example: @see Net_Sample::$foo, Net_Other::someMethod()
if (is_string($seeEntry)) {// Support bad formatted @see tags
$seeEntries = explode(',', $seeEntry);
foreach ($seeEntries as $entry) {
$return[] = $this->getParsedSeeEntry(trim($entry, " \n\t"), '');
}
continue;
}
$reference = $seeEntry[1];
$description = $seeEntry[2] ?? '';
$return[] = $this->getParsedSeeEntry($reference, $description);
}
return $return;
}
/**
* @return array<int,string|false>
*/
protected function getParsedSeeEntry(string $reference, string $description): array
{
$matches = [];
if ((bool) preg_match('/^[\w]+:\/\/.+$/', $reference)) { //URL
return [
$reference,
$description,
false,
false,
$reference,
];
} elseif ((bool) preg_match('/(.+)\:\:(.+)\(.*\)/', $reference, $matches)) { //Method
return [
$reference,
$description,
$this->resolveAlias($matches[1]),
$matches[2],
false,
];
} else { // We assume, that this is a class reference.
return [
$reference,
$description,
$this->resolveAlias($reference),
false,
false,
];
}
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use Doctum\Message;
use Doctum\Project;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\FunctionReflection;
use Doctum\Reflection\LazyClassReflection;
use Doctum\Store\StoreInterface;
use Symfony\Component\Finder\Finder;
class Parser
{
/** @var StoreInterface */
protected $store;
/** @var Finder */
protected $iterator;
/** @var CodeParser */
protected $parser;
/** @var ClassTraverser */
protected $traverser;
public function __construct($iterator, StoreInterface $store, CodeParser $parser, ClassTraverser $traverser)
{
$this->iterator = $this->createIterator($iterator);
$this->store = $store;
$this->parser = $parser;
$this->traverser = $traverser;
}
public function parse(Project $project, $callback = null)
{
$step = 0;
$steps = iterator_count($this->iterator);
$context = $this->parser->getContext();
$transaction = new Transaction($project);
$toStore = new \SplObjectStorage();
foreach ($this->iterator as $file) {
$file = $file->getPathname();
++$step;
$code = file_get_contents($file);
if ($code === false) {
continue;
}
$hash = sha1($code);
if ($transaction->hasHash($hash)) {
continue;
}
$context->enterFile((string) $file, $hash);
$this->parser->parse($code);
if (null !== $callback) {
call_user_func($callback, Message::PARSE_ERROR, $context->getErrors());
}
foreach ($context->getFunctions() as $addr => $fun) {
$project->addFunction($fun);
$toStore->attach($fun);
}
foreach ($context->leaveFile() as $class) {
if (null !== $callback) {
call_user_func($callback, Message::PARSE_CLASS, [$step, $steps, $class]);
}
$project->addClass($class);
$transaction->addClass($class);
$toStore->attach($class);
$class->notFromCache();
}
}
// cleanup
foreach ($transaction->getRemovedClasses() as $class) {
$project->removeClass(new LazyClassReflection($class));
$this->store->removeClass($project, $class);
}
// visit each class for stuff that can only be done when all classes are parsed
$toStore->addAll($this->traverser->traverse($project));
foreach ($toStore as $classOrFun) {
if ($classOrFun instanceof FunctionReflection) {
$this->store->writeFunction($project, $classOrFun);
} elseif ($classOrFun instanceof ClassReflection) {
$this->store->writeClass($project, $classOrFun);
}
}
return $transaction;
}
/**
* @param string|Finder $iterator
* @return Finder
*/
private function createIterator($iterator)
{
if (is_string($iterator)) {
$it = new Finder();
$it->files()->name('*.php')->in($iterator);
return $it;
} elseif (!$iterator instanceof \Traversable) {
throw new \InvalidArgumentException('The iterator must be a directory name or a Finder instance.');
}
return $iterator;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use Doctum\Project;
class ClassTraverser
{
protected $visitors;
public function __construct(array $visitors = [])
{
$this->visitors = [];
foreach ($visitors as $visitor) {
$this->addVisitor($visitor);
}
}
public function addVisitor(ClassVisitorInterface $visitor)
{
$this->visitors[] = $visitor;
}
public function traverse(Project $project)
{
// parent classes/interfaces are visited before their "children"
$classes = $project->getProjectClasses();
$modified = new \SplObjectStorage();
while ($class = array_shift($classes)) {
// re-push the class at the end if parent class/interfaces have not been visited yet
if (($parent = $class->getParent()) && isset($classes[$parent->getName()])) {
$classes[$class->getName()] = $class;
continue;
}
$interfaces = $class->getInterfaces();
foreach ($interfaces as $interface) {
if (isset($classes[$interface->getName()])) {
$classes[$class->getName()] = $class;
continue 2;
}
}
// only visits classes not coming from the cache
// and for which parent/interfaces also come from the cache
$visit = !$class->isFromCache() || ($parent && !$parent->isFromCache());
foreach ($interfaces as $interface) {
if (!$interface->isFromCache()) {
$visit = true;
break;
}
}
if (!$visit) {
continue;
}
$isModified = false;
foreach ($this->visitors as $visitor) {
$isModified = $visitor->visit($class) || $isModified;
}
if ($isModified) {
$modified->attach($class);
}
}
return $modified;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\ClassVisitor;
use Doctum\Parser\ClassVisitorInterface;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\MethodReflection;
use Doctum\Reflection\ParameterReflection;
use Doctum\Reflection\Reflection;
/**
* Looks for @method tags on classes in the format of:
*
* @phpstan-ignore-next-line
* @method [[static] return type] [name]([[type] [parameter]<, ...>]) [<description>]
*/
class MethodClassVisitor implements ClassVisitorInterface
{
public function visit(ClassReflection $class)
{
$modified = false;
$methods = $class->getTags('method');
if (!empty($methods)) {
foreach ($methods as $methodTag) {
if ($this->injectMethod($class, implode(' ', $methodTag))) {
$modified = true;
}
}
}
return $modified;
}
/**
* @license MIT
* @copyright 2010 Mike van Riel
*
* Parse the parts of an @method tag into an associative array.
*
* Original @method parsing by https://github.com/phpDocumentor/ReflectionDocBlock/blob/5.2.0/src/DocBlock/Tags/Method.php#L84
*
* @param string $body Method tag contents
*
* @return array|null
*/
protected function parseMethod($body): ?array
{
// 1. none or more whitespace
// 2. optionally the keyword "static" followed by whitespace
// 3. optionally a word with underscores followed by whitespace : as
// type for the return value
// 4. then optionally a word with underscores followed by () and
// whitespace : as method name as used by phpDocumentor
// 5. then a word with underscores, followed by ( and any character
// until a ) and whitespace : as method name with signature
// 6. any remaining text : as description
$regex = '/^
# Static keyword
# Declares a static method ONLY if type is also present
(?:
(static)
\s+
)?
# Return type
(?:
(
(?:[\w\|_\\\\]*\$this[\w\|_\\\\]*)
|
(?:
(?:[\w\|_\\\\]+)
# array notation
(?:\[\])*
)*+
)
\s+
)?
# Method name
([\w_]+)
# Arguments
(?:
\((.*(?=\)))\)
)?
\s*
# Description
(.*)
$/sux';
if (!preg_match($regex, $body, $matches)) {
return null;
}
[, $static, $returnType, $methodName, $argumentLines, $description] = $matches;
$isStatic = $static === 'static';
if ($returnType === '') {
$returnType = 'void';
}
$arguments = [];
if ($argumentLines !== '') {
$argumentsExploded = explode(',', $argumentLines);
foreach ($argumentsExploded as $argument) {
$argument = explode(' ', trim($argument), 2);
$defaultValue = '';
$argumentType = '';
$argumentName = '';
$hasVariadicAtStart = strpos($argument[0], '...$') === 0;
if (strpos($argument[0], '$') === 0 || $hasVariadicAtStart) {// Only param name, example: $param1
$argumentName = substr($argument[0], $hasVariadicAtStart ? 4 : 1);// Remove $
} else {// Type and param name, example: string $param1 or just a type, example: string
$argumentType = $argument[0];
if (isset($argument[1])) {// Type and param name
$hasVariadicAtStart = strpos($argument[1], '...$') === 0;
$argumentName = substr($argument[1], $hasVariadicAtStart ? 4 : 1);// Remove $
$defaultPart = explode('=', $argumentName, 2);
if (count($defaultPart) === 2) {// detected varName = defaultValue
$argumentName = $defaultPart[0];
$defaultValue = $defaultPart[1];
}
}
}
$argumentName = trim($argumentName);
$argumentType = trim($argumentType);
$defaultValue = trim($defaultValue);
$arguments[$argumentName] = [
'isVariadic' => $hasVariadicAtStart,
'name' => $argumentName,
'hint' => $argumentType,
'default' => $defaultValue,
];
}
}
return [
'isStatic' => $isStatic,
'hint' => trim($returnType),
'name' => $methodName,
'args' => $arguments,
'description' => $description,
];
}
/**
* Adds a new method to the class using an array of tokens.
*
* @param ClassReflection $class Class reflection
* @param string $methodTag Method tag contents
*
* @return bool
*/
protected function injectMethod(ClassReflection $class, $methodTag)
{
$data = $this->parseMethod($methodTag);
// Bail if the method format is invalid
if ($data === null) {
return false;
}
$method = new MethodReflection($data['name'], $class->getLine());
$method->setDocComment($data['description']);
$method->setShortDesc($data['description']);
if ($data['hint']) {
$method->setHint([[$data['hint'], null]]);
}
if ($data['isStatic']) {
$method->setModifiers(Reflection::MODIFIER_STATIC);
}
// Add arguments to the method
foreach ($data['args'] as $name => $arg) {
$param = new ParameterReflection($name, $class->getLine());
if (!empty($arg['hint'])) {
$param->setHint([[$arg['hint'], null]]);
}
if (!empty($arg['default'])) {
$param->setDefault($arg['default']);
}
$param->setVariadic($arg['isVariadic']);
$method->addParameter($param);
}
$class->addMethod($method);
return true;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\ClassVisitor;
use Doctum\Parser\ClassVisitorInterface;
use Doctum\Parser\ParserContext;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\PropertyReflection;
/**
* Looks for @property tags on classes in the format of:.
*
* @phpstan-ignore-next-line
* @property [<type>] [name] [<description>]
*
* Or -read -write properties
*
* @phpstan-ignore-next-line
* @property-read [Type] [name] [<description>]
* @property-write [Type] [name] [<description>]
*/
class PropertyClassVisitor implements ClassVisitorInterface
{
protected $context;
public function __construct(ParserContext $context)
{
$this->context = $context;
}
public function visit(ClassReflection $class)
{
$modified = false;
$properties = $class->getTags('property');
$propertiesRead = $class->getTags('property-read');
$propertiesWrite = $class->getTags('property-write');
if (!empty($properties)) {
foreach ($properties as $propertyTag) {
if ($this->injectProperty($class, $propertyTag, 'property')) {
$modified = true;
}
}
}
if (!empty($propertiesRead)) {
foreach ($propertiesRead as $propertyTag) {
if ($this->injectProperty($class, $propertyTag, 'property-read')) {
$modified = true;
}
}
}
if (!empty($propertiesWrite)) {
foreach ($propertiesWrite as $propertyTag) {
if ($this->injectProperty($class, $propertyTag, 'property-write')) {
$modified = true;
}
}
}
return $modified;
}
/**
* Adds a new property to the class using an array of tokens.
*
* @param ClassReflection $class Class reflection
* @param array $propertyTag Property tag contents
* @param string $tagName The tag name, for example: property-read
*/
protected function injectProperty(ClassReflection $class, array $propertyTag, string $tagName): bool
{
if (count($propertyTag) === 3 && !empty($propertyTag[1])) {
$property = new PropertyReflection($propertyTag[1], $class->getLine());
$property->setDocComment($propertyTag[2]);
$property->setShortDesc($propertyTag[2]);
if (!empty($propertyTag[0])) {
$property->setHint($propertyTag[0]);
}
if ($tagName === 'property-read') {
$property->setReadOnly(true);
} elseif ($tagName === 'property-write') {
$property->setWriteOnly(true);
}
$class->addProperty($property);
return true;
}
return false;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\ClassVisitor;
use Doctum\Reflection\ClassReflection;
use Doctum\Parser\ClassVisitorInterface;
use Doctum\RemoteRepository\AbstractRemoteRepository;
class ViewSourceClassVisitor implements ClassVisitorInterface
{
/** @var AbstractRemoteRepository */
protected $remoteRepository;
public function __construct(AbstractRemoteRepository $remoteRepository)
{
$this->remoteRepository = $remoteRepository;
}
public function visit(ClassReflection $class)
{
$filePath = $this->remoteRepository->getRelativePath($class->getFile());
if ($class->getRelativeFilePath() != $filePath) {
$class->setRelativeFilePath($filePath);
return true;
}
return false;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\ClassVisitor;
use Doctum\Parser\ClassVisitorInterface;
use Doctum\Reflection\ClassReflection;
class InheritdocClassVisitor implements ClassVisitorInterface
{
public function visit(ClassReflection $class)
{
$modified = false;
foreach ($class->getMethods() as $name => $method) {
if (!$parentMethod = $class->getParentMethod($name)) {
continue;
}
foreach ($method->getParameters() as $name => $parameter) {
if (!$parentParameter = $parentMethod->getParameter($name)) {
continue;
}
if ($parameter->getShortDesc() != $parentParameter->getShortDesc()) {
$parameter->setShortDesc($parentParameter->getShortDesc());
$modified = true;
}
if ($parameter->getHint() != $parentParameter->getRawHint()) {
// FIXME: should test for a raw hint from tags, not the one from PHP itself
$parameter->setHint($parentParameter->getRawHint());
$modified = true;
}
}
if ($method->getHint() != $parentMethod->getRawHint()) {
$method->setHint($parentMethod->getRawHint());
$modified = true;
}
if ($method->getHintDesc() != $parentMethod->getHintDesc()) {
$method->setHintDesc($parentMethod->getHintDesc());
$modified = true;
}
$shortDesc = $method->getShortDesc() ?? '';
if ('{@inheritdoc}' === strtolower(trim($shortDesc)) || !$method->getDocComment()) {
if ($shortDesc != $parentMethod->getShortDesc()) {
$method->setShortDesc($parentMethod->getShortDesc());
$modified = true;
}
if ($method->getLongDesc() != $parentMethod->getLongDesc()) {
$method->setLongDesc($parentMethod->getLongDesc());
$modified = true;
}
if ($method->getExceptions() != $parentMethod->getRawExceptions()) {
$method->setExceptions($parentMethod->getRawExceptions());
$modified = true;
}
}
}
return $modified;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser;
use Doctum\Project;
use Doctum\Reflection\ClassReflection;
class Transaction
{
protected $hashes;
protected $classes;
protected $visited;
protected $modified;
public function __construct(Project $project)
{
$this->hashes = [];
$this->classes = [];
foreach ($project->getProjectClasses() as $class) {
$this->addClass($class);
}
$this->visited = [];
$this->modified = [];
}
public function hasHash($hash)
{
if (!array_key_exists($hash, $this->hashes)) {
return false;
}
$this->visited[$hash] = true;
return true;
}
public function getModifiedClasses()
{
return $this->modified;
}
public function getRemovedClasses()
{
$classes = [];
foreach ($this->hashes as $hash => $c) {
if (!isset($this->visited[$hash])) {
$classes = array_merge($classes, $c);
}
}
return array_keys($classes);
}
public function addClass(ClassReflection $class)
{
$name = $class->getName();
$hash = $class->getHash();
if (isset($this->classes[$name])) {
unset($this->hashes[$this->classes[$name]][$name]);
if (!$this->hashes[$this->classes[$name]]) {
unset($this->hashes[$this->classes[$name]]);
}
}
$this->hashes[$hash][$name] = true;
$this->classes[$name] = $hash;
$this->modified[] = $name;
$this->visited[$hash] = true;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\Filter;
use Doctum\Reflection\MethodReflection;
use Doctum\Reflection\PropertyReflection;
class DefaultFilter extends TrueFilter
{
public function acceptMethod(MethodReflection $method)
{
return !$method->isPrivate();
}
public function acceptProperty(PropertyReflection $property)
{
return !$property->isPrivate();
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\Filter;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\MethodReflection;
use Doctum\Reflection\PropertyReflection;
interface FilterInterface
{
public function acceptClass(ClassReflection $class);
public function acceptMethod(MethodReflection $method);
public function acceptProperty(PropertyReflection $property);
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\Filter;
use Doctum\Reflection\ClassReflection;
use Doctum\Reflection\MethodReflection;
use Doctum\Reflection\PropertyReflection;
class TrueFilter implements FilterInterface
{
public function acceptClass(ClassReflection $class)
{
return true;
}
public function acceptMethod(MethodReflection $method)
{
return true;
}
public function acceptProperty(PropertyReflection $property)
{
return true;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\Parser\Filter;
use Doctum\Reflection\MethodReflection;
use Doctum\Reflection\PropertyReflection;
class PublicFilter extends TrueFilter
{
public function acceptMethod(MethodReflection $method)
{
return $method->isPublic();
}
public function acceptProperty(PropertyReflection $property)
{
return $property->isPublic();
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\RemoteRepository;
class GitLabRemoteRepository extends AbstractRemoteRepository
{
protected $url = 'https://gitlab.com/';
public function __construct($name, $localPath, $url = '')
{
if (!empty($url)) {
$this->url = $url;
}
parent::__construct($name, $localPath);
}
public function getFileUrl($projectVersion, $relativePath, $line)
{
$url = $this->url . $this->name . '/blob/' . $this->buildProjectPath($projectVersion, $relativePath);
if (null !== $line) {
$url .= '#L' . (int) $line;
}
return $url;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\RemoteRepository;
class GitHubRemoteRepository extends AbstractRemoteRepository
{
public function getFileUrl($projectVersion, $relativePath, $line)
{
$url = 'https://github.com/' . $this->name . '/blob/' . $this->buildProjectPath($projectVersion, $relativePath);
if (null !== $line) {
$url .= '#L' . (int) $line;
}
return $url;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\RemoteRepository;
class BitBucketRemoteRepository extends AbstractRemoteRepository
{
public function getFileUrl($projectVersion, $relativePath, $line)
{
$url = 'https://bitbucket.org/' . $this->name . '/src/' . $this->buildProjectPath($projectVersion, $relativePath);
if (null !== $line) {
$filename = basename($relativePath);
$url .= '#' . $filename . '-' . $line;
}
return $url;
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum\RemoteRepository;
abstract class AbstractRemoteRepository
{
protected $name;
protected $localPath;
public function __construct($name, $localPath)
{
$this->name = $name;
$this->localPath = $localPath;
}
abstract public function getFileUrl($projectVersion, $relativePath, $line);
public function getRelativePath($file)
{
$replacementCount = 0;
$filePath = str_replace($this->localPath, '', $file, $replacementCount);
if (1 === $replacementCount) {
return $filePath;
}
return '';
}
protected function buildProjectPath(string $projectVersion, string $relativePath): string
{
return str_replace('\\', '/', $projectVersion . '/' . ltrim($relativePath, '/'));
}
}
<?php
declare(strict_types = 1);
/*
* This file is part of the Doctum utility.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Doctum;
use ArrayAccess;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter\Standard as PrettyPrinter;
use PhpParser\Parser as PhpParser;
use Doctum\Parser\ClassTraverser;
use Doctum\Parser\ClassVisitor;
use Doctum\Parser\CodeParser;
use Doctum\Parser\DocBlockParser;
use Doctum\Parser\Filter\DefaultFilter;
use Doctum\Parser\Filter\FilterInterface;
use Doctum\Parser\NodeVisitor;
use Doctum\Parser\Parser;
use Doctum\Parser\ParserContext;
use Doctum\RemoteRepository\AbstractRemoteRepository;
use Doctum\Renderer\Renderer;
use Doctum\Renderer\ThemeSet;
use Doctum\Renderer\TwigExtension;
use Doctum\Store\JsonStore;
use Doctum\Store\StoreInterface;
use Doctum\Version\SingleVersionCollection;
use Doctum\Version\Version;
use Doctum\Version\VersionCollection;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Wdes\phpI18nL10n\Twig\Extension\I18n as I18nExtension;
use Wdes\phpI18nL10n\plugins\MoReader;
use Wdes\phpI18nL10n\Launcher;
/**
* @implements \ArrayAccess<string,mixed>
*/
class Doctum implements ArrayAccess
{
public const VERSION_MAJOR = 5;
public const VERSION_MINOR = 4;
public const VERSION_PATCH = 1;
public const IS_DEV = false;
//@phpstan-ignore-next-line
public const VERSION = self::VERSION_MAJOR . '.' . self::VERSION_MINOR . '.' . self::VERSION_PATCH . (self::IS_DEV ? '-dev' : '');
public static $defaultVersionName = 'main';
/**
* @var \Symfony\Component\Finder\Finder
*/
private $files;
/**
* @var Environment
*/
private $twig;
/**
* @var ThemeSet
*/
private $themes;
/**
* @var Project
*/
private $project;
/**
* @var Parser
*/
private $parser;
/**
* @var Indexer
*/
private $indexer;
/**
* @var Tree
*/
private $tree;
/**
* @var ClassTraverser
*/
private $traverser;
/**
* @var Renderer
*/
private $renderer;
/**
* @var StoreInterface
*/
private $store;
/**
* @var \Doctum\Parser\Filter\FilterInterface
*/
private $filter;
/**
* @var PrettyPrinter
*/
private $pretty_printer;
/**
* @var CodeParser
*/
private $code_parser;
/**
* @var NodeTraverser
*/
private $php_traverser;
/**
* @var PhpParser
*/
private $php_parser;
/**
* @var DocBlockParser
*/
private $docblock_parser;
/**
* @var ParserContext
*/
private $parser_context;
/**
* @var VersionCollection
*/
private $_versions = null;// phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
/** @var array<string,bool|int|string|array|object> */
private $config = null;
/** @var string */
private $theme = 'default';
/** @var string */
private $title = 'API';
/** @var string */
private $version;
/** @var VersionCollection|string|null */
private $versions = null;
/** @var string[] */
private $template_dirs = [];
/** @var string */
private $build_dir;
/** @var string */
private $cache_dir;
/** @var string */
private $project_dir;
/** @var AbstractRemoteRepository|null */
private $remoteRepository = null;
/** @var string */
private $source_url = '';
/** @var string */
private $source_dir = '';
/** @var int */
private $default_opened_level = 2;
/** @var bool */
private $insert_todos = false;
/** @var bool */
private $sort_class_properties = false;
/** @var bool */
private $sort_class_methods = false;
/** @var bool */
private $sort_class_constants = false;
/** @var bool */
private $sort_class_traits = false;
/** @var bool */
private $sort_class_interfaces = false;
/** @var AbstractRemoteRepository|null */
private $remote_repository = null;
/** @var string|null */
private $base_url = null;
/** @var string|null */
private $favicon = null;
/**
* @var array<string,string>|null
*/
private $footer_link = null;
/**
* include parent properties and methods on class pages
*
* @var bool
*/
private $include_parent_data = true;
public function __construct($iterator = null, array $config = [])
{
$this->config = $config;
if (null !== $iterator) {
$this->files = $iterator;
}
$this->version = self::$defaultVersionName;
$this->build_dir = getcwd() . DIRECTORY_SEPARATOR . 'build';
$this->cache_dir = getcwd() . DIRECTORY_SEPARATOR . 'cache';
$this->source_dir = getcwd() . DIRECTORY_SEPARATOR;
foreach ($config as $key => $value) {
$this->{$key} = $value;
}
// I explain what I understood about the code:
// This file is used in each Doctum config
// The console commands boots the app and requires this config file
// If it returns a Doctum instance, then okay it can go to the next step
// -- Next step
// The console does things and calls getProject() on this class and does the render
// The console can call setVersion and make only one version render, using the --only-version name provided
// End
// -- Code explained
// The ArrayAccess extends is because before the project was using pimple
// I decided it was overkill and replaced it by pure PHP code, we all love pure PHP code.
// But I still have to be backwards compatible with "hackers" that used this system "badly"
// By replacing the things they did want, bug or feature nobody cares.
// So allow the non standard configs to do this for example
// $doctum['store'] = function () { return new ApiJsonStore(); };
// Yes, it feels strange but that can not be removed silently now.
// So allow array access get/set on this class
// Without array access it still could be possible using the config because we do not whitelist configs params
}
public function setVersion(string $version): void
{
$this->versions = $version;
}
public function getProject(): Project
{
$project = $this->offsetGet('project');
$project->setSourceDir($this->source_dir);
return $project;
}
/**
* @param string $offset
* @param mixed $value
* @return void
*/
public function offsetSet($offset, $value)
{
$this->{$offset} = $value;
}
/**
* @param string $offset
* @return bool
*/
public function offsetExists($offset)
{
return isset($this->{$offset});
}
/**
* @param string $offset
* @return void
*/
public function offsetUnset($offset)
{
unset($this->{$offset});
}
/**
* @param string $offset
* @return mixed
*/
public function offsetGet($offset)
{
// TODO: maybe drop this in the next major release
if (! isset($this->{$offset}) && ! is_callable($this->{$offset})) {
switch ($offset) {
case 'project':
$this->project = $this->getBuiltProject();
break;
case 'twig':
$this->twig = $this->getTwig();
break;
case '_versions':
$this->_versions = $this->getVersions();
break;
case 'traverser':
$this->traverser = $this->getTraverser();
break;
case 'themes':
$this->themes = $this->getThemes();
break;
case 'renderer':
$this->renderer = $this->getRenderer();
break;
case 'parser':
$this->parser = $this->getParser();
break;
case 'tree':
$this->tree = $this->getTree();
break;
case 'indexer':
$this->indexer = $this->getIndexer();
break;
case 'store':
$this->store = $this->getStore();
break;
case 'filter':
$this->filter = $this->getFilter();
break;
case 'parser_context':
$this->parser_context = $this->getParserContext();
break;
case 'pretty_printer':
$this->pretty_printer = $this->getPrettyPrinter();
break;
case 'code_parser':
$this->code_parser = $this->getCodeParser();
break;
case 'docblock_parser':
$this->docblock_parser = $this->getDocblockParser();
break;
case 'php_parser':
$this->php_parser = $this->getPhpParser();
break;
case 'php_traverser':
$this->php_traverser = $this->getPhpTraverser();
break;
}
}
// This is for users to be able to override our properties like they want..
// TODO: maybe drop this in the next major release
if (isset($this->{$offset}) && is_callable($this->{$offset})) {
$this->{$offset} = ($this->{$offset})($this);
}
return isset($this->{$offset}) ? $this->{$offset} : null;
}
private function getTwig(): Environment
{
$dataDir = __DIR__ . '/../locale/';
$moReader = new MoReader(
['localeDir' => $dataDir]
);
/** @var string $configLanguage */
$configLanguage = $this->config['language'] ?? 'en';
$moReader->readFile($dataDir . $configLanguage . '.mo');
Launcher::setPlugin($moReader);
$twig = new Environment(
new FilesystemLoader(['/']),
[
'strict_variables' => true,
'debug' => true,
'auto_reload' => true,
'cache' => false,
]
);
Doctum::addTwigExtensions($twig);
return $twig;
}
/**
* @internal Do not use outside of the project
*/
public static function addTwigExtensions(Environment $twig): void
{
$twig->addExtension(new TwigExtension());
$twig->addExtension(new I18nExtension());
}
private function getBuiltProject(): Project
{
$project = new Project(
$this['store'],
$this['_versions'],
[
'build_dir' => $this->build_dir,
'cache_dir' => $this->cache_dir,
'remote_repository' => $this->remote_repository,
'include_parent_data' => $this->include_parent_data,
'default_opened_level' => $this->default_opened_level,
'theme' => $this->theme,
'title' => $this->title,
'source_url' => $this->source_url,
'base_url' => $this->base_url,
'favicon' => $this->favicon,
'insert_todos' => $this->insert_todos,
'footer_link' => $this->footer_link,
'sort_class_properties' => $this->sort_class_properties,
'sort_class_methods' => $this->sort_class_methods,
'sort_class_constants' => $this->sort_class_constants,
'sort_class_traits' => $this->sort_class_traits,
'sort_class_interfaces' => $this->sort_class_interfaces,
]
);
$project->setRenderer($this['renderer']);
$project->setParser($this['parser']);
return $project;
}
private function getVersions(): VersionCollection
{
$versions = $this->versions ?? $this->version;
if (is_string($versions)) {
$versions = new Version($versions);
}
if ($versions instanceof Version) {
$versions = new SingleVersionCollection($versions);
}
return $versions;
}
private function getTraverser(): ClassTraverser
{
$visitors = [
new ClassVisitor\InheritdocClassVisitor(),
new ClassVisitor\MethodClassVisitor(),
new ClassVisitor\PropertyClassVisitor($this['parser_context']),
];
if ($this['remote_repository'] instanceof AbstractRemoteRepository) {
$visitors[] = new ClassVisitor\ViewSourceClassVisitor($this['remote_repository']);
}
return new ClassTraverser($visitors);
}
private function getThemes(): ThemeSet
{
/** @var string[] $templates */
$templates = $this->config['template_dirs'] ?? [];
$templates[] = __DIR__ . '/Resources/themes';
return new ThemeSet($templates);
}
private function getRenderer(): Renderer
{
return new Renderer($this['twig'], $this['themes'], $this['tree'], $this['indexer']);
}
private function getParser(): Parser
{
return new Parser($this->files, $this['store'], $this['code_parser'], $this['traverser']);
}
private function getTree(): Tree
{
return new Tree();
}
private function getIndexer(): Indexer
{
return new Indexer();
}
private function getStore(): StoreInterface
{
return new JsonStore();
}
private function getFilter(): FilterInterface
{
return new DefaultFilter();
}
private function getParserContext(): ParserContext
{
return new ParserContext($this['filter'], $this['docblock_parser'], $this['pretty_printer']);
}
private function getPrettyPrinter(): PrettyPrinter
{
return new PrettyPrinter();
}
private function getCodeParser(): CodeParser
{
return new CodeParser($this['parser_context'], $this['php_parser'], $this['php_traverser']);
}
private function getDocblockParser(): DocBlockParser
{
return new DocBlockParser();
}
private function getPhpParser(): PhpParser
{
return (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
}
private function getPhpTraverser(): NodeTraverser
{
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver());
$traverser->addVisitor(new NodeVisitor($this['parser_context']));
return $traverser;
}
}
pGSUB %züTOS/2> IøPVcmapˆC쩨~cvt Õÿ4 fpgmŠ<C5A0>YT pgasp,glyfœäÖ(âheadrés 6hhea<VD$hmtx ¸h loca`qtmaxpÝ ·| name œUpostßûE4ô7prepåA+¼Ä
0>DFLTlatnligaè<01>z¼Œz¼à1PfEdÀèèRÿjZR,VP,
V$èÿÿèÿÿ (èèèèÿüVÖ&E@B G D`TXL! &&
+23'5'#"'.4>"2>4.ŒI}$%" !Ð>Ñ $[1J?=II|J3V32WfV22VÕI=?J1[$ Ñ>Ð! "%$}“|IS3VfV22VfW2|³-+MË|ý¹#<00>œr@_ èÛ5ÒÞÛ5ÒÞÿüèÖRÿjèèèèè`q'sV pÞ"( .9
+? j *}  § ³  Á  Í Ù  ï
Vû &QMaterial Design IconsdoctumRegulardoctumdoctumVersion 1.0doctumGenerated by svg2ttf from Fontello project.http://fontello.comMaterial Design IconsdoctumRegulardoctumdoctumVersion 1.0doctumGenerated by svg2ttf from Fontello project.http://fontello.com
searchplayÿÿRÿjRÿj°, °UXEY K¸QK°SZX°4°(Y`f ŠUX°%a¹cc#b!!°C#D²C`B-°,° `f-°, d °ÀP°&Z²(
CEcER[X!#!ŠX °PPX!°@Y °8PX!°8YY ±
CEcEad°(PX!±
CEcE °0PX!°0Y °ÀPX f ŠŠa °
PX` ° PX!°
` °6PX!°6``YYY°+YY#°PXeYY-°, E °%ad °CPX°#B°#B!!Y°`-°,#!#! d±bB °#B±
CEc±
`Ec°*! °C Š Š°+±0&QX`PaRYX#Y! °@SX°+!°@Y#°PXeY-°C+²C`B-°#B# °#Ba°bf°*-°, E ° Cc¸b °PX°@`Yf°c`D°`-° CEB*!²C`B-° ,°C#D²C`B-°
, E °+#°%` EŠ#a d ° PX!°°0PX° °@YY#°PXeY°%#aDD°`-° , E °+#°%` EŠ#a d°$PX°°@Y#°PXeY°%#aDD°`-° , °#B²
EX!#!Y*!-° ,±E°daD-°` ° CJ°PX ° #BY° CJ°RX ° #BY-°, °bf°c ¸cŠ#a°C` Š` °#B#-°,KTX±dDY$° e#x-°,KQXKSX±dDY!Y$°e#x-°CUX±aB°+Y°%B± %B± %B°# °%PX±C`°%BŠŠ Š#a°*!#°a Š#a°*!±C`°%B°%a°*!Y° CG° CG`°b °PX°@`Yf°c ° Cc¸b °PX°@`Yf°c`±#D°C`B-°,±ETX°#B E° #B°
`B `°BBŠ`±+°r+"Y-°+-°+-°+-°+-°+-°+-°+-°+-°+-°+-°,° +±ETX°#B E° #B°
`B `°BBŠ`±+°r+"Y-°+-° ,±+-°!,±+-°",±+-°#,±+-°$,±+-°%,±+-°&,±+-°',±+-°(,± +-°), <°`-°*, `°` C#°`C°%a°`°)*!-°+,°*+°**-°,, G ° Cc¸b °PX°@`Yf°c`#a8# ŠUX G ° Cc¸b °PX°@`Yf°c`#a8!Y-°-,±ETX°°,*°0"Y-°.,° +±ETX°°,*°0"Y-°/, 5°`-°0,°Ec¸b °PX°@`Yf° Cc¸b °PX°@`Yf°´D>#8±/*-°1, < G ° Cc¸b °PX°@`Yf°c`°Ca8-°2,.<-°3, < G ° Cc¸b °PX°@`Yf°c`°Ca°Cc8-°4,±% . G°#B°%IŠŠG#G#a Xb!Y°#B²3*-°5,°°%G#G#a° C+eŠ.# <Š8-°6,°°% .G#G#a °#B° C+ °`PX °@QX³  ³&YBB# °C Š#G#G#a#F`°b °PX°@`Yf°c` °+ ŠŠa °C`d#°CadPX°Ca°C`Y°b °PX°@`Yf°ca# °&#Fa8CF°CG#G#a` °b °PX°@`Yf°c`# °+#°C`°%a°b °PX°@`Yf°&a °%`d#°%`dPX!#!Y# °&#Fa8Y-°7,° °& .G#G#a#<8-°8,° °#B F#G°+#a8-°9,°°%G#G#a°TX. <#!°%G#G#a °%G#G#a°%I°%a¹cc# Xb!Yc¸b °PX°@`Yf°c`#.# <Š8#!Y-°:,° °C .G#G#a `° `f°b °PX°@`Yf°c# <Š8-°;,# .F°%FRX <Y.±++-°<,# .F°%FPX <Y.±++-°=,# .F°%FRX <Y# .F°%FPX <Y.±++-°>,°5+# .F°%FRX <Y.±++-°?,°6+Š <°#BŠ8# .F°%FRX <Y.±+C.°++-°@,°°& .G#G#a° C+# < .#8±++-°A,±%B°°% .G#G#a °#B° C+ °`PX °@QX³  ³&YBB# G°b °PX°@`Yf°c` °+ ŠŠa °C`d#°CadPX°Ca°C`Y°b °PX°@`Yf°ca°%Fa8# <#8! F#G°+#a8!Y±++-°B,°5+.±++-°C,°6+!# <°#B#8±+C.°++-°D,°#B².°1*-°E,°#B².°1*-°F,±°2*-°G,°4*-°H,°E# . FŠ#a8±++-°I,°#B°H+-°J,²A+-°K,²A+-°L,²A+-°M,²A+-°N,²B+-°O,²B+-°P,²B+-°Q,²B+-°R,²>+-°S,²>+-°T,²>+-°U,²>+-°V,²@+-°W,²@+-°X,²@+-°Y,²@+-°Z,²C+-°[,²C+-°\,²C+-°],²C+-°^,²?+-°_,²?+-°`,²?+-°a,²?+-°b,°7+.±++-°c,°7+°;+-°d,°7+°<+-°e,°°7+°=+-°f,°8+.±++-°g,°8+°;+-°h,°8+°<+-°i,°8+°=+-°j,°9+.±++-°k,°9+°;+-°l,°9+°<+-°m,°9+°=+-°n,°:+.±++-°o,°:+°;+-°p,°:+°<+-°q,°:+°=+-°r,³ EX!#!YB+°$Px°0-K¸ÈRX±ŽY°¹cp±
À@D±$ˆQX°@ˆdD±&ˆQXº@ˆcTX±DYYYY³  *¸ÿ…°<04>±DwOFF 4LGSUBX;T %zOS/2CV> IøcmapØP~ˆCì©cvt ( Õÿfpgm<<05> <C5A0>YgaspÌglyfÔÝâœäÖhead´16réshheaè$<Vhmtx  ¸loca `qmaxp  Ý ·name 8XU ,°post
<EFBFBD>&7ßûE4prep
¸z†åA+¼xœc`d``àb0`°c`rqó aàËI,Éc<C389>b`a€<00><21'3=<>Æʱ€iˆ&;Hxœc`d~Á8<C381><38><EFBFBD><E280A2>©ŠiC„f|À`ÈÈe`efÀ
Ò\S¼`xÁÈô?!Š9ˆaP˜$ xœí<C593>± €0 ω¡@ŒAAÁ0Tì_²Eò¶ƒ—2°]\ÂÁŒÈ­Õrïl¹;GÞxì¯<C3AC>!V÷t“Vþìéók-þTÔç
Ú̸ ]xœc`@Èô? lÝ­ViwÓFyIœ„,% -jaÄÄi°F&lÁ€ A²c ]œ­• ‹;é¾ñ‰ßà_ódÚsè7~Zï/$<24>´çpš“£wçÍÕÌÛeZØ ë‘”›/ÅÔÖ&<26>ì< é†MËQ|(;{!eŠÉßQ­–Ú·‡DD"Pµ®°DûYšd|èQF˶¤WMÊ-=é.[ãA½Uß~:ʱ;¡¤f3th=²%UU¢H¦=RÒ¦e¨ú+I+¼¿ÂÌWÍPˆN"i¼ÆÐHÞg´Êh5¶ã(Šl²Ü(R$šáAy”ÕçäŠ ÊÍ<>òʧåÃüˆ¬Ø£œV°K¶Óü¾/y§w9?±_oQ¶ä@ÈŽìàìt%_„[[aÜ´“í(Tv×wBlÙìTÿf<C3BF>òšF·+2½ÐŒ`©|…+?¡Ìþ!Y-œOùG£Z²AëeNìK><3E>Öãˆ)qÍYÐÝÑ Ôý3 ö˜>üñÞ) xËzG%œ)as4IÚ0r`%e*©õ®˜8åuZÄ[Â~íÚÑ—ÎhãPwb<[[9QÉñhR§™L<E284A2>ÚIÍ£) ¢”t&x̯(?¢I^mc5‰•GÓ8fÆ„D"-ÜKSA,;±¤)Í£½¹¦¹v-Z¤ÉõÜ£÷ôæV¸¹ÓSÚô³FV§b:Ø Óéiä/ñiÚå"Eéúé~LâAÖ2-6ÔƒÒË×–…×Øîíó+¨}ÖDð¤ûÐOÕ) L…˜UˆV@b­kYÉÕ¬©ÈÔwCšV¾¬ÓŠrǾŒqý_33˜¾ß‰Ó³#.=síKÓ|u=š×©ÅrqfyN§Yïë4Çò<03>æYž×éK[§£,?Ôi<C394>åG:cyA štßÁ<C39F><0E>0„¥CX^!, CXaË%ÂòcÂr†°¼¬eÕÔSIãÚ™XlB`bŽ¹ÌEåj*¹TB»hTjCžn•TϪe ^<ò†9°H—Èš_1Ε<C38E>FåøÖ-o;WÀ±êoŽö9ñRÖ…?ÿÕÖT%½bÍÓ«ð†žl'ª6©xtM—ÏU=ºþ_TTX ôH…X(ʲlpg"„<>:<3A>†j •CÌlÌ<´ëuËšŸÃý71BPýø7 ܃NYIYíà¬Õ×Û²Ü;ƒr8,I17óúVø"#³Ò~YÊž<C38A>|pŒJeØj­¼Ù'1™Þ$Ïq[Q6HÚØÎó€yó<79>&aêª äNᆠøanÁy'\¢z£,‡îEìó(¤ü[§âDö¨hŒÀ³Ùa¯ïBÊoq $4ù¥~ T¡©5Ð4Rn¨_ÆÙºmBÆô#*vò¬âÃÇ÷•m„|¤ˆÕ££ŸÖ^¢NªÜ~f—ï<E28094>¾Á 51{ßtq<74>Ê»ZÉ2Gm•SךCãU‡êæQõ½ãì9kšnº'z_ÓªÛÁÅ\,°ömÒR&Ôõa… ¢ËÅ¥Pêe4Iï¸ÐïPŠ<50>ÿ«úØ|ž+U…Ñq$ßNÔ·Ñç` üØÐ÷cèr .Ï÷šŸnôál™®¢ë§è70k­¹YºÜÐtâ!G­Ž¸Ê |§qz¤¹é!àcÝâÀ&€ÅàݵŒæS£ùŒ9>ÀçÌaÐdƒ-æ0ØfÎÀsì2‡Ás|Áœu€/™Ã dƒˆ9 ž0'_1‡Á×Ìað sî1s$Ìa°¯éÖ0Ì-^Ð] ¶AU SOXÜÃâPSeÈþŽ†ý½AÌþÁ ¦þ¨éö<C3A9>ú/ õgƒ˜úALýUÓ<55>!õ7^êúL¿Ëe¿Œ|—
”]l>çï‰÷ÛÅ@¹ÿÿŠ±NÂPFÿï¿ÒªWj•ZH„Ònq¤£1·ÑIs1aqêS”7`óQœxäi4Øx3œCLtüäSL<53>w÷§€¸ÁO’ù¡² @âã`Æ[%ð;UÃs§
¸7çgä÷kîªåµ®\ËöRÏNný0cšÁ ¸7P¦c`­WÑ ìÊ`—íe´œäó™ÖežšT-
£”áo=ç“e$÷Ù.<2E>Ýp­>Kýššÿ\,<14>*Ä5—Äd}<7D>0¢¼`Ëå硫%Kxœc`d``âÞ9b<ñü6_¸™_En^º‡ ÿÿa~Át Èå``Wð +xœc`d``úŸ$_00€IF šèèè`q'sV pxœ]<5D>=NÃ0…_ú‡h!<21>ÒŸ<C392>.,U«èÐ=Mœ4UGŽ[©çá=à,\<5C>°ðšx ÄŠó½çç—ÄÕuÅ»b-ªŠk8Á<38>å:ý[Ë ò½å&:p-·8ž,·ñˆ\bͧqJõ€WËÎðf¹†sì-×é¿[n<>?,7q<37>OË-úßÛXàÇrwÎËÌ3RÇ^"F²ˆ£LL}•<15>òÍ&<26>Ëh“xºÕ¼<C395>ºˆU&ún¯2&2“š<15>XîD±<44>Æ„"Ô*c•™$JäZ­¥oÜ•1ù°Û ­ïú*Å  $4bR<02>UA!£šÂ‡"øô™Ý Åœ™ˆ”p<E2809D>>Zù˲÷Хʮ>¿w”˜0•©ê+¦Øq.°åt Bê<42>Åã²í<C2B2>N8<14>¼\[Ó9ôºX•»r ÑåÿåÝò<C39D>Ò_·jVxœc`b€.쀙<E28098>­85±(9ƒ¥ '±<C2B1>%<25>RxœcðÞÁp"(b##c_äÆ<>  ÉX<>6102h<32>›¹˜9 ,>0ÍiÓ 4'<27>Íî´ÁÂffpÙ¨ÂرÁ¡#b#sŠËF5oG#CGrHHI$læabäÑÚÁø¿uKïF& v#ô<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Material Design Icons</metadata>
<defs>
<font id="doctum" horiz-adv-x="1000" >
<font-face font-family="doctum" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="search" unicode="&#xe800;" d="M396 725c149 0 271-121 271-271 0-67-25-129-65-176l11-11h33l208-209-62-62-209 208v33l-11 11c-47-40-109-65-176-65-150 0-271 122-271 271 0 150 121 271 271 271m0-83c-104 0-188-84-188-188 0-104 84-187 188-187 104 0 187 83 187 187 0 104-83 188-187 188z" horiz-adv-x="1000" />
<glyph glyph-name="play" unicode="&#xe801;" d="M333 636v-583l459 291-459 292z" horiz-adv-x="1000" />
</font>
</defs>
</svg>wOF2ÈLqTV~ p
<EFBFBD>d<EFBFBD>v 6$  „U7 <0C>M£¢NmR²¿8°Û|¼ƒ`Á<> áŠvê£QßéÄÿM´ÛnÞýu~dÓ÷ÍìÞ Ègeñ3íOR!<21>ºT©<54>ˆ“½ÛM†“]H"Ý€w—EÑ Ž8,Ã0ø´}³¢OsÃ[±|ÄVË7ðu4³VÔõtÕÉàV.—Pj»¹|¹5Š„BáòÍ85­×ÿÿk©½ïÎl`3‰ŒþIZ…«3Ý7p¶?8“pA¨€k…AÖ@X¹Ê
ç*|Ø-QCì €QªB¼UÔµÁz ¯- €½4Ó“ÃÐã“H}­rÃÁÕu(À/~ÀeýçÓ<C3A7>=˜2@¸bÓ£| /ºà"ÑòÿŒ¦ wËËBÊXäDÈ&ÚCg5+Œ¾Y"É .úÿ•(‘àÿ<š xfűf3¸@¸ÎSíÇè$RøuU<75>(ОÆYzóÖæ7¨ßO‰Œ3±¿r¡®=ÁÚþ×»ªS;qÞƒnPj£  Å‹CgâE‡ ŸúXo,ŸŽ÷?'oÞu]¢i7yœÙ ‡9Ûñ˪ &´gÄz#׈8¦ÚÔTxq>]¨»ØZ0Î,NháÖH1 YïAÉ9;Èr6ДÎsh,¤¶gBánBˆ*ÑyŸ¡<18>0o^gU)£˜°bU_ǧˆÜ •ê´ÿƒ³VS»QÈ°—Ê®k÷s²ì…ó Aæ<NîBè¸Þƒ‡êS×I4<49>ˆØÂåTA%è <20>…‰§ÿ¤+šÎ’£ʨ<C38A>—Ó£×<C2A3>Áq\ó Æ_m,áædJZ4î"
<EFBFBD><EFBFBD>¤<14>#rÚç¨Y ¡ÖŽŠßSÖ„ÈĦ˜µ3O©7<C2A9>DyòÎ|^ó *·ÊkG+ (aæk8kÌ”˜±¸¤E,lnÃñõOºª(°²¨V‰ÃTe=Ê Xûp÷MÞKoÈÄyÒñ¬ì<C2AC>
[éQΦ^*"6Ë ÖX͈ íLÝM• Õ]T•Kåޠ®vqà¬0ógÌœbORàñ´9cïºXlöä‰~<1SqùaÂ&QcL1
£
Tüv[Ìb†w솪G/<2F>•uw+¼Pá¹ðF%>døRÀ<52>þT@<15>Ô!ˆzÓÐpo[#$TBDSad„S TEÑÔ!†zÄÒ0)jf%Ä/TpV_5Ý)7Eý1 ÞˆÏ<>¦ê‰ÖnÚjjóc—q>Fÿª´#1‡¶w:V<><B6<)zØÆÓHÅ }
Y… ûûÀç€í©mÖï½°C<C2B0>¦p+(Å£@ך@z² O“¾° Tv»E 4ÖWÝÅ©M­U:q>CûtOÞ—×>HEjô™1pý¼K©éQƒd5h€ÏRbŠ{C»I8÷ÒO~Ìkî'‡ù€£ =ÕE¤<E$ÙE<C399>­ÄÏÀ,g†`{†g<49>.KFhú»jMB´ÎÐs«ÔpÈ"Ä8ÒBmí]+Ú¢”8Z´JP %\~­WÏÄÈî?PB«5AŽc ô%M´"<22>P6',h|42/‡¶4PcÞÁÇ e¸U];¼Z¯†ci“3m4Q¬L»6—×3]´xAÅãqfvÆÉ~R“5Á{­åç_ÀýMÇw_2­Àù@ušO§”Cuø2HH°€sæbˆ¦¿ñ¦FxÍfQã7ªžÚÛq:ü'¦3ž6]W)Ût~R7ùÜ>[2qßYÚä =ˆmvWʪ½çÌ´Ç"˜»V`χ¥|zÅF9Ò<>dh¨º|Ʀ<C386>&]D1ŠÒê<C392>i1¡ÊZ f£‡A1&Ay$šÁøP"Áy$ž!øP(!y$™¡øà”ÊÐ<Î0|(•É°<Íp|(Ëðx$/›ö1Äöw$Ì0<C38C>pÆ$ã)|@)ÀD<C380>#%&]¦\¦«fòs0ëæ`ÎÍÁ¼›ƒ…ü\,º¹Xrs±ìæ.µøˆAòNÑ9JÔ<>?("¶6%“@hRÁªU3A<”qŠ—Ô>6F'˜½ôYñK8ú4ÑÛÔÞÔA>O~j¼ØðþçÇ_|Ñ~ñÓO<C393>>ûìøìŽ?ÿ|VN^ú´z¯*¿¡ÆÖóËa|ø݇„=PØf¯ùõ•ª½[U»»UÕ-éíiiíööûD»Ýä:ˆUÊëv+ßW‡ ÞþÿGìüù×$VÞs艄ÍjL\i61—)5AwIÄLmˆ¸ ×ÂÎÑ<>ÑžîFüó×)^ <09>è’ì¿}ºégáàÌ<E280BA>¿§µjç?[<5B>!upHi2PA
V„Æ|
ò5‰ÉY™Y…X<EFBFBD><EFBFBD>Žy B_¾ã ¶!*oÃt<02> 
™‰5
“d£Rå£E77ñ4C Ñ Þ>`ˆÙ=ì1ÙK"3ï<>¯ŒÊìOcÔáï¹&Ñ5ß(ž<>Ñ»”šVÇþè§fþÉÆ!ªÑåŒÑöäTÏק,¼º1™¶¶6qIBÄÒcƒñ'+¢Á5az,å®PoØšq+¦ ›£)ãdÕŸ´Ðª<>Z´˜E£Ö¬×bܤus†­°2¶£Ì g±³]²ÔX©´ ô:F3b{ÅU&¥YƒgLÐL`.š§©`5eÛœ9x”f)|ÔŒñ2G%Ê1cI®qgiG%-e4|#¬.b$¡žú±ò¤Î Ô~ñfK×CDW»¤Ý€‰Ï‡ lRñÆìGŒLz´ÈŠÕëÓü"bÀ¡+†óÌeŸH:ßÜõ ó|Ž1FÏ÷´þ4z“Ñ6¼ e7áÚ<C3A1>˜™ÌñOÒÿg<C3BF>Wb”L<00>LP@rœ<72> doctumRegularVersion 1.0 doctumpGSUB %züTOS/2> IøPVcmapˆC쩨~cvt Õÿ4 fpgmŠ<C5A0>YT pgasp,glyfœäÖ(âheadrés 6hhea<VD$hmtx ¸h loca`qtmaxpÝ ·| name œUpostßûE4ô7prepåA+¼Ä
0>DFLTlatnligaè<01>z¼Œz¼à1PfEdÀèèRÿjZR,VP,
V$èÿÿèÿÿ (èèèèÿüVÖ&E@B G D`TXL! &&
+23'5'#"'.4>"2>4.ŒI}$%" !Ð>Ñ $[1J?=II|J3V32WfV22VÕI=?J1[$ Ñ>Ð! "%$}“|IS3VfV22VfW2|³-+MË|ý¹#<00>œr@_ èÛ5ÒÞÛ5ÒÞÿüèÖRÿjèèèèè`q'sV pÞ"( .9
+? j *}  § ³  Á  Í Ù  ï
Vû &QMaterial Design IconsdoctumRegulardoctumdoctumVersion 1.0doctumGenerated by svg2ttf from Fontello project.http://fontello.comMaterial Design IconsdoctumRegulardoctumdoctumVersion 1.0doctumGenerated by svg2ttf from Fontello project.http://fontello.com
searchplayÿÿRÿjRÿj°, °UXEY K¸QK°SZX°4°(Y`f ŠUX°%a¹cc#b!!°C#D²C`B-°,° `f-°, d °ÀP°&Z²(
CEcER[X!#!ŠX °PPX!°@Y °8PX!°8YY ±
CEcEad°(PX!±
CEcE °0PX!°0Y °ÀPX f ŠŠa °
PX` ° PX!°
` °6PX!°6``YYY°+YY#°PXeYY-°, E °%ad °CPX°#B°#B!!Y°`-°,#!#! d±bB °#B±
CEc±
`Ec°*! °C Š Š°+±0&QX`PaRYX#Y! °@SX°+!°@Y#°PXeY-°C+²C`B-°#B# °#Ba°bf°*-°, E ° Cc¸b °PX°@`Yf°c`D°`-° CEB*!²C`B-° ,°C#D²C`B-°
, E °+#°%` EŠ#a d ° PX!°°0PX° °@YY#°PXeY°%#aDD°`-° , E °+#°%` EŠ#a d°$PX°°@Y#°PXeY°%#aDD°`-° , °#B²
EX!#!Y*!-° ,±E°daD-°` ° CJ°PX ° #BY° CJ°RX ° #BY-°, °bf°c ¸cŠ#a°C` Š` °#B#-°,KTX±dDY$° e#x-°,KQXKSX±dDY!Y$°e#x-°CUX±aB°+Y°%B± %B± %B°# °%PX±C`°%BŠŠ Š#a°*!#°a Š#a°*!±C`°%B°%a°*!Y° CG° CG`°b °PX°@`Yf°c ° Cc¸b °PX°@`Yf°c`±#D°C`B-°,±ETX°#B E° #B°
`B `°BBŠ`±+°r+"Y-°+-°+-°+-°+-°+-°+-°+-°+-°+-°+-°,° +±ETX°#B E° #B°
`B `°BBŠ`±+°r+"Y-°+-° ,±+-°!,±+-°",±+-°#,±+-°$,±+-°%,±+-°&,±+-°',±+-°(,± +-°), <°`-°*, `°` C#°`C°%a°`°)*!-°+,°*+°**-°,, G ° Cc¸b °PX°@`Yf°c`#a8# ŠUX G ° Cc¸b °PX°@`Yf°c`#a8!Y-°-,±ETX°°,*°0"Y-°.,° +±ETX°°,*°0"Y-°/, 5°`-°0,°Ec¸b °PX°@`Yf° Cc¸b °PX°@`Yf°´D>#8±/*-°1, < G ° Cc¸b °PX°@`Yf°c`°Ca8-°2,.<-°3, < G ° Cc¸b °PX°@`Yf°c`°Ca°Cc8-°4,±% . G°#B°%IŠŠG#G#a Xb!Y°#B²3*-°5,°°%G#G#a° C+eŠ.# <Š8-°6,°°% .G#G#a °#B° C+ °`PX °@QX³  ³&YBB# °C Š#G#G#a#F`°b °PX°@`Yf°c` °+ ŠŠa °C`d#°CadPX°Ca°C`Y°b °PX°@`Yf°ca# °&#Fa8CF°CG#G#a` °b °PX°@`Yf°c`# °+#°C`°%a°b °PX°@`Yf°&a °%`d#°%`dPX!#!Y# °&#Fa8Y-°7,° °& .G#G#a#<8-°8,° °#B F#G°+#a8-°9,°°%G#G#a°TX. <#!°%G#G#a °%G#G#a°%I°%a¹cc# Xb!Yc¸b °PX°@`Yf°c`#.# <Š8#!Y-°:,° °C .G#G#a `° `f°b °PX°@`Yf°c# <Š8-°;,# .F°%FRX <Y.±++-°<,# .F°%FPX <Y.±++-°=,# .F°%FRX <Y# .F°%FPX <Y.±++-°>,°5+# .F°%FRX <Y.±++-°?,°6+Š <°#BŠ8# .F°%FRX <Y.±+C.°++-°@,°°& .G#G#a° C+# < .#8±++-°A,±%B°°% .G#G#a °#B° C+ °`PX °@QX³  ³&YBB# G°b °PX°@`Yf°c` °+ ŠŠa °C`d#°CadPX°Ca°C`Y°b °PX°@`Yf°ca°%Fa8# <#8! F#G°+#a8!Y±++-°B,°5+.±++-°C,°6+!# <°#B#8±+C.°++-°D,°#B².°1*-°E,°#B².°1*-°F,±°2*-°G,°4*-°H,°E# . FŠ#a8±++-°I,°#B°H+-°J,²A+-°K,²A+-°L,²A+-°M,²A+-°N,²B+-°O,²B+-°P,²B+-°Q,²B+-°R,²>+-°S,²>+-°T,²>+-°U,²>+-°V,²@+-°W,²@+-°X,²@+-°Y,²@+-°Z,²C+-°[,²C+-°\,²C+-°],²C+-°^,²?+-°_,²?+-°`,²?+-°a,²?+-°b,°7+.±++-°c,°7+°;+-°d,°7+°<+-°e,°°7+°=+-°f,°8+.±++-°g,°8+°;+-°h,°8+°<+-°i,°8+°=+-°j,°9+.±++-°k,°9+°;+-°l,°9+°<+-°m,°9+°=+-°n,°:+.±++-°o,°:+°;+-°p,°:+°<+-°q,°:+°=+-°r,³ EX!#!YB+°$Px°0-K¸ÈRX±ŽY°¹cp±
À@D±$ˆQX°@ˆdD±&ˆQXº@ˆcTX±DYYYY³  *¸ÿ…°<04>±D@font-face {
font-family: "doctum";
src: url("./doctum.eot?39101248");
src: url("./doctum.eot?39101248#iefix") format("embedded-opentype"),
url("./doctum.woff2?39101248") format("woff2"), url("./doctum.woff?39101248") format("woff"),
url("./doctum.ttf?39101248") format("truetype"), url("./doctum.svg?39101248#doctum") format("svg");
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'doctum';
src: url('./doctum.svg?39101248#doctum') format('svg');
}
}
*/
.icon {
font-family: "doctum";
font-style: normal;
font-weight: normal;
speak: never;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: 0.2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: 0.2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-search:before {
content: "\e800";
} /* 'î €' */
.icon-play:before {
content: "\e801";
} /* 'î <C3AE>' */
{% extends "layout/layout.twig" %}
{% from "macros.twig" import render_classes %}
{% block title %}{% trans 'Interfaces' %} | {{ parent() }}{% endblock %}
{% block body_class 'interfaces' %}
{% block page_content %}
<div class="page-header">
<h1>{% trans 'Interfaces' %}</h1>
</div>
{{ render_classes(interfaces) }}
{% endblock %}
{% extends "layout/layout.twig" %}
{% from "macros.twig" import class_link %}
{% block title %}{% trans 'Traits' %} | {{ parent() }}{% endblock %}
{% block body_class 'traits' %}
{% block page_content %}
<div class="page-header">
<h1>{% trans 'Traits' %}</h1>
</div>
<div class="container-fluid underlined">
{% for class in classes %}
{% if class.trait %}
<div class="row">
<div class="col-md-6">
{{ class_link(class, true) }}
</div>
<div class="col-md-6">
{{ class.shortdesc|desc(class) }}
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endblock %}
name: default
static:
'css/doctum.css': 'css/doctum.css'
'css/bootstrap.min.css': 'css/bootstrap.min.css'
'css/bootstrap-theme.min.css': 'css/bootstrap-theme.min.css'
'fonts/doctum-font.css': 'fonts/doctum-font.css'
'fonts/doctum.woff': 'fonts/doctum.woff'
'fonts/doctum.woff2': 'fonts/doctum.woff2'
'fonts/doctum.ttf': 'fonts/doctum.ttf'
'fonts/doctum.svg': 'fonts/doctum.svg'
'fonts/doctum.eot': 'fonts/doctum.eot'
'js/jquery-3.5.1.slim.min.js': 'js/jquery-3.5.1.slim.min.js'
'js/bootstrap.min.js': 'js/bootstrap.min.js'
'js/typeahead.min.js': 'js/typeahead.min.js'
global:
'index.twig': 'index.html'
'doc-index.twig': 'doc-index.html'
'namespaces.twig': 'namespaces.html'
'classes.twig': 'classes.html'
'interfaces.twig': 'interfaces.html'
'traits.twig': 'traits.html'
'opensearch.twig': 'opensearch.xml'
'search.twig': 'search.html'
'doctum.js.twig': 'doctum.js'
namespace:
'namespace.twig': '%s.html'
class:
'class.twig': '%s.html'
{% if has_namespaces %}
{% set extension = 'namespaces.twig' %}
{% else %}
{% set extension = 'classes.twig' %}
{% endif %}
{% extends extension %}
{% block body_class 'index' %}
{% from _self import element %}
{% macro add_class_methods_index(class, prettyJsonOptions) %}
{% if class.methods %}
{% set from_link = class_path(class) %}
{% for meth in class.methods %}
{{-
{
type: 'Method'|trans,
fromName: class.name,
fromLink: from_link,
link: method_path(meth),
name: meth.__toString(),
doc: meth.shortdesc|desc(class),
}|json_encode(prettyJsonOptions)|raw
-}},
{% endfor %}
{% endif %}
{% endmacro %}
(function(root) {
var bhIndex = null;
var rootPath = '';
var treeHtml = '{{ element(tree, project.config('default_opened_level'), 0)|replace({"'": "\\'", "\n": ''})|spaceless|raw }}';
var searchTypeClasses = {
'{{ 'Namespace'|trans|escape('js') }}': 'label-default',
'{{ 'Class'|trans|escape('js') }}': 'label-info',
'{{ 'Interface'|trans|escape('js') }}': 'label-primary',
'{{ 'Trait'|trans|escape('js') }}': 'label-success',
'{{ 'Method'|trans|escape('js') }}': 'label-danger',
'_': 'label-warning'
};
var searchIndex = [
{% from _self import add_class_methods_index %}
{% set prettyJsonOptions = constant('JSON_UNESCAPED_SLASHES') b-or constant('JSON_UNESCAPED_UNICODE') %}
{% for ns in namespaces -%}
{{-
{
type: 'Namespace'|trans,
link: namespace_path(ns),
name: ns,
doc: 'Namespace %s'|format(ns),
}|json_encode(prettyJsonOptions)|raw
-}},
{%- endfor %}
{% for class in interfaces -%}
{% set interface = {
type: 'Interface'|trans,
link: class_path(class),
name: class.name,
doc: class.shortdesc|desc(class),
} %}
{% if class.namespace %}
{% set interface = {
type: interface.type,
fromName: class.namespace,
fromLink: namespace_path(class.namespace),
link: interface.link,
name: interface.name,
doc: interface.doc,
} %}
{% endif %}
{{-
interface|json_encode(prettyJsonOptions)|raw
-}},
{{ add_class_methods_index(class, prettyJsonOptions) }}
{% endfor %}
{% for class in classes -%}
{% set classOrTrait = {
type: class.isTrait ? 'Trait'|trans : 'Class'|trans,
link: class_path(class),
name: class.name,
doc: class.shortdesc|desc(class),
} %}
{% if class.namespace %}
{% set classOrTrait = {
type: classOrTrait.type,
fromName: class.namespace,
fromLink: namespace_path(class.namespace),
link: classOrTrait.link,
name: classOrTrait.name,
doc: classOrTrait.doc,
} %}
{% endif %}
{{-
classOrTrait|json_encode(prettyJsonOptions)|raw
-}},
{{ add_class_methods_index(class, prettyJsonOptions) }}
{% endfor %}
{# Override this block, search_index_extra, to add custom search entries! #}
{% block search_index_extra '' %}
// Fix trailing commas in the index
{}
];
/** Tokenizes strings by namespaces and functions */
function tokenizer(term) {
if (!term) {
return [];
}
var tokens = [term];
var meth = term.indexOf('::');
// Split tokens into methods if "::" is found.
if (meth > -1) {
tokens.push(term.substr(meth + 2));
term = term.substr(0, meth - 2);
}
// Split by namespace or fake namespace.
if (term.indexOf('\\') > -1) {
tokens = tokens.concat(term.split('\\'));
} else if (term.indexOf('_') > 0) {
tokens = tokens.concat(term.split('_'));
}
// Merge in splitting the string by case and return
tokens = tokens.concat(term.match(/(([A-Z]?[^A-Z]*)|([a-z]?[^a-z]*))/g).slice(0,-1));
return tokens;
};
root.Doctum = {
/**
* Cleans the provided term. If no term is provided, then one is
* grabbed from the query string "search" parameter.
*/
cleanSearchTerm: function(term) {
// Grab from the query string
if (typeof term === 'undefined') {
var name = 'search';
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)");
var results = regex.exec(location.search);
if (results === null) {
return null;
}
term = decodeURIComponent(results[1].replace(/\+/g, " "));
}
return term.replace(/<(?:.|\n)*?>/gm, '');
},
/** Searches through the index for a given term */
search: function(term) {
// Create a new search index if needed
if (!bhIndex) {
bhIndex = new Bloodhound({
limit: 500,
local: searchIndex,
datumTokenizer: function (d) {
return tokenizer(d.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace
});
bhIndex.initialize();
}
results = [];
bhIndex.get(term, function(matches) {
results = matches;
});
if (!rootPath) {
return results;
}
// Fix the element links based on the current page depth.
return $.map(results, function(ele) {
if (ele.link.indexOf('..') > -1) {
return ele;
}
ele.link = rootPath + ele.link;
if (ele.fromLink) {
ele.fromLink = rootPath + ele.fromLink;
}
return ele;
});
},
/** Get a search class for a specific type */
getSearchClass: function(type) {
return searchTypeClasses[type] || searchTypeClasses['_'];
},
/** Add the left-nav tree to the site */
injectApiTree: function(ele) {
ele.html(treeHtml);
}
};
$(function() {
// Modify the HTML to work correctly based on the current depth
rootPath = $('body').attr('data-root-path');
treeHtml = treeHtml.replace(/href="/g, 'href="' + rootPath);
Doctum.injectApiTree($('#api-tree'));
});
return root.Doctum;
})(window);
$(function() {
{% if project.versions|length > 1 %}
// Enable the version switcher
$('#version-switcher').on('change', function() {
window.location = $(this).val()
});
var versionSwitcher = document.getElementById('version-switcher');
if (versionSwitcher) {
var versionToSelect = document.evaluate(
'//option[@data-version="{{ project.version|escape('js') }}"]',
versionSwitcher,
null,
XPathResult.FIRST_ORDERED_NODE_TYPE,
null
).singleNodeValue;
if (versionToSelect && typeof versionToSelect.selected === 'boolean') {
versionToSelect.selected = true;
}
}
{% endif %}
{% block treejs %}
// Toggle left-nav divs on click
$('#api-tree .hd span').on('click', function() {
$(this).parent().parent().toggleClass('opened');
});
// Expand the parent namespaces of the current page.
var expected = $('body').attr('data-name');
if (expected) {
// Open the currently selected node and its parents.
var container = $('#api-tree');
var node = $('#api-tree li[data-name="' + expected + '"]');
// Node might not be found when simulating namespaces
if (node.length > 0) {
node.addClass('active').addClass('opened');
node.parents('li').addClass('opened');
var scrollPos = node.offset().top - container.offset().top + container.scrollTop();
// Position the item nearer to the top of the screen.
scrollPos -= 200;
container.scrollTop(scrollPos);
}
}
{% endblock %}
{% verbatim %}
var form = $('#search-form .typeahead');
form.typeahead({
hint: true,
highlight: true,
minLength: 1
}, {
name: 'search',
displayKey: 'name',
source: function (q, cb) {
cb(Doctum.search(q));
}
});
// The selection is direct-linked when the user selects a suggestion.
form.on('typeahead:selected', function(e, suggestion) {
window.location = suggestion.link;
});
// The form is submitted when the user hits enter.
form.keypress(function (e) {
if (e.which == 13) {
$('#search-form').submit();
return true;
}
});
{% endverbatim %}
});
{% macro element(tree, opened, depth) %}
{% from _self import element %}
<ul>
{%- for element in tree -%}
{%- if element[2] -%}
<li data-name="namespace:{{ element[1]|replace({'\\': '_'})|raw }}" {% if depth < opened %}class="opened"{% endif %}>
<div style="padding-left:{{ (depth * 18)|raw }}px" class="hd">
<span class="icon icon-play"></span>
<a href="{{ namespace_path(element[1]) }}">{{ element[0]|raw }}</a>
</div>
<div class="bd">
{{ element(element[2], opened, depth + 1) -}}
</div>
</li>
{%- else -%}
<li data-name="class:{{ element[1]|replace({'\\': '_'}) }}" {% if depth < opened %}class="opened"{% endif %}>
<div style="padding-left:{{ 8 + (depth * 18) }}px" class="hd leaf">
<a href="{{ class_path(element[1]) }}">{{ element[0] }}</a>
</div>
</li>
{%- endif -%}
{%- endfor %}
</ul>
{% endmacro %}
{% extends "layout/layout.twig" %}
{% from "macros.twig" import namespace_link %}
{% block title %}{% trans 'Namespaces' %} | {{ parent() }}{% endblock %}
{% block body_class 'namespaces' %}
{% block page_content %}
<div class="page-header">
<h1>{% trans 'Namespaces' %}</h1>
</div>
{% if namespaces %}
<div class="namespaces clearfix">
{% set last_name = '' %}
{% for namespace in namespaces %}
{% set top_level = namespace|split('\\')|first %}
{% if top_level != last_name %}
{% if last_name %}</ul></div>{% endif %}
<div class="namespace-container">
<h2>{{ top_level|raw }}</h2>
<ul>
{% set last_name = top_level %}
{% endif %}
<li>{{ namespace_link(namespace) }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% endblock %}
{% extends "layout/layout.twig" %}
{% from "macros.twig" import class_link, namespace_link, method_link, property_link %}
{% block title %}{% trans 'Index' %} | {{ parent() }}{% endblock %}
{% block body_class 'doc-index' %}
{% block page_content %}
<div class="page-header">
<h1>{% trans 'Index' %}</h1>
</div>
<ul class="pagination">
{% for letter in 'A'..'Z' %}
{% if items[letter] is defined and items[letter]|length > 1 %}
<li><a href="#letter{{ letter|raw }}">{{ letter|raw }}</a></li>
{% else %}
<li class="disabled"><a href="#letter{{ letter|raw }}">{{ letter|raw }}</a></li>
{% endif %}
{% endfor %}
</ul>
{% for letter, elements in items -%}
<h2 id="letter{{ letter|raw }}">{{ letter|raw }}</h2>
<dl id="index{{ letter|raw }}">
{%- for element in elements %}
{%- set type = element[0] %}
{%- set value = element[1] %}
{%- if 'class' == type -%}
<dt>{{ class_link(value) }}{% if has_namespaces %} &mdash; <em>{{'Class in namespace %s'|trans|format(
namespace_link(value.namespace)
)|raw}}{% endif %}</em></dt>
<dd>{{ value.shortdesc|desc(value) }}</dd>
{%- elseif 'method' == type -%}
<dt>{{ method_link(value) }}() &mdash; <em>{{ 'Method in class %s'|trans|format(
class_link(value.class)
)|raw }}</em></dt>
<dd>{{ value.shortdesc|desc(value.class) }}</dd>
{%- elseif 'property' == type -%}
<dt>${{ property_link(value) }} &mdash; <em>{{ 'Property in class %s'|trans|format(
class_link(value.class)
)|raw}}</em></dt>
<dd>{{ value.shortdesc|desc(value.class) }}</dd>
{%- endif %}
{%- endfor %}
</dl>
{%- endfor %}
{% endblock %}
{% extends "layout/layout.twig" %}
{% from "macros.twig" import render_classes, breadcrumbs, namespace_link, class_link, property_link, method_link, hint_link, source_link, method_source_link, deprecated, deprecations, internals, todo, todos, class_category_name %}
{% block title %}{{ class|raw }} | {{ parent() }}{% endblock %}
{% block body_class 'class' %}
{% block page_id 'class:' ~ (class.name|replace({'\\': '_'})) %}
{% block below_menu %}
{% if class.namespace %}
<div class="namespace-breadcrumbs">
<ol class="breadcrumb">
<li><span class="label label-default">{{ class_category_name(class.getCategoryId()) }}</span></li>
{{ breadcrumbs(class.namespace) -}}
<li>{{ class.shortname|raw }}</li>
</ol>
</div>
{% endif %}
{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>
{{- class.name|split('\\')|last|raw -}}
{{- deprecated(class) }}
{% if class.isReadOnly() %}<small><span class="label label-primary">{% trans 'read-only' %}</span></small>{% endif -%}
</h1>
</div>
{% if class.hasSince() %}
<i>{{ 'Since:'|trans }} {{ class.getSince() }}</i>
<br>
{% endif %}
<p>{{ block('class_signature') }}</p>
{{ deprecations(class) }}
{{ internals(class) }}
{% if class.shortdesc or class.longdesc %}
<div class="description">
{% if class.shortdesc -%}
<p>{{ class.shortdesc|desc(class) }}</p>
{%- endif %}
{% if class.longdesc -%}
<p>{{ class.longdesc|desc(class) }}</p>
{%- endif %}
</div>
{% endif %}
{{- todos(class) -}}
{% if traits %}
<h2>{% trans 'Traits' %}</h2>
{{ render_classes(traits) }}
{% endif %}
{% if constants %}
<h2>{% trans 'Constants' %}</h2>
{{- block('constants') }}
{% endif %}
{% if properties %}
<h2>{% trans 'Properties' %}</h2>
{{ block('properties') }}
{% endif %}
{% if methods %}
<h2>{% trans 'Methods' %}</h2>
{{ block('methods') }}
<h2>{% trans 'Details' %}</h2>
{{ block('methods_details') }}
{% endif %}
{% endblock %}
{% block class_signature -%}
{% if class.final %}final {% endif %}
{% if not class.interface and class.abstract %}abstract {% endif %}
{{ class_category_name(class.getCategoryId()) }}
<strong>{{ class.shortname|raw }}</strong>
{%- if class.parent %}
extends {{ class_link(class.parent) }}
{%- endif %}
{%- if class.interfaces|length > 0 %}
implements
{% for interface in class.interfaces %}
{{- class_link(interface) }}
{%- if not loop.last %}, {% endif %}
{%- endfor %}
{%- endif %}
{%- if class.hasMixins %}
{% for mixin in class.getMixins() %}
<i>mixin</i> {{ class_link(mixin.class) }}
{% endfor %}
{%- endif %}
{{- source_link(project, class) }}
{% endblock %}
{% block method_signature -%}
{% if method.final %}final{% endif %}
{% if method.abstract %}abstract{% endif %}
{% if method.static %}static{% endif %}
{% if method.protected %}protected{% endif %}
{% if method.private %}private{% endif %}
{{ hint_link(method.hint) }}
<strong>{{ method.name|raw }}</strong>{{ block('method_parameters_signature') }}
{%- endblock %}
{% block method_parameters_signature -%}
{%- from "macros.twig" import method_parameters_signature -%}
{{ method_parameters_signature(method) }}
{{ deprecated(method) }}
{%- endblock %}
{% block parameters %}
<table class="table table-condensed">
{% for parameter in method.parameters %}
<tr>
<td>{% if parameter.hint %}{{ hint_link(parameter.hint) }}{% endif %}</td>
<td>{%- if parameter.variadic %}...{% endif %}${{ parameter.name|raw }}</td>
<td>{{ parameter.shortdesc|desc(class) }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% block return %}
<table class="table table-condensed">
<tr>
<td>{{ hint_link(method.hint) }}</td>
<td>{{ method.hintDesc|desc(class) }}</td>
</tr>
</table>
{% endblock %}
{% block exceptions %}
<table class="table table-condensed">
{% for exception in method.exceptions %}
<tr>
<td>{{ class_link(exception[0]) }}</td>
<td>{{ exception[1]|desc(class) }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% block examples %}
<table class="table table-condensed">
{% for example in method.getExamples() %}
<tr>
<td><pre class="examples">
{{- example|join(' ')|nl2br -}}
</pre></td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% block see %}
<table class="table table-condensed">
{% for see in method.getSee() %}
<tr>
<td>
{% if see[4] %}
<a href="{{see[4]}}">{{see[4]}}</a>
{% elseif see[3] %}
{{ method_link(see[3], false, false) }}
{% elseif see[2] %}
{{ class_link(see[2]) }}
{% else %}
{{ see[0]|raw }}
{% endif %}
</td>
<td>{{ see[1]|raw }}</td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% block constants %}
<table class="table table-condensed">
{% for constant in constants %}
<tr>
<td>
{# Keep in order with an else if, it can be set by typehints and by annotations #}
{# More restricted wins #}
{% if constant.isPrivate() %}private
{% elseif constant.isProtected() %}protected{% endif %}
{% if constant.isInternal() %}<span class="label label-warning">{% trans 'internal' %}</span>{% endif %}
{% if constant.isDeprecated() %}<span class="label label-danger">{% trans 'deprecated' %}</span>{% endif %}
{{ constant.name|raw }}
{% if constant.hasSince() %}
<i>{{ 'Since:'|trans }} {{ constant.getSince() }}</i>
<br>
{% endif %}
</td>
<td class="last">
<p><em>{{ constant.shortdesc|desc(class) }}</em></p>
<p>{{ constant.longdesc|desc(class) }}</p>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% block properties %}
<table class="table table-condensed">
{% for property in properties %}
<tr>
<td class="type" id="property_{{ property.name|raw }}">
{% if property.isStatic() %}static{% endif %}
{% if property.isProtected() %}protected{% endif %}
{% if property.isPrivate() %}private{% endif %}
{{ hint_link(property.hint) }}
{% if property.isInternal() %}<span class="label label-warning">{% trans 'internal' %}</span>{% endif %}
{% if property.isDeprecated() %}<span class="label label-danger">{% trans 'deprecated' %}</span>{% endif %}
{% if property.isReadOnly() %}<span class="label label-primary">{% trans 'read-only' %}</span>{% endif %}
{% if property.isWriteOnly() %}<span class="label label-success">{% trans 'write-only' %}</span>{% endif %}
{% if property.hasSince() %}
<i>{{ 'Since:'|trans }} {{ property.getSince() }}</i>
<br>
{% endif %}
</td>
<td>${{ property.name|raw }}</td>
<td class="last">{{ property.shortdesc|desc(class) }}</td>
<td>
{%- if property.class is not same as(class) -%}
<small>{{ 'from&nbsp;%s'|trans|format(property_link(property, false, true))|raw }}</small>
{%- endif -%}
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
{% block methods %}
<div class="container-fluid underlined">
{% for method in methods %}
<div class="row">
<div class="col-md-2 type">
{% if method.static %}static&nbsp;{% endif %}{{ hint_link(method.hint) }}
</div>
<div class="col-md-8">
<a href="#method_{{ method.name|raw }}">{{ method.name|raw }}</a>{{ block('method_parameters_signature') }}
{% if not method.shortdesc %}
<p class="no-description">{% trans 'No description' %}</p>
{% else %}
<p>{{ method.shortdesc|desc(class) }}</p>
{%- endif %}
</div>
<div class="col-md-2">
{%- if method.class is not same as(class) -%}
<small>{{ 'from&nbsp;%s'|trans|format(method_link(method, false, true))|raw }}</small>
{%- endif -%}
</div>
</div>
{% endfor %}
</div>
{% endblock %}
{% block methods_details %}
<div id="method-details">
{% for method in methods %}
<div class="method-item">
{{ block('method') }}
</div>
{% endfor %}
</div>
{% endblock %}
{% block method %}
<h3 id="method_{{ method.name|raw }}">
<div class="location">{% if method.class is not same as(class) %}{{ 'in %s'|trans|format(method_link(method, false, true))|raw }} {% endif %}{{ method_source_link(method) }}</div>
<code>{{ block('method_signature') }}</code>
</h3>
<div class="details">
{%- if method.hasSince() -%}
<i>{{ 'Since:'|trans }} {{ method.getSince() }}</i>
<br>
{%- endif -%}
{{ deprecations(method) }}
{{ internals(method) }}
<div class="method-description">
{% if not method.shortdesc and not method.longdesc %}
<p class="no-description">{% trans 'No description' %}</p>
{% else %}
{% if method.shortdesc -%}
<p>{{ method.shortdesc|desc(class) }}</p>
{%- endif %}
{% if method.longdesc -%}
<p>{{ method.longdesc|desc(class) }}</p>
{%- endif %}
{%- endif %}
{{- todos(method) -}}
</div>
<div class="tags">
{% if method.parameters %}
<h4>{% trans 'Parameters' %}</h4>
{{ block('parameters') }}
{% endif %}
{% if method.hintDesc or method.hint %}
<h4>{% trans 'Return Value' %}</h4>
{{ block('return') }}
{% endif %}
{% if method.exceptions %}
<h4>{% trans 'Exceptions' %}</h4>
{{ block('exceptions') }}
{% endif %}
{% if method.tags('see') %}
<h4>{% trans 'See also' %}</h4>
{{ block('see') }}
{% endif %}
{% if method.hasExamples() %}
<h4>{% trans 'Examples' %}</h4>
{{ block('examples') }}
{% endif %}
</div>
</div>
{% endblock %}
{% extends "layout/layout.twig" %}
{% from "macros.twig" import class_link, namespace_link, method_link, property_link %}
{% block title %}{% trans 'Search' %} | {{ parent() }}{% endblock %}
{% block body_class 'search-page' %}
{% block page_content %}
<div class="page-header">
<h1>{% trans 'Search' %}</h1>
</div>
<p>{% trans 'This page allows you to search through the API documentation for
specific terms. Enter your search words into the box below and click
"submit". The search will be performed on namespaces, classes, interfaces,
traits, functions, and methods.' %}</p>
<form class="form-inline" role="form" action="{{ path('search.html') }}">
<div class="form-group">
<label class="sr-only" for="search">{% trans 'Search' %}</label>
<input type="search" class="form-control" name="search" id="search" placeholder="{% trans 'Search' %}">
</div>
<button type="submit" class="btn btn-default">{% trans 'submit' %}</button>
</form>
<h2>{% trans 'Search Results' %}</h2>
<div class="container-fluid">
<ul class="search-results"></ul>
</div>
{{ block('js_search') }}
{% endblock %}
{% block js_search %}
<script type="text/javascript">
(function() {
var term = Doctum.cleanSearchTerm();
if (!term) {
$('h2').hide();
return;
}
$('#search').val(term);
var container = $('.search-results');
var results = Doctum.search(term);
var len = results.length;
if (len == 0) {
container.html('{% trans 'No results were found'%}');
return;
}
for (var i = 0, text_content = ''; i < len; i++) {
var ele = results[i];
var contents = '<li><h2 class="clearfix">';
var class_name = Doctum.getSearchClass(ele.type);
contents += '<a href="' + ele.link + '">' + ele.name + '</a>';
contents += '<div class="search-type type-' + ele.type + '"><span class="pull-right label ' + class_name + '">' + ele.type + '</span></div>';
contents += '</h2>';
if (ele.fromName && ele.fromLink) {
contents += '<div class="search-from">from <a href="' + ele.fromLink + '">' + ele.fromName + '</a></div>';
}
contents += '<div class="search-description">';
// Add description, decode entities, and strip wrapping quotes
if (ele.doc) {
text_content = $('<p>').html(ele.doc).html();
contents += text_content;
}
contents += '</div></li>';
container.append($(contents));
}
})();
</script>
{% endblock %}
{% extends "layout/layout.twig" %}
{% from "macros.twig" import breadcrumbs, render_classes, class_link, namespace_link, hint_link, deprecated, function_parameters_signature %}
{% block title %}{{ namespace == '' ? global_namespace_name() : namespace|raw }} | {{ parent() }}{% endblock %}
{% block body_class 'namespace' %}
{% block page_id 'namespace:' ~ (namespace|replace({'\\': '_'})) %}
{% block below_menu %}
<div class="namespace-breadcrumbs">
<ol class="breadcrumb">
<li><span class="label label-default">{% trans 'Namespace' %}</span></li>
{{ breadcrumbs(namespace) }}
</ol>
</div>
{% endblock %}
{% block page_content %}
<div class="page-header">
<h1>{{ namespace == '' ? global_namespace_name() : namespace|raw }}</h1>
</div>
{% if subnamespaces %}
<h2>{% trans 'Namespaces' %}</h2>
<div class="namespace-list">
{% for ns in subnamespaces %}{{ namespace_link(ns) }}{% endfor %}
</div>
{% endif %}
{% if classes %}
<h2>{% trans 'Classes' %}</h2>
{{- render_classes(classes) -}}
{% endif %}
{% if interfaces %}
<h2>{% trans 'Interfaces' %}</h2>
{{- render_classes(interfaces) -}}
{% endif %}
{% if functions %}
<h2>{% trans 'Functions' %}</h2>
<div class="container-fluid underlined">
{% for function in functions %}
<div class="row" id="function_{{ function.name|raw }}">
<div class="col-md-2 type">
{% if function.isStatic() %}static{% endif %}
{% if function.isProtected() %}protected{% endif %}
{% if function.isPrivate() %}private{% endif %}
{{ hint_link(function.hint) }}
</div>
<div class="col-md-8">
{{ function.name|raw }}
{{- function_parameters_signature(function) -}}
<br>
{% if function.isInternal() %}<span class="label label-warning">{% trans 'internal' %}</span>{% endif %}
{% if function.isDeprecated() %}<span class="label label-danger">{% trans 'deprecated' %}</span>{% endif %}
{% if function.hasSince() %}
<i>{{ 'Since:'|trans }} {{ function.getSince() }}</i>
<br>
{% endif %}
{% if not function.shortdesc %}
<p class="no-description">{% trans 'No description' %}</p>
{% else %}
<p>{{ function.shortdesc|desc(function) }}</p>
{%- endif %}
</div>
<div class="col-md-2">
{%- if function.namespace is not same as(namespace) -%}
<em><a href="{{ function_path(function) }}">{{ function|raw }}</em>
{%- endif -%}
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% if exceptions %}
<h2>{% trans 'Exceptions' %}</h2>
{{- render_classes(exceptions) -}}
{% endif %}
{% endblock %}
{% macro class_category_name(categoryId) -%}
{% if categoryId == 1 %}{% trans 'class' %}{% endif %}
{% if categoryId == 2 %}{% trans 'interface' %}{% endif %}
{% if categoryId == 3 %}{% trans 'trait' %}{% endif %}
{%- endmacro %}
{% macro namespace_link(namespace) -%}
<a href="{{ namespace_path(namespace) }}">{{ namespace == '' ? global_namespace_name() : namespace|raw }}</a>
{%- endmacro %}
{% macro class_link(class, absolute) -%}
{%- if class.isProjectClass() -%}
<a href="{{ class_path(class) }}">
{%- elseif class.isPhpClass() -%}
<a target="_blank" rel="noopener" href="https://www.php.net/{{ class|raw }}">
{%- endif %}
{{- abbr_class(class, absolute|default(false)) }}
{%- if class.isProjectClass() or class.isPhpClass() %}</a>{% endif %}
{%- endmacro %}
{% macro method_link(method, absolute, classonly) -%}
{# #}<a href="{{ method_path(method) }}">
{# #}{{- abbr_class(method.class) }}{% if not classonly|default(false) %}::{{ method.name|raw }}{% endif -%}
{# #}</a>
{%- endmacro %}
{% macro property_link(property, absolute, classonly) -%}
{# #}<a href="{{ property_path(property) }}">
{# #}{{- abbr_class(property.class) }}{% if not classonly|default(false) %}#{{ property.name|raw }}{% endif -%}
{# #}</a>
{%- endmacro %}
{% macro hint_link(hints) -%}
{%- from _self import class_link %}
{%- if hints %}
{%- for hint in hints %}
{%- if hint.class %}
{{- class_link(hint.name) }}
{%- elseif hint.name %}
{{- abbr_class(hint.name) }}
{%- endif %}
{%- if hint.array %}[]{% endif %}
{%- if not loop.last %}|{% endif %}
{%- endfor %}
{%- endif %}
{%- endmacro %}
{% macro source_link(project, class) -%}
{% if class.sourcepath %}
(<a href="{{ class.sourcepath }}">{% trans 'View source'%}</a>)
{%- endif %}
{%- endmacro %}
{% macro method_source_link(method) -%}
{% if method.sourcepath %}
{#- l10n: Method at line %s -#}
<a href="{{ method.sourcepath }}">{{'at line %s'|trans|format(
method.line
)|raw }}</a>
{%- else %}
{#- l10n: Method at line %s -#}
{{- 'at line %s'|trans|format(
method.line
)|raw -}}
{%- endif %}
{%- endmacro %}
{% macro method_parameters_signature(method) -%}
{%- from "macros.twig" import hint_link -%}
(
{%- for parameter in method.parameters %}
{%- if parameter.hashint %}{{ hint_link(parameter.hint) }} {% endif -%}
{%- if parameter.variadic %}...{% endif %}${{ parameter.name|raw }}
{%- if parameter.default is not null %} = {{ parameter.default }}{% endif %}
{%- if not loop.last %}, {% endif %}
{%- endfor -%}
)
{%- endmacro %}
{% macro function_parameters_signature(method) -%}
{%- from "macros.twig" import hint_link -%}
(
{%- for parameter in method.parameters %}
{%- if parameter.hashint %}{{ hint_link(parameter.hint) }} {% endif -%}
{%- if parameter.variadic %}...{% endif %}${{ parameter.name|raw }}
{%- if parameter.default is not null %} = {{ parameter.default }}{% endif %}
{%- if not loop.last %}, {% endif %}
{%- endfor -%}
)
{%- endmacro %}
{% macro render_classes(classes) -%}
{% from _self import class_link, deprecated %}
<div class="container-fluid underlined">
{% for class in classes %}
<div class="row">
<div class="col-md-6">
{% if class.isInterface %}
<em>{{- class_link(class, true) -}}</em>
{% else %}
{{- class_link(class, true) -}}
{% endif %}
{{- deprecated(class) -}}
</div>
<div class="col-md-6">
{{- class.shortdesc|desc(class) -}}
</div>
</div>
{% endfor %}
</div>
{%- endmacro %}
{% macro breadcrumbs(namespace) %}
{% set current_ns = '' %}
{% for ns in namespace|split('\\') %}
{%- if current_ns -%}
{% set current_ns = current_ns ~ '\\' ~ ns %}
{%- else -%}
{% set current_ns = ns %}
{%- endif -%}
<li><a href="{{ namespace_path(current_ns) }}">{{ ns|raw }}</a></li><li class="backslash">\</li>
{%- endfor %}
{% endmacro %}
{% macro deprecated(reflection) %}
{% if reflection.deprecated %}<small><span class="label label-danger">{% trans 'deprecated' %}</span></small>{% endif %}
{% endmacro %}
{% macro deprecations(reflection) %}
{% from _self import deprecated %}
{% if reflection.deprecated %}
<p>
{{ deprecated(reflection )}}
{% for tag in reflection.deprecated %}
<tr>
<td>{{ tag[0]|raw }}</td>
<td>{{ tag[1:]|join(' ')|raw }}</td>
</tr>
{% endfor %}
</p>
{% endif %}
{% endmacro %}
{% macro internals(reflection) %}
{% if reflection.isInternal() %}
{% for internalTag in reflection.getInternal() %}
<table>
<tr>
<td><span class="label label-warning">{% trans 'internal' %}</span></td>
<td>&nbsp;{{ internalTag[0]|raw }} {{ internalTag[1:]|join(' ')|raw }}</td>
</tr>
</table>
{% endfor %}
&nbsp;
{% endif %}
{% endmacro %}
{% macro todo(reflection) %}
{% if project.config('insert_todos') == true %}
{% if reflection.todo %}<small><span class="label label-info">{% trans 'todo' %}</span></small>{% endif %}
{% endif %}
{% endmacro %}
{% macro todos(reflection) %}
{% from _self import todo %}
{% if reflection.todo %}
<p>
{{ todo(reflection )}}
{% for tag in reflection.todo %}
<tr>
<td>{{ tag[0]|raw }}</td>
<td>{{ tag[1:]|join(' ')|raw }}</td>
</tr>
{% endfor %}
</p>
{% endif %}
{% endmacro %}
{% extends "layout/layout.twig" %}
{% from "macros.twig" import render_classes %}
{% block title %}{% trans 'All Classes' %} | {{ parent() }}{% endblock %}
{% block body_class 'classes' %}
{% block page_content %}
<div class="page-header">
<h1>{% trans 'Classes' %}</h1>
</div>
{{ render_classes(classes) }}
{% endblock %}
{% extends "layout/base.twig" %}
{% block content %}
<div id="content">
<div id="left-column">
{{ block('control_panel') }}
{{ block('leftnav') }}
</div>
<div id="right-column">
{{ block('menu') }}
{% block below_menu '' %}
<div id="page-content">
{%- block page_content '' -%}
</div>
{{- block('footer') -}}
</div>
</div>
{% endblock %}
{% block menu %}
<nav id="site-nav" class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-elements">
<span class="sr-only">{% trans 'Toggle navigation' %}</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ path('index.html') }}">{{ project.config('title') }}</a>
</div>
<div class="collapse navbar-collapse" id="navbar-elements">
<ul class="nav navbar-nav">
<li><a href="{{ path('classes.html') }}">{% trans 'Classes' %}</a></li>
{% if has_namespaces -%}
{# #}<li><a href="{{ path('namespaces.html') }}">{% trans 'Namespaces' %}</a></li>
{% endif -%}
<li><a href="{{ path('interfaces.html') }}">{% trans 'Interfaces' %}</a></li>
<li><a href="{{ path('traits.html') }}">{% trans 'Traits' %}</a></li>
<li><a href="{{ path('doc-index.html') }}">{% trans 'Index' %}</a></li>
<li><a href="{{ path('search.html') }}">{% trans 'Search' %}</a></li>
</ul>
</div>
</div>
</nav>
{% endblock %}
{% block leftnav %}
<div id="api-tree"></div>
{% endblock %}
{% block control_panel %}
<div id="control-panel">
{% if project.versions|length > 1 %}
<form action="#">
<select class="form-control" id="version-switcher" name="version">
{% for version in project.versions %}
<option value="{{ path('../' ~ version ~ '/index.html') }}" data-version="{{ version }}">{{ version.longname }}</option>
{% endfor %}
</select>
</form>
{% endif %}
<form id="search-form" action="{{ path('search.html') }}">
<span class="icon icon-search"></span>
<input name="search"
class="typeahead form-control"
type="search"
placeholder="{% trans 'Search' %}">
</form>
</div>
{% endblock %}
{%- block footer -%}
<div id="footer">
{{ 'Generated by %sDoctum, a API Documentation generator and fork of Sami%s.'|trans|format(
'<a href="https://github.com/code-lts/doctum">', '</a>'
)|raw }}
{%- if project.hasFooterLink() -%}
{% set link = project.getFooterLink() %}
<br/>
{{- link.before_text }}
{%- if link.href is not empty -%}
{{ " " }}<a href="{{ link.href }}" rel="{{ link.rel }}" target="{{ link.target }}">{{ link.link_text }}</a>{{ " " }}
{%- endif -%}
{{ link.after_text -}}
{%- endif -%}
</div>
{%- endblock -%}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="robots" content="index, follow, all" />
<title>{% block title project.config('title') %}</title>
{% block head %}
<link rel="stylesheet" type="text/css" href="{{ path('css/bootstrap.min.css') }}">
<link rel="stylesheet" type="text/css" href="{{ path('css/bootstrap-theme.min.css') }}">
<link rel="stylesheet" type="text/css" href="{{ path('css/doctum.css') }}">
<link rel="stylesheet" type="text/css" href="{{ path('fonts/doctum-font.css') }}">
<script src="{{ path('js/jquery-3.5.1.slim.min.js') }}"></script>
<script async defer src="{{ path('js/bootstrap.min.js') }}"></script>
<script src="{{ path('js/typeahead.min.js') }}"></script>
<script src="{{ path('doctum.js') }}"></script>
<meta name="MobileOptimized" content="width">
<meta name="HandheldFriendly" content="true">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1">
{%- endblock %}
{##}{% if project.config('favicon') %}
<link rel="shortcut icon" href="{{ project.config('favicon') }}" />
{%- endif %}
{% if project.getBaseUrl() %}
{# #}{%- for version in project.versions -%}
{# #}<link rel="search"
{# #} type="application/opensearchdescription+xml"
{# #} href="{{ project.getBaseUrl()|replace({'%version%': version}) }}/opensearch.xml"
{# #} title="{{ project.config('title') }} ({{ version }})" />
{# #}{% endfor -%}
{% endif %}
</head>
{% block html %}
<body id="{% block body_class '' %}" data-name="{% block page_id '' %}" data-root-path="{{ root_path }}">
{% block content '' %}
</body>
{% endblock %}
</html>
{% if project.getBaseUrl() -%}
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:referrer="http://a9.com/-/opensearch/extensions/referrer/">
<ShortName>{{ project.config('title') }} ({{ project.version }})</ShortName>
<Description>{{ 'Searches'|trans }} {{ project.config('title') }} ({{ project.version }})</Description>
<Tags>{{ project.config('title') }}</Tags>
{% if project.config('favicon') -%}
<Image height="16" width="16" type="image/x-icon">{{ project.config('favicon') }}</Image>
{% endif -%}
<Url type="text/html" method="GET" template="{{ project.getBaseUrl()|replace({'%version%': project.version}) ~ '/search.html?search={searchTerms}&utm_source={referrer:source?}' }}"/>
<InputEncoding>UTF-8</InputEncoding>
<AdultContent>false</AdultContent>
</OpenSearchDescription>
{% endif %}
/*!
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
*//*!
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 1px rgba(0,0,0,0.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-default.disabled,.btn-primary.disabled,.btn-success.disabled,.btn-info.disabled,.btn-warning.disabled,.btn-danger.disabled,.btn-default[disabled],.btn-primary[disabled],.btn-success[disabled],.btn-info[disabled],.btn-warning[disabled],.btn-danger[disabled],fieldset[disabled] .btn-default,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-info,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-danger{-webkit-box-shadow:none;box-shadow:none}.btn-default .badge,.btn-primary .badge,.btn-success .badge,.btn-info .badge,.btn-warning .badge,.btn-danger .badge{text-shadow:none}.btn:active,.btn.active{background-image:none}.btn-default{background-image:-webkit-linear-gradient(top, #fff 0, #e0e0e0 100%);background-image:-o-linear-gradient(top, #fff 0, #e0e0e0 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), to(#e0e0e0));background-image:linear-gradient(to bottom, #fff 0, #e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#dbdbdb;text-shadow:0 1px 0 #fff;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top, #428bca 0, #2d6ca2 100%);background-image:-o-linear-gradient(top, #428bca 0, #2d6ca2 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #428bca), to(#2d6ca2));background-image:linear-gradient(to bottom, #428bca 0, #2d6ca2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#2b669a}.btn-primary:hover,.btn-primary:focus{background-color:#2d6ca2;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#2d6ca2;border-color:#2b669a}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#2d6ca2;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #419641 100%);background-image:-o-linear-gradient(top, #5cb85c 0, #419641 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #5cb85c), to(#419641));background-image:linear-gradient(to bottom, #5cb85c 0, #419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top, #5bc0de 0, #2aabd2 100%);background-image:-o-linear-gradient(top, #5bc0de 0, #2aabd2 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #5bc0de), to(#2aabd2));background-image:linear-gradient(to bottom, #5bc0de 0, #2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #eb9316 100%);background-image:-o-linear-gradient(top, #f0ad4e 0, #eb9316 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f0ad4e), to(#eb9316));background-image:linear-gradient(to bottom, #f0ad4e 0, #eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c12e2a 100%);background-image:-o-linear-gradient(top, #d9534f 0, #c12e2a 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #d9534f), to(#c12e2a));background-image:linear-gradient(to bottom, #d9534f 0, #c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-o-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f5f5f5), to(#e8e8e8));background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x;background-color:#e8e8e8}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:-o-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #428bca), to(#357ebd));background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x;background-color:#357ebd}.navbar-default{background-image:-webkit-linear-gradient(top, #fff 0, #f8f8f8 100%);background-image:-o-linear-gradient(top, #fff 0, #f8f8f8 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fff), to(#f8f8f8));background-image:linear-gradient(to bottom, #fff 0, #f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.15),0 1px 5px rgba(0,0,0,0.075)}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #dbdbdb 0, #e2e2e2 100%);background-image:-o-linear-gradient(top, #dbdbdb 0, #e2e2e2 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #dbdbdb), to(#e2e2e2));background-image:linear-gradient(to bottom, #dbdbdb 0, #e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.075);box-shadow:inset 0 3px 9px rgba(0,0,0,0.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,0.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top, #3c3c3c 0, #222 100%);background-image:-o-linear-gradient(top, #3c3c3c 0, #222 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #3c3c3c), to(#222));background-image:linear-gradient(to bottom, #3c3c3c 0, #222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);border-radius:4px}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top, #080808 0, #0f0f0f 100%);background-image:-o-linear-gradient(top, #080808 0, #0f0f0f 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #080808), to(#0f0f0f));background-image:linear-gradient(to bottom, #080808 0, #0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,0.25);box-shadow:inset 0 3px 9px rgba(0,0,0,0.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:-o-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #428bca), to(#357ebd));background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,0.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 2px rgba(0,0,0,0.05)}.alert-success{background-image:-webkit-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);background-image:-o-linear-gradient(top, #dff0d8 0, #c8e5bc 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #dff0d8), to(#c8e5bc));background-image:linear-gradient(to bottom, #dff0d8 0, #c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top, #d9edf7 0, #b9def0 100%);background-image:-o-linear-gradient(top, #d9edf7 0, #b9def0 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #d9edf7), to(#b9def0));background-image:linear-gradient(to bottom, #d9edf7 0, #b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);background-image:-o-linear-gradient(top, #fcf8e3 0, #f8efc0 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fcf8e3), to(#f8efc0));background-image:linear-gradient(to bottom, #fcf8e3 0, #f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top, #f2dede 0, #e7c3c3 100%);background-image:-o-linear-gradient(top, #f2dede 0, #e7c3c3 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f2dede), to(#e7c3c3));background-image:linear-gradient(to bottom, #f2dede 0, #e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top, #ebebeb 0, #f5f5f5 100%);background-image:-o-linear-gradient(top, #ebebeb 0, #f5f5f5 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #ebebeb), to(#f5f5f5));background-image:linear-gradient(to bottom, #ebebeb 0, #f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top, #428bca 0, #3071a9 100%);background-image:-o-linear-gradient(top, #428bca 0, #3071a9 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #428bca), to(#3071a9));background-image:linear-gradient(to bottom, #428bca 0, #3071a9 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top, #5cb85c 0, #449d44 100%);background-image:-o-linear-gradient(top, #5cb85c 0, #449d44 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #5cb85c), to(#449d44));background-image:linear-gradient(to bottom, #5cb85c 0, #449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top, #5bc0de 0, #31b0d5 100%);background-image:-o-linear-gradient(top, #5bc0de 0, #31b0d5 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #5bc0de), to(#31b0d5));background-image:linear-gradient(to bottom, #5bc0de 0, #31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top, #f0ad4e 0, #ec971f 100%);background-image:-o-linear-gradient(top, #f0ad4e 0, #ec971f 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f0ad4e), to(#ec971f));background-image:linear-gradient(to bottom, #f0ad4e 0, #ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top, #d9534f 0, #c9302c 100%);background-image:-o-linear-gradient(top, #d9534f 0, #c9302c 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #d9534f), to(#c9302c));background-image:linear-gradient(to bottom, #d9534f 0, #c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:-o-linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent);background-image:linear-gradient(45deg, rgba(255,255,255,0.15) 25%, transparent 25%, transparent 50%, rgba(255,255,255,0.15) 50%, rgba(255,255,255,0.15) 75%, transparent 75%, transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.075);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #3071a9;background-image:-webkit-linear-gradient(top, #428bca 0, #3278b3 100%);background-image:-o-linear-gradient(top, #428bca 0, #3278b3 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #428bca), to(#3278b3));background-image:linear-gradient(to bottom, #428bca 0, #3278b3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);background-repeat:repeat-x;border-color:#3278b3}.list-group-item.active .badge,.list-group-item.active:hover .badge,.list-group-item.active:focus .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-o-linear-gradient(top, #f5f5f5 0, #e8e8e8 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f5f5f5), to(#e8e8e8));background-image:linear-gradient(to bottom, #f5f5f5 0, #e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:-o-linear-gradient(top, #428bca 0, #357ebd 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #428bca), to(#357ebd));background-image:linear-gradient(to bottom, #428bca 0, #357ebd 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top, #dff0d8 0, #d0e9c6 100%);background-image:-o-linear-gradient(top, #dff0d8 0, #d0e9c6 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #dff0d8), to(#d0e9c6));background-image:linear-gradient(to bottom, #dff0d8 0, #d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top, #d9edf7 0, #c4e3f3 100%);background-image:-o-linear-gradient(top, #d9edf7 0, #c4e3f3 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #d9edf7), to(#c4e3f3));background-image:linear-gradient(to bottom, #d9edf7 0, #c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top, #fcf8e3 0, #faf2cc 100%);background-image:-o-linear-gradient(top, #fcf8e3 0, #faf2cc 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #fcf8e3), to(#faf2cc));background-image:linear-gradient(to bottom, #fcf8e3 0, #faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top, #f2dede 0, #ebcccc 100%);background-image:-o-linear-gradient(top, #f2dede 0, #ebcccc 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #f2dede), to(#ebcccc));background-image:linear-gradient(to bottom, #f2dede 0, #ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top, #e8e8e8 0, #f5f5f5 100%);background-image:-o-linear-gradient(top, #e8e8e8 0, #f5f5f5 100%);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0, #e8e8e8), to(#f5f5f5));background-image:linear-gradient(to bottom, #e8e8e8 0, #f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 3px rgba(0,0,0,0.05),0 1px 0 rgba(255,255,255,0.1)}/*!
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
*//*!
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*//*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,*:before,*:after{color:#000 !important;text-shadow:none !important;background:transparent !important;-webkit-box-shadow:none !important;box-shadow:none !important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="#"]:after,a[href^="javascript:"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100% !important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000 !important}.label{border:1px solid #000}.table{border-collapse:collapse !important}.table td,.table th{background-color:#fff !important}.table-bordered th,.table-bordered td{border:1px solid #ddd !important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}*:before,*:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}[role="button"]{cursor:pointer}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#777}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}mark,.mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#428bca}a.text-primary:hover,a.text-primary:focus{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover,a.text-success:focus{color:#2b542c}.text-info{color:#31708f}a.text-info:hover,a.text-info:focus{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover,a.text-warning:focus{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover,a.text-danger:focus{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover,a.bg-primary:focus{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover,a.bg-success:focus{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover,a.bg-info:focus{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover,a.bg-warning:focus{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover,a.bg-danger:focus{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote footer:before,blockquote small:before,blockquote .small:before{content:"\2014 \00A0"}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:""}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:"\00A0 \2014"}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.row-no-gutters{margin-right:0;margin-left:0}.row-no-gutters [class*="col-"]{padding-right:0;padding-left:0}.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}table col[class*="col-"]{position:static;display:table-column;float:none}table td[class*="col-"],table th[class*="col-"]{position:static;display:table-cell;float:none}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type="search"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none;appearance:none}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"].disabled,input[type="checkbox"].disabled,fieldset[disabled] input[type="radio"],fieldset[disabled] input[type="checkbox"]{cursor:not-allowed}input[type="file"]{display:block}input[type="range"]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s, box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);box-shadow:inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6)}.form-control::-moz-placeholder{color:#777;opacity:1}.form-control:-ms-input-placeholder{color:#777}.form-control::-webkit-input-placeholder{color:#777}.form-control::-ms-expand{background-color:transparent;border:0}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1}.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed}textarea.form-control{height:auto}@media screen and (-webkit-min-device-pixel-ratio:0){input[type="date"].form-control,input[type="time"].form-control,input[type="datetime-local"].form-control,input[type="month"].form-control{line-height:34px}input[type="date"].input-sm,input[type="time"].input-sm,input[type="datetime-local"].input-sm,input[type="month"].input-sm,.input-group-sm input[type="date"],.input-group-sm input[type="time"],.input-group-sm input[type="datetime-local"],.input-group-sm input[type="month"]{line-height:30px}input[type="date"].input-lg,input[type="time"].input-lg,input[type="datetime-local"].input-lg,input[type="month"].input-lg,.input-group-lg input[type="date"],.input-group-lg input[type="time"],.input-group-lg input[type="datetime-local"],.input-group-lg input[type="month"]{line-height:46px}}.form-group{margin-bottom:15px}.radio,.checkbox{position:relative;display:block;margin-top:10px;margin-bottom:10px}.radio.disabled label,.checkbox.disabled label,fieldset[disabled] .radio label,fieldset[disabled] .checkbox label{cursor:not-allowed}.radio label,.checkbox label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.radio input[type="radio"],.radio-inline input[type="radio"],.checkbox input[type="checkbox"],.checkbox-inline input[type="checkbox"]{position:absolute;margin-top:4px \9;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.radio-inline.disabled,.checkbox-inline.disabled,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.form-group-sm select.form-control{height:30px;line-height:30px}.form-group-sm textarea.form-control,.form-group-sm select[multiple].form-control{height:auto}.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.form-group-lg select.form-control{height:46px;line-height:46px}.form-group-lg textarea.form-control,.form-group-lg select[multiple].form-control{height:auto}.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.33}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback,.input-group-lg+.form-control-feedback,.form-group-lg .form-control+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback,.input-group-sm+.form-control-feedback,.form-group-sm .form-control+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline,.has-success.radio label,.has-success.checkbox label,.has-success.radio-inline label,.has-success.checkbox-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline,.has-warning.radio label,.has-warning.checkbox label,.has-warning.radio-inline label,.has-warning.checkbox-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline,.has-error.radio label,.has-error.checkbox label,.has-error.radio-inline label,.has-error.checkbox-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn,.form-inline .input-group .form-control{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .radio label,.form-inline .checkbox label{padding-left:0}.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px}}.btn{display:inline-block;margin-bottom:0;font-weight:normal;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;background-image:none;border:1px solid transparent;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn.active.focus{outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus,.btn.focus{color:#333;text-decoration:none}.btn:active,.btn.active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);opacity:.65;-webkit-box-shadow:none;box-shadow:none}a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:focus,.btn-default.focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c}.btn-default:hover{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default:active,.btn-default.active,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;background-image:none;border-color:#adadad}.btn-default:active:hover,.btn-default.active:hover,.open>.dropdown-toggle.btn-default:hover,.btn-default:active:focus,.btn-default.active:focus,.open>.dropdown-toggle.btn-default:focus,.btn-default:active.focus,.btn-default.active.focus,.open>.dropdown-toggle.btn-default.focus{color:#333;background-color:#d4d4d4;border-color:#8c8c8c}.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled.focus,.btn-default[disabled].focus,fieldset[disabled] .btn-default.focus{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:focus,.btn-primary.focus{color:#fff;background-color:#3071a9;border-color:#193c5a}.btn-primary:hover{color:#fff;background-color:#3071a9;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#3071a9;background-image:none;border-color:#285e8e}.btn-primary:active:hover,.btn-primary.active:hover,.open>.dropdown-toggle.btn-primary:hover,.btn-primary:active:focus,.btn-primary.active:focus,.open>.dropdown-toggle.btn-primary:focus,.btn-primary:active.focus,.btn-primary.active.focus,.open>.dropdown-toggle.btn-primary.focus{color:#fff;background-color:#285e8e;border-color:#193c5a}.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled.focus,.btn-primary[disabled].focus,fieldset[disabled] .btn-primary.focus{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:focus,.btn-success.focus{color:#fff;background-color:#449d44;border-color:#255625}.btn-success:hover{color:#fff;background-color:#449d44;border-color:#398439}.btn-success:active,.btn-success.active,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;background-image:none;border-color:#398439}.btn-success:active:hover,.btn-success.active:hover,.open>.dropdown-toggle.btn-success:hover,.btn-success:active:focus,.btn-success.active:focus,.open>.dropdown-toggle.btn-success:focus,.btn-success:active.focus,.btn-success.active.focus,.open>.dropdown-toggle.btn-success.focus{color:#fff;background-color:#398439;border-color:#255625}.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled.focus,.btn-success[disabled].focus,fieldset[disabled] .btn-success.focus{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:focus,.btn-info.focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85}.btn-info:hover{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info:active,.btn-info.active,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;background-image:none;border-color:#269abc}.btn-info:active:hover,.btn-info.active:hover,.open>.dropdown-toggle.btn-info:hover,.btn-info:active:focus,.btn-info.active:focus,.open>.dropdown-toggle.btn-info:focus,.btn-info:active.focus,.btn-info.active.focus,.open>.dropdown-toggle.btn-info.focus{color:#fff;background-color:#269abc;border-color:#1b6d85}.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled.focus,.btn-info[disabled].focus,fieldset[disabled] .btn-info.focus{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:focus,.btn-warning.focus{color:#fff;background-color:#ec971f;border-color:#985f0d}.btn-warning:hover{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;background-image:none;border-color:#d58512}.btn-warning:active:hover,.btn-warning.active:hover,.open>.dropdown-toggle.btn-warning:hover,.btn-warning:active:focus,.btn-warning.active:focus,.open>.dropdown-toggle.btn-warning:focus,.btn-warning:active.focus,.btn-warning.active.focus,.open>.dropdown-toggle.btn-warning.focus{color:#fff;background-color:#d58512;border-color:#985f0d}.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled.focus,.btn-warning[disabled].focus,fieldset[disabled] .btn-warning.focus{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:focus,.btn-danger.focus{color:#fff;background-color:#c9302c;border-color:#761c19}.btn-danger:hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;background-image:none;border-color:#ac2925}.btn-danger:active:hover,.btn-danger.active:hover,.open>.dropdown-toggle.btn-danger:hover,.btn-danger:active:focus,.btn-danger.active:focus,.open>.dropdown-toggle.btn-danger:focus,.btn-danger:active.focus,.btn-danger.active.focus,.open>.dropdown-toggle.btn-danger.focus{color:#fff;background-color:#ac2925;border-color:#761c19}.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled.focus,.btn-danger[disabled].focus,fieldset[disabled] .btn-danger.focus{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#428bca;border-radius:0}.btn-link,.btn-link:active,.btn-link.active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#777;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-property:height, visibility;-o-transition-property:height, visibility;transition-property:height, visibility;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,0.125);box-shadow:inset 0 3px 5px rgba(0,0,0,0.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle="buttons"]>.btn input[type="radio"],[data-toggle="buttons"]>.btn-group>.btn input[type="radio"],[data-toggle="buttons"]>.btn input[type="checkbox"],[data-toggle="buttons"]>.btn-group>.btn input[type="checkbox"]{position:absolute;clip:rect(0, 0, 0, 0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*="col-"]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group .form-control:focus{z-index:3}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type="radio"],.input-group-addon input[type="checkbox"]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block !important;height:auto !important;padding-bottom:0;overflow:visible !important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{max-height:200px}}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-right:15px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-right:-15px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn,.navbar-form .input-group .form-control{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .radio label,.navbar-form .checkbox label{padding-left:0}.navbar-form .radio input[type="radio"],.navbar-form .checkbox input[type="checkbox"]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left !important}.navbar-right{float:right !important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:hover,.navbar-default .btn-link:focus{color:#333}.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:hover,.navbar-default .btn-link[disabled]:focus,fieldset[disabled] .navbar-default .btn-link:focus{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#777}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#777}.navbar-inverse .navbar-nav>li>a{color:#777}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-link{color:#777}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#777}.navbar-inverse .btn-link:hover,.navbar-inverse .btn-link:focus{color:#fff}.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:hover,.navbar-inverse .btn-link[disabled]:focus,fieldset[disabled] .navbar-inverse .btn-link:focus{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#428bca;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{z-index:2;color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:3;color:#fff;cursor:default;background-color:#428bca;border-color:#428bca}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.33}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:hover,a.label:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:hover,.label-default[href]:focus{background-color:#5e5e5e}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:bold;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:middle;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge,.btn-group-xs>.btn .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item.disabled,.list-group-item.disabled:hover,.list-group-item.disabled:focus{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}.list-group-item.active .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>.small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:hover .list-group-item-text,.list-group-item.active:focus .list-group-item-text{color:#e1edf7}a.list-group-item,button.list-group-item{color:#555}a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,button.list-group-item:hover,a.list-group-item:focus,button.list-group-item:focus{color:#555;text-decoration:none;background-color:#f5f5f5}button.list-group-item{width:100%;text-align:left}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success,button.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,button.list-group-item-success:hover,a.list-group-item-success:focus,button.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,button.list-group-item-success.active,a.list-group-item-success.active:hover,button.list-group-item-success.active:hover,a.list-group-item-success.active:focus,button.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info,button.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,button.list-group-item-info:hover,a.list-group-item-info:focus,button.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,button.list-group-item-info.active,a.list-group-item-info.active:hover,button.list-group-item-info.active:hover,a.list-group-item-info.active:focus,button.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,button.list-group-item-warning:hover,a.list-group-item-warning:focus,button.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,button.list-group-item-warning.active,a.list-group-item-warning.active:hover,button.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus,button.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger,button.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,button.list-group-item-danger:hover,a.list-group-item-danger:focus,button.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,button.list-group-item-danger.active,a.list-group-item-danger.active:hover,button.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus,button.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.05);box-shadow:0 1px 1px rgba(0,0,0,0.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a,.panel-title>small,.panel-title>.small,.panel-title>small>a,.panel-title>.small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.table,.panel>.table-responsive>.table,.panel>.panel-collapse>.table{margin-bottom:0}.panel>.table caption,.panel>.table-responsive>.table caption,.panel>.panel-collapse>.table caption{padding-right:15px;padding-left:15px}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.panel-body,.panel-group .panel-heading+.panel-collapse>.list-group{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#428bca}.panel-primary>.panel-heading .badge{color:#428bca;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.clearfix:before,.clearfix:after,.dl-horizontal dd:before,.dl-horizontal dd:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.panel-body:before,.panel-body:after{display:table;content:" "}.clearfix:after,.dl-horizontal dd:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.panel-body:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right !important}.pull-left{float:left !important}.hide{display:none !important}.show{display:block !important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none !important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none !important}.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block{display:none !important}@media (max-width:767px){.visible-xs{display:block !important}table.visible-xs{display:table !important}tr.visible-xs{display:table-row !important}th.visible-xs,td.visible-xs{display:table-cell !important}}@media (max-width:767px){.visible-xs-block{display:block !important}}@media (max-width:767px){.visible-xs-inline{display:inline !important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block !important}table.visible-sm{display:table !important}tr.visible-sm{display:table-row !important}th.visible-sm,td.visible-sm{display:table-cell !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline !important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block !important}table.visible-md{display:table !important}tr.visible-md{display:table-row !important}th.visible-md,td.visible-md{display:table-cell !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline !important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block !important}}@media (min-width:1200px){.visible-lg{display:block !important}table.visible-lg{display:table !important}tr.visible-lg{display:table-row !important}th.visible-lg,td.visible-lg{display:table-cell !important}}@media (min-width:1200px){.visible-lg-block{display:block !important}}@media (min-width:1200px){.visible-lg-inline{display:inline !important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block !important}}@media (max-width:767px){.hidden-xs{display:none !important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none !important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none !important}}@media (min-width:1200px){.hidden-lg{display:none !important}}.visible-print{display:none !important}@media print{.visible-print{display:block !important}table.visible-print{display:table !important}tr.visible-print{display:table-row !important}th.visible-print,td.visible-print{display:table-cell !important}}.visible-print-block{display:none !important}@media print{.visible-print-block{display:block !important}}.visible-print-inline{display:none !important}@media print{.visible-print-inline{display:inline !important}}.visible-print-inline-block{display:none !important}@media print{.visible-print-inline-block{display:inline-block !important}}@media print{.hidden-print{display:none !important}}html,
body,
#content {
height: 100%;
}
/* Site menu */
#site-nav.navbar-default {
margin: 0;
border-radius: 0;
border-bottom: 1px solid #ccc;
background-color: #edf3fe;
background-image: none;
}
#site-nav.navbar-default .navbar-brand,
#site-nav.navbar-default .navbar-nav > li > a {
color: #000;
}
#site-nav.navbar-default .navbar-nav > li > a:hover {
text-decoration: underline;
}
#navbar-elements {
float: right;
}
@media (max-width: 768px) {
#navbar-elements {
float: none !important;
}
}
/* Namespace breadcrumbs */
.namespace-breadcrumbs .breadcrumb {
margin: 0 0 12px;
border-radius: 0 0 4px 4px;
padding-left: 35px;
}
.namespace-breadcrumbs .breadcrumb > li + li:before {
content: "";
}
.namespace-breadcrumbs .breadcrumb > .backslash {
color: #ccc;
}
/* Site columns */
#right-column {
margin-left: 20%;
}
#page-content {
padding: 0 30px;
}
#left-column {
width: 20%;
position: fixed;
height: 100%;
border-right: 1px solid #ccc;
line-height: 18px;
font-size: 13px;
display: flex;
flex-flow: column;
}
@media (max-width: 991px) {
#left-column {
display: none;
}
#right-column {
width: 100%;
margin-left: 0;
}
}
/* API Tree */
#api-tree {
background: linear-gradient(to bottom, #fff, #fff 50%, #edf3fe 50%, #edf3fe);
background-size: 100% 56px;
overflow: auto;
height: 100%;
background-attachment: local;
}
#api-tree ul {
list-style-type: none;
margin: 0;
padding: 0;
}
#api-tree ul li {
padding: 0;
margin: 0;
}
/* Prevents the menu from jittering on lad */
#api-tree .icon-play {
width: 26px;
}
#api-tree ul li .hd {
padding: 5px;
}
#api-tree li .hd:nth-child(even) {
background-color: #edf3fe;
}
#api-tree ul li.opened > .hd span {
-webkit-transform: rotate(90deg);
-moz-transform: rotate(90deg);
-o-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);
}
#api-tree .bd {
display: none;
}
#api-tree li.opened > .bd {
display: block;
}
#api-tree li .hd:hover {
background-color: #eee;
}
#api-tree li.active > .hd {
background-color: #3875d7;
}
#api-tree li.active > .hd a {
color: #eee;
font-weight: bold;
}
#api-tree a {
color: #222;
}
#api-tree div.leaf a {
margin-left: 20px;
}
#api-tree .hd span {
padding: 2px 8px;
font-size: 15px;
line-height: 85%;
}
/* Control panel, search form, version drop-down */
#control-panel {
background: #e8e8e8;
border-bottom: 1px solid #666;
padding: 4px;
}
#control-panel form {
margin: 4px 4px 5px 4px;
}
#search-form {
position: relative;
}
#search-form input {
width: 100%;
padding-left: 28px;
}
#search-form span.icon-search {
position: absolute;
left: 5px;
top: 8px;
font-size: 20px;
z-index: 2;
}
/* Typeahead */
.twitter-typeahead {
width: 100%;
z-index: 1;
}
.tt-dropdown-menu {
overflow: auto;
max-height: 260px;
margin-top: 9px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 8px;
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
padding: 8px;
}
.tt-dropdown-menu p {
margin: 0;
padding: 0;
}
.tt-suggestion {
padding: 8px;
border-bottom: 1px solid #ccc;
font-size: 1.1em;
}
.tt-cursor {
background-color: #3875d7;
color: #fff;
}
/** General typography **/
.navbar {
border-bottom: 0;
}
.page-header {
margin: 0 0 20px;
}
abbr[title],
abbr[data-original-title],
abbr {
border-bottom: none;
cursor: pointer;
}
a abbr {
cursor: pointer;
}
.method-description table,
.description table {
border: solid 1px #ccc;
padding: 1em;
margin: 1em;
}
.method-description td,
.method-description th,
.description td,
.description th {
padding: 0.75em 1.25em;
}
.method-description tbody tr:nth-child(even),
.description tbody tr:nth-child(even) {
background: #edf3fe;
}
.method-description tbody tr:nth-child(odd),
.description tbody tr:nth-child(odd) {
background: #fff;
}
.method-description thead tr,
.description thead tr {
background: #edf3fe;
}
/** General Doctum styling **/
.underlined > .row {
padding: 8px 0;
border-bottom: 1px solid #ddd;
}
#footer {
text-align: right;
margin: 30px;
font-size: 11px;
}
.description {
margin: 10px 0;
padding: 10px;
background-color: #efefef;
}
.description p {
padding: 0;
margin: 8px 0;
}
.method-description {
margin: 0 0 24px 0;
}
.details {
padding-left: 30px;
}
#method-details .method-item {
margin-bottom: 30px;
}
.method-item h3,
.method-item h3 code {
background-color: #eee;
}
.method-item h3 {
padding: 4px;
margin-bottom: 20px;
font-size: 20px;
}
.location {
font-size: 11px;
float: right;
font-style: italic;
}
.namespace-list a {
padding: 3px 8px;
margin: 0 5px 5px 0;
border: 1px solid #ddd;
background-color: #f9f9f9;
display: inline-block;
border-radius: 4px;
}
.no-description {
color: #ccc;
font-size: 90%;
}
.type {
overflow-wrap: break-word;
}
/* Namespaces page */
.namespaces {
clear: both;
}
.namespaces .namespace-container {
float: left;
margin: 0 14px 14px 0;
min-width: 30%;
}
.namespaces h2 {
margin: 0 0 20px 0;
}
.namespace-container > h2 {
background-color: #edf3fe;
padding: 4px 4px 4px 8px;
font-size: 25px;
margin: 20px 0;
}
@media (max-width: 991px) {
.namespaces .namespace-container {
margin-right: 0;
width: 100%;
}
}
/** Code and pre tags **/
tt,
code,
pre {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
code {
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
margin: 0;
font-size: 85%;
background-color: rgba(0, 0, 0, 0.04);
border-radius: 3px;
color: #333;
}
pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f7f7f7;
border-radius: 3px;
}
pre.examples {
padding: 1rem;
}
#page-content > h2 {
background-color: #edf3fe;
padding: 4px 4px 4px 8px;
font-size: 25px;
margin: 20px 0;
}
/** Doc index **/
dt {
font-weight: normal;
}
dd {
margin-left: 30px;
line-height: 1.5em;
}
#doc-index h2 {
font-weight: bold;
margin: 30px 0;
}
#doc-index .pagination {
margin: 0;
}
/* Search page */
.search-results {
list-style-type: none;
padding: 0;
margin: 0;
}
.search-results li {
list-style-type: none;
margin: 0;
padding: 14px 0;
border-bottom: 1px solid #ccc;
}
.search-results > li > h2 {
background: none;
margin: 0;
padding: 0;
font-size: 18px;
}
.search-results > li > h2 > a {
float: left;
display: block;
margin: 0 0 4px 0;
}
.search-results .search-type {
float: right;
margin: 0 0 4px 0;
}
.search-results .search-from {
margin: 0 0 12px 0;
font-size: 12px;
color: #999;
}
.search-results .search-from a {
font-style: italic;
}
.search-results .search-description {
margin: 8px 0 0 30px;
}
.search-description {
white-space: pre;
}
/*!
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
*/
/*!
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
audio,
canvas,
progress,
video {
display: inline-block;
vertical-align: baseline;
}
audio:not([controls]) {
display: none;
height: 0;
}
[hidden],
template {
display: none;
}
a {
background-color: transparent;
}
a:active,
a:hover {
outline: 0;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted;
}
b,
strong {
font-weight: bold;
}
dfn {
font-style: italic;
}
h1 {
font-size: 2em;
margin: 0.67em 0;
}
mark {
background: #ff0;
color: #000;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
img {
border: 0;
}
svg:not(:root) {
overflow: hidden;
}
figure {
margin: 1em 40px;
}
hr {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
height: 0;
}
pre {
overflow: auto;
}
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
button,
input,
optgroup,
select,
textarea {
color: inherit;
font: inherit;
margin: 0;
}
button {
overflow: visible;
}
button,
select {
text-transform: none;
}
button,
html input[type="button"],
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button;
cursor: pointer;
}
button[disabled],
html input[disabled] {
cursor: default;
}
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
input {
line-height: normal;
}
input[type="checkbox"],
input[type="radio"] {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
padding: 0;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
input[type="search"] {
-webkit-appearance: textfield;
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
legend {
border: 0;
padding: 0;
}
textarea {
overflow: auto;
}
optgroup {
font-weight: bold;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */
@media print {
*,
*:before,
*:after {
color: #000 !important;
text-shadow: none !important;
background: transparent !important;
-webkit-box-shadow: none !important;
box-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
abbr[title]:after {
content: " (" attr(title) ")";
}
a[href^="#"]:after,
a[href^="javascript:"]:after {
content: "";
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
.navbar {
display: none;
}
.btn > .caret,
.dropup > .btn > .caret {
border-top-color: #000 !important;
}
.label {
border: 1px solid #000;
}
.table {
border-collapse: collapse !important;
}
.table td,
.table th {
background-color: #fff !important;
}
.table-bordered th,
.table-bordered td {
border: 1px solid #ddd !important;
}
}
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
*:before,
*:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html {
font-size: 10px;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.42857143;
color: #333333;
background-color: #ffffff;
}
input,
button,
select,
textarea {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
a {
color: #428bca;
text-decoration: none;
}
a:hover,
a:focus {
color: #2a6496;
text-decoration: underline;
}
a:focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
figure {
margin: 0;
}
img {
vertical-align: middle;
}
.img-responsive {
display: block;
max-width: 100%;
height: auto;
}
.img-rounded {
border-radius: 6px;
}
.img-thumbnail {
padding: 4px;
line-height: 1.42857143;
background-color: #ffffff;
border: 1px solid #dddddd;
border-radius: 4px;
-webkit-transition: all 0.2s ease-in-out;
-o-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
display: inline-block;
max-width: 100%;
height: auto;
}
.img-circle {
border-radius: 50%;
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eeeeee;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
border: 0;
}
.sr-only-focusable:active,
.sr-only-focusable:focus {
position: static;
width: auto;
height: auto;
margin: 0;
overflow: visible;
clip: auto;
}
[role="button"] {
cursor: pointer;
}
h1,
h2,
h3,
h4,
h5,
h6,
.h1,
.h2,
.h3,
.h4,
.h5,
.h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
h1 small,
h2 small,
h3 small,
h4 small,
h5 small,
h6 small,
.h1 small,
.h2 small,
.h3 small,
.h4 small,
.h5 small,
.h6 small,
h1 .small,
h2 .small,
h3 .small,
h4 .small,
h5 .small,
h6 .small,
.h1 .small,
.h2 .small,
.h3 .small,
.h4 .small,
.h5 .small,
.h6 .small {
font-weight: 400;
line-height: 1;
color: #777777;
}
h1,
.h1,
h2,
.h2,
h3,
.h3 {
margin-top: 20px;
margin-bottom: 10px;
}
h1 small,
.h1 small,
h2 small,
.h2 small,
h3 small,
.h3 small,
h1 .small,
.h1 .small,
h2 .small,
.h2 .small,
h3 .small,
.h3 .small {
font-size: 65%;
}
h4,
.h4,
h5,
.h5,
h6,
.h6 {
margin-top: 10px;
margin-bottom: 10px;
}
h4 small,
.h4 small,
h5 small,
.h5 small,
h6 small,
.h6 small,
h4 .small,
.h4 .small,
h5 .small,
.h5 .small,
h6 .small,
.h6 .small {
font-size: 75%;
}
h1,
.h1 {
font-size: 36px;
}
h2,
.h2 {
font-size: 30px;
}
h3,
.h3 {
font-size: 24px;
}
h4,
.h4 {
font-size: 18px;
}
h5,
.h5 {
font-size: 14px;
}
h6,
.h6 {
font-size: 12px;
}
p {
margin: 0 0 10px;
}
.lead {
margin-bottom: 20px;
font-size: 16px;
font-weight: 300;
line-height: 1.4;
}
@media (min-width: 768px) {
.lead {
font-size: 21px;
}
}
small,
.small {
font-size: 85%;
}
mark,
.mark {
padding: .2em;
background-color: #fcf8e3;
}
.text-left {
text-align: left;
}
.text-right {
text-align: right;
}
.text-center {
text-align: center;
}
.text-justify {
text-align: justify;
}
.text-nowrap {
white-space: nowrap;
}
.text-lowercase {
text-transform: lowercase;
}
.text-uppercase {
text-transform: uppercase;
}
.text-capitalize {
text-transform: capitalize;
}
.text-muted {
color: #777777;
}
.text-primary {
color: #428bca;
}
a.text-primary:hover,
a.text-primary:focus {
color: #3071a9;
}
.text-success {
color: #3c763d;
}
a.text-success:hover,
a.text-success:focus {
color: #2b542c;
}
.text-info {
color: #31708f;
}
a.text-info:hover,
a.text-info:focus {
color: #245269;
}
.text-warning {
color: #8a6d3b;
}
a.text-warning:hover,
a.text-warning:focus {
color: #66512c;
}
.text-danger {
color: #a94442;
}
a.text-danger:hover,
a.text-danger:focus {
color: #843534;
}
.bg-primary {
color: #fff;
background-color: #428bca;
}
a.bg-primary:hover,
a.bg-primary:focus {
background-color: #3071a9;
}
.bg-success {
background-color: #dff0d8;
}
a.bg-success:hover,
a.bg-success:focus {
background-color: #c1e2b3;
}
.bg-info {
background-color: #d9edf7;
}
a.bg-info:hover,
a.bg-info:focus {
background-color: #afd9ee;
}
.bg-warning {
background-color: #fcf8e3;
}
a.bg-warning:hover,
a.bg-warning:focus {
background-color: #f7ecb5;
}
.bg-danger {
background-color: #f2dede;
}
a.bg-danger:hover,
a.bg-danger:focus {
background-color: #e4b9b9;
}
.page-header {
padding-bottom: 9px;
margin: 40px 0 20px;
border-bottom: 1px solid #eeeeee;
}
ul,
ol {
margin-top: 0;
margin-bottom: 10px;
}
ul ul,
ol ul,
ul ol,
ol ol {
margin-bottom: 0;
}
.list-unstyled {
padding-left: 0;
list-style: none;
}
.list-inline {
padding-left: 0;
list-style: none;
margin-left: -5px;
}
.list-inline > li {
display: inline-block;
padding-right: 5px;
padding-left: 5px;
}
dl {
margin-top: 0;
margin-bottom: 20px;
}
dt,
dd {
line-height: 1.42857143;
}
dt {
font-weight: 700;
}
dd {
margin-left: 0;
}
@media (min-width: 768px) {
.dl-horizontal dt {
float: left;
width: 160px;
clear: left;
text-align: right;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.dl-horizontal dd {
margin-left: 180px;
}
}
abbr[title],
abbr[data-original-title] {
cursor: help;
}
.initialism {
font-size: 90%;
text-transform: uppercase;
}
blockquote {
padding: 10px 20px;
margin: 0 0 20px;
font-size: 17.5px;
border-left: 5px solid #eeeeee;
}
blockquote p:last-child,
blockquote ul:last-child,
blockquote ol:last-child {
margin-bottom: 0;
}
blockquote footer,
blockquote small,
blockquote .small {
display: block;
font-size: 80%;
line-height: 1.42857143;
color: #777777;
}
blockquote footer:before,
blockquote small:before,
blockquote .small:before {
content: "\2014 \00A0";
}
.blockquote-reverse,
blockquote.pull-right {
padding-right: 15px;
padding-left: 0;
text-align: right;
border-right: 5px solid #eeeeee;
border-left: 0;
}
.blockquote-reverse footer:before,
blockquote.pull-right footer:before,
.blockquote-reverse small:before,
blockquote.pull-right small:before,
.blockquote-reverse .small:before,
blockquote.pull-right .small:before {
content: "";
}
.blockquote-reverse footer:after,
blockquote.pull-right footer:after,
.blockquote-reverse small:after,
blockquote.pull-right small:after,
.blockquote-reverse .small:after,
blockquote.pull-right .small:after {
content: "\00A0 \2014";
}
address {
margin-bottom: 20px;
font-style: normal;
line-height: 1.42857143;
}
code,
kbd,
pre,
samp {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}
code {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
background-color: #f9f2f4;
border-radius: 4px;
}
kbd {
padding: 2px 4px;
font-size: 90%;
color: #ffffff;
background-color: #333333;
border-radius: 3px;
-webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);
}
kbd kbd {
padding: 0;
font-size: 100%;
font-weight: 700;
-webkit-box-shadow: none;
box-shadow: none;
}
pre {
display: block;
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
line-height: 1.42857143;
color: #333333;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #cccccc;
border-radius: 4px;
}
pre code {
padding: 0;
font-size: inherit;
color: inherit;
white-space: pre-wrap;
background-color: transparent;
border-radius: 0;
}
.pre-scrollable {
max-height: 340px;
overflow-y: scroll;
}
.container {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
@media (min-width: 768px) {
.container {
width: 750px;
}
}
@media (min-width: 992px) {
.container {
width: 970px;
}
}
@media (min-width: 1200px) {
.container {
width: 1170px;
}
}
.container-fluid {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
.row {
margin-right: -15px;
margin-left: -15px;
}
.row-no-gutters {
margin-right: 0;
margin-left: 0;
}
.row-no-gutters [class*="col-"] {
padding-right: 0;
padding-left: 0;
}
.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {
position: relative;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
}
.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {
float: left;
}
.col-xs-12 {
width: 100%;
}
.col-xs-11 {
width: 91.66666667%;
}
.col-xs-10 {
width: 83.33333333%;
}
.col-xs-9 {
width: 75%;
}
.col-xs-8 {
width: 66.66666667%;
}
.col-xs-7 {
width: 58.33333333%;
}
.col-xs-6 {
width: 50%;
}
.col-xs-5 {
width: 41.66666667%;
}
.col-xs-4 {
width: 33.33333333%;
}
.col-xs-3 {
width: 25%;
}
.col-xs-2 {
width: 16.66666667%;
}
.col-xs-1 {
width: 8.33333333%;
}
.col-xs-pull-12 {
right: 100%;
}
.col-xs-pull-11 {
right: 91.66666667%;
}
.col-xs-pull-10 {
right: 83.33333333%;
}
.col-xs-pull-9 {
right: 75%;
}
.col-xs-pull-8 {
right: 66.66666667%;
}
.col-xs-pull-7 {
right: 58.33333333%;
}
.col-xs-pull-6 {
right: 50%;
}
.col-xs-pull-5 {
right: 41.66666667%;
}
.col-xs-pull-4 {
right: 33.33333333%;
}
.col-xs-pull-3 {
right: 25%;
}
.col-xs-pull-2 {
right: 16.66666667%;
}
.col-xs-pull-1 {
right: 8.33333333%;
}
.col-xs-pull-0 {
right: auto;
}
.col-xs-push-12 {
left: 100%;
}
.col-xs-push-11 {
left: 91.66666667%;
}
.col-xs-push-10 {
left: 83.33333333%;
}
.col-xs-push-9 {
left: 75%;
}
.col-xs-push-8 {
left: 66.66666667%;
}
.col-xs-push-7 {
left: 58.33333333%;
}
.col-xs-push-6 {
left: 50%;
}
.col-xs-push-5 {
left: 41.66666667%;
}
.col-xs-push-4 {
left: 33.33333333%;
}
.col-xs-push-3 {
left: 25%;
}
.col-xs-push-2 {
left: 16.66666667%;
}
.col-xs-push-1 {
left: 8.33333333%;
}
.col-xs-push-0 {
left: auto;
}
.col-xs-offset-12 {
margin-left: 100%;
}
.col-xs-offset-11 {
margin-left: 91.66666667%;
}
.col-xs-offset-10 {
margin-left: 83.33333333%;
}
.col-xs-offset-9 {
margin-left: 75%;
}
.col-xs-offset-8 {
margin-left: 66.66666667%;
}
.col-xs-offset-7 {
margin-left: 58.33333333%;
}
.col-xs-offset-6 {
margin-left: 50%;
}
.col-xs-offset-5 {
margin-left: 41.66666667%;
}
.col-xs-offset-4 {
margin-left: 33.33333333%;
}
.col-xs-offset-3 {
margin-left: 25%;
}
.col-xs-offset-2 {
margin-left: 16.66666667%;
}
.col-xs-offset-1 {
margin-left: 8.33333333%;
}
.col-xs-offset-0 {
margin-left: 0%;
}
@media (min-width: 768px) {
.col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {
float: left;
}
.col-sm-12 {
width: 100%;
}
.col-sm-11 {
width: 91.66666667%;
}
.col-sm-10 {
width: 83.33333333%;
}
.col-sm-9 {
width: 75%;
}
.col-sm-8 {
width: 66.66666667%;
}
.col-sm-7 {
width: 58.33333333%;
}
.col-sm-6 {
width: 50%;
}
.col-sm-5 {
width: 41.66666667%;
}
.col-sm-4 {
width: 33.33333333%;
}
.col-sm-3 {
width: 25%;
}
.col-sm-2 {
width: 16.66666667%;
}
.col-sm-1 {
width: 8.33333333%;
}
.col-sm-pull-12 {
right: 100%;
}
.col-sm-pull-11 {
right: 91.66666667%;
}
.col-sm-pull-10 {
right: 83.33333333%;
}
.col-sm-pull-9 {
right: 75%;
}
.col-sm-pull-8 {
right: 66.66666667%;
}
.col-sm-pull-7 {
right: 58.33333333%;
}
.col-sm-pull-6 {
right: 50%;
}
.col-sm-pull-5 {
right: 41.66666667%;
}
.col-sm-pull-4 {
right: 33.33333333%;
}
.col-sm-pull-3 {
right: 25%;
}
.col-sm-pull-2 {
right: 16.66666667%;
}
.col-sm-pull-1 {
right: 8.33333333%;
}
.col-sm-pull-0 {
right: auto;
}
.col-sm-push-12 {
left: 100%;
}
.col-sm-push-11 {
left: 91.66666667%;
}
.col-sm-push-10 {
left: 83.33333333%;
}
.col-sm-push-9 {
left: 75%;
}
.col-sm-push-8 {
left: 66.66666667%;
}
.col-sm-push-7 {
left: 58.33333333%;
}
.col-sm-push-6 {
left: 50%;
}
.col-sm-push-5 {
left: 41.66666667%;
}
.col-sm-push-4 {
left: 33.33333333%;
}
.col-sm-push-3 {
left: 25%;
}
.col-sm-push-2 {
left: 16.66666667%;
}
.col-sm-push-1 {
left: 8.33333333%;
}
.col-sm-push-0 {
left: auto;
}
.col-sm-offset-12 {
margin-left: 100%;
}
.col-sm-offset-11 {
margin-left: 91.66666667%;
}
.col-sm-offset-10 {
margin-left: 83.33333333%;
}
.col-sm-offset-9 {
margin-left: 75%;
}
.col-sm-offset-8 {
margin-left: 66.66666667%;
}
.col-sm-offset-7 {
margin-left: 58.33333333%;
}
.col-sm-offset-6 {
margin-left: 50%;
}
.col-sm-offset-5 {
margin-left: 41.66666667%;
}
.col-sm-offset-4 {
margin-left: 33.33333333%;
}
.col-sm-offset-3 {
margin-left: 25%;
}
.col-sm-offset-2 {
margin-left: 16.66666667%;
}
.col-sm-offset-1 {
margin-left: 8.33333333%;
}
.col-sm-offset-0 {
margin-left: 0%;
}
}
@media (min-width: 992px) {
.col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {
float: left;
}
.col-md-12 {
width: 100%;
}
.col-md-11 {
width: 91.66666667%;
}
.col-md-10 {
width: 83.33333333%;
}
.col-md-9 {
width: 75%;
}
.col-md-8 {
width: 66.66666667%;
}
.col-md-7 {
width: 58.33333333%;
}
.col-md-6 {
width: 50%;
}
.col-md-5 {
width: 41.66666667%;
}
.col-md-4 {
width: 33.33333333%;
}
.col-md-3 {
width: 25%;
}
.col-md-2 {
width: 16.66666667%;
}
.col-md-1 {
width: 8.33333333%;
}
.col-md-pull-12 {
right: 100%;
}
.col-md-pull-11 {
right: 91.66666667%;
}
.col-md-pull-10 {
right: 83.33333333%;
}
.col-md-pull-9 {
right: 75%;
}
.col-md-pull-8 {
right: 66.66666667%;
}
.col-md-pull-7 {
right: 58.33333333%;
}
.col-md-pull-6 {
right: 50%;
}
.col-md-pull-5 {
right: 41.66666667%;
}
.col-md-pull-4 {
right: 33.33333333%;
}
.col-md-pull-3 {
right: 25%;
}
.col-md-pull-2 {
right: 16.66666667%;
}
.col-md-pull-1 {
right: 8.33333333%;
}
.col-md-pull-0 {
right: auto;
}
.col-md-push-12 {
left: 100%;
}
.col-md-push-11 {
left: 91.66666667%;
}
.col-md-push-10 {
left: 83.33333333%;
}
.col-md-push-9 {
left: 75%;
}
.col-md-push-8 {
left: 66.66666667%;
}
.col-md-push-7 {
left: 58.33333333%;
}
.col-md-push-6 {
left: 50%;
}
.col-md-push-5 {
left: 41.66666667%;
}
.col-md-push-4 {
left: 33.33333333%;
}
.col-md-push-3 {
left: 25%;
}
.col-md-push-2 {
left: 16.66666667%;
}
.col-md-push-1 {
left: 8.33333333%;
}
.col-md-push-0 {
left: auto;
}
.col-md-offset-12 {
margin-left: 100%;
}
.col-md-offset-11 {
margin-left: 91.66666667%;
}
.col-md-offset-10 {
margin-left: 83.33333333%;
}
.col-md-offset-9 {
margin-left: 75%;
}
.col-md-offset-8 {
margin-left: 66.66666667%;
}
.col-md-offset-7 {
margin-left: 58.33333333%;
}
.col-md-offset-6 {
margin-left: 50%;
}
.col-md-offset-5 {
margin-left: 41.66666667%;
}
.col-md-offset-4 {
margin-left: 33.33333333%;
}
.col-md-offset-3 {
margin-left: 25%;
}
.col-md-offset-2 {
margin-left: 16.66666667%;
}
.col-md-offset-1 {
margin-left: 8.33333333%;
}
.col-md-offset-0 {
margin-left: 0%;
}
}
@media (min-width: 1200px) {
.col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {
float: left;
}
.col-lg-12 {
width: 100%;
}
.col-lg-11 {
width: 91.66666667%;
}
.col-lg-10 {
width: 83.33333333%;
}
.col-lg-9 {
width: 75%;
}
.col-lg-8 {
width: 66.66666667%;
}
.col-lg-7 {
width: 58.33333333%;
}
.col-lg-6 {
width: 50%;
}
.col-lg-5 {
width: 41.66666667%;
}
.col-lg-4 {
width: 33.33333333%;
}
.col-lg-3 {
width: 25%;
}
.col-lg-2 {
width: 16.66666667%;
}
.col-lg-1 {
width: 8.33333333%;
}
.col-lg-pull-12 {
right: 100%;
}
.col-lg-pull-11 {
right: 91.66666667%;
}
.col-lg-pull-10 {
right: 83.33333333%;
}
.col-lg-pull-9 {
right: 75%;
}
.col-lg-pull-8 {
right: 66.66666667%;
}
.col-lg-pull-7 {
right: 58.33333333%;
}
.col-lg-pull-6 {
right: 50%;
}
.col-lg-pull-5 {
right: 41.66666667%;
}
.col-lg-pull-4 {
right: 33.33333333%;
}
.col-lg-pull-3 {
right: 25%;
}
.col-lg-pull-2 {
right: 16.66666667%;
}
.col-lg-pull-1 {
right: 8.33333333%;
}
.col-lg-pull-0 {
right: auto;
}
.col-lg-push-12 {
left: 100%;
}
.col-lg-push-11 {
left: 91.66666667%;
}
.col-lg-push-10 {
left: 83.33333333%;
}
.col-lg-push-9 {
left: 75%;
}
.col-lg-push-8 {
left: 66.66666667%;
}
.col-lg-push-7 {
left: 58.33333333%;
}
.col-lg-push-6 {
left: 50%;
}
.col-lg-push-5 {
left: 41.66666667%;
}
.col-lg-push-4 {
left: 33.33333333%;
}
.col-lg-push-3 {
left: 25%;
}
.col-lg-push-2 {
left: 16.66666667%;
}
.col-lg-push-1 {
left: 8.33333333%;
}
.col-lg-push-0 {
left: auto;
}
.col-lg-offset-12 {
margin-left: 100%;
}
.col-lg-offset-11 {
margin-left: 91.66666667%;
}
.col-lg-offset-10 {
margin-left: 83.33333333%;
}
.col-lg-offset-9 {
margin-left: 75%;
}
.col-lg-offset-8 {
margin-left: 66.66666667%;
}
.col-lg-offset-7 {
margin-left: 58.33333333%;
}
.col-lg-offset-6 {
margin-left: 50%;
}
.col-lg-offset-5 {
margin-left: 41.66666667%;
}
.col-lg-offset-4 {
margin-left: 33.33333333%;
}
.col-lg-offset-3 {
margin-left: 25%;
}
.col-lg-offset-2 {
margin-left: 16.66666667%;
}
.col-lg-offset-1 {
margin-left: 8.33333333%;
}
.col-lg-offset-0 {
margin-left: 0%;
}
}
table {
background-color: transparent;
}
table col[class*="col-"] {
position: static;
display: table-column;
float: none;
}
table td[class*="col-"],
table th[class*="col-"] {
position: static;
display: table-cell;
float: none;
}
caption {
padding-top: 8px;
padding-bottom: 8px;
color: #777777;
text-align: left;
}
th {
text-align: left;
}
.table {
width: 100%;
max-width: 100%;
margin-bottom: 20px;
}
.table > thead > tr > th,
.table > tbody > tr > th,
.table > tfoot > tr > th,
.table > thead > tr > td,
.table > tbody > tr > td,
.table > tfoot > tr > td {
padding: 8px;
line-height: 1.42857143;
vertical-align: top;
border-top: 1px solid #dddddd;
}
.table > thead > tr > th {
vertical-align: bottom;
border-bottom: 2px solid #dddddd;
}
.table > caption + thead > tr:first-child > th,
.table > colgroup + thead > tr:first-child > th,
.table > thead:first-child > tr:first-child > th,
.table > caption + thead > tr:first-child > td,
.table > colgroup + thead > tr:first-child > td,
.table > thead:first-child > tr:first-child > td {
border-top: 0;
}
.table > tbody + tbody {
border-top: 2px solid #dddddd;
}
.table .table {
background-color: #ffffff;
}
.table-condensed > thead > tr > th,
.table-condensed > tbody > tr > th,
.table-condensed > tfoot > tr > th,
.table-condensed > thead > tr > td,
.table-condensed > tbody > tr > td,
.table-condensed > tfoot > tr > td {
padding: 5px;
}
.table-bordered {
border: 1px solid #dddddd;
}
.table-bordered > thead > tr > th,
.table-bordered > tbody > tr > th,
.table-bordered > tfoot > tr > th,
.table-bordered > thead > tr > td,
.table-bordered > tbody > tr > td,
.table-bordered > tfoot > tr > td {
border: 1px solid #dddddd;
}
.table-bordered > thead > tr > th,
.table-bordered > thead > tr > td {
border-bottom-width: 2px;
}
.table-striped > tbody > tr:nth-of-type(odd) {
background-color: #f9f9f9;
}
.table-hover > tbody > tr:hover {
background-color: #f5f5f5;
}
.table > thead > tr > td.active,
.table > tbody > tr > td.active,
.table > tfoot > tr > td.active,
.table > thead > tr > th.active,
.table > tbody > tr > th.active,
.table > tfoot > tr > th.active,
.table > thead > tr.active > td,
.table > tbody > tr.active > td,
.table > tfoot > tr.active > td,
.table > thead > tr.active > th,
.table > tbody > tr.active > th,
.table > tfoot > tr.active > th {
background-color: #f5f5f5;
}
.table-hover > tbody > tr > td.active:hover,
.table-hover > tbody > tr > th.active:hover,
.table-hover > tbody > tr.active:hover > td,
.table-hover > tbody > tr:hover > .active,
.table-hover > tbody > tr.active:hover > th {
background-color: #e8e8e8;
}
.table > thead > tr > td.success,
.table > tbody > tr > td.success,
.table > tfoot > tr > td.success,
.table > thead > tr > th.success,
.table > tbody > tr > th.success,
.table > tfoot > tr > th.success,
.table > thead > tr.success > td,
.table > tbody > tr.success > td,
.table > tfoot > tr.success > td,
.table > thead > tr.success > th,
.table > tbody > tr.success > th,
.table > tfoot > tr.success > th {
background-color: #dff0d8;
}
.table-hover > tbody > tr > td.success:hover,
.table-hover > tbody > tr > th.success:hover,
.table-hover > tbody > tr.success:hover > td,
.table-hover > tbody > tr:hover > .success,
.table-hover > tbody > tr.success:hover > th {
background-color: #d0e9c6;
}
.table > thead > tr > td.info,
.table > tbody > tr > td.info,
.table > tfoot > tr > td.info,
.table > thead > tr > th.info,
.table > tbody > tr > th.info,
.table > tfoot > tr > th.info,
.table > thead > tr.info > td,
.table > tbody > tr.info > td,
.table > tfoot > tr.info > td,
.table > thead > tr.info > th,
.table > tbody > tr.info > th,
.table > tfoot > tr.info > th {
background-color: #d9edf7;
}
.table-hover > tbody > tr > td.info:hover,
.table-hover > tbody > tr > th.info:hover,
.table-hover > tbody > tr.info:hover > td,
.table-hover > tbody > tr:hover > .info,
.table-hover > tbody > tr.info:hover > th {
background-color: #c4e3f3;
}
.table > thead > tr > td.warning,
.table > tbody > tr > td.warning,
.table > tfoot > tr > td.warning,
.table > thead > tr > th.warning,
.table > tbody > tr > th.warning,
.table > tfoot > tr > th.warning,
.table > thead > tr.warning > td,
.table > tbody > tr.warning > td,
.table > tfoot > tr.warning > td,
.table > thead > tr.warning > th,
.table > tbody > tr.warning > th,
.table > tfoot > tr.warning > th {
background-color: #fcf8e3;
}
.table-hover > tbody > tr > td.warning:hover,
.table-hover > tbody > tr > th.warning:hover,
.table-hover > tbody > tr.warning:hover > td,
.table-hover > tbody > tr:hover > .warning,
.table-hover > tbody > tr.warning:hover > th {
background-color: #faf2cc;
}
.table > thead > tr > td.danger,
.table > tbody > tr > td.danger,
.table > tfoot > tr > td.danger,
.table > thead > tr > th.danger,
.table > tbody > tr > th.danger,
.table > tfoot > tr > th.danger,
.table > thead > tr.danger > td,
.table > tbody > tr.danger > td,
.table > tfoot > tr.danger > td,
.table > thead > tr.danger > th,
.table > tbody > tr.danger > th,
.table > tfoot > tr.danger > th {
background-color: #f2dede;
}
.table-hover > tbody > tr > td.danger:hover,
.table-hover > tbody > tr > th.danger:hover,
.table-hover > tbody > tr.danger:hover > td,
.table-hover > tbody > tr:hover > .danger,
.table-hover > tbody > tr.danger:hover > th {
background-color: #ebcccc;
}
.table-responsive {
min-height: .01%;
overflow-x: auto;
}
@media screen and (max-width: 767px) {
.table-responsive {
width: 100%;
margin-bottom: 15px;
overflow-y: hidden;
-ms-overflow-style: -ms-autohiding-scrollbar;
border: 1px solid #dddddd;
}
.table-responsive > .table {
margin-bottom: 0;
}
.table-responsive > .table > thead > tr > th,
.table-responsive > .table > tbody > tr > th,
.table-responsive > .table > tfoot > tr > th,
.table-responsive > .table > thead > tr > td,
.table-responsive > .table > tbody > tr > td,
.table-responsive > .table > tfoot > tr > td {
white-space: nowrap;
}
.table-responsive > .table-bordered {
border: 0;
}
.table-responsive > .table-bordered > thead > tr > th:first-child,
.table-responsive > .table-bordered > tbody > tr > th:first-child,
.table-responsive > .table-bordered > tfoot > tr > th:first-child,
.table-responsive > .table-bordered > thead > tr > td:first-child,
.table-responsive > .table-bordered > tbody > tr > td:first-child,
.table-responsive > .table-bordered > tfoot > tr > td:first-child {
border-left: 0;
}
.table-responsive > .table-bordered > thead > tr > th:last-child,
.table-responsive > .table-bordered > tbody > tr > th:last-child,
.table-responsive > .table-bordered > tfoot > tr > th:last-child,
.table-responsive > .table-bordered > thead > tr > td:last-child,
.table-responsive > .table-bordered > tbody > tr > td:last-child,
.table-responsive > .table-bordered > tfoot > tr > td:last-child {
border-right: 0;
}
.table-responsive > .table-bordered > tbody > tr:last-child > th,
.table-responsive > .table-bordered > tfoot > tr:last-child > th,
.table-responsive > .table-bordered > tbody > tr:last-child > td,
.table-responsive > .table-bordered > tfoot > tr:last-child > td {
border-bottom: 0;
}
}
fieldset {
min-width: 0;
padding: 0;
margin: 0;
border: 0;
}
legend {
display: block;
width: 100%;
padding: 0;
margin-bottom: 20px;
font-size: 21px;
line-height: inherit;
color: #333333;
border: 0;
border-bottom: 1px solid #e5e5e5;
}
label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
font-weight: 700;
}
input[type="search"] {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance: none;
appearance: none;
}
input[type="radio"],
input[type="checkbox"] {
margin: 4px 0 0;
margin-top: 1px \9;
line-height: normal;
}
input[type="radio"][disabled],
input[type="checkbox"][disabled],
input[type="radio"].disabled,
input[type="checkbox"].disabled,
fieldset[disabled] input[type="radio"],
fieldset[disabled] input[type="checkbox"] {
cursor: not-allowed;
}
input[type="file"] {
display: block;
}
input[type="range"] {
display: block;
width: 100%;
}
select[multiple],
select[size] {
height: auto;
}
input[type="file"]:focus,
input[type="radio"]:focus,
input[type="checkbox"]:focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
output {
display: block;
padding-top: 7px;
font-size: 14px;
line-height: 1.42857143;
color: #555555;
}
.form-control {
display: block;
width: 100%;
height: 34px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #555555;
background-color: #ffffff;
background-image: none;
border: 1px solid #cccccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
}
.form-control:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, 0.6);
}
.form-control::-moz-placeholder {
color: #777777;
opacity: 1;
}
.form-control:-ms-input-placeholder {
color: #777777;
}
.form-control::-webkit-input-placeholder {
color: #777777;
}
.form-control::-ms-expand {
background-color: transparent;
border: 0;
}
.form-control[disabled],
.form-control[readonly],
fieldset[disabled] .form-control {
background-color: #eeeeee;
opacity: 1;
}
.form-control[disabled],
fieldset[disabled] .form-control {
cursor: not-allowed;
}
textarea.form-control {
height: auto;
}
@media screen and (-webkit-min-device-pixel-ratio: 0) {
input[type="date"].form-control,
input[type="time"].form-control,
input[type="datetime-local"].form-control,
input[type="month"].form-control {
line-height: 34px;
}
input[type="date"].input-sm,
input[type="time"].input-sm,
input[type="datetime-local"].input-sm,
input[type="month"].input-sm,
.input-group-sm input[type="date"],
.input-group-sm input[type="time"],
.input-group-sm input[type="datetime-local"],
.input-group-sm input[type="month"] {
line-height: 30px;
}
input[type="date"].input-lg,
input[type="time"].input-lg,
input[type="datetime-local"].input-lg,
input[type="month"].input-lg,
.input-group-lg input[type="date"],
.input-group-lg input[type="time"],
.input-group-lg input[type="datetime-local"],
.input-group-lg input[type="month"] {
line-height: 46px;
}
}
.form-group {
margin-bottom: 15px;
}
.radio,
.checkbox {
position: relative;
display: block;
margin-top: 10px;
margin-bottom: 10px;
}
.radio.disabled label,
.checkbox.disabled label,
fieldset[disabled] .radio label,
fieldset[disabled] .checkbox label {
cursor: not-allowed;
}
.radio label,
.checkbox label {
min-height: 20px;
padding-left: 20px;
margin-bottom: 0;
font-weight: 400;
cursor: pointer;
}
.radio input[type="radio"],
.radio-inline input[type="radio"],
.checkbox input[type="checkbox"],
.checkbox-inline input[type="checkbox"] {
position: absolute;
margin-top: 4px \9;
margin-left: -20px;
}
.radio + .radio,
.checkbox + .checkbox {
margin-top: -5px;
}
.radio-inline,
.checkbox-inline {
position: relative;
display: inline-block;
padding-left: 20px;
margin-bottom: 0;
font-weight: 400;
vertical-align: middle;
cursor: pointer;
}
.radio-inline.disabled,
.checkbox-inline.disabled,
fieldset[disabled] .radio-inline,
fieldset[disabled] .checkbox-inline {
cursor: not-allowed;
}
.radio-inline + .radio-inline,
.checkbox-inline + .checkbox-inline {
margin-top: 0;
margin-left: 10px;
}
.form-control-static {
min-height: 34px;
padding-top: 7px;
padding-bottom: 7px;
margin-bottom: 0;
}
.form-control-static.input-lg,
.form-control-static.input-sm {
padding-right: 0;
padding-left: 0;
}
.input-sm {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
select.input-sm {
height: 30px;
line-height: 30px;
}
textarea.input-sm,
select[multiple].input-sm {
height: auto;
}
.form-group-sm .form-control {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
.form-group-sm select.form-control {
height: 30px;
line-height: 30px;
}
.form-group-sm textarea.form-control,
.form-group-sm select[multiple].form-control {
height: auto;
}
.form-group-sm .form-control-static {
height: 30px;
min-height: 32px;
padding: 6px 10px;
font-size: 12px;
line-height: 1.5;
}
.input-lg {
height: 46px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
border-radius: 6px;
}
select.input-lg {
height: 46px;
line-height: 46px;
}
textarea.input-lg,
select[multiple].input-lg {
height: auto;
}
.form-group-lg .form-control {
height: 46px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
border-radius: 6px;
}
.form-group-lg select.form-control {
height: 46px;
line-height: 46px;
}
.form-group-lg textarea.form-control,
.form-group-lg select[multiple].form-control {
height: auto;
}
.form-group-lg .form-control-static {
height: 46px;
min-height: 38px;
padding: 11px 16px;
font-size: 18px;
line-height: 1.33;
}
.has-feedback {
position: relative;
}
.has-feedback .form-control {
padding-right: 42.5px;
}
.form-control-feedback {
position: absolute;
top: 0;
right: 0;
z-index: 2;
display: block;
width: 34px;
height: 34px;
line-height: 34px;
text-align: center;
pointer-events: none;
}
.input-lg + .form-control-feedback,
.input-group-lg + .form-control-feedback,
.form-group-lg .form-control + .form-control-feedback {
width: 46px;
height: 46px;
line-height: 46px;
}
.input-sm + .form-control-feedback,
.input-group-sm + .form-control-feedback,
.form-group-sm .form-control + .form-control-feedback {
width: 30px;
height: 30px;
line-height: 30px;
}
.has-success .help-block,
.has-success .control-label,
.has-success .radio,
.has-success .checkbox,
.has-success .radio-inline,
.has-success .checkbox-inline,
.has-success.radio label,
.has-success.checkbox label,
.has-success.radio-inline label,
.has-success.checkbox-inline label {
color: #3c763d;
}
.has-success .form-control {
border-color: #3c763d;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.has-success .form-control:focus {
border-color: #2b542c;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;
}
.has-success .input-group-addon {
color: #3c763d;
background-color: #dff0d8;
border-color: #3c763d;
}
.has-success .form-control-feedback {
color: #3c763d;
}
.has-warning .help-block,
.has-warning .control-label,
.has-warning .radio,
.has-warning .checkbox,
.has-warning .radio-inline,
.has-warning .checkbox-inline,
.has-warning.radio label,
.has-warning.checkbox label,
.has-warning.radio-inline label,
.has-warning.checkbox-inline label {
color: #8a6d3b;
}
.has-warning .form-control {
border-color: #8a6d3b;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.has-warning .form-control:focus {
border-color: #66512c;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;
}
.has-warning .input-group-addon {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #8a6d3b;
}
.has-warning .form-control-feedback {
color: #8a6d3b;
}
.has-error .help-block,
.has-error .control-label,
.has-error .radio,
.has-error .checkbox,
.has-error .radio-inline,
.has-error .checkbox-inline,
.has-error.radio label,
.has-error.checkbox label,
.has-error.radio-inline label,
.has-error.checkbox-inline label {
color: #a94442;
}
.has-error .form-control {
border-color: #a94442;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.has-error .form-control:focus {
border-color: #843534;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;
}
.has-error .input-group-addon {
color: #a94442;
background-color: #f2dede;
border-color: #a94442;
}
.has-error .form-control-feedback {
color: #a94442;
}
.has-feedback label ~ .form-control-feedback {
top: 25px;
}
.has-feedback label.sr-only ~ .form-control-feedback {
top: 0;
}
.help-block {
display: block;
margin-top: 5px;
margin-bottom: 10px;
color: #737373;
}
@media (min-width: 768px) {
.form-inline .form-group {
display: inline-block;
margin-bottom: 0;
vertical-align: middle;
}
.form-inline .form-control {
display: inline-block;
width: auto;
vertical-align: middle;
}
.form-inline .form-control-static {
display: inline-block;
}
.form-inline .input-group {
display: inline-table;
vertical-align: middle;
}
.form-inline .input-group .input-group-addon,
.form-inline .input-group .input-group-btn,
.form-inline .input-group .form-control {
width: auto;
}
.form-inline .input-group > .form-control {
width: 100%;
}
.form-inline .control-label {
margin-bottom: 0;
vertical-align: middle;
}
.form-inline .radio,
.form-inline .checkbox {
display: inline-block;
margin-top: 0;
margin-bottom: 0;
vertical-align: middle;
}
.form-inline .radio label,
.form-inline .checkbox label {
padding-left: 0;
}
.form-inline .radio input[type="radio"],
.form-inline .checkbox input[type="checkbox"] {
position: relative;
margin-left: 0;
}
.form-inline .has-feedback .form-control-feedback {
top: 0;
}
}
.form-horizontal .radio,
.form-horizontal .checkbox,
.form-horizontal .radio-inline,
.form-horizontal .checkbox-inline {
padding-top: 7px;
margin-top: 0;
margin-bottom: 0;
}
.form-horizontal .radio,
.form-horizontal .checkbox {
min-height: 27px;
}
.form-horizontal .form-group {
margin-right: -15px;
margin-left: -15px;
}
@media (min-width: 768px) {
.form-horizontal .control-label {
padding-top: 7px;
margin-bottom: 0;
text-align: right;
}
}
.form-horizontal .has-feedback .form-control-feedback {
right: 15px;
}
@media (min-width: 768px) {
.form-horizontal .form-group-lg .control-label {
padding-top: 11px;
font-size: 18px;
}
}
@media (min-width: 768px) {
.form-horizontal .form-group-sm .control-label {
padding-top: 6px;
font-size: 12px;
}
}
.btn {
display: inline-block;
margin-bottom: 0;
font-weight: normal;
text-align: center;
white-space: nowrap;
vertical-align: middle;
-ms-touch-action: manipulation;
touch-action: manipulation;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.btn:focus,
.btn:active:focus,
.btn.active:focus,
.btn.focus,
.btn:active.focus,
.btn.active.focus {
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
.btn:hover,
.btn:focus,
.btn.focus {
color: #333333;
text-decoration: none;
}
.btn:active,
.btn.active {
background-image: none;
outline: 0;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn.disabled,
.btn[disabled],
fieldset[disabled] .btn {
cursor: not-allowed;
filter: alpha(opacity=65);
opacity: 0.65;
-webkit-box-shadow: none;
box-shadow: none;
}
a.btn.disabled,
fieldset[disabled] a.btn {
pointer-events: none;
}
.btn-default {
color: #333333;
background-color: #ffffff;
border-color: #cccccc;
}
.btn-default:focus,
.btn-default.focus {
color: #333333;
background-color: #e6e6e6;
border-color: #8c8c8c;
}
.btn-default:hover {
color: #333333;
background-color: #e6e6e6;
border-color: #adadad;
}
.btn-default:active,
.btn-default.active,
.open > .dropdown-toggle.btn-default {
color: #333333;
background-color: #e6e6e6;
background-image: none;
border-color: #adadad;
}
.btn-default:active:hover,
.btn-default.active:hover,
.open > .dropdown-toggle.btn-default:hover,
.btn-default:active:focus,
.btn-default.active:focus,
.open > .dropdown-toggle.btn-default:focus,
.btn-default:active.focus,
.btn-default.active.focus,
.open > .dropdown-toggle.btn-default.focus {
color: #333333;
background-color: #d4d4d4;
border-color: #8c8c8c;
}
.btn-default.disabled:hover,
.btn-default[disabled]:hover,
fieldset[disabled] .btn-default:hover,
.btn-default.disabled:focus,
.btn-default[disabled]:focus,
fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus {
background-color: #ffffff;
border-color: #cccccc;
}
.btn-default .badge {
color: #ffffff;
background-color: #333333;
}
.btn-primary {
color: #ffffff;
background-color: #428bca;
border-color: #357ebd;
}
.btn-primary:focus,
.btn-primary.focus {
color: #ffffff;
background-color: #3071a9;
border-color: #193c5a;
}
.btn-primary:hover {
color: #ffffff;
background-color: #3071a9;
border-color: #285e8e;
}
.btn-primary:active,
.btn-primary.active,
.open > .dropdown-toggle.btn-primary {
color: #ffffff;
background-color: #3071a9;
background-image: none;
border-color: #285e8e;
}
.btn-primary:active:hover,
.btn-primary.active:hover,
.open > .dropdown-toggle.btn-primary:hover,
.btn-primary:active:focus,
.btn-primary.active:focus,
.open > .dropdown-toggle.btn-primary:focus,
.btn-primary:active.focus,
.btn-primary.active.focus,
.open > .dropdown-toggle.btn-primary.focus {
color: #ffffff;
background-color: #285e8e;
border-color: #193c5a;
}
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
.btn-primary.disabled:focus,
.btn-primary[disabled]:focus,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus {
background-color: #428bca;
border-color: #357ebd;
}
.btn-primary .badge {
color: #428bca;
background-color: #ffffff;
}
.btn-success {
color: #ffffff;
background-color: #5cb85c;
border-color: #4cae4c;
}
.btn-success:focus,
.btn-success.focus {
color: #ffffff;
background-color: #449d44;
border-color: #255625;
}
.btn-success:hover {
color: #ffffff;
background-color: #449d44;
border-color: #398439;
}
.btn-success:active,
.btn-success.active,
.open > .dropdown-toggle.btn-success {
color: #ffffff;
background-color: #449d44;
background-image: none;
border-color: #398439;
}
.btn-success:active:hover,
.btn-success.active:hover,
.open > .dropdown-toggle.btn-success:hover,
.btn-success:active:focus,
.btn-success.active:focus,
.open > .dropdown-toggle.btn-success:focus,
.btn-success:active.focus,
.btn-success.active.focus,
.open > .dropdown-toggle.btn-success.focus {
color: #ffffff;
background-color: #398439;
border-color: #255625;
}
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
.btn-success.disabled:focus,
.btn-success[disabled]:focus,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus {
background-color: #5cb85c;
border-color: #4cae4c;
}
.btn-success .badge {
color: #5cb85c;
background-color: #ffffff;
}
.btn-info {
color: #ffffff;
background-color: #5bc0de;
border-color: #46b8da;
}
.btn-info:focus,
.btn-info.focus {
color: #ffffff;
background-color: #31b0d5;
border-color: #1b6d85;
}
.btn-info:hover {
color: #ffffff;
background-color: #31b0d5;
border-color: #269abc;
}
.btn-info:active,
.btn-info.active,
.open > .dropdown-toggle.btn-info {
color: #ffffff;
background-color: #31b0d5;
background-image: none;
border-color: #269abc;
}
.btn-info:active:hover,
.btn-info.active:hover,
.open > .dropdown-toggle.btn-info:hover,
.btn-info:active:focus,
.btn-info.active:focus,
.open > .dropdown-toggle.btn-info:focus,
.btn-info:active.focus,
.btn-info.active.focus,
.open > .dropdown-toggle.btn-info.focus {
color: #ffffff;
background-color: #269abc;
border-color: #1b6d85;
}
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
.btn-info.disabled:focus,
.btn-info[disabled]:focus,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus {
background-color: #5bc0de;
border-color: #46b8da;
}
.btn-info .badge {
color: #5bc0de;
background-color: #ffffff;
}
.btn-warning {
color: #ffffff;
background-color: #f0ad4e;
border-color: #eea236;
}
.btn-warning:focus,
.btn-warning.focus {
color: #ffffff;
background-color: #ec971f;
border-color: #985f0d;
}
.btn-warning:hover {
color: #ffffff;
background-color: #ec971f;
border-color: #d58512;
}
.btn-warning:active,
.btn-warning.active,
.open > .dropdown-toggle.btn-warning {
color: #ffffff;
background-color: #ec971f;
background-image: none;
border-color: #d58512;
}
.btn-warning:active:hover,
.btn-warning.active:hover,
.open > .dropdown-toggle.btn-warning:hover,
.btn-warning:active:focus,
.btn-warning.active:focus,
.open > .dropdown-toggle.btn-warning:focus,
.btn-warning:active.focus,
.btn-warning.active.focus,
.open > .dropdown-toggle.btn-warning.focus {
color: #ffffff;
background-color: #d58512;
border-color: #985f0d;
}
.btn-warning.disabled:hover,
.btn-warning[disabled]:hover,
fieldset[disabled] .btn-warning:hover,
.btn-warning.disabled:focus,
.btn-warning[disabled]:focus,
fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus {
background-color: #f0ad4e;
border-color: #eea236;
}
.btn-warning .badge {
color: #f0ad4e;
background-color: #ffffff;
}
.btn-danger {
color: #ffffff;
background-color: #d9534f;
border-color: #d43f3a;
}
.btn-danger:focus,
.btn-danger.focus {
color: #ffffff;
background-color: #c9302c;
border-color: #761c19;
}
.btn-danger:hover {
color: #ffffff;
background-color: #c9302c;
border-color: #ac2925;
}
.btn-danger:active,
.btn-danger.active,
.open > .dropdown-toggle.btn-danger {
color: #ffffff;
background-color: #c9302c;
background-image: none;
border-color: #ac2925;
}
.btn-danger:active:hover,
.btn-danger.active:hover,
.open > .dropdown-toggle.btn-danger:hover,
.btn-danger:active:focus,
.btn-danger.active:focus,
.open > .dropdown-toggle.btn-danger:focus,
.btn-danger:active.focus,
.btn-danger.active.focus,
.open > .dropdown-toggle.btn-danger.focus {
color: #ffffff;
background-color: #ac2925;
border-color: #761c19;
}
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
.btn-danger.disabled:focus,
.btn-danger[disabled]:focus,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus {
background-color: #d9534f;
border-color: #d43f3a;
}
.btn-danger .badge {
color: #d9534f;
background-color: #ffffff;
}
.btn-link {
font-weight: 400;
color: #428bca;
border-radius: 0;
}
.btn-link,
.btn-link:active,
.btn-link.active,
.btn-link[disabled],
fieldset[disabled] .btn-link {
background-color: transparent;
-webkit-box-shadow: none;
box-shadow: none;
}
.btn-link,
.btn-link:hover,
.btn-link:focus,
.btn-link:active {
border-color: transparent;
}
.btn-link:hover,
.btn-link:focus {
color: #2a6496;
text-decoration: underline;
background-color: transparent;
}
.btn-link[disabled]:hover,
fieldset[disabled] .btn-link:hover,
.btn-link[disabled]:focus,
fieldset[disabled] .btn-link:focus {
color: #777777;
text-decoration: none;
}
.btn-lg,
.btn-group-lg > .btn {
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
border-radius: 6px;
}
.btn-sm,
.btn-group-sm > .btn {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
.btn-xs,
.btn-group-xs > .btn {
padding: 1px 5px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
.btn-block {
display: block;
width: 100%;
}
.btn-block + .btn-block {
margin-top: 5px;
}
input[type="submit"].btn-block,
input[type="reset"].btn-block,
input[type="button"].btn-block {
width: 100%;
}
.fade {
opacity: 0;
-webkit-transition: opacity 0.15s linear;
-o-transition: opacity 0.15s linear;
transition: opacity 0.15s linear;
}
.fade.in {
opacity: 1;
}
.collapse {
display: none;
}
.collapse.in {
display: block;
}
tr.collapse.in {
display: table-row;
}
tbody.collapse.in {
display: table-row-group;
}
.collapsing {
position: relative;
height: 0;
overflow: hidden;
-webkit-transition-property: height, visibility;
-o-transition-property: height, visibility;
transition-property: height, visibility;
-webkit-transition-duration: 0.35s;
-o-transition-duration: 0.35s;
transition-duration: 0.35s;
-webkit-transition-timing-function: ease;
-o-transition-timing-function: ease;
transition-timing-function: ease;
}
.btn-group,
.btn-group-vertical {
position: relative;
display: inline-block;
vertical-align: middle;
}
.btn-group > .btn,
.btn-group-vertical > .btn {
position: relative;
float: left;
}
.btn-group > .btn:hover,
.btn-group-vertical > .btn:hover,
.btn-group > .btn:focus,
.btn-group-vertical > .btn:focus,
.btn-group > .btn:active,
.btn-group-vertical > .btn:active,
.btn-group > .btn.active,
.btn-group-vertical > .btn.active {
z-index: 2;
}
.btn-group .btn + .btn,
.btn-group .btn + .btn-group,
.btn-group .btn-group + .btn,
.btn-group .btn-group + .btn-group {
margin-left: -1px;
}
.btn-toolbar {
margin-left: -5px;
}
.btn-toolbar .btn,
.btn-toolbar .btn-group,
.btn-toolbar .input-group {
float: left;
}
.btn-toolbar > .btn,
.btn-toolbar > .btn-group,
.btn-toolbar > .input-group {
margin-left: 5px;
}
.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
border-radius: 0;
}
.btn-group > .btn:first-child {
margin-left: 0;
}
.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.btn-group > .btn:last-child:not(:first-child),
.btn-group > .dropdown-toggle:not(:first-child) {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.btn-group > .btn-group {
float: left;
}
.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0;
}
.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,
.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.btn-group .dropdown-toggle:active,
.btn-group.open .dropdown-toggle {
outline: 0;
}
.btn-group > .btn + .dropdown-toggle {
padding-right: 8px;
padding-left: 8px;
}
.btn-group > .btn-lg + .dropdown-toggle {
padding-right: 12px;
padding-left: 12px;
}
.btn-group.open .dropdown-toggle {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-group.open .dropdown-toggle.btn-link {
-webkit-box-shadow: none;
box-shadow: none;
}
.btn .caret {
margin-left: 0;
}
.btn-lg .caret {
border-width: 5px 5px 0;
border-bottom-width: 0;
}
.dropup .btn-lg .caret {
border-width: 0 5px 5px;
}
.btn-group-vertical > .btn,
.btn-group-vertical > .btn-group,
.btn-group-vertical > .btn-group > .btn {
display: block;
float: none;
width: 100%;
max-width: 100%;
}
.btn-group-vertical > .btn-group > .btn {
float: none;
}
.btn-group-vertical > .btn + .btn,
.btn-group-vertical > .btn + .btn-group,
.btn-group-vertical > .btn-group + .btn,
.btn-group-vertical > .btn-group + .btn-group {
margin-top: -1px;
margin-left: 0;
}
.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
border-radius: 0;
}
.btn-group-vertical > .btn:first-child:not(:last-child) {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.btn-group-vertical > .btn:last-child:not(:first-child) {
border-top-left-radius: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
border-radius: 0;
}
.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,
.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.btn-group-justified {
display: table;
width: 100%;
table-layout: fixed;
border-collapse: separate;
}
.btn-group-justified > .btn,
.btn-group-justified > .btn-group {
display: table-cell;
float: none;
width: 1%;
}
.btn-group-justified > .btn-group .btn {
width: 100%;
}
.btn-group-justified > .btn-group .dropdown-menu {
left: auto;
}
[data-toggle="buttons"] > .btn input[type="radio"],
[data-toggle="buttons"] > .btn-group > .btn input[type="radio"],
[data-toggle="buttons"] > .btn input[type="checkbox"],
[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
}
.input-group {
position: relative;
display: table;
border-collapse: separate;
}
.input-group[class*="col-"] {
float: none;
padding-right: 0;
padding-left: 0;
}
.input-group .form-control {
position: relative;
z-index: 2;
float: left;
width: 100%;
margin-bottom: 0;
}
.input-group .form-control:focus {
z-index: 3;
}
.input-group-lg > .form-control,
.input-group-lg > .input-group-addon,
.input-group-lg > .input-group-btn > .btn {
height: 46px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
border-radius: 6px;
}
select.input-group-lg > .form-control,
select.input-group-lg > .input-group-addon,
select.input-group-lg > .input-group-btn > .btn {
height: 46px;
line-height: 46px;
}
textarea.input-group-lg > .form-control,
textarea.input-group-lg > .input-group-addon,
textarea.input-group-lg > .input-group-btn > .btn,
select[multiple].input-group-lg > .form-control,
select[multiple].input-group-lg > .input-group-addon,
select[multiple].input-group-lg > .input-group-btn > .btn {
height: auto;
}
.input-group-sm > .form-control,
.input-group-sm > .input-group-addon,
.input-group-sm > .input-group-btn > .btn {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
select.input-group-sm > .form-control,
select.input-group-sm > .input-group-addon,
select.input-group-sm > .input-group-btn > .btn {
height: 30px;
line-height: 30px;
}
textarea.input-group-sm > .form-control,
textarea.input-group-sm > .input-group-addon,
textarea.input-group-sm > .input-group-btn > .btn,
select[multiple].input-group-sm > .form-control,
select[multiple].input-group-sm > .input-group-addon,
select[multiple].input-group-sm > .input-group-btn > .btn {
height: auto;
}
.input-group-addon,
.input-group-btn,
.input-group .form-control {
display: table-cell;
}
.input-group-addon:not(:first-child):not(:last-child),
.input-group-btn:not(:first-child):not(:last-child),
.input-group .form-control:not(:first-child):not(:last-child) {
border-radius: 0;
}
.input-group-addon,
.input-group-btn {
width: 1%;
white-space: nowrap;
vertical-align: middle;
}
.input-group-addon {
padding: 6px 12px;
font-size: 14px;
font-weight: 400;
line-height: 1;
color: #555555;
text-align: center;
background-color: #eeeeee;
border: 1px solid #cccccc;
border-radius: 4px;
}
.input-group-addon.input-sm {
padding: 5px 10px;
font-size: 12px;
border-radius: 3px;
}
.input-group-addon.input-lg {
padding: 10px 16px;
font-size: 18px;
border-radius: 6px;
}
.input-group-addon input[type="radio"],
.input-group-addon input[type="checkbox"] {
margin-top: 0;
}
.input-group .form-control:first-child,
.input-group-addon:first-child,
.input-group-btn:first-child > .btn,
.input-group-btn:first-child > .btn-group > .btn,
.input-group-btn:first-child > .dropdown-toggle,
.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),
.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.input-group-addon:first-child {
border-right: 0;
}
.input-group .form-control:last-child,
.input-group-addon:last-child,
.input-group-btn:last-child > .btn,
.input-group-btn:last-child > .btn-group > .btn,
.input-group-btn:last-child > .dropdown-toggle,
.input-group-btn:first-child > .btn:not(:first-child),
.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.input-group-addon:last-child {
border-left: 0;
}
.input-group-btn {
position: relative;
font-size: 0;
white-space: nowrap;
}
.input-group-btn > .btn {
position: relative;
}
.input-group-btn > .btn + .btn {
margin-left: -1px;
}
.input-group-btn > .btn:hover,
.input-group-btn > .btn:focus,
.input-group-btn > .btn:active {
z-index: 2;
}
.input-group-btn:first-child > .btn,
.input-group-btn:first-child > .btn-group {
margin-right: -1px;
}
.input-group-btn:last-child > .btn,
.input-group-btn:last-child > .btn-group {
z-index: 2;
margin-left: -1px;
}
.nav {
padding-left: 0;
margin-bottom: 0;
list-style: none;
}
.nav > li {
position: relative;
display: block;
}
.nav > li > a {
position: relative;
display: block;
padding: 10px 15px;
}
.nav > li > a:hover,
.nav > li > a:focus {
text-decoration: none;
background-color: #eeeeee;
}
.nav > li.disabled > a {
color: #777777;
}
.nav > li.disabled > a:hover,
.nav > li.disabled > a:focus {
color: #777777;
text-decoration: none;
cursor: not-allowed;
background-color: transparent;
}
.nav .open > a,
.nav .open > a:hover,
.nav .open > a:focus {
background-color: #eeeeee;
border-color: #428bca;
}
.nav .nav-divider {
height: 1px;
margin: 9px 0;
overflow: hidden;
background-color: #e5e5e5;
}
.nav > li > a > img {
max-width: none;
}
.nav-tabs {
border-bottom: 1px solid #dddddd;
}
.nav-tabs > li {
float: left;
margin-bottom: -1px;
}
.nav-tabs > li > a {
margin-right: 2px;
line-height: 1.42857143;
border: 1px solid transparent;
border-radius: 4px 4px 0 0;
}
.nav-tabs > li > a:hover {
border-color: #eeeeee #eeeeee #dddddd;
}
.nav-tabs > li.active > a,
.nav-tabs > li.active > a:hover,
.nav-tabs > li.active > a:focus {
color: #555555;
cursor: default;
background-color: #ffffff;
border: 1px solid #dddddd;
border-bottom-color: transparent;
}
.nav-tabs.nav-justified {
width: 100%;
border-bottom: 0;
}
.nav-tabs.nav-justified > li {
float: none;
}
.nav-tabs.nav-justified > li > a {
margin-bottom: 5px;
text-align: center;
}
.nav-tabs.nav-justified > .dropdown .dropdown-menu {
top: auto;
left: auto;
}
@media (min-width: 768px) {
.nav-tabs.nav-justified > li {
display: table-cell;
width: 1%;
}
.nav-tabs.nav-justified > li > a {
margin-bottom: 0;
}
}
.nav-tabs.nav-justified > li > a {
margin-right: 0;
border-radius: 4px;
}
.nav-tabs.nav-justified > .active > a,
.nav-tabs.nav-justified > .active > a:hover,
.nav-tabs.nav-justified > .active > a:focus {
border: 1px solid #dddddd;
}
@media (min-width: 768px) {
.nav-tabs.nav-justified > li > a {
border-bottom: 1px solid #dddddd;
border-radius: 4px 4px 0 0;
}
.nav-tabs.nav-justified > .active > a,
.nav-tabs.nav-justified > .active > a:hover,
.nav-tabs.nav-justified > .active > a:focus {
border-bottom-color: #ffffff;
}
}
.nav-pills > li {
float: left;
}
.nav-pills > li > a {
border-radius: 4px;
}
.nav-pills > li + li {
margin-left: 2px;
}
.nav-pills > li.active > a,
.nav-pills > li.active > a:hover,
.nav-pills > li.active > a:focus {
color: #ffffff;
background-color: #428bca;
}
.nav-stacked > li {
float: none;
}
.nav-stacked > li + li {
margin-top: 2px;
margin-left: 0;
}
.nav-justified {
width: 100%;
}
.nav-justified > li {
float: none;
}
.nav-justified > li > a {
margin-bottom: 5px;
text-align: center;
}
.nav-justified > .dropdown .dropdown-menu {
top: auto;
left: auto;
}
@media (min-width: 768px) {
.nav-justified > li {
display: table-cell;
width: 1%;
}
.nav-justified > li > a {
margin-bottom: 0;
}
}
.nav-tabs-justified {
border-bottom: 0;
}
.nav-tabs-justified > li > a {
margin-right: 0;
border-radius: 4px;
}
.nav-tabs-justified > .active > a,
.nav-tabs-justified > .active > a:hover,
.nav-tabs-justified > .active > a:focus {
border: 1px solid #dddddd;
}
@media (min-width: 768px) {
.nav-tabs-justified > li > a {
border-bottom: 1px solid #dddddd;
border-radius: 4px 4px 0 0;
}
.nav-tabs-justified > .active > a,
.nav-tabs-justified > .active > a:hover,
.nav-tabs-justified > .active > a:focus {
border-bottom-color: #ffffff;
}
}
.tab-content > .tab-pane {
display: none;
}
.tab-content > .active {
display: block;
}
.nav-tabs .dropdown-menu {
margin-top: -1px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.navbar {
position: relative;
min-height: 50px;
margin-bottom: 20px;
border: 1px solid transparent;
}
@media (min-width: 768px) {
.navbar {
border-radius: 4px;
}
}
@media (min-width: 768px) {
.navbar-header {
float: left;
}
}
.navbar-collapse {
padding-right: 15px;
padding-left: 15px;
overflow-x: visible;
border-top: 1px solid transparent;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
-webkit-overflow-scrolling: touch;
}
.navbar-collapse.in {
overflow-y: auto;
}
@media (min-width: 768px) {
.navbar-collapse {
width: auto;
border-top: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.navbar-collapse.collapse {
display: block !important;
height: auto !important;
padding-bottom: 0;
overflow: visible !important;
}
.navbar-collapse.in {
overflow-y: visible;
}
.navbar-fixed-top .navbar-collapse,
.navbar-static-top .navbar-collapse,
.navbar-fixed-bottom .navbar-collapse {
padding-right: 0;
padding-left: 0;
}
}
.navbar-fixed-top,
.navbar-fixed-bottom {
position: fixed;
right: 0;
left: 0;
z-index: 1030;
}
.navbar-fixed-top .navbar-collapse,
.navbar-fixed-bottom .navbar-collapse {
max-height: 340px;
}
@media (max-device-width: 480px) and (orientation: landscape) {
.navbar-fixed-top .navbar-collapse,
.navbar-fixed-bottom .navbar-collapse {
max-height: 200px;
}
}
@media (min-width: 768px) {
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
}
.navbar-fixed-top {
top: 0;
border-width: 0 0 1px;
}
.navbar-fixed-bottom {
bottom: 0;
margin-bottom: 0;
border-width: 1px 0 0;
}
.container > .navbar-header,
.container-fluid > .navbar-header,
.container > .navbar-collapse,
.container-fluid > .navbar-collapse {
margin-right: -15px;
margin-left: -15px;
}
@media (min-width: 768px) {
.container > .navbar-header,
.container-fluid > .navbar-header,
.container > .navbar-collapse,
.container-fluid > .navbar-collapse {
margin-right: 0;
margin-left: 0;
}
}
.navbar-static-top {
z-index: 1000;
border-width: 0 0 1px;
}
@media (min-width: 768px) {
.navbar-static-top {
border-radius: 0;
}
}
.navbar-brand {
float: left;
height: 50px;
padding: 15px 15px;
font-size: 18px;
line-height: 20px;
}
.navbar-brand:hover,
.navbar-brand:focus {
text-decoration: none;
}
.navbar-brand > img {
display: block;
}
@media (min-width: 768px) {
.navbar > .container .navbar-brand,
.navbar > .container-fluid .navbar-brand {
margin-left: -15px;
}
}
.navbar-toggle {
position: relative;
float: right;
padding: 9px 10px;
margin-right: 15px;
margin-top: 8px;
margin-bottom: 8px;
background-color: transparent;
background-image: none;
border: 1px solid transparent;
border-radius: 4px;
}
.navbar-toggle:focus {
outline: 0;
}
.navbar-toggle .icon-bar {
display: block;
width: 22px;
height: 2px;
border-radius: 1px;
}
.navbar-toggle .icon-bar + .icon-bar {
margin-top: 4px;
}
@media (min-width: 768px) {
.navbar-toggle {
display: none;
}
}
.navbar-nav {
margin: 7.5px -15px;
}
.navbar-nav > li > a {
padding-top: 10px;
padding-bottom: 10px;
line-height: 20px;
}
@media (max-width: 767px) {
.navbar-nav .open .dropdown-menu {
position: static;
float: none;
width: auto;
margin-top: 0;
background-color: transparent;
border: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
.navbar-nav .open .dropdown-menu > li > a,
.navbar-nav .open .dropdown-menu .dropdown-header {
padding: 5px 15px 5px 25px;
}
.navbar-nav .open .dropdown-menu > li > a {
line-height: 20px;
}
.navbar-nav .open .dropdown-menu > li > a:hover,
.navbar-nav .open .dropdown-menu > li > a:focus {
background-image: none;
}
}
@media (min-width: 768px) {
.navbar-nav {
float: left;
margin: 0;
}
.navbar-nav > li {
float: left;
}
.navbar-nav > li > a {
padding-top: 15px;
padding-bottom: 15px;
}
}
.navbar-form {
padding: 10px 15px;
margin-right: -15px;
margin-left: -15px;
border-top: 1px solid transparent;
border-bottom: 1px solid transparent;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
margin-top: 8px;
margin-bottom: 8px;
}
@media (min-width: 768px) {
.navbar-form .form-group {
display: inline-block;
margin-bottom: 0;
vertical-align: middle;
}
.navbar-form .form-control {
display: inline-block;
width: auto;
vertical-align: middle;
}
.navbar-form .form-control-static {
display: inline-block;
}
.navbar-form .input-group {
display: inline-table;
vertical-align: middle;
}
.navbar-form .input-group .input-group-addon,
.navbar-form .input-group .input-group-btn,
.navbar-form .input-group .form-control {
width: auto;
}
.navbar-form .input-group > .form-control {
width: 100%;
}
.navbar-form .control-label {
margin-bottom: 0;
vertical-align: middle;
}
.navbar-form .radio,
.navbar-form .checkbox {
display: inline-block;
margin-top: 0;
margin-bottom: 0;
vertical-align: middle;
}
.navbar-form .radio label,
.navbar-form .checkbox label {
padding-left: 0;
}
.navbar-form .radio input[type="radio"],
.navbar-form .checkbox input[type="checkbox"] {
position: relative;
margin-left: 0;
}
.navbar-form .has-feedback .form-control-feedback {
top: 0;
}
}
@media (max-width: 767px) {
.navbar-form .form-group {
margin-bottom: 5px;
}
.navbar-form .form-group:last-child {
margin-bottom: 0;
}
}
@media (min-width: 768px) {
.navbar-form {
width: auto;
padding-top: 0;
padding-bottom: 0;
margin-right: 0;
margin-left: 0;
border: 0;
-webkit-box-shadow: none;
box-shadow: none;
}
}
.navbar-nav > li > .dropdown-menu {
margin-top: 0;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
margin-bottom: 0;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.navbar-btn {
margin-top: 8px;
margin-bottom: 8px;
}
.navbar-btn.btn-sm {
margin-top: 10px;
margin-bottom: 10px;
}
.navbar-btn.btn-xs {
margin-top: 14px;
margin-bottom: 14px;
}
.navbar-text {
margin-top: 15px;
margin-bottom: 15px;
}
@media (min-width: 768px) {
.navbar-text {
float: left;
margin-right: 15px;
margin-left: 15px;
}
}
@media (min-width: 768px) {
.navbar-left {
float: left !important;
}
.navbar-right {
float: right !important;
margin-right: -15px;
}
.navbar-right ~ .navbar-right {
margin-right: 0;
}
}
.navbar-default {
background-color: #f8f8f8;
border-color: #e7e7e7;
}
.navbar-default .navbar-brand {
color: #777777;
}
.navbar-default .navbar-brand:hover,
.navbar-default .navbar-brand:focus {
color: #5e5e5e;
background-color: transparent;
}
.navbar-default .navbar-text {
color: #777777;
}
.navbar-default .navbar-nav > li > a {
color: #777777;
}
.navbar-default .navbar-nav > li > a:hover,
.navbar-default .navbar-nav > li > a:focus {
color: #333333;
background-color: transparent;
}
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
color: #555555;
background-color: #e7e7e7;
}
.navbar-default .navbar-nav > .disabled > a,
.navbar-default .navbar-nav > .disabled > a:hover,
.navbar-default .navbar-nav > .disabled > a:focus {
color: #cccccc;
background-color: transparent;
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .open > a:hover,
.navbar-default .navbar-nav > .open > a:focus {
color: #555555;
background-color: #e7e7e7;
}
@media (max-width: 767px) {
.navbar-default .navbar-nav .open .dropdown-menu > li > a {
color: #777777;
}
.navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
.navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
color: #333333;
background-color: transparent;
}
.navbar-default .navbar-nav .open .dropdown-menu > .active > a,
.navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #555555;
background-color: #e7e7e7;
}
.navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
.navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
.navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
color: #cccccc;
background-color: transparent;
}
}
.navbar-default .navbar-toggle {
border-color: #dddddd;
}
.navbar-default .navbar-toggle:hover,
.navbar-default .navbar-toggle:focus {
background-color: #dddddd;
}
.navbar-default .navbar-toggle .icon-bar {
background-color: #888888;
}
.navbar-default .navbar-collapse,
.navbar-default .navbar-form {
border-color: #e7e7e7;
}
.navbar-default .navbar-link {
color: #777777;
}
.navbar-default .navbar-link:hover {
color: #333333;
}
.navbar-default .btn-link {
color: #777777;
}
.navbar-default .btn-link:hover,
.navbar-default .btn-link:focus {
color: #333333;
}
.navbar-default .btn-link[disabled]:hover,
fieldset[disabled] .navbar-default .btn-link:hover,
.navbar-default .btn-link[disabled]:focus,
fieldset[disabled] .navbar-default .btn-link:focus {
color: #cccccc;
}
.navbar-inverse {
background-color: #222222;
border-color: #080808;
}
.navbar-inverse .navbar-brand {
color: #777777;
}
.navbar-inverse .navbar-brand:hover,
.navbar-inverse .navbar-brand:focus {
color: #ffffff;
background-color: transparent;
}
.navbar-inverse .navbar-text {
color: #777777;
}
.navbar-inverse .navbar-nav > li > a {
color: #777777;
}
.navbar-inverse .navbar-nav > li > a:hover,
.navbar-inverse .navbar-nav > li > a:focus {
color: #ffffff;
background-color: transparent;
}
.navbar-inverse .navbar-nav > .active > a,
.navbar-inverse .navbar-nav > .active > a:hover,
.navbar-inverse .navbar-nav > .active > a:focus {
color: #ffffff;
background-color: #080808;
}
.navbar-inverse .navbar-nav > .disabled > a,
.navbar-inverse .navbar-nav > .disabled > a:hover,
.navbar-inverse .navbar-nav > .disabled > a:focus {
color: #444444;
background-color: transparent;
}
.navbar-inverse .navbar-nav > .open > a,
.navbar-inverse .navbar-nav > .open > a:hover,
.navbar-inverse .navbar-nav > .open > a:focus {
color: #ffffff;
background-color: #080808;
}
@media (max-width: 767px) {
.navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
border-color: #080808;
}
.navbar-inverse .navbar-nav .open .dropdown-menu .divider {
background-color: #080808;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
color: #777777;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
color: #ffffff;
background-color: transparent;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #ffffff;
background-color: #080808;
}
.navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
.navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
.navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
color: #444444;
background-color: transparent;
}
}
.navbar-inverse .navbar-toggle {
border-color: #333333;
}
.navbar-inverse .navbar-toggle:hover,
.navbar-inverse .navbar-toggle:focus {
background-color: #333333;
}
.navbar-inverse .navbar-toggle .icon-bar {
background-color: #ffffff;
}
.navbar-inverse .navbar-collapse,
.navbar-inverse .navbar-form {
border-color: #101010;
}
.navbar-inverse .navbar-link {
color: #777777;
}
.navbar-inverse .navbar-link:hover {
color: #ffffff;
}
.navbar-inverse .btn-link {
color: #777777;
}
.navbar-inverse .btn-link:hover,
.navbar-inverse .btn-link:focus {
color: #ffffff;
}
.navbar-inverse .btn-link[disabled]:hover,
fieldset[disabled] .navbar-inverse .btn-link:hover,
.navbar-inverse .btn-link[disabled]:focus,
fieldset[disabled] .navbar-inverse .btn-link:focus {
color: #444444;
}
.breadcrumb {
padding: 8px 15px;
margin-bottom: 20px;
list-style: none;
background-color: #f5f5f5;
border-radius: 4px;
}
.breadcrumb > li {
display: inline-block;
}
.breadcrumb > li + li:before {
padding: 0 5px;
color: #cccccc;
content: "/\00a0";
}
.breadcrumb > .active {
color: #777777;
}
.pagination {
display: inline-block;
padding-left: 0;
margin: 20px 0;
border-radius: 4px;
}
.pagination > li {
display: inline;
}
.pagination > li > a,
.pagination > li > span {
position: relative;
float: left;
padding: 6px 12px;
margin-left: -1px;
line-height: 1.42857143;
color: #428bca;
text-decoration: none;
background-color: #ffffff;
border: 1px solid #dddddd;
}
.pagination > li > a:hover,
.pagination > li > span:hover,
.pagination > li > a:focus,
.pagination > li > span:focus {
z-index: 2;
color: #2a6496;
background-color: #eeeeee;
border-color: #dddddd;
}
.pagination > li:first-child > a,
.pagination > li:first-child > span {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.pagination > li:last-child > a,
.pagination > li:last-child > span {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.pagination > .active > a,
.pagination > .active > span,
.pagination > .active > a:hover,
.pagination > .active > span:hover,
.pagination > .active > a:focus,
.pagination > .active > span:focus {
z-index: 3;
color: #ffffff;
cursor: default;
background-color: #428bca;
border-color: #428bca;
}
.pagination > .disabled > span,
.pagination > .disabled > span:hover,
.pagination > .disabled > span:focus,
.pagination > .disabled > a,
.pagination > .disabled > a:hover,
.pagination > .disabled > a:focus {
color: #777777;
cursor: not-allowed;
background-color: #ffffff;
border-color: #dddddd;
}
.pagination-lg > li > a,
.pagination-lg > li > span {
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
}
.pagination-lg > li:first-child > a,
.pagination-lg > li:first-child > span {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
.pagination-lg > li:last-child > a,
.pagination-lg > li:last-child > span {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
.pagination-sm > li > a,
.pagination-sm > li > span {
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
}
.pagination-sm > li:first-child > a,
.pagination-sm > li:first-child > span {
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
}
.pagination-sm > li:last-child > a,
.pagination-sm > li:last-child > span {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
.label {
display: inline;
padding: .2em .6em .3em;
font-size: 75%;
font-weight: 700;
line-height: 1;
color: #ffffff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25em;
}
a.label:hover,
a.label:focus {
color: #ffffff;
text-decoration: none;
cursor: pointer;
}
.label:empty {
display: none;
}
.btn .label {
position: relative;
top: -1px;
}
.label-default {
background-color: #777777;
}
.label-default[href]:hover,
.label-default[href]:focus {
background-color: #5e5e5e;
}
.label-primary {
background-color: #428bca;
}
.label-primary[href]:hover,
.label-primary[href]:focus {
background-color: #3071a9;
}
.label-success {
background-color: #5cb85c;
}
.label-success[href]:hover,
.label-success[href]:focus {
background-color: #449d44;
}
.label-info {
background-color: #5bc0de;
}
.label-info[href]:hover,
.label-info[href]:focus {
background-color: #31b0d5;
}
.label-warning {
background-color: #f0ad4e;
}
.label-warning[href]:hover,
.label-warning[href]:focus {
background-color: #ec971f;
}
.label-danger {
background-color: #d9534f;
}
.label-danger[href]:hover,
.label-danger[href]:focus {
background-color: #c9302c;
}
.badge {
display: inline-block;
min-width: 10px;
padding: 3px 7px;
font-size: 12px;
font-weight: bold;
line-height: 1;
color: #ffffff;
text-align: center;
white-space: nowrap;
vertical-align: middle;
background-color: #777777;
border-radius: 10px;
}
.badge:empty {
display: none;
}
.btn .badge {
position: relative;
top: -1px;
}
.btn-xs .badge,
.btn-group-xs > .btn .badge {
top: 0;
padding: 1px 5px;
}
a.badge:hover,
a.badge:focus {
color: #ffffff;
text-decoration: none;
cursor: pointer;
}
.list-group-item.active > .badge,
.nav-pills > .active > a > .badge {
color: #428bca;
background-color: #ffffff;
}
.list-group-item > .badge {
float: right;
}
.list-group-item > .badge + .badge {
margin-right: 5px;
}
.nav-pills > li > a > .badge {
margin-left: 3px;
}
.list-group {
padding-left: 0;
margin-bottom: 20px;
}
.list-group-item {
position: relative;
display: block;
padding: 10px 15px;
margin-bottom: -1px;
background-color: #ffffff;
border: 1px solid #dddddd;
}
.list-group-item:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.list-group-item:last-child {
margin-bottom: 0;
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
.list-group-item.disabled,
.list-group-item.disabled:hover,
.list-group-item.disabled:focus {
color: #777777;
cursor: not-allowed;
background-color: #eeeeee;
}
.list-group-item.disabled .list-group-item-heading,
.list-group-item.disabled:hover .list-group-item-heading,
.list-group-item.disabled:focus .list-group-item-heading {
color: inherit;
}
.list-group-item.disabled .list-group-item-text,
.list-group-item.disabled:hover .list-group-item-text,
.list-group-item.disabled:focus .list-group-item-text {
color: #777777;
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
z-index: 2;
color: #ffffff;
background-color: #428bca;
border-color: #428bca;
}
.list-group-item.active .list-group-item-heading,
.list-group-item.active:hover .list-group-item-heading,
.list-group-item.active:focus .list-group-item-heading,
.list-group-item.active .list-group-item-heading > small,
.list-group-item.active:hover .list-group-item-heading > small,
.list-group-item.active:focus .list-group-item-heading > small,
.list-group-item.active .list-group-item-heading > .small,
.list-group-item.active:hover .list-group-item-heading > .small,
.list-group-item.active:focus .list-group-item-heading > .small {
color: inherit;
}
.list-group-item.active .list-group-item-text,
.list-group-item.active:hover .list-group-item-text,
.list-group-item.active:focus .list-group-item-text {
color: #e1edf7;
}
a.list-group-item,
button.list-group-item {
color: #555555;
}
a.list-group-item .list-group-item-heading,
button.list-group-item .list-group-item-heading {
color: #333333;
}
a.list-group-item:hover,
button.list-group-item:hover,
a.list-group-item:focus,
button.list-group-item:focus {
color: #555555;
text-decoration: none;
background-color: #f5f5f5;
}
button.list-group-item {
width: 100%;
text-align: left;
}
.list-group-item-success {
color: #3c763d;
background-color: #dff0d8;
}
a.list-group-item-success,
button.list-group-item-success {
color: #3c763d;
}
a.list-group-item-success .list-group-item-heading,
button.list-group-item-success .list-group-item-heading {
color: inherit;
}
a.list-group-item-success:hover,
button.list-group-item-success:hover,
a.list-group-item-success:focus,
button.list-group-item-success:focus {
color: #3c763d;
background-color: #d0e9c6;
}
a.list-group-item-success.active,
button.list-group-item-success.active,
a.list-group-item-success.active:hover,
button.list-group-item-success.active:hover,
a.list-group-item-success.active:focus,
button.list-group-item-success.active:focus {
color: #fff;
background-color: #3c763d;
border-color: #3c763d;
}
.list-group-item-info {
color: #31708f;
background-color: #d9edf7;
}
a.list-group-item-info,
button.list-group-item-info {
color: #31708f;
}
a.list-group-item-info .list-group-item-heading,
button.list-group-item-info .list-group-item-heading {
color: inherit;
}
a.list-group-item-info:hover,
button.list-group-item-info:hover,
a.list-group-item-info:focus,
button.list-group-item-info:focus {
color: #31708f;
background-color: #c4e3f3;
}
a.list-group-item-info.active,
button.list-group-item-info.active,
a.list-group-item-info.active:hover,
button.list-group-item-info.active:hover,
a.list-group-item-info.active:focus,
button.list-group-item-info.active:focus {
color: #fff;
background-color: #31708f;
border-color: #31708f;
}
.list-group-item-warning {
color: #8a6d3b;
background-color: #fcf8e3;
}
a.list-group-item-warning,
button.list-group-item-warning {
color: #8a6d3b;
}
a.list-group-item-warning .list-group-item-heading,
button.list-group-item-warning .list-group-item-heading {
color: inherit;
}
a.list-group-item-warning:hover,
button.list-group-item-warning:hover,
a.list-group-item-warning:focus,
button.list-group-item-warning:focus {
color: #8a6d3b;
background-color: #faf2cc;
}
a.list-group-item-warning.active,
button.list-group-item-warning.active,
a.list-group-item-warning.active:hover,
button.list-group-item-warning.active:hover,
a.list-group-item-warning.active:focus,
button.list-group-item-warning.active:focus {
color: #fff;
background-color: #8a6d3b;
border-color: #8a6d3b;
}
.list-group-item-danger {
color: #a94442;
background-color: #f2dede;
}
a.list-group-item-danger,
button.list-group-item-danger {
color: #a94442;
}
a.list-group-item-danger .list-group-item-heading,
button.list-group-item-danger .list-group-item-heading {
color: inherit;
}
a.list-group-item-danger:hover,
button.list-group-item-danger:hover,
a.list-group-item-danger:focus,
button.list-group-item-danger:focus {
color: #a94442;
background-color: #ebcccc;
}
a.list-group-item-danger.active,
button.list-group-item-danger.active,
a.list-group-item-danger.active:hover,
button.list-group-item-danger.active:hover,
a.list-group-item-danger.active:focus,
button.list-group-item-danger.active:focus {
color: #fff;
background-color: #a94442;
border-color: #a94442;
}
.list-group-item-heading {
margin-top: 0;
margin-bottom: 5px;
}
.list-group-item-text {
margin-bottom: 0;
line-height: 1.3;
}
.panel {
margin-bottom: 20px;
background-color: #ffffff;
border: 1px solid transparent;
border-radius: 4px;
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
}
.panel-body {
padding: 15px;
}
.panel-heading {
padding: 10px 15px;
border-bottom: 1px solid transparent;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.panel-heading > .dropdown .dropdown-toggle {
color: inherit;
}
.panel-title {
margin-top: 0;
margin-bottom: 0;
font-size: 16px;
color: inherit;
}
.panel-title > a,
.panel-title > small,
.panel-title > .small,
.panel-title > small > a,
.panel-title > .small > a {
color: inherit;
}
.panel-footer {
padding: 10px 15px;
background-color: #f5f5f5;
border-top: 1px solid #dddddd;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
.panel > .list-group,
.panel > .panel-collapse > .list-group {
margin-bottom: 0;
}
.panel > .list-group .list-group-item,
.panel > .panel-collapse > .list-group .list-group-item {
border-width: 1px 0;
border-radius: 0;
}
.panel > .list-group:first-child .list-group-item:first-child,
.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {
border-top: 0;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.panel > .list-group:last-child .list-group-item:last-child,
.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {
border-bottom: 0;
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
.panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.panel-heading + .list-group .list-group-item:first-child {
border-top-width: 0;
}
.list-group + .panel-footer {
border-top-width: 0;
}
.panel > .table,
.panel > .table-responsive > .table,
.panel > .panel-collapse > .table {
margin-bottom: 0;
}
.panel > .table caption,
.panel > .table-responsive > .table caption,
.panel > .panel-collapse > .table caption {
padding-right: 15px;
padding-left: 15px;
}
.panel > .table:first-child,
.panel > .table-responsive:first-child > .table:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.panel > .table:first-child > thead:first-child > tr:first-child,
.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,
.panel > .table:first-child > tbody:first-child > tr:first-child,
.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,
.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,
.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,
.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,
.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,
.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,
.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,
.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {
border-top-left-radius: 3px;
}
.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,
.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,
.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,
.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,
.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,
.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,
.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,
.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {
border-top-right-radius: 3px;
}
.panel > .table:last-child,
.panel > .table-responsive:last-child > .table:last-child {
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
.panel > .table:last-child > tbody:last-child > tr:last-child,
.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,
.panel > .table:last-child > tfoot:last-child > tr:last-child,
.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {
border-bottom-right-radius: 3px;
border-bottom-left-radius: 3px;
}
.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,
.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,
.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,
.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,
.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,
.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,
.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {
border-bottom-left-radius: 3px;
}
.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,
.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,
.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,
.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,
.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,
.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,
.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {
border-bottom-right-radius: 3px;
}
.panel > .panel-body + .table,
.panel > .panel-body + .table-responsive,
.panel > .table + .panel-body,
.panel > .table-responsive + .panel-body {
border-top: 1px solid #dddddd;
}
.panel > .table > tbody:first-child > tr:first-child th,
.panel > .table > tbody:first-child > tr:first-child td {
border-top: 0;
}
.panel > .table-bordered,
.panel > .table-responsive > .table-bordered {
border: 0;
}
.panel > .table-bordered > thead > tr > th:first-child,
.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,
.panel > .table-bordered > tbody > tr > th:first-child,
.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,
.panel > .table-bordered > tfoot > tr > th:first-child,
.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,
.panel > .table-bordered > thead > tr > td:first-child,
.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,
.panel > .table-bordered > tbody > tr > td:first-child,
.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,
.panel > .table-bordered > tfoot > tr > td:first-child,
.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {
border-left: 0;
}
.panel > .table-bordered > thead > tr > th:last-child,
.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,
.panel > .table-bordered > tbody > tr > th:last-child,
.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,
.panel > .table-bordered > tfoot > tr > th:last-child,
.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,
.panel > .table-bordered > thead > tr > td:last-child,
.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,
.panel > .table-bordered > tbody > tr > td:last-child,
.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,
.panel > .table-bordered > tfoot > tr > td:last-child,
.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {
border-right: 0;
}
.panel > .table-bordered > thead > tr:first-child > td,
.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,
.panel > .table-bordered > tbody > tr:first-child > td,
.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,
.panel > .table-bordered > thead > tr:first-child > th,
.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,
.panel > .table-bordered > tbody > tr:first-child > th,
.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {
border-bottom: 0;
}
.panel > .table-bordered > tbody > tr:last-child > td,
.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,
.panel > .table-bordered > tfoot > tr:last-child > td,
.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,
.panel > .table-bordered > tbody > tr:last-child > th,
.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,
.panel > .table-bordered > tfoot > tr:last-child > th,
.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {
border-bottom: 0;
}
.panel > .table-responsive {
margin-bottom: 0;
border: 0;
}
.panel-group {
margin-bottom: 20px;
}
.panel-group .panel {
margin-bottom: 0;
border-radius: 4px;
}
.panel-group .panel + .panel {
margin-top: 5px;
}
.panel-group .panel-heading {
border-bottom: 0;
}
.panel-group .panel-heading + .panel-collapse > .panel-body,
.panel-group .panel-heading + .panel-collapse > .list-group {
border-top: 1px solid #dddddd;
}
.panel-group .panel-footer {
border-top: 0;
}
.panel-group .panel-footer + .panel-collapse .panel-body {
border-bottom: 1px solid #dddddd;
}
.panel-default {
border-color: #dddddd;
}
.panel-default > .panel-heading {
color: #333333;
background-color: #f5f5f5;
border-color: #dddddd;
}
.panel-default > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #dddddd;
}
.panel-default > .panel-heading .badge {
color: #f5f5f5;
background-color: #333333;
}
.panel-default > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #dddddd;
}
.panel-primary {
border-color: #428bca;
}
.panel-primary > .panel-heading {
color: #ffffff;
background-color: #428bca;
border-color: #428bca;
}
.panel-primary > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #428bca;
}
.panel-primary > .panel-heading .badge {
color: #428bca;
background-color: #ffffff;
}
.panel-primary > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #428bca;
}
.panel-success {
border-color: #d6e9c6;
}
.panel-success > .panel-heading {
color: #3c763d;
background-color: #dff0d8;
border-color: #d6e9c6;
}
.panel-success > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #d6e9c6;
}
.panel-success > .panel-heading .badge {
color: #dff0d8;
background-color: #3c763d;
}
.panel-success > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #d6e9c6;
}
.panel-info {
border-color: #bce8f1;
}
.panel-info > .panel-heading {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
}
.panel-info > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #bce8f1;
}
.panel-info > .panel-heading .badge {
color: #d9edf7;
background-color: #31708f;
}
.panel-info > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #bce8f1;
}
.panel-warning {
border-color: #faebcc;
}
.panel-warning > .panel-heading {
color: #8a6d3b;
background-color: #fcf8e3;
border-color: #faebcc;
}
.panel-warning > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #faebcc;
}
.panel-warning > .panel-heading .badge {
color: #fcf8e3;
background-color: #8a6d3b;
}
.panel-warning > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #faebcc;
}
.panel-danger {
border-color: #ebccd1;
}
.panel-danger > .panel-heading {
color: #a94442;
background-color: #f2dede;
border-color: #ebccd1;
}
.panel-danger > .panel-heading + .panel-collapse > .panel-body {
border-top-color: #ebccd1;
}
.panel-danger > .panel-heading .badge {
color: #f2dede;
background-color: #a94442;
}
.panel-danger > .panel-footer + .panel-collapse > .panel-body {
border-bottom-color: #ebccd1;
}
.clearfix:before,
.clearfix:after,
.dl-horizontal dd:before,
.dl-horizontal dd:after,
.container:before,
.container:after,
.container-fluid:before,
.container-fluid:after,
.row:before,
.row:after,
.form-horizontal .form-group:before,
.form-horizontal .form-group:after,
.btn-toolbar:before,
.btn-toolbar:after,
.btn-group-vertical > .btn-group:before,
.btn-group-vertical > .btn-group:after,
.nav:before,
.nav:after,
.navbar:before,
.navbar:after,
.navbar-header:before,
.navbar-header:after,
.navbar-collapse:before,
.navbar-collapse:after,
.panel-body:before,
.panel-body:after {
display: table;
content: " ";
}
.clearfix:after,
.dl-horizontal dd:after,
.container:after,
.container-fluid:after,
.row:after,
.form-horizontal .form-group:after,
.btn-toolbar:after,
.btn-group-vertical > .btn-group:after,
.nav:after,
.navbar:after,
.navbar-header:after,
.navbar-collapse:after,
.panel-body:after {
clear: both;
}
.center-block {
display: block;
margin-right: auto;
margin-left: auto;
}
.pull-right {
float: right !important;
}
.pull-left {
float: left !important;
}
.hide {
display: none !important;
}
.show {
display: block !important;
}
.invisible {
visibility: hidden;
}
.text-hide {
font: 0/0 a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
.hidden {
display: none !important;
}
.affix {
position: fixed;
}
@-ms-viewport {
width: device-width;
}
.visible-xs,
.visible-sm,
.visible-md,
.visible-lg {
display: none !important;
}
.visible-xs-block,
.visible-xs-inline,
.visible-xs-inline-block,
.visible-sm-block,
.visible-sm-inline,
.visible-sm-inline-block,
.visible-md-block,
.visible-md-inline,
.visible-md-inline-block,
.visible-lg-block,
.visible-lg-inline,
.visible-lg-inline-block {
display: none !important;
}
@media (max-width: 767px) {
.visible-xs {
display: block !important;
}
table.visible-xs {
display: table !important;
}
tr.visible-xs {
display: table-row !important;
}
th.visible-xs,
td.visible-xs {
display: table-cell !important;
}
}
@media (max-width: 767px) {
.visible-xs-block {
display: block !important;
}
}
@media (max-width: 767px) {
.visible-xs-inline {
display: inline !important;
}
}
@media (max-width: 767px) {
.visible-xs-inline-block {
display: inline-block !important;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.visible-sm {
display: block !important;
}
table.visible-sm {
display: table !important;
}
tr.visible-sm {
display: table-row !important;
}
th.visible-sm,
td.visible-sm {
display: table-cell !important;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.visible-sm-block {
display: block !important;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.visible-sm-inline {
display: inline !important;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.visible-sm-inline-block {
display: inline-block !important;
}
}
@media (min-width: 992px) and (max-width: 1199px) {
.visible-md {
display: block !important;
}
table.visible-md {
display: table !important;
}
tr.visible-md {
display: table-row !important;
}
th.visible-md,
td.visible-md {
display: table-cell !important;
}
}
@media (min-width: 992px) and (max-width: 1199px) {
.visible-md-block {
display: block !important;
}
}
@media (min-width: 992px) and (max-width: 1199px) {
.visible-md-inline {
display: inline !important;
}
}
@media (min-width: 992px) and (max-width: 1199px) {
.visible-md-inline-block {
display: inline-block !important;
}
}
@media (min-width: 1200px) {
.visible-lg {
display: block !important;
}
table.visible-lg {
display: table !important;
}
tr.visible-lg {
display: table-row !important;
}
th.visible-lg,
td.visible-lg {
display: table-cell !important;
}
}
@media (min-width: 1200px) {
.visible-lg-block {
display: block !important;
}
}
@media (min-width: 1200px) {
.visible-lg-inline {
display: inline !important;
}
}
@media (min-width: 1200px) {
.visible-lg-inline-block {
display: inline-block !important;
}
}
@media (max-width: 767px) {
.hidden-xs {
display: none !important;
}
}
@media (min-width: 768px) and (max-width: 991px) {
.hidden-sm {
display: none !important;
}
}
@media (min-width: 992px) and (max-width: 1199px) {
.hidden-md {
display: none !important;
}
}
@media (min-width: 1200px) {
.hidden-lg {
display: none !important;
}
}
.visible-print {
display: none !important;
}
@media print {
.visible-print {
display: block !important;
}
table.visible-print {
display: table !important;
}
tr.visible-print {
display: table-row !important;
}
th.visible-print,
td.visible-print {
display: table-cell !important;
}
}
.visible-print-block {
display: none !important;
}
@media print {
.visible-print-block {
display: block !important;
}
}
.visible-print-inline {
display: none !important;
}
@media print {
.visible-print-inline {
display: inline !important;
}
}
.visible-print-inline-block {
display: none !important;
}
@media print {
.visible-print-inline-block {
display: inline-block !important;
}
}
@media print {
.hidden-print {
display: none !important;
}
}
/*!
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
*/
/*!
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-default.disabled,
.btn-primary.disabled,
.btn-success.disabled,
.btn-info.disabled,
.btn-warning.disabled,
.btn-danger.disabled,
.btn-default[disabled],
.btn-primary[disabled],
.btn-success[disabled],
.btn-info[disabled],
.btn-warning[disabled],
.btn-danger[disabled],
fieldset[disabled] .btn-default,
fieldset[disabled] .btn-primary,
fieldset[disabled] .btn-success,
fieldset[disabled] .btn-info,
fieldset[disabled] .btn-warning,
fieldset[disabled] .btn-danger {
-webkit-box-shadow: none;
box-shadow: none;
}
.btn-default .badge,
.btn-primary .badge,
.btn-success .badge,
.btn-info .badge,
.btn-warning .badge,
.btn-danger .badge {
text-shadow: none;
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
text-shadow: 0 1px 0 #fff;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default.disabled,
.btn-default[disabled],
fieldset[disabled] .btn-default,
.btn-default.disabled:hover,
.btn-default[disabled]:hover,
fieldset[disabled] .btn-default:hover,
.btn-default.disabled:focus,
.btn-default[disabled]:focus,
fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus,
.btn-default.disabled:active,
.btn-default[disabled]:active,
fieldset[disabled] .btn-default:active,
.btn-default.disabled.active,
.btn-default[disabled].active,
fieldset[disabled] .btn-default.active {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #2d6ca2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#2d6ca2));
background-image: linear-gradient(to bottom, #428bca 0%, #2d6ca2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff2d6ca2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #2b669a;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #2d6ca2;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #2d6ca2;
border-color: #2b669a;
}
.btn-primary.disabled,
.btn-primary[disabled],
fieldset[disabled] .btn-primary,
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
.btn-primary.disabled:focus,
.btn-primary[disabled]:focus,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus,
.btn-primary.disabled:active,
.btn-primary[disabled]:active,
fieldset[disabled] .btn-primary:active,
.btn-primary.disabled.active,
.btn-primary[disabled].active,
fieldset[disabled] .btn-primary.active {
background-color: #2d6ca2;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success.disabled,
.btn-success[disabled],
fieldset[disabled] .btn-success,
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
.btn-success.disabled:focus,
.btn-success[disabled]:focus,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus,
.btn-success.disabled:active,
.btn-success[disabled]:active,
fieldset[disabled] .btn-success:active,
.btn-success.disabled.active,
.btn-success[disabled].active,
fieldset[disabled] .btn-success.active {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info.disabled,
.btn-info[disabled],
fieldset[disabled] .btn-info,
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
.btn-info.disabled:focus,
.btn-info[disabled]:focus,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus,
.btn-info.disabled:active,
.btn-info[disabled]:active,
fieldset[disabled] .btn-info:active,
.btn-info.disabled.active,
.btn-info[disabled].active,
fieldset[disabled] .btn-info.active {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning.disabled,
.btn-warning[disabled],
fieldset[disabled] .btn-warning,
.btn-warning.disabled:hover,
.btn-warning[disabled]:hover,
fieldset[disabled] .btn-warning:hover,
.btn-warning.disabled:focus,
.btn-warning[disabled]:focus,
fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus,
.btn-warning.disabled:active,
.btn-warning[disabled]:active,
fieldset[disabled] .btn-warning:active,
.btn-warning.disabled.active,
.btn-warning[disabled].active,
fieldset[disabled] .btn-warning.active {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger.disabled,
.btn-danger[disabled],
fieldset[disabled] .btn-danger,
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
.btn-danger.disabled:focus,
.btn-danger[disabled]:focus,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus,
.btn-danger.disabled:active,
.btn-danger[disabled]:active,
fieldset[disabled] .btn-danger:active,
.btn-danger.disabled.active,
.btn-danger[disabled].active,
fieldset[disabled] .btn-danger.active {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
background-color: #e8e8e8;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x;
background-color: #357ebd;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#f8f8f8));
background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222222));
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
border-radius: 4px;
}
.navbar-inverse .navbar-nav > .open > a,
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
@media (max-width: 767px) {
.navbar .navbar-nav .open .dropdown-menu > .active > a,
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #fff;
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x;
}
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #3071a9 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3071a9));
background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #3071a9;
background-image: -webkit-linear-gradient(top, #428bca 0%, #3278b3 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #3278b3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#3278b3));
background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
background-repeat: repeat-x;
border-color: #3278b3;
}
.list-group-item.active .badge,
.list-group-item.active:hover .badge,
.list-group-item.active:focus .badge {
text-shadow: none;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -o-linear-gradient(top, #428bca 0%, #357ebd 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#428bca), to(#357ebd));
background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x;
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
}
/*!
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
*/
/*!
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2020 Twitter, Inc.
* Licensed under the MIT license
*/
if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(t){"use strict";var e=t.fn.jquery.split(" ")[0].split(".");if(e[0]<2&&e[1]<9||1==e[0]&&9==e[1]&&e[2]<1||e[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(t){"use strict";function e(e){var i,s=e.attr("data-target")||(i=e.attr("href"))&&i.replace(/.*(?=#[^\s]+$)/,"");return t(document).find(s)}function i(e){return this.each(function(){var i=t(this),n=i.data("bs.collapse"),a=t.extend({},s.DEFAULTS,i.data(),"object"==typeof e&&e);!n&&a.toggle&&/show|hide/.test(e)&&(a.toggle=!1),n||i.data("bs.collapse",n=new s(this,a)),"string"==typeof e&&n[e]()})}var s=function(e,i){this.$element=t(e),this.options=t.extend({},s.DEFAULTS,i),this.$trigger=t('[data-toggle="collapse"][href="#'+e.id+'"],[data-toggle="collapse"][data-target="#'+e.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};s.VERSION="3.4.1",s.TRANSITION_DURATION=350,s.DEFAULTS={toggle:!0},s.prototype.dimension=function(){var t=this.$element.hasClass("width");return t?"width":"height"},s.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var e,n=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(n&&n.length&&(e=n.data("bs.collapse"),e&&e.transitioning))){var a=t.Event("show.bs.collapse");if(this.$element.trigger(a),!a.isDefaultPrevented()){n&&n.length&&(i.call(n,"hide"),e||n.data("bs.collapse",null));var o=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[o](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var r=function(){this.$element.removeClass("collapsing").addClass("collapse in")[o](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!t.support.transition)return r.call(this);var l=t.camelCase(["scroll",o].join("-"));this.$element.one("bsTransitionEnd",t.proxy(r,this)).emulateTransitionEnd(s.TRANSITION_DURATION)[o](this.$element[0][l])}}}},s.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var e=t.Event("hide.bs.collapse");if(this.$element.trigger(e),!e.isDefaultPrevented()){var i=this.dimension();this.$element[i](this.$element[i]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var n=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return t.support.transition?void this.$element[i](0).one("bsTransitionEnd",t.proxy(n,this)).emulateTransitionEnd(s.TRANSITION_DURATION):n.call(this)}}},s.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},s.prototype.getParent=function(){return t(document).find(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(t.proxy(function(i,s){var n=t(s);this.addAriaAndCollapsedClass(e(n),n)},this)).end()},s.prototype.addAriaAndCollapsedClass=function(t,e){var i=t.hasClass("in");t.attr("aria-expanded",i),e.toggleClass("collapsed",!i).attr("aria-expanded",i)};var n=t.fn.collapse;t.fn.collapse=i,t.fn.collapse.Constructor=s,t.fn.collapse.noConflict=function(){return t.fn.collapse=n,this},t(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(s){var n=t(this);n.attr("data-target")||s.preventDefault();var a=e(n),o=a.data("bs.collapse"),r=o?"toggle":n.data();i.call(a,r)})}(jQuery);/*! jQuery v3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector | (c) JS Foundation and other contributors | jquery.org/license */
!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(g,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,v=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,y=n.hasOwnProperty,a=y.toString,l=a.call(Object),m={},b=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},w=g.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function C(e,t,n){var r,i,o=(n=n||w).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function T(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.5.1 -ajax,-ajax/jsonp,-ajax/load,-ajax/script,-ajax/var/location,-ajax/var/nonce,-ajax/var/rquery,-ajax/xhr,-manipulation/_evalUrl,-deprecated/ajax-event-alias,-effects,-effects/Tween,-effects/animatedSelector",E=function(e,t){return new E.fn.init(e,t)};function d(e){var t=!!e&&"length"in e&&e.length,n=T(e);return!b(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0<t&&t-1 in e)}E.fn=E.prototype={jquery:f,constructor:E,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=E.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return E.each(this,e)},map:function(n){return this.pushStack(E.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(E.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(E.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},E.extend=E.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for("boolean"==typeof a&&(l=a,a=arguments[s]||{},s++),"object"==typeof a||b(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],"__proto__"!==t&&a!==r&&(l&&r&&(E.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||E.isPlainObject(n)?n:{},i=!1,a[t]=E.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},E.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||"[object Object]"!==o.call(e))&&(!(t=r(e))||"function"==typeof(n=y.call(t,"constructor")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){C(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(d(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(d(Object(e))?E.merge(n,"string"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(d(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return v(a)},guid:1,support:m}),"function"==typeof Symbol&&(E.fn[Symbol.iterator]=t[Symbol.iterator]),E.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(e,t){n["[object "+t+"]"]=t.toLowerCase()});var p=function(n){var e,p,x,o,i,h,f,g,w,u,l,C,T,a,E,v,s,c,y,A="sizzle"+1*new Date,d=n.document,N=0,r=0,m=ue(),b=ue(),S=ue(),k=ue(),D=function(e,t){return e===t&&(l=!0),0},L={}.hasOwnProperty,t=[],j=t.pop,q=t.push,O=t.push,P=t.slice,H=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},I="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",R="[\\x20\\t\\r\\n\\f]",B="(?:\\\\[\\da-fA-F]{1,6}"+R+"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",M="\\["+R+"*("+B+")(?:"+R+"*([*^$|!~]?=)"+R+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+B+"))|)"+R+"*\\]",W=":("+B+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+M+")*)|.*)\\)|)",F=new RegExp(R+"+","g"),$=new RegExp("^"+R+"+|((?:^|[^\\\\])(?:\\\\.)*)"+R+"+$","g"),z=new RegExp("^"+R+"*,"+R+"*"),_=new RegExp("^"+R+"*([>+~]|"+R+")"+R+"*"),U=new RegExp(R+"|>"),V=new RegExp(W),X=new RegExp("^"+B+"$"),Q={ID:new RegExp("^#("+B+")"),CLASS:new RegExp("^\\.("+B+")"),TAG:new RegExp("^("+B+"|[*])"),ATTR:new RegExp("^"+M),PSEUDO:new RegExp("^"+W),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+I+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,G=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,J=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+R+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){C()},ae=xe(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{O.apply(t=P.call(d.childNodes),d.childNodes),t[d.childNodes.length].nodeType}catch(e){O={apply:t.length?function(e,t){q.apply(e,P.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,d=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==d&&9!==d&&11!==d)return n;if(!r&&(C(e),e=e||T,E)){if(11!==d&&(u=Z.exec(t)))if(i=u[1]){if(9===d){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return O.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&p.getElementsByClassName&&e.getElementsByClassName)return O.apply(n,e.getElementsByClassName(i)),n}if(p.qsa&&!k[t+" "]&&(!v||!v.test(t))&&(1!==d||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===d&&(U.test(t)||_.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&p.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=A)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+be(l[o]);c=l.join(",")}try{return O.apply(n,f.querySelectorAll(c)),n}catch(e){k(t,!0)}finally{s===A&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>x.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[A]=!0,e}function ce(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)x.attrHandle[n[r]]=t}function de(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function pe(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in p=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},C=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:d;return r!=T&&9===r.nodeType&&r.documentElement&&(a=(T=r).documentElement,E=!i(T),d!=T&&(n=T.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),p.scope=ce(function(e){return a.appendChild(e).appendChild(T.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),p.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),p.getElementsByTagName=ce(function(e){return e.appendChild(T.createComment("")),!e.getElementsByTagName("*").length}),p.getElementsByClassName=J.test(T.getElementsByClassName),p.getById=ce(function(e){return a.appendChild(e).id=A,!T.getElementsByName||!T.getElementsByName(A).length}),p.getById?(x.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(x.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},x.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),x.find.TAG=p.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):p.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},x.find.CLASS=p.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(p.qsa=J.test(T.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="<a id='"+A+"'></a><select id='"+A+"-\r\\' msallowcapture=''><option selected=''></option></select>",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+R+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+R+"*(?:value|"+I+")"),e.querySelectorAll("[id~="+A+"-]").length||v.push("~="),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+R+"*name"+R+"*="+R+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+A+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>";var t=T.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+R+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(p.matchesSelector=J.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){p.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",W)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=J.test(a.compareDocumentPosition),y=t||J.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!p.sortDetached&&t.compareDocumentPosition(e)===n?e==T||e.ownerDocument==d&&y(d,e)?-1:t==T||t.ownerDocument==d&&y(d,t)?1:u?H(u,e)-H(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==T?-1:t==T?1:i?-1:o?1:u?H(u,e)-H(u,t):0;if(i===o)return de(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?de(a[r],s[r]):a[r]==d?-1:s[r]==d?1:0}),T},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(C(e),p.matchesSelector&&E&&!k[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||p.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){k(t,!0)}return 0<se(t,T,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=T&&C(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=T&&C(e);var n=x.attrHandle[t.toLowerCase()],r=n&&L.call(x.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:p.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+"").replace(re,ie)},se.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!p.detectDuplicates,u=!p.sortStable&&e.slice(0),e.sort(D),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(x=se.selectors={cacheLength:50,createPseudo:le,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&V.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+R+")"+e+"("+R+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1<t.indexOf(i):"$="===r?i&&t.slice(-i.length)===i:"~="===r?-1<(" "+t.replace(F," ")+" ").indexOf(i):"|="===r&&(t===i||t.slice(0,i.length+1)===i+"-"))}},CHILD:function(h,e,t,g,v){var y="nth"!==h.slice(0,3),m="last"!==h.slice(-4),b="of-type"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?"nextSibling":"previousSibling",c=e.parentNode,f=b&&e.nodeName.toLowerCase(),d=!n&&!b,p=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(b?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l="only"===h&&!u&&"nextSibling"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&d){p=(s=(r=(i=(o=(a=c)[A]||(a[A]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===N&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(p=s=0)||u.pop())if(1===a.nodeType&&++p&&a===e){i[h]=[N,s,p];break}}else if(d&&(p=s=(r=(i=(o=(a=e)[A]||(a[A]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===N&&r[1]),!1===p)while(a=++s&&a&&a[l]||(p=s=0)||u.pop())if((b?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++p&&(d&&((i=(o=a[A]||(a[A]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[N,p]),a===e))break;return(p-=v)===g||p%g==0&&0<=p/g}}},PSEUDO:function(e,o){var t,a=x.pseudos[e]||x.setFilters[e.toLowerCase()]||se.error("unsupported pseudo: "+e);return a[A]?a(o):1<a.length?(t=[e,e,"",o],x.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=H(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,"$1"));return s[A]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return X.test(n||"")||se.error("unsupported lang: "+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(t=t.toLowerCase())===n||0===t.indexOf(n+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===T.activeElement&&(!T.hasFocus||T.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!x.pseudos.empty(e)},header:function(e){return K.test(e.nodeName)},input:function(e){return G.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=x.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})x.pseudos[e]=pe(e);for(e in{submit:!0,reset:!0})x.pseudos[e]=he(e);function me(){}function be(e){for(var t=0,n=e.length,r="";t<n;t++)r+=e[t].value;return r}function xe(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&"parentNode"===c,d=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[N,d];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[A]||(e[A]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===N&&r[1]===d)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Ce(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Te(p,h,g,v,y,e){return v&&!v[A]&&(v=Te(v)),y&&!y[A]&&(y=Te(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||"*",n.nodeType?[n]:n,[]),f=!p||!e&&h?c:Ce(c,s,p,n,r),d=g?y||(e?p:l||v)?[]:t:f;if(g&&g(f,d,n,r),v){i=Ce(d,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(d[u[o]]=!(f[u[o]]=a))}if(e){if(y||p){if(y){i=[],o=d.length;while(o--)(a=d[o])&&i.push(f[o]=a);y(null,d=[],i,r)}o=d.length;while(o--)(a=d[o])&&-1<(i=y?H(e,a):s[o])&&(e[i]=!(t[i]=a))}}else d=Ce(d===t?d.splice(l,d.length):d),y?y(null,t,d,r):O.apply(t,d)})}function Ee(e){for(var i,t,n,r=e.length,o=x.relative[e[0].type],a=o||x.relative[" "],s=o?1:0,u=xe(function(e){return e===i},a,!0),l=xe(function(e){return-1<H(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=x.relative[e[s].type])c=[xe(we(c),t)];else{if((t=x.filter[e[s].type].apply(null,e[s].matches))[A]){for(n=++s;n<r;n++)if(x.relative[e[n].type])break;return Te(1<s&&we(c),1<s&&be(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace($,"$1"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&be(e))}c.push(t)}return we(c)}return me.prototype=x.filters=x.pseudos,x.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=b[e+" "];if(l)return t?0:l.slice(0);a=e,s=[],u=x.preFilter;while(a){for(o in n&&!(r=z.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=_.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($," ")}),a=a.slice(n.length)),x.filter)!(r=Q[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):b(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,b,r,i=[],o=[],a=S[e+" "];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[A]?i.push(a):o.push(a);(a=S(e,(v=o,m=0<(y=i).length,b=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l="0",c=e&&[],f=[],d=w,p=e||b&&x.find.TAG("*",i),h=N+=null==d?1:Math.random()||.1,g=p.length;for(i&&(w=t==T||t||i);l!==g&&null!=(o=p[l]);l++){if(b&&o){a=0,t||o.ownerDocument==T||(C(o),n=!E);while(s=v[a++])if(s(o,t||T,n)){r.push(o);break}i&&(N=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=j.call(r));f=Ce(f)}O.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(N=h,w=d),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l="function"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&"ID"===(a=o[0]).type&&9===t.nodeType&&E&&x.relative[o[1].type]){if(!(t=(x.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=Q.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],x.relative[s=a.type])break;if((u=x.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&be(o)))return O.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},p.sortStable=A.split("").sort(D).join("")===A,p.detectDuplicates=!!l,C(),p.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(T.createElement("fieldset"))}),ce(function(e){return e.innerHTML="<a href='#'></a>","#"===e.firstChild.getAttribute("href")})||fe("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),p.attributes&&ce(function(e){return e.innerHTML="<input/>",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||fe("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute("disabled")})||fe(I,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(g);E.find=p,E.expr=p.selectors,E.expr[":"]=E.expr.pseudos,E.uniqueSort=E.unique=p.uniqueSort,E.text=p.getText,E.isXMLDoc=p.isXML,E.contains=p.contains,E.escapeSelector=p.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&E(e).is(n))break;r.push(e)}return r},A=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},N=E.expr.match.needsContext;function S(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var k=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function D(e,n,r){return b(n)?E.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?E.grep(e,function(e){return e===n!==r}):"string"!=typeof n?E.grep(e,function(e){return-1<i.call(n,e)!==r}):E.filter(n,e,r)}E.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?E.find.matchesSelector(r,e)?[r]:[]:E.find.matches(e,E.grep(t,function(e){return 1===e.nodeType}))},E.fn.extend({find:function(e){var t,n,r=this.length,i=this;if("string"!=typeof e)return this.pushStack(E(e).filter(function(){for(t=0;t<r;t++)if(E.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)E.find(e,i[t],n);return 1<r?E.uniqueSort(n):n},filter:function(e){return this.pushStack(D(this,e||[],!1))},not:function(e){return this.pushStack(D(this,e||[],!0))},is:function(e){return!!D(this,"string"==typeof e&&N.test(e)?E(e):e||[],!1).length}});var L,j=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(E.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||L,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:j.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof E?t[0]:t,E.merge(this,E.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:w,!0)),k.test(r[1])&&E.isPlainObject(t))for(r in t)b(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=w.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):b(e)?void 0!==n.ready?n.ready(e):e(E):E.makeArray(e,this)}).prototype=E.fn,L=E(w);var q=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};function P(e,t){while((e=e[t])&&1!==e.nodeType);return e}E.fn.extend({has:function(e){var t=E(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(E.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a="string"!=typeof e&&E(e);if(!N.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&E.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?E.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?i.call(E(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(E.uniqueSort(E.merge(this.get(),E(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),E.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,"parentNode")},parentsUntil:function(e,t,n){return h(e,"parentNode",n)},next:function(e){return P(e,"nextSibling")},prev:function(e){return P(e,"previousSibling")},nextAll:function(e){return h(e,"nextSibling")},prevAll:function(e){return h(e,"previousSibling")},nextUntil:function(e,t,n){return h(e,"nextSibling",n)},prevUntil:function(e,t,n){return h(e,"previousSibling",n)},siblings:function(e){return A((e.parentNode||{}).firstChild,e)},children:function(e){return A(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(S(e,"template")&&(e=e.content||e),E.merge([],e.childNodes))}},function(r,i){E.fn[r]=function(e,t){var n=E.map(this,i,e);return"Until"!==r.slice(-5)&&(t=e),t&&"string"==typeof t&&(n=E.filter(t,n)),1<this.length&&(O[r]||E.uniqueSort(n),q.test(r)&&n.reverse()),this.pushStack(n)}});var H=/[^\x20\t\r\n\f]+/g;function I(e){return e}function R(e){throw e}function B(e,t,n,r){var i;try{e&&b(i=e.promise)?i.call(e).done(t).fail(n):e&&b(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}E.Callbacks=function(r){var e,n;r="string"==typeof r?(e=r,n={},E.each(e.match(H)||[],function(e,t){n[t]=!0}),n):E.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:"")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){E.each(e,function(e,t){b(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&"string"!==T(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return E.each(arguments,function(e,t){var n;while(-1<(n=E.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<E.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t="",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=""),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},E.extend({Deferred:function(e){var o=[["notify","progress",E.Callbacks("memory"),E.Callbacks("memory"),2],["resolve","done",E.Callbacks("once memory"),E.Callbacks("once memory"),0,"resolved"],["reject","fail",E.Callbacks("once memory"),E.Callbacks("once memory"),1,"rejected"]],i="pending",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},"catch":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return E.Deferred(function(r){E.each(o,function(e,t){var n=b(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&b(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+"With"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError("Thenable self-resolution");t=e&&("object"==typeof e||"function"==typeof e)&&e.then,b(t)?s?t.call(e,l(u,o,I,s),l(u,o,R,s)):(u++,t.call(e,l(u,o,I,s),l(u,o,R,s),l(u,o,I,o.notifyWith))):(a!==I&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){E.Deferred.exceptionHook&&E.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==R&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(E.Deferred.getStackHook&&(t.stackTrace=E.Deferred.getStackHook()),g.setTimeout(t))}}return E.Deferred(function(e){o[0][3].add(l(0,e,b(r)?r:I,e.notifyWith)),o[1][3].add(l(0,e,b(t)?t:I)),o[2][3].add(l(0,e,b(n)?n:R))}).promise()},promise:function(e){return null!=e?E.extend(e,a):a}},s={};return E.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+"With"](this===s?void 0:this,arguments),this},s[t[0]+"With"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=E.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(B(e,o.done(a(t)).resolve,o.reject,!n),"pending"===o.state()||b(i[t]&&i[t].then)))return o.then();while(t--)B(i[t],a(t),o.reject);return o.promise()}});var M=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;E.Deferred.exceptionHook=function(e,t){g.console&&g.console.warn&&e&&M.test(e.name)&&g.console.warn("jQuery.Deferred exception: "+e.message,e.stack,t)},E.readyException=function(e){g.setTimeout(function(){throw e})};var W=E.Deferred();function F(){w.removeEventListener("DOMContentLoaded",F),g.removeEventListener("load",F),E.ready()}E.fn.ready=function(e){return W.then(e)["catch"](function(e){E.readyException(e)}),this},E.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--E.readyWait:E.isReady)||(E.isReady=!0)!==e&&0<--E.readyWait||W.resolveWith(w,[E])}}),E.ready.then=W.then,"complete"===w.readyState||"loading"!==w.readyState&&!w.documentElement.doScroll?g.setTimeout(E.ready):(w.addEventListener("DOMContentLoaded",F),g.addEventListener("load",F));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if("object"===T(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,b(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(E(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},z=/^-ms-/,_=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function V(e){return e.replace(z,"ms-").replace(_,U)}var X=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function Q(){this.expando=E.expando+Q.uid++}Q.uid=1,Q.prototype={cache:function(e){var t=e[this.expando];return t||(t={},X(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if("string"==typeof t)i[V(t)]=n;else for(r in t)i[V(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][V(t)]},access:function(e,t,n){return void 0===t||t&&"string"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(V):(t=V(t))in r?[t]:t.match(H)||[]).length;while(n--)delete r[t[n]]}(void 0===t||E.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!E.isEmptyObject(t)}};var Y=new Q,G=new Q,K=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,J=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r="data-"+t.replace(J,"-$&").toLowerCase(),"string"==typeof(n=e.getAttribute(r))){try{n="true"===(i=n)||"false"!==i&&("null"===i?null:i===+i+""?+i:K.test(i)?JSON.parse(i):i)}catch(e){}G.set(e,t,n)}else n=void 0;return n}E.extend({hasData:function(e){return G.hasData(e)||Y.hasData(e)},data:function(e,t,n){return G.access(e,t,n)},removeData:function(e,t){G.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),E.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=G.get(o),1===o.nodeType&&!Y.get(o,"hasDataAttrs"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf("data-")&&(r=V(r.slice(5)),Z(o,r,i[r]));Y.set(o,"hasDataAttrs",!0)}return i}return"object"==typeof n?this.each(function(){G.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=G.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){G.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){G.remove(this,e)})}}),E.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,E.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=E.queue(e,t),r=n.length,i=n.shift(),o=E._queueHooks(e,t);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,function(){E.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return Y.get(e,n)||Y.access(e,n,{empty:E.Callbacks("once memory").add(function(){Y.remove(e,[t+"queue",n])})})}}),E.fn.extend({queue:function(t,n){var e=2;return"string"!=typeof t&&(n=t,t="fx",e--),arguments.length<e?E.queue(this[0],t):void 0===n?this:this.each(function(){var e=E.queue(this,t,n);E._queueHooks(this,t),"fx"===t&&"inprogress"!==e[0]&&E.dequeue(this,t)})},dequeue:function(e){return this.each(function(){E.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,t){var n,r=1,i=E.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};"string"!=typeof e&&(t=e,e=void 0),e=e||"fx";while(a--)(n=Y.get(o[a],e+"queueHooks"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,te=new RegExp("^(?:([+-])=|)("+ee+")([a-z%]*)$","i"),ne=["Top","Right","Bottom","Left"],re=w.documentElement,ie=function(e){return E.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return E.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return"none"===(e=t||e).style.display||""===e.style.display&&ie(e)&&"none"===E.css(e,"display")};var se={};function ue(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?("none"===n&&(l[c]=Y.get(r,"display")||null,l[c]||(r.style.display="")),""===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=se[s])||(o=a.body.appendChild(a.createElement(s)),u=E.css(o,"display"),o.parentNode.removeChild(o),"none"===u&&(u="block"),se[s]=u)))):"none"!==n&&(l[c]="none",Y.set(r,"display",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}E.fn.extend({show:function(){return ue(this,!0)},hide:function(){return ue(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?E(this).show():E(this).hide()})}});var le,ce,fe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,pe=/^$|^module$|\/(?:java|ecma)script/i;le=w.createDocumentFragment().appendChild(w.createElement("div")),(ce=w.createElement("input")).setAttribute("type","radio"),ce.setAttribute("checked","checked"),ce.setAttribute("name","t"),le.appendChild(ce),m.checkClone=le.cloneNode(!0).cloneNode(!0).lastChild.checked,le.innerHTML="<textarea>x</textarea>",m.noCloneChecked=!!le.cloneNode(!0).lastChild.defaultValue,le.innerHTML="<option></option>",m.option=!!le.lastChild;var he={thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};function ge(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&S(e,t)?E.merge([e],n):n}function ve(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],"globalEval",!t||Y.get(t[n],"globalEval"))}he.tbody=he.tfoot=he.colgroup=he.caption=he.thead,he.th=he.td,m.option||(he.optgroup=he.option=[1,"<select multiple='multiple'>","</select>"]);var ye=/<|&#?\w+;/;function me(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),d=[],p=0,h=e.length;p<h;p++)if((o=e[p])||0===o)if("object"===T(o))E.merge(d,o.nodeType?[o]:o);else if(ye.test(o)){a=a||f.appendChild(t.createElement("div")),s=(de.exec(o)||["",""])[1].toLowerCase(),u=he[s]||he._default,a.innerHTML=u[1]+E.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;E.merge(d,a.childNodes),(a=f.firstChild).textContent=""}else d.push(t.createTextNode(o));f.textContent="",p=0;while(o=d[p++])if(r&&-1<E.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ge(f.appendChild(o),"script"),l&&ve(a),n){c=0;while(o=a[c++])pe.test(o.type||"")&&n.push(o)}return f}var be=/^key/,xe=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,we=/^([^.]*)(?:\.(.+)|)/;function Ce(){return!0}function Te(){return!1}function Ee(e,t){return e===function(){try{return w.activeElement}catch(e){}}()==("focus"===t)}function Ae(e,t,n,r,i,o){var a,s;if("object"==typeof t){for(s in"string"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Te;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return E().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=E.guid++)),e.each(function(){E.event.add(this,t,i,r,n)})}function Ne(e,i,o){o?(Y.set(e,i,!1),E.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(E.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Y.set(this,i,{value:E.event.trigger(E.extend(r[0],E.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&E.event.add(e,i,Ce)}E.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=Y.get(t);if(X(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&E.find.matchesSelector(re,i),n.guid||(n.guid=E.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return"undefined"!=typeof E&&E.event.triggered!==e.type?E.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||"").match(H)||[""]).length;while(l--)p=g=(s=we.exec(e[l])||[])[1],h=(s[2]||"").split(".").sort(),p&&(f=E.event.special[p]||{},p=(i?f.delegateType:f.bindType)||p,f=E.event.special[p]||{},c=E.extend({type:p,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&E.expr.match.needsContext.test(i),namespace:h.join(".")},o),(d=u[p])||((d=u[p]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(p,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?d.splice(d.delegateCount++,0,c):d.push(c),E.event.global[p]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,d,p,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||"").match(H)||[""]).length;while(l--)if(p=g=(s=we.exec(t[l])||[])[1],h=(s[2]||"").split(".").sort(),p){f=E.event.special[p]||{},d=u[p=(r?f.delegateType:f.bindType)||p]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=d.length;while(o--)c=d[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&("**"!==r||!c.selector)||(d.splice(o,1),c.selector&&d.delegateCount--,f.remove&&f.remove.call(e,c));a&&!d.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||E.removeEvent(e,p,v.handle),delete u[p])}else for(p in u)E.event.remove(e,p+t[l],n,r,!0);E.isEmptyObject(u)&&Y.remove(e,"handle events")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=E.event.fix(e),l=(Y.get(this,"events")||Object.create(null))[u.type]||[],c=E.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=E.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((E.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!("click"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&("click"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+" "]&&(a[i]=r.needsContext?-1<E(i,this).index(l):E.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(E.Event.prototype,t,{enumerable:!0,configurable:!0,get:b(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[E.expando]?e:new E.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return fe.test(t.type)&&t.click&&S(t,"input")&&Ne(t,"click",Ce),!1},trigger:function(e){var t=this||e;return fe.test(t.type)&&t.click&&S(t,"input")&&Ne(t,"click"),!0},_default:function(e){var t=e.target;return fe.test(t.type)&&t.click&&S(t,"input")&&Y.get(t,"click")||S(t,"a")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},E.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},E.Event=function(e,t){if(!(this instanceof E.Event))return new E.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?Ce:Te,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&E.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[E.expando]=!0},E.Event.prototype={constructor:E.Event,isDefaultPrevented:Te,isPropagationStopped:Te,isImmediatePropagationStopped:Te,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=Ce,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=Ce,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=Ce,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},E.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,"char":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&be.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&xe.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},E.event.addProp),E.each({focus:"focusin",blur:"focusout"},function(e,t){E.event.special[e]={setup:function(){return Ne(this,e,Ee),!1},trigger:function(){return Ne(this,e),!0},delegateType:t}}),E.each({mouseenter:"mouseover",mouseleave:"mouseout",pointerenter:"pointerover",pointerleave:"pointerout"},function(e,i){E.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||E.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),E.fn.extend({on:function(e,t,n,r){return Ae(this,e,t,n,r)},one:function(e,t,n,r){return Ae(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,E(e.delegateTarget).off(r.namespace?r.origType+"."+r.namespace:r.origType,r.selector,r.handler),this;if("object"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&"function"!=typeof t||(n=t,t=void 0),!1===n&&(n=Te),this.each(function(){E.event.remove(this,e,n,t)})}});var Se=/<script|<style|<link/i,ke=/checked\s*(?:[^=]|=\s*.checked.)/i,De=/^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g;function Le(e,t){return S(e,"table")&&S(11!==t.nodeType?t:t.firstChild,"tr")&&E(e).children("tbody")[0]||e}function je(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Oe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n<r;n++)E.event.add(t,i,s[i][n]);G.hasData(e)&&(o=G.access(e),a=E.extend({},o),G.set(t,a))}}function Pe(n,r,i,o){r=v(r);var e,t,a,s,u,l,c=0,f=n.length,d=f-1,p=r[0],h=b(p);if(h||1<f&&"string"==typeof p&&!m.checkClone&&ke.test(p))return n.each(function(e){var t=n.eq(e);h&&(r[0]=p.call(this,e,t.html())),Pe(t,r,i,o)});if(f&&(t=(e=me(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=E.map(ge(e,"script"),je)).length;c<f;c++)u=e,c!==d&&(u=E.clone(u,!0,!0),s&&E.merge(a,ge(u,"script"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,E.map(a,qe),c=0;c<s;c++)u=a[c],pe.test(u.type||"")&&!Y.access(u,"globalEval")&&E.contains(l,u)&&(u.src&&"module"!==(u.type||"").toLowerCase()?E._evalUrl&&!u.noModule&&E._evalUrl(u.src,{nonce:u.nonce||u.getAttribute("nonce")},l):C(u.textContent.replace(De,""),u,l))}return n}function He(e,t,n){for(var r,i=t?E.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||E.cleanData(ge(r)),r.parentNode&&(n&&ie(r)&&ve(ge(r,"script")),r.parentNode.removeChild(r));return e}E.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(m.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||E.isXMLDoc(e)))for(a=ge(c),r=0,i=(o=ge(e)).length;r<i;r++)s=o[r],u=a[r],void 0,"input"===(l=u.nodeName.toLowerCase())&&fe.test(s.type)?u.checked=s.checked:"input"!==l&&"textarea"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ge(e),a=a||ge(c),r=0,i=o.length;r<i;r++)Oe(o[r],a[r]);else Oe(e,c);return 0<(a=ge(c,"script")).length&&ve(a,!f&&ge(e,"script")),c},cleanData:function(e){for(var t,n,r,i=E.event.special,o=0;void 0!==(n=e[o]);o++)if(X(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?E.event.remove(n,r):E.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[G.expando]&&(n[G.expando]=void 0)}}}),E.fn.extend({detach:function(e){return He(this,e,!0)},remove:function(e){return He(this,e)},text:function(e){return $(this,function(e){return void 0===e?E.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Pe(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Le(this,e).appendChild(e)})},prepend:function(){return Pe(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Le(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Pe(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(E.cleanData(ge(e,!1)),e.textContent="");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return E.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if("string"==typeof e&&!Se.test(e)&&!he[(de.exec(e)||["",""])[1].toLowerCase()]){e=E.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(E.cleanData(ge(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Pe(this,arguments,function(e){var t=this.parentNode;E.inArray(this,n)<0&&(E.cleanData(ge(this)),t&&t.replaceChild(e,this))},n)}}),E.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,a){E.fn[e]=function(e){for(var t,n=[],r=E(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),E(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Ie=new RegExp("^("+ee+")(?!px)[a-z%]+$","i"),Re=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=g),t.getComputedStyle(e)},Be=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Me=new RegExp(ne.join("|"),"i");function We(e,t,n){var r,i,o,a,s=e.style;return(n=n||Re(e))&&(""!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=E.style(e,t)),!m.pixelBoxStyles()&&Ie.test(a)&&Me.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+"":a}function Fe(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText="position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0",l.style.cssText="position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%",re.appendChild(u).appendChild(l);var e=g.getComputedStyle(l);n="1%"!==e.top,s=12===t(e.marginLeft),l.style.right="60%",o=36===t(e.right),r=36===t(e.width),l.style.position="absolute",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=w.createElement("div"),l=w.createElement("div");l.style&&(l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",m.clearCloneStyle="content-box"===l.style.backgroundClip,E.extend(m,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=w.createElement("table"),t=w.createElement("tr"),n=w.createElement("div"),e.style.cssText="position:absolute;left:-11111px",t.style.height="1px",n.style.height="9px",re.appendChild(e).appendChild(t).appendChild(n),r=g.getComputedStyle(t),a=3<parseInt(r.height),re.removeChild(e)),a}}))}();var $e=["Webkit","Moz","ms"],ze=w.createElement("div").style,_e={};function Ue(e){var t=E.cssProps[e]||_e[e];return t||(e in ze?e:_e[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=$e.length;while(n--)if((e=$e[n]+t)in ze)return e}(e)||e)}var Ve,Xe,Qe=/^(none|table(?!-c[ea]).+)/,Ye=/^--/,Ge={position:"absolute",visibility:"hidden",display:"block"},Ke={letterSpacing:"0",fontWeight:"400"};function Je(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||"px"):t}function Ze(e,t,n,r,i,o){var a="width"===t?1:0,s=0,u=0;if(n===(r?"border":"content"))return 0;for(;a<4;a+=2)"margin"===n&&(u+=E.css(e,n+ne[a],!0,i)),r?("content"===n&&(u-=E.css(e,"padding"+ne[a],!0,i)),"margin"!==n&&(u-=E.css(e,"border"+ne[a]+"Width",!0,i))):(u+=E.css(e,"padding"+ne[a],!0,i),"padding"!==n?u+=E.css(e,"border"+ne[a]+"Width",!0,i):s+=E.css(e,"border"+ne[a]+"Width",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e["offset"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function et(e,t,n){var r=Re(e),i=(!m.boxSizingReliable()||n)&&"border-box"===E.css(e,"boxSizing",!1,r),o=i,a=We(e,t,r),s="offset"+t[0].toUpperCase()+t.slice(1);if(Ie.test(a)){if(!n)return a;a="auto"}return(!m.boxSizingReliable()&&i||!m.reliableTrDimensions()&&S(e,"tr")||"auto"===a||!parseFloat(a)&&"inline"===E.css(e,"display",!1,r))&&e.getClientRects().length&&(i="border-box"===E.css(e,"boxSizing",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Ze(e,t,n||(i?"border":"content"),o,r,a)+"px"}E.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=We(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=V(t),u=Ye.test(t),l=e.style;if(u||(t=Ue(s)),a=E.cssHooks[t]||E.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];"string"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=function(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return E.css(e,t,"")},u=s(),l=n&&n[3]||(E.cssNumber[t]?"":"px"),c=e.nodeType&&(E.cssNumber[t]||"px"!==l&&+u)&&te.exec(E.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)E.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,E.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}(e,t,i),o="number"),null!=n&&n==n&&("number"!==o||u||(n+=i&&i[3]||(E.cssNumber[s]?"":"px")),m.clearCloneStyle||""!==n||0!==t.indexOf("background")||(l[t]="inherit"),a&&"set"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=V(t);return Ye.test(t)||(t=Ue(s)),(a=E.cssHooks[t]||E.cssHooks[s])&&"get"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=We(e,t,r)),"normal"===i&&t in Ke&&(i=Ke[t]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),E.each(["height","width"],function(e,u){E.cssHooks[u]={get:function(e,t,n){if(t)return!Qe.test(E.css(e,"display"))||e.getClientRects().length&&e.getBoundingClientRect().width?et(e,u,n):Be(e,Ge,function(){return et(e,u,n)})},set:function(e,t,n){var r,i=Re(e),o=!m.scrollboxSize()&&"absolute"===i.position,a=(o||n)&&"border-box"===E.css(e,"boxSizing",!1,i),s=n?Ze(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e["offset"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Ze(e,u,"border",!1,i)-.5)),s&&(r=te.exec(t))&&"px"!==(r[3]||"px")&&(e.style[u]=t,t=E.css(e,u)),Je(0,t,s)}}}),E.cssHooks.marginLeft=Fe(m.reliableMarginLeft,function(e,t){if(t)return(parseFloat(We(e,"marginLeft"))||e.getBoundingClientRect().left-Be(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+"px"}),E.each({margin:"",padding:"",border:"Width"},function(i,o){E.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r="string"==typeof e?e.split(" "):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},"margin"!==i&&(E.cssHooks[i+o].set=Je)}),E.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Re(e),i=t.length;a<i;a++)o[t[a]]=E.css(e,t[a],!1,r);return o}return void 0!==n?E.style(e,t,n):E.css(e,t)},e,t,1<arguments.length)}}),E.fn.delay=function(r,e){return r=E.fx&&E.fx.speeds[r]||r,e=e||"fx",this.queue(e,function(e,t){var n=g.setTimeout(e,r);t.stop=function(){g.clearTimeout(n)}})},Ve=w.createElement("input"),Xe=w.createElement("select").appendChild(w.createElement("option")),Ve.type="checkbox",m.checkOn=""!==Ve.value,m.optSelected=Xe.selected,(Ve=w.createElement("input")).value="t",Ve.type="radio",m.radioValue="t"===Ve.value;var tt,nt=E.expr.attrHandle;E.fn.extend({attr:function(e,t){return $(this,E.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){E.removeAttr(this,e)})}}),E.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?E.prop(e,t,n):(1===o&&E.isXMLDoc(e)||(i=E.attrHooks[t.toLowerCase()]||(E.expr.match.bool.test(t)?tt:void 0)),void 0!==n?null===n?void E.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:null==(r=E.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!m.radioValue&&"radio"===t&&S(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(H);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),tt={set:function(e,t,n){return!1===t?E.removeAttr(e,n):e.setAttribute(n,n),n}},E.each(E.expr.match.bool.source.match(/\w+/g),function(e,t){var a=nt[t]||E.find.attr;nt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=nt[o],nt[o]=r,r=null!=a(e,t,n)?o:null,nt[o]=i),r}});var rt=/^(?:input|select|textarea|button)$/i,it=/^(?:a|area)$/i;function ot(e){return(e.match(H)||[]).join(" ")}function at(e){return e.getAttribute&&e.getAttribute("class")||""}function st(e){return Array.isArray(e)?e:"string"==typeof e&&e.match(H)||[]}E.fn.extend({prop:function(e,t){return $(this,E.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[E.propFix[e]||e]})}}),E.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&E.isXMLDoc(e)||(t=E.propFix[t]||t,i=E.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=E.find.attr(e,"tabindex");return t?parseInt(t,10):rt.test(e.nodeName)||it.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),m.optSelected||(E.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),E.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){E.propFix[this.toLowerCase()]=this}),E.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(b(t))return this.each(function(e){E(this).addClass(t.call(this,e,at(this)))});if((e=st(t)).length)while(n=this[u++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=e[a++])r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=ot(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(b(t))return this.each(function(e){E(this).removeClass(t.call(this,e,at(this)))});if(!arguments.length)return this.attr("class","");if((e=st(t)).length)while(n=this[u++])if(i=at(n),r=1===n.nodeType&&" "+ot(i)+" "){a=0;while(o=e[a++])while(-1<r.indexOf(" "+o+" "))r=r.replace(" "+o+" "," ");i!==(s=ot(r))&&n.setAttribute("class",s)}return this},toggleClass:function(i,t){var o=typeof i,a="string"===o||Array.isArray(i);return"boolean"==typeof t&&a?t?this.addClass(i):this.removeClass(i):b(i)?this.each(function(e){E(this).toggleClass(i.call(this,e,at(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=E(this),r=st(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&"boolean"!==o||((e=at(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===i?"":Y.get(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;t=" "+e+" ";while(n=this[r++])if(1===n.nodeType&&-1<(" "+ot(at(n))+" ").indexOf(t))return!0;return!1}});var ut=/\r/g;E.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=b(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,E(this).val()):n)?t="":"number"==typeof t?t+="":Array.isArray(t)&&(t=E.map(t,function(e){return null==e?"":e+""})),(r=E.valHooks[this.type]||E.valHooks[this.nodeName.toLowerCase()])&&"set"in r&&void 0!==r.set(this,t,"value")||(this.value=t))})):t?(r=E.valHooks[t.type]||E.valHooks[t.nodeName.toLowerCase()])&&"get"in r&&void 0!==(e=r.get(t,"value"))?e:"string"==typeof(e=t.value)?e.replace(ut,""):null==e?"":e:void 0}}),E.extend({valHooks:{option:{get:function(e){var t=E.find.attr(e,"value");return null!=t?t:ot(E.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a="select-one"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!S(n.parentNode,"optgroup"))){if(t=E(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=E.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<E.inArray(E.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),E.each(["radio","checkbox"],function(){E.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<E.inArray(E(e).val(),t)}},m.checkOn||(E.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})}),m.focusin="onfocusin"in g;var lt=/^(?:focusinfocus|focusoutblur)$/,ct=function(e){e.stopPropagation()};E.extend(E.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,d=[n||w],p=y.call(e,"type")?e.type:e,h=y.call(e,"namespace")?e.namespace.split("."):[];if(o=f=a=n=n||w,3!==n.nodeType&&8!==n.nodeType&&!lt.test(p+E.event.triggered)&&(-1<p.indexOf(".")&&(p=(h=p.split(".")).shift(),h.sort()),u=p.indexOf(":")<0&&"on"+p,(e=e[E.expando]?e:new E.Event(p,"object"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join("."),e.rnamespace=e.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:E.makeArray(t,[e]),c=E.event.special[p]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||p,lt.test(s+p)||(o=o.parentNode);o;o=o.parentNode)d.push(o),a=o;a===(n.ownerDocument||w)&&d.push(a.defaultView||a.parentWindow||g)}i=0;while((o=d[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||p,(l=(Y.get(o,"events")||Object.create(null))[e.type]&&Y.get(o,"handle"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&X(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=p,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(d.pop(),t)||!X(n)||u&&b(n[p])&&!x(n)&&((a=n[u])&&(n[u]=null),E.event.triggered=p,e.isPropagationStopped()&&f.addEventListener(p,ct),n[p](),e.isPropagationStopped()&&f.removeEventListener(p,ct),E.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=E.extend(new E.Event,n,{type:e,isSimulated:!0});E.event.trigger(r,null,t)}}),E.fn.extend({trigger:function(e,t){return this.each(function(){E.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return E.event.trigger(e,t,n,!0)}}),m.focusin||E.each({focus:"focusin",blur:"focusout"},function(n,r){var i=function(e){E.event.simulate(r,e.target,E.event.fix(e))};E.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}}),E.parseXML=function(e){var t;if(!e||"string"!=typeof e)return null;try{t=(new g.DOMParser).parseFromString(e,"text/xml")}catch(e){t=void 0}return t&&!t.getElementsByTagName("parsererror").length||E.error("Invalid XML: "+e),t};var ft,dt=/\[\]$/,pt=/\r?\n/g,ht=/^(?:submit|button|image|reset|file)$/i,gt=/^(?:input|select|textarea|keygen)/i;function vt(n,e,r,i){var t;if(Array.isArray(e))E.each(e,function(e,t){r||dt.test(n)?i(n,t):vt(n+"["+("object"==typeof t&&null!=t?e:"")+"]",t,r,i)});else if(r||"object"!==T(e))i(n,e);else for(t in e)vt(n+"["+t+"]",e[t],r,i)}E.param=function(e,t){var n,r=[],i=function(e,t){var n=b(t)?t():t;r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(null==n?"":n)};if(null==e)return"";if(Array.isArray(e)||e.jquery&&!E.isPlainObject(e))E.each(e,function(){i(this.name,this.value)});else for(n in e)vt(n,e[n],t,i);return r.join("&")},E.fn.extend({serialize:function(){return E.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=E.prop(this,"elements");return e?E.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!E(this).is(":disabled")&&gt.test(this.nodeName)&&!ht.test(e)&&(this.checked||!fe.test(e))}).map(function(e,t){var n=E(this).val();return null==n?null:Array.isArray(n)?E.map(n,function(e){return{name:t.name,value:e.replace(pt,"\r\n")}}):{name:t.name,value:n.replace(pt,"\r\n")}}).get()}}),E.fn.extend({wrapAll:function(e){var t;return this[0]&&(b(e)&&(e=e.call(this[0])),t=E(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return b(n)?this.each(function(e){E(this).wrapInner(n.call(this,e))}):this.each(function(){var e=E(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=b(t);return this.each(function(e){E(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not("body").each(function(){E(this).replaceWith(this.childNodes)}),this}}),E.expr.pseudos.hidden=function(e){return!E.expr.pseudos.visible(e)},E.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},m.createHTMLDocument=((ft=w.implementation.createHTMLDocument("").body).innerHTML="<form></form><form></form>",2===ft.childNodes.length),E.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(m.createHTMLDocument?((r=(t=w.implementation.createHTMLDocument("")).createElement("base")).href=w.location.href,t.head.appendChild(r)):t=w),o=!n&&[],(i=k.exec(e))?[t.createElement(i[1])]:(i=me([e],t,o),o&&o.length&&E(o).remove(),E.merge([],i.childNodes)));var r,i,o},E.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=E.css(e,"position"),c=E(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=E.css(e,"top"),u=E.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),b(t)&&(t=t.call(e,n,E.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):("number"==typeof f.top&&(f.top+="px"),"number"==typeof f.left&&(f.left+="px"),c.css(f))}},E.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){E.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===E.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===E.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=E(e).offset()).top+=E.css(e,"borderTopWidth",!0),i.left+=E.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-E.css(r,"marginTop",!0),left:t.left-i.left-E.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===E.css(e,"position"))e=e.offsetParent;return e||re})}}),E.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;E.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),E.each(["top","left"],function(e,n){E.cssHooks[n]=Fe(m.pixelPosition,function(e,t){if(t)return t=We(e,n),Ie.test(t)?E(e).position()[n]+"px":t})}),E.each({Height:"height",Width:"width"},function(a,s){E.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){E.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?E.css(e,t,i):E.style(e,t,n,i)},s,n?e:void 0,n)}})}),E.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),E.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){E.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var yt=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;E.proxy=function(e,t){var n,r,i;if("string"==typeof t&&(n=e[t],t=e,e=n),b(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||E.guid++,i},E.holdReady=function(e){e?E.readyWait++:E.ready(!0)},E.isArray=Array.isArray,E.parseJSON=JSON.parse,E.nodeName=S,E.isFunction=b,E.isWindow=x,E.camelCase=V,E.type=T,E.now=Date.now,E.isNumeric=function(e){var t=E.type(e);return("number"===t||"string"===t)&&!isNaN(e-parseFloat(e))},E.trim=function(e){return null==e?"":(e+"").replace(yt,"")},"function"==typeof define&&define.amd&&define("jquery",[],function(){return E});var mt=g.jQuery,bt=g.$;return E.noConflict=function(e){return g.$===E&&(g.$=bt),e&&g.jQuery===E&&(g.jQuery=mt),E},"undefined"==typeof e&&(g.jQuery=g.$=E),E});
/*!
* typeahead.js 0.10.5
* https://github.com/twitter/typeahead.js
* Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT
*/
!function(t){var e=function(){"use strict";return{isMsie:function(){return/(msie|trident)/i.test(navigator.userAgent)?navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2]:!1},isBlankString:function(t){return!t||/^\s*$/.test(t)},escapeRegExChars:function(t){return t.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&")},isString:function(t){return"string"==typeof t},isNumber:function(t){return"number"==typeof t},isArray:t.isArray,isFunction:t.isFunction,isObject:t.isPlainObject,isUndefined:function(t){return"undefined"==typeof t},toStr:function(t){return e.isUndefined(t)||null===t?"":t+""},bind:t.proxy,each:function(e,n){function i(t,e){return n(e,t)}t.each(e,i)},map:t.map,filter:t.grep,every:function(e,n){var i=!0;return e?(t.each(e,function(t,r){return(i=n.call(null,r,t,e))?void 0:!1}),!!i):i},some:function(e,n){var i=!1;return e?(t.each(e,function(t,r){return(i=n.call(null,r,t,e))?!1:void 0}),!!i):i},mixin:t.extend,getUniqueId:function(){var t=0;return function(){return t++}}(),templatify:function(e){function n(){return String(e)}return t.isFunction(e)?e:n},defer:function(t){setTimeout(t,0)},debounce:function(t,e,n){var i,r;return function(){var s,o,u=this,a=arguments;return s=function(){i=null,n||(r=t.apply(u,a))},o=n&&!i,clearTimeout(i),i=setTimeout(s,e),o&&(r=t.apply(u,a)),r}},throttle:function(t,e){var n,i,r,s,o,u;return o=0,u=function(){o=new Date,r=null,s=t.apply(n,i)},function(){var a=new Date,c=e-(a-o);return n=this,i=arguments,0>=c?(clearTimeout(r),r=null,o=a,s=t.apply(n,i)):r||(r=setTimeout(u,c)),s}},noop:function(){}}}(),n="0.10.5",i=function(){"use strict";function t(t){return t=e.toStr(t),t?t.split(/\s+/):[]}function n(t){return t=e.toStr(t),t?t.split(/\W+/):[]}function i(t){return function(){var n=[].slice.call(arguments,0);return function(i){var r=[];return e.each(n,function(n){r=r.concat(t(e.toStr(i[n])))}),r}}}return{nonword:n,whitespace:t,obj:{nonword:i(n),whitespace:i(t)}}}(),r=function(){"use strict";function n(n){this.maxSize=e.isNumber(n)?n:100,this.reset(),this.maxSize<=0&&(this.set=this.get=t.noop)}function i(){this.head=this.tail=null}function r(t,e){this.key=t,this.val=e,this.prev=this.next=null}return e.mixin(n.prototype,{set:function(t,e){var n,i=this.list.tail;this.size>=this.maxSize&&(this.list.remove(i),delete this.hash[i.key]),(n=this.hash[t])?(n.val=e,this.list.moveToFront(n)):(n=new r(t,e),this.list.add(n),this.hash[t]=n,this.size++)},get:function(t){var e=this.hash[t];return e?(this.list.moveToFront(e),e.val):void 0},reset:function(){this.size=0,this.hash={},this.list=new i}}),e.mixin(i.prototype,{add:function(t){this.head&&(t.next=this.head,this.head.prev=t),this.head=t,this.tail=this.tail||t},remove:function(t){t.prev?t.prev.next=t.next:this.head=t.next,t.next?t.next.prev=t.prev:this.tail=t.prev},moveToFront:function(t){this.remove(t),this.add(t)}}),n}(),s=function(){"use strict";function t(t){this.prefix=["__",t,"__"].join(""),this.ttlKey="__ttl__",this.keyMatcher=new RegExp("^"+e.escapeRegExChars(this.prefix))}function n(){return(new Date).getTime()}function i(t){return JSON.stringify(e.isUndefined(t)?null:t)}function r(t){return JSON.parse(t)}var s,o;try{s=window.localStorage,s.setItem("~~~","!"),s.removeItem("~~~")}catch(u){s=null}return o=s&&window.JSON?{_prefix:function(t){return this.prefix+t},_ttlKey:function(t){return this._prefix(t)+this.ttlKey},get:function(t){return this.isExpired(t)&&this.remove(t),r(s.getItem(this._prefix(t)))},set:function(t,r,o){return e.isNumber(o)?s.setItem(this._ttlKey(t),i(n()+o)):s.removeItem(this._ttlKey(t)),s.setItem(this._prefix(t),i(r))},remove:function(t){return s.removeItem(this._ttlKey(t)),s.removeItem(this._prefix(t)),this},clear:function(){var t,e,n=[],i=s.length;for(t=0;i>t;t++)(e=s.key(t)).match(this.keyMatcher)&&n.push(e.replace(this.keyMatcher,""));for(t=n.length;t--;)this.remove(n[t]);return this},isExpired:function(t){var i=r(s.getItem(this._ttlKey(t)));return e.isNumber(i)&&n()>i?!0:!1}}:{get:e.noop,set:e.noop,remove:e.noop,clear:e.noop,isExpired:e.noop},e.mixin(t.prototype,o),t}(),o=function(){"use strict";function n(e){e=e||{},this.cancelled=!1,this.lastUrl=null,this._send=e.transport?i(e.transport):t.ajax,this._get=e.rateLimiter?e.rateLimiter(this._get):this._get,this._cache=e.cache===!1?new r(0):a}function i(n){return function(i,r){function s(t){e.defer(function(){u.resolve(t)})}function o(t){e.defer(function(){u.reject(t)})}var u=t.Deferred();return n(i,r,s,o),u}}var s=0,o={},u=6,a=new r(10);return n.setMaxPendingRequests=function(t){u=t},n.resetCache=function(){a.reset()},e.mixin(n.prototype,{_get:function(t,e,n){function i(e){n&&n(null,e),h._cache.set(t,e)}function r(){n&&n(!0)}function a(){s--,delete o[t],h.onDeckRequestArgs&&(h._get.apply(h,h.onDeckRequestArgs),h.onDeckRequestArgs=null)}var c,h=this;this.cancelled||t!==this.lastUrl||((c=o[t])?c.done(i).fail(r):u>s?(s++,o[t]=this._send(t,e).done(i).fail(r).always(a)):this.onDeckRequestArgs=[].slice.call(arguments,0))},get:function(t,n,i){var r;return e.isFunction(n)&&(i=n,n={}),this.cancelled=!1,this.lastUrl=t,(r=this._cache.get(t))?e.defer(function(){i&&i(null,r)}):this._get(t,n,i),!!r},cancel:function(){this.cancelled=!0}}),n}(),u=function(){"use strict";function n(e){e=e||{},e.datumTokenizer&&e.queryTokenizer||t.error("datumTokenizer and queryTokenizer are both required"),this.datumTokenizer=e.datumTokenizer,this.queryTokenizer=e.queryTokenizer,this.reset()}function i(t){return t=e.filter(t,function(t){return!!t}),t=e.map(t,function(t){return t.toLowerCase()})}function r(){return{ids:[],children:{}}}function s(t){for(var e={},n=[],i=0,r=t.length;r>i;i++)e[t[i]]||(e[t[i]]=!0,n.push(t[i]));return n}function o(t,e){function n(t,e){return t-e}var i=0,r=0,s=[];t=t.sort(n),e=e.sort(n);for(var o=t.length,u=e.length;o>i&&u>r;)t[i]<e[r]?i++:t[i]>e[r]?r++:(s.push(t[i]),i++,r++);return s}return e.mixin(n.prototype,{bootstrap:function(t){this.datums=t.datums,this.trie=t.trie},add:function(t){var n=this;t=e.isArray(t)?t:[t],e.each(t,function(t){var s,o;s=n.datums.push(t)-1,o=i(n.datumTokenizer(t)),e.each(o,function(t){var e,i,o;for(e=n.trie,i=t.split("");o=i.shift();)e=e.children[o]||(e.children[o]=r()),e.ids.push(s)})})},get:function(t){var n,r,u=this;return n=i(this.queryTokenizer(t)),e.each(n,function(t){var e,n,i,s;if(r&&0===r.length)return!1;for(e=u.trie,n=t.split("");e&&(i=n.shift());)e=e.children[i];return e&&0===n.length?(s=e.ids.slice(0),void(r=r?o(r,s):s)):(r=[],!1)}),r?e.map(s(r),function(t){return u.datums[t]}):[]},reset:function(){this.datums=[],this.trie=r()},serialize:function(){return{datums:this.datums,trie:this.trie}}}),n}(),a=function(){"use strict";function i(t){return t.local||null}function r(i){var r,s;return s={url:null,thumbprint:"",ttl:864e5,filter:null,ajax:{}},(r=i.prefetch||null)&&(r=e.isString(r)?{url:r}:r,r=e.mixin(s,r),r.thumbprint=n+r.thumbprint,r.ajax.type=r.ajax.type||"GET",r.ajax.dataType=r.ajax.dataType||"json",!r.url&&t.error("prefetch requires url to be set")),r}function s(n){function i(t){return function(n){return e.debounce(n,t)}}function r(t){return function(n){return e.throttle(n,t)}}var s,o;return o={url:null,cache:!0,wildcard:"%QUERY",replace:null,rateLimitBy:"debounce",rateLimitWait:300,send:null,filter:null,ajax:{}},(s=n.remote||null)&&(s=e.isString(s)?{url:s}:s,s=e.mixin(o,s),s.rateLimiter=/^throttle$/i.test(s.rateLimitBy)?r(s.rateLimitWait):i(s.rateLimitWait),s.ajax.type=s.ajax.type||"GET",s.ajax.dataType=s.ajax.dataType||"json",delete s.rateLimitBy,delete s.rateLimitWait,!s.url&&t.error("remote requires url to be set")),s}return{local:i,prefetch:r,remote:s}}();!function(n){"use strict";function r(e){e&&(e.local||e.prefetch||e.remote)||t.error("one of local, prefetch, or remote is required"),this.limit=e.limit||5,this.sorter=c(e.sorter),this.dupDetector=e.dupDetector||h,this.local=a.local(e),this.prefetch=a.prefetch(e),this.remote=a.remote(e),this.cacheKey=this.prefetch?this.prefetch.cacheKey||this.prefetch.url:null,this.index=new u({datumTokenizer:e.datumTokenizer,queryTokenizer:e.queryTokenizer}),this.storage=this.cacheKey?new s(this.cacheKey):null}function c(t){function n(e){return e.sort(t)}function i(t){return t}return e.isFunction(t)?n:i}function h(){return!1}var l,d;return l=n.Bloodhound,d={data:"data",protocol:"protocol",thumbprint:"thumbprint"},n.Bloodhound=r,r.noConflict=function(){return n.Bloodhound=l,r},r.tokenizers=i,e.mixin(r.prototype,{_loadPrefetch:function(e){function n(t){s.clear(),s.add(e.filter?e.filter(t):t),s._saveToStorage(s.index.serialize(),e.thumbprint,e.ttl)}var i,r,s=this;return(i=this._readFromStorage(e.thumbprint))?(this.index.bootstrap(i),r=t.Deferred().resolve()):r=t.ajax(e.url,e.ajax).done(n),r},_getFromRemote:function(t,e){function n(t,n){e(t?[]:s.remote.filter?s.remote.filter(n):n)}var i,r,s=this;if(this.transport)return t=t||"",r=encodeURIComponent(t),i=this.remote.replace?this.remote.replace(this.remote.url,t):this.remote.url.replace(this.remote.wildcard,r),this.transport.get(i,this.remote.ajax,n)},_cancelLastRemoteRequest:function(){this.transport&&this.transport.cancel()},_saveToStorage:function(t,e,n){this.storage&&(this.storage.set(d.data,t,n),this.storage.set(d.protocol,location.protocol,n),this.storage.set(d.thumbprint,e,n))},_readFromStorage:function(t){var e,n={};return this.storage&&(n.data=this.storage.get(d.data),n.protocol=this.storage.get(d.protocol),n.thumbprint=this.storage.get(d.thumbprint)),e=n.thumbprint!==t||n.protocol!==location.protocol,n.data&&!e?n.data:null},_initialize:function(){function n(){r.add(e.isFunction(s)?s():s)}var i,r=this,s=this.local;return i=this.prefetch?this._loadPrefetch(this.prefetch):t.Deferred().resolve(),s&&i.done(n),this.transport=this.remote?new o(this.remote):null,this.initPromise=i.promise()},initialize:function(t){return!this.initPromise||t?this._initialize():this.initPromise},add:function(t){this.index.add(t)},get:function(t,n){function i(t){var i=s.slice(0);e.each(t,function(t){var n;return n=e.some(i,function(e){return r.dupDetector(t,e)}),!n&&i.push(t),i.length<r.limit}),n&&n(r.sorter(i))}var r=this,s=[],o=!1;s=this.index.get(t),s=this.sorter(s).slice(0,this.limit),s.length<this.limit?o=this._getFromRemote(t,i):this._cancelLastRemoteRequest(),o||(s.length>0||!this.transport)&&n&&n(s)},clear:function(){this.index.reset()},clearPrefetchCache:function(){this.storage&&this.storage.clear()},clearRemoteCache:function(){this.transport&&o.resetCache()},ttAdapter:function(){return e.bind(this.get,this)}}),r}(this);var c=function(){return{wrapper:'<span class="twitter-typeahead"></span>',dropdown:'<span class="tt-dropdown-menu"></span>',dataset:'<div class="tt-dataset-%CLASS%"></div>',suggestions:'<span class="tt-suggestions"></span>',suggestion:'<div class="tt-suggestion"></div>'}}(),h=function(){"use strict";var t={wrapper:{position:"relative",display:"inline-block"},hint:{position:"absolute",top:"0",left:"0",borderColor:"transparent",boxShadow:"none",opacity:"1"},input:{position:"relative",verticalAlign:"top",backgroundColor:"transparent"},inputWithNoHint:{position:"relative",verticalAlign:"top"},dropdown:{position:"absolute",top:"100%",left:"0",zIndex:"100",display:"none"},suggestions:{display:"block"},suggestion:{whiteSpace:"nowrap",cursor:"pointer"},suggestionChild:{whiteSpace:"normal"},ltr:{left:"0",right:"auto"},rtl:{left:"auto",right:" 0"}};return e.isMsie()&&e.mixin(t.input,{backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)"}),e.isMsie()&&e.isMsie()<=7&&e.mixin(t.input,{marginTop:"-1px"}),t}(),l=function(){"use strict";function n(e){e&&e.el||t.error("EventBus initialized without el"),this.$el=t(e.el)}var i="typeahead:";return e.mixin(n.prototype,{trigger:function(t){var e=[].slice.call(arguments,1);this.$el.trigger(i+t,e)}}),n}(),d=function(){"use strict";function t(t,e,n,i){var r;if(!n)return this;for(e=e.split(a),n=i?u(n,i):n,this._callbacks=this._callbacks||{};r=e.shift();)this._callbacks[r]=this._callbacks[r]||{sync:[],async:[]},this._callbacks[r][t].push(n);return this}function e(e,n,i){return t.call(this,"async",e,n,i)}function n(e,n,i){return t.call(this,"sync",e,n,i)}function i(t){var e;if(!this._callbacks)return this;for(t=t.split(a);e=t.shift();)delete this._callbacks[e];return this}function r(t){var e,n,i,r,o;if(!this._callbacks)return this;for(t=t.split(a),i=[].slice.call(arguments,1);(e=t.shift())&&(n=this._callbacks[e]);)r=s(n.sync,this,[e].concat(i)),o=s(n.async,this,[e].concat(i)),r()&&c(o);return this}function s(t,e,n){function i(){for(var i,r=0,s=t.length;!i&&s>r;r+=1)i=t[r].apply(e,n)===!1;return!i}return i}function o(){var t;return t=window.setImmediate?function(t){setImmediate(function(){t()})}:function(t){setTimeout(function(){t()},0)}}function u(t,e){return t.bind?t.bind(e):function(){t.apply(e,[].slice.call(arguments,0))}}var a=/\s+/,c=o();return{onSync:n,onAsync:e,off:i,trigger:r}}(),p=function(t){"use strict";function n(t,n,i){for(var r,s=[],o=0,u=t.length;u>o;o++)s.push(e.escapeRegExChars(t[o]));return r=i?"\\b("+s.join("|")+")\\b":"("+s.join("|")+")",n?new RegExp(r):new RegExp(r,"i")}var i={node:null,pattern:null,tagName:"strong",className:null,wordsOnly:!1,caseSensitive:!1};return function(r){function s(e){var n,i,s;return(n=u.exec(e.data))&&(s=t.createElement(r.tagName),r.className&&(s.className=r.className),i=e.splitText(n.index),i.splitText(n[0].length),s.appendChild(i.cloneNode(!0)),e.parentNode.replaceChild(s,i)),!!n}function o(t,e){for(var n,i=3,r=0;r<t.childNodes.length;r++)n=t.childNodes[r],n.nodeType===i?r+=e(n)?1:0:o(n,e)}var u;r=e.mixin({},i,r),r.node&&r.pattern&&(r.pattern=e.isArray(r.pattern)?r.pattern:[r.pattern],u=n(r.pattern,r.caseSensitive,r.wordsOnly),o(r.node,s))}}(window.document),f=function(){"use strict";function n(n){var r,s,u,a,c=this;n=n||{},n.input||t.error("input is missing"),r=e.bind(this._onBlur,this),s=e.bind(this._onFocus,this),u=e.bind(this._onKeydown,this),a=e.bind(this._onInput,this),this.$hint=t(n.hint),this.$input=t(n.input).on("blur.tt",r).on("focus.tt",s).on("keydown.tt",u),0===this.$hint.length&&(this.setHint=this.getHint=this.clearHint=this.clearHintIfInvalid=e.noop),e.isMsie()?this.$input.on("keydown.tt keypress.tt cut.tt paste.tt",function(t){o[t.which||t.keyCode]||e.defer(e.bind(c._onInput,c,t))}):this.$input.on("input.tt",a),this.query=this.$input.val(),this.$overflowHelper=i(this.$input)}function i(e){return t('<pre aria-hidden="true"></pre>').css({position:"absolute",visibility:"hidden",whiteSpace:"pre",fontFamily:e.css("font-family"),fontSize:e.css("font-size"),fontStyle:e.css("font-style"),fontVariant:e.css("font-variant"),fontWeight:e.css("font-weight"),wordSpacing:e.css("word-spacing"),letterSpacing:e.css("letter-spacing"),textIndent:e.css("text-indent"),textRendering:e.css("text-rendering"),textTransform:e.css("text-transform")}).insertAfter(e)}function r(t,e){return n.normalizeQuery(t)===n.normalizeQuery(e)}function s(t){return t.altKey||t.ctrlKey||t.metaKey||t.shiftKey}var o;return o={9:"tab",27:"esc",37:"left",39:"right",13:"enter",38:"up",40:"down"},n.normalizeQuery=function(t){return(t||"").replace(/^\s*/g,"").replace(/\s{2,}/g," ")},e.mixin(n.prototype,d,{_onBlur:function(){this.resetInputValue(),this.trigger("blurred")},_onFocus:function(){this.trigger("focused")},_onKeydown:function(t){var e=o[t.which||t.keyCode];this._managePreventDefault(e,t),e&&this._shouldTrigger(e,t)&&this.trigger(e+"Keyed",t)},_onInput:function(){this._checkInputValue()},_managePreventDefault:function(t,e){var n,i,r;switch(t){case"tab":i=this.getHint(),r=this.getInputValue(),n=i&&i!==r&&!s(e);break;case"up":case"down":n=!s(e);break;default:n=!1}n&&e.preventDefault()},_shouldTrigger:function(t,e){var n;switch(t){case"tab":n=!s(e);break;default:n=!0}return n},_checkInputValue:function(){var t,e,n;t=this.getInputValue(),e=r(t,this.query),n=e?this.query.length!==t.length:!1,this.query=t,e?n&&this.trigger("whitespaceChanged",this.query):this.trigger("queryChanged",this.query)},focus:function(){this.$input.focus()},blur:function(){this.$input.blur()},getQuery:function(){return this.query},setQuery:function(t){this.query=t},getInputValue:function(){return this.$input.val()},setInputValue:function(t,e){this.$input.val(t),e?this.clearHint():this._checkInputValue()},resetInputValue:function(){this.setInputValue(this.query,!0)},getHint:function(){return this.$hint.val()},setHint:function(t){this.$hint.val(t)},clearHint:function(){this.setHint("")},clearHintIfInvalid:function(){var t,e,n,i;t=this.getInputValue(),e=this.getHint(),n=t!==e&&0===e.indexOf(t),i=""!==t&&n&&!this.hasOverflow(),!i&&this.clearHint()},getLanguageDirection:function(){return(this.$input.css("direction")||"ltr").toLowerCase()},hasOverflow:function(){var t=this.$input.width()-2;return this.$overflowHelper.text(this.getInputValue()),this.$overflowHelper.width()>=t},isCursorAtEnd:function(){var t,n,i;return t=this.$input.val().length,n=this.$input[0].selectionStart,e.isNumber(n)?n===t:document.selection?(i=document.selection.createRange(),i.moveStart("character",-t),t===i.text.length):!0},destroy:function(){this.$hint.off(".tt"),this.$input.off(".tt"),this.$hint=this.$input=this.$overflowHelper=null}}),n}(),g=function(){"use strict";function n(n){n=n||{},n.templates=n.templates||{},n.source||t.error("missing source"),n.name&&!s(n.name)&&t.error("invalid dataset name: "+n.name),this.query=null,this.highlight=!!n.highlight,this.name=n.name||e.getUniqueId(),this.source=n.source,this.displayFn=i(n.display||n.displayKey),this.templates=r(n.templates,this.displayFn),this.$el=t(c.dataset.replace("%CLASS%",this.name))}function i(t){function n(e){return e[t]}return t=t||"value",e.isFunction(t)?t:n}function r(t,n){function i(t){return"<p>"+n(t)+"</p>"}return{empty:t.empty&&e.templatify(t.empty),header:t.header&&e.templatify(t.header),footer:t.footer&&e.templatify(t.footer),suggestion:t.suggestion||i}}function s(t){return/^[_a-zA-Z0-9-]+$/.test(t)}var o="ttDataset",u="ttValue",a="ttDatum";return n.extractDatasetName=function(e){return t(e).data(o)},n.extractValue=function(e){return t(e).data(u)},n.extractDatum=function(e){return t(e).data(a)},e.mixin(n.prototype,d,{_render:function(n,i){function r(){return g.templates.empty({query:n,isEmpty:!0})}function s(){function r(e){var n;return n=t(c.suggestion).append(g.templates.suggestion(e)).data(o,g.name).data(u,g.displayFn(e)).data(a,e),n.children().each(function(){t(this).css(h.suggestionChild)}),n}var s,l;return s=t(c.suggestions).css(h.suggestions),l=e.map(i,r),s.append.apply(s,l),g.highlight&&p({className:"tt-highlight",node:s[0],pattern:n}),s}function l(){return g.templates.header({query:n,isEmpty:!f})}function d(){return g.templates.footer({query:n,isEmpty:!f})}if(this.$el){var f,g=this;this.$el.empty(),f=i&&i.length,!f&&this.templates.empty?this.$el.html(r()).prepend(g.templates.header?l():null).append(g.templates.footer?d():null):f&&this.$el.html(s()).prepend(g.templates.header?l():null).append(g.templates.footer?d():null),this.trigger("rendered")}},getRoot:function(){return this.$el},update:function(t){function e(e){n.canceled||t!==n.query||n._render(t,e)}var n=this;this.query=t,this.canceled=!1,this.source(t,e)},cancel:function(){this.canceled=!0},clear:function(){this.cancel(),this.$el.empty(),this.trigger("rendered")},isEmpty:function(){return this.$el.is(":empty")},destroy:function(){this.$el=null}}),n}(),m=function(){"use strict";function n(n){var r,s,o,u=this;n=n||{},n.menu||t.error("menu is required"),this.isOpen=!1,this.isEmpty=!0,this.datasets=e.map(n.datasets,i),r=e.bind(this._onSuggestionClick,this),s=e.bind(this._onSuggestionMouseEnter,this),o=e.bind(this._onSuggestionMouseLeave,this),this.$menu=t(n.menu).on("click.tt",".tt-suggestion",r).on("mouseenter.tt",".tt-suggestion",s).on("mouseleave.tt",".tt-suggestion",o),e.each(this.datasets,function(t){u.$menu.append(t.getRoot()),t.onSync("rendered",u._onRendered,u)})}function i(t){return new g(t)}return e.mixin(n.prototype,d,{_onSuggestionClick:function(e){this.trigger("suggestionClicked",t(e.currentTarget))},_onSuggestionMouseEnter:function(e){this._removeCursor(),this._setCursor(t(e.currentTarget),!0)},_onSuggestionMouseLeave:function(){this._removeCursor()},_onRendered:function(){function t(t){return t.isEmpty()}this.isEmpty=e.every(this.datasets,t),this.isEmpty?this._hide():this.isOpen&&this._show(),this.trigger("datasetRendered")},_hide:function(){this.$menu.hide()},_show:function(){this.$menu.css("display","block")},_getSuggestions:function(){return this.$menu.find(".tt-suggestion")},_getCursor:function(){return this.$menu.find(".tt-cursor").first()},_setCursor:function(t,e){t.first().addClass("tt-cursor"),!e&&this.trigger("cursorMoved")},_removeCursor:function(){this._getCursor().removeClass("tt-cursor")},_moveCursor:function(t){var e,n,i,r;if(this.isOpen){if(n=this._getCursor(),e=this._getSuggestions(),this._removeCursor(),i=e.index(n)+t,i=(i+1)%(e.length+1)-1,-1===i)return void this.trigger("cursorRemoved");-1>i&&(i=e.length-1),this._setCursor(r=e.eq(i)),this._ensureVisible(r)}},_ensureVisible:function(t){var e,n,i,r;e=t.position().top,n=e+t.outerHeight(!0),i=this.$menu.scrollTop(),r=this.$menu.height()+parseInt(this.$menu.css("paddingTop"),10)+parseInt(this.$menu.css("paddingBottom"),10),0>e?this.$menu.scrollTop(i+e):n>r&&this.$menu.scrollTop(i+(n-r))},close:function(){this.isOpen&&(this.isOpen=!1,this._removeCursor(),this._hide(),this.trigger("closed"))},open:function(){this.isOpen||(this.isOpen=!0,!this.isEmpty&&this._show(),this.trigger("opened"))},setLanguageDirection:function(t){this.$menu.css("ltr"===t?h.ltr:h.rtl)},moveCursorUp:function(){this._moveCursor(-1)},moveCursorDown:function(){this._moveCursor(1)},getDatumForSuggestion:function(t){var e=null;return t.length&&(e={raw:g.extractDatum(t),value:g.extractValue(t),datasetName:g.extractDatasetName(t)}),e},getDatumForCursor:function(){return this.getDatumForSuggestion(this._getCursor().first())},getDatumForTopSuggestion:function(){return this.getDatumForSuggestion(this._getSuggestions().first())},update:function(t){function n(e){e.update(t)}e.each(this.datasets,n)},empty:function(){function t(t){t.clear()}e.each(this.datasets,t),this.isEmpty=!0},isVisible:function(){return this.isOpen&&!this.isEmpty},destroy:function(){function t(t){t.destroy()}this.$menu.off(".tt"),this.$menu=null,e.each(this.datasets,t)}}),n}(),y=function(){"use strict";function n(n){var r,s,o;n=n||{},n.input||t.error("missing input"),this.isActivated=!1,this.autoselect=!!n.autoselect,this.minLength=e.isNumber(n.minLength)?n.minLength:1,this.$node=i(n.input,n.withHint),r=this.$node.find(".tt-dropdown-menu"),s=this.$node.find(".tt-input"),o=this.$node.find(".tt-hint"),s.on("blur.tt",function(t){var n,i,o;n=document.activeElement,i=r.is(n),o=r.has(n).length>0,e.isMsie()&&(i||o)&&(t.preventDefault(),t.stopImmediatePropagation(),e.defer(function(){s.focus()}))}),r.on("mousedown.tt",function(t){t.preventDefault()}),this.eventBus=n.eventBus||new l({el:s}),this.dropdown=new m({menu:r,datasets:n.datasets}).onSync("suggestionClicked",this._onSuggestionClicked,this).onSync("cursorMoved",this._onCursorMoved,this).onSync("cursorRemoved",this._onCursorRemoved,this).onSync("opened",this._onOpened,this).onSync("closed",this._onClosed,this).onAsync("datasetRendered",this._onDatasetRendered,this),this.input=new f({input:s,hint:o}).onSync("focused",this._onFocused,this).onSync("blurred",this._onBlurred,this).onSync("enterKeyed",this._onEnterKeyed,this).onSync("tabKeyed",this._onTabKeyed,this).onSync("escKeyed",this._onEscKeyed,this).onSync("upKeyed",this._onUpKeyed,this).onSync("downKeyed",this._onDownKeyed,this).onSync("leftKeyed",this._onLeftKeyed,this).onSync("rightKeyed",this._onRightKeyed,this).onSync("queryChanged",this._onQueryChanged,this).onSync("whitespaceChanged",this._onWhitespaceChanged,this),this._setLanguageDirection()}function i(e,n){var i,s,u,a;i=t(e),s=t(c.wrapper).css(h.wrapper),u=t(c.dropdown).css(h.dropdown),a=i.clone().css(h.hint).css(r(i)),a.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder required").prop("readonly",!0).attr({autocomplete:"off",spellcheck:"false",tabindex:-1}),i.data(o,{dir:i.attr("dir"),autocomplete:i.attr("autocomplete"),spellcheck:i.attr("spellcheck"),style:i.attr("style")}),i.addClass("tt-input").attr({autocomplete:"off",spellcheck:!1}).css(n?h.input:h.inputWithNoHint);try{!i.attr("dir")&&i.attr("dir","auto")}catch(l){}return i.wrap(s).parent().prepend(n?a:null).append(u)}function r(t){return{backgroundAttachment:t.css("background-attachment"),backgroundClip:t.css("background-clip"),backgroundColor:t.css("background-color"),backgroundImage:t.css("background-image"),backgroundOrigin:t.css("background-origin"),backgroundPosition:t.css("background-position"),backgroundRepeat:t.css("background-repeat"),backgroundSize:t.css("background-size")}}function s(t){var n=t.find(".tt-input");e.each(n.data(o),function(t,i){e.isUndefined(t)?n.removeAttr(i):n.attr(i,t)}),n.detach().removeData(o).removeClass("tt-input").insertAfter(t),t.remove()}var o="ttAttrs";return e.mixin(n.prototype,{_onSuggestionClicked:function(t,e){var n;(n=this.dropdown.getDatumForSuggestion(e))&&this._select(n)},_onCursorMoved:function(){var t=this.dropdown.getDatumForCursor();this.input.setInputValue(t.value,!0),this.eventBus.trigger("cursorchanged",t.raw,t.datasetName)},_onCursorRemoved:function(){this.input.resetInputValue(),this._updateHint()},_onDatasetRendered:function(){this._updateHint()},_onOpened:function(){this._updateHint(),this.eventBus.trigger("opened")},_onClosed:function(){this.input.clearHint(),this.eventBus.trigger("closed")},_onFocused:function(){this.isActivated=!0,this.dropdown.open()},_onBlurred:function(){this.isActivated=!1,this.dropdown.empty(),this.dropdown.close()},_onEnterKeyed:function(t,e){var n,i;n=this.dropdown.getDatumForCursor(),i=this.dropdown.getDatumForTopSuggestion(),n?(this._select(n),e.preventDefault()):this.autoselect&&i&&(this._select(i),e.preventDefault())},_onTabKeyed:function(t,e){var n;(n=this.dropdown.getDatumForCursor())?(this._select(n),e.preventDefault()):this._autocomplete(!0)},_onEscKeyed:function(){this.dropdown.close(),this.input.resetInputValue()},_onUpKeyed:function(){var t=this.input.getQuery();this.dropdown.isEmpty&&t.length>=this.minLength?this.dropdown.update(t):this.dropdown.moveCursorUp(),this.dropdown.open()},_onDownKeyed:function(){var t=this.input.getQuery();this.dropdown.isEmpty&&t.length>=this.minLength?this.dropdown.update(t):this.dropdown.moveCursorDown(),this.dropdown.open()},_onLeftKeyed:function(){"rtl"===this.dir&&this._autocomplete()},_onRightKeyed:function(){"ltr"===this.dir&&this._autocomplete()},_onQueryChanged:function(t,e){this.input.clearHintIfInvalid(),e.length>=this.minLength?this.dropdown.update(e):this.dropdown.empty(),this.dropdown.open(),this._setLanguageDirection()},_onWhitespaceChanged:function(){this._updateHint(),this.dropdown.open()},_setLanguageDirection:function(){var t;this.dir!==(t=this.input.getLanguageDirection())&&(this.dir=t,this.$node.css("direction",t),this.dropdown.setLanguageDirection(t))},_updateHint:function(){var t,n,i,r,s,o;t=this.dropdown.getDatumForTopSuggestion(),t&&this.dropdown.isVisible()&&!this.input.hasOverflow()?(n=this.input.getInputValue(),i=f.normalizeQuery(n),r=e.escapeRegExChars(i),s=new RegExp("^(?:"+r+")(.+$)","i"),o=s.exec(t.value),o?this.input.setHint(n+o[1]):this.input.clearHint()):this.input.clearHint()},_autocomplete:function(t){var e,n,i,r;e=this.input.getHint(),n=this.input.getQuery(),i=t||this.input.isCursorAtEnd(),e&&n!==e&&i&&(r=this.dropdown.getDatumForTopSuggestion(),r&&this.input.setInputValue(r.value),this.eventBus.trigger("autocompleted",r.raw,r.datasetName))},_select:function(t){this.input.setQuery(t.value),this.input.setInputValue(t.value,!0),this._setLanguageDirection(),this.eventBus.trigger("selected",t.raw,t.datasetName),this.dropdown.close(),e.defer(e.bind(this.dropdown.empty,this.dropdown))},open:function(){this.dropdown.open()},close:function(){this.dropdown.close()},setVal:function(t){t=e.toStr(t),this.isActivated?this.input.setInputValue(t):(this.input.setQuery(t),this.input.setInputValue(t,!0)),this._setLanguageDirection()},getVal:function(){return this.input.getQuery()},destroy:function(){this.input.destroy(),this.dropdown.destroy(),s(this.$node),this.$node=null}}),n}();!function(){"use strict";var n,i,r;n=t.fn.typeahead,i="ttTypeahead",r={initialize:function(n,r){function s(){var s,o,u=t(this);e.each(r,function(t){t.highlight=!!n.highlight}),o=new y({input:u,eventBus:s=new l({el:u}),withHint:e.isUndefined(n.hint)?!0:!!n.hint,minLength:n.minLength,autoselect:n.autoselect,datasets:r}),u.data(i,o)}return r=e.isArray(r)?r:[].slice.call(arguments,1),n=n||{},this.each(s)},open:function(){function e(){var e,n=t(this);(e=n.data(i))&&e.open()}return this.each(e)},close:function(){function e(){var e,n=t(this);(e=n.data(i))&&e.close()}return this.each(e)},val:function(e){function n(){var n,r=t(this);(n=r.data(i))&&n.setVal(e)}function r(t){var e,n;return(e=t.data(i))&&(n=e.getVal()),n}return arguments.length?this.each(n):r(this.first())},destroy:function(){function e(){var e,n=t(this);(e=n.data(i))&&(e.destroy(),n.removeData(i))}return this.each(e)}},t.fn.typeahead=function(e){var n;return r[e]&&"initialize"!==e?(n=this.filter(function(){return!!t(this).data(i)}),r[e].apply(n,[].slice.call(arguments,1))):r.initialize.apply(this,arguments)},t.fn.typeahead.noConflict=function(){return t.fn.typeahead=n,this}}()}(window.jQuery);
/*!
* Generated using the Bootstrap Customizer (https://getbootstrap.com/docs/3.4/customize/)
*/
/*!
* Bootstrap v3.4.1 (https://getbootstrap.com/)
* Copyright 2011-2020 Twitter, Inc.
* Licensed under the MIT license
*/
if (typeof jQuery === 'undefined') {
throw new Error('Bootstrap\'s JavaScript requires jQuery')
}
+function ($) {
'use strict';
var version = $.fn.jquery.split(' ')[0].split('.')
if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {
throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')
}
}(jQuery);
/* ========================================================================
* Bootstrap: collapse.js v3.4.1
* https://getbootstrap.com/docs/3.4/javascript/#collapse
* ========================================================================
* Copyright 2011-2019 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
/* jshint latedef: false */
+function ($) {
'use strict';
// COLLAPSE PUBLIC CLASS DEFINITION
// ================================
var Collapse = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Collapse.DEFAULTS, options)
this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
'[data-toggle="collapse"][data-target="#' + element.id + '"]')
this.transitioning = null
if (this.options.parent) {
this.$parent = this.getParent()
} else {
this.addAriaAndCollapsedClass(this.$element, this.$trigger)
}
if (this.options.toggle) this.toggle()
}
Collapse.VERSION = '3.4.1'
Collapse.TRANSITION_DURATION = 350
Collapse.DEFAULTS = {
toggle: true
}
Collapse.prototype.dimension = function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
}
Collapse.prototype.show = function () {
if (this.transitioning || this.$element.hasClass('in')) return
var activesData
var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
if (actives && actives.length) {
activesData = actives.data('bs.collapse')
if (activesData && activesData.transitioning) return
}
var startEvent = $.Event('show.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
if (actives && actives.length) {
Plugin.call(actives, 'hide')
activesData || actives.data('bs.collapse', null)
}
var dimension = this.dimension()
this.$element
.removeClass('collapse')
.addClass('collapsing')[dimension](0)
.attr('aria-expanded', true)
this.$trigger
.removeClass('collapsed')
.attr('aria-expanded', true)
this.transitioning = 1
var complete = function () {
this.$element
.removeClass('collapsing')
.addClass('collapse in')[dimension]('')
this.transitioning = 0
this.$element
.trigger('shown.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
var scrollSize = $.camelCase(['scroll', dimension].join('-'))
this.$element
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
}
Collapse.prototype.hide = function () {
if (this.transitioning || !this.$element.hasClass('in')) return
var startEvent = $.Event('hide.bs.collapse')
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
var dimension = this.dimension()
this.$element[dimension](this.$element[dimension]())[0].offsetHeight
this.$element
.addClass('collapsing')
.removeClass('collapse in')
.attr('aria-expanded', false)
this.$trigger
.addClass('collapsed')
.attr('aria-expanded', false)
this.transitioning = 1
var complete = function () {
this.transitioning = 0
this.$element
.removeClass('collapsing')
.addClass('collapse')
.trigger('hidden.bs.collapse')
}
if (!$.support.transition) return complete.call(this)
this.$element
[dimension](0)
.one('bsTransitionEnd', $.proxy(complete, this))
.emulateTransitionEnd(Collapse.TRANSITION_DURATION)
}
Collapse.prototype.toggle = function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']()
}
Collapse.prototype.getParent = function () {
return $(document).find(this.options.parent)
.find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
.each($.proxy(function (i, element) {
var $element = $(element)
this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
}, this))
.end()
}
Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
var isOpen = $element.hasClass('in')
$element.attr('aria-expanded', isOpen)
$trigger
.toggleClass('collapsed', !isOpen)
.attr('aria-expanded', isOpen)
}
function getTargetFromTrigger($trigger) {
var href
var target = $trigger.attr('data-target')
|| (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
return $(document).find(target)
}
// COLLAPSE PLUGIN DEFINITION
// ==========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.collapse')
var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
}
var old = $.fn.collapse
$.fn.collapse = Plugin
$.fn.collapse.Constructor = Collapse
// COLLAPSE NO CONFLICT
// ====================
$.fn.collapse.noConflict = function () {
$.fn.collapse = old
return this
}
// COLLAPSE DATA-API
// =================
$(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
var $this = $(this)
if (!$this.attr('data-target')) e.preventDefault()
var $target = getTargetFromTrigger($this)
var data = $target.data('bs.collapse')
var option = data ? 'toggle' : $this.data()
Plugin.call($target, option)
})
}(jQuery);
<?php
if (PHP_VERSION_ID < 70220) {// 70 2 20
echo 'You need to use PHP 7.2.20 or above to run Doctum.' . PHP_EOL;
echo 'Current detected version: (' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . ') (' . PHP_VERSION_ID . ').' . PHP_EOL;
exit(1);
}
// installed via composer?
$doctumComposerAutoLoadFile = __DIR__ . '/../../../autoload.php';
$doctumComposerAutoLoadFileEnv = getenv('DOCTUM_COMPOSER_AUTOLOAD_FILE');
if (is_string($doctumComposerAutoLoadFileEnv)) {
$doctumComposerAutoLoadFile = $doctumComposerAutoLoadFileEnv;
}
if (file_exists($doctumComposerAutoLoadFile)) {
require_once $doctumComposerAutoLoadFile;
} else {
require_once __DIR__ . '/../vendor/autoload.php';
}
use Doctum\Console\Application;
$application = new Application();
$application->run();
#!/usr/bin/env php
<?php
require_once __DIR__ . DIRECTORY_SEPARATOR . 'doctum-binary.php';
<?php
$grammarFileToName = [
__DIR__ . '/php5.y' => 'Php5',
__DIR__ . '/php7.y' => 'Php7',
];
$tokensFile = __DIR__ . '/tokens.y';
$tokensTemplate = __DIR__ . '/tokens.template';
$skeletonFile = __DIR__ . '/parser.template';
$tmpGrammarFile = __DIR__ . '/tmp_parser.phpy';
$tmpResultFile = __DIR__ . '/tmp_parser.php';
$resultDir = __DIR__ . '/../lib/PhpParser/Parser';
$tokensResultsFile = $resultDir . '/Tokens.php';
$kmyacc = getenv('KMYACC');
if (!$kmyacc) {
// Use phpyacc from dev dependencies by default.
$kmyacc = __DIR__ . '/../vendor/bin/phpyacc';
}
$options = array_flip($argv);
$optionDebug = isset($options['--debug']);
$optionKeepTmpGrammar = isset($options['--keep-tmp-grammar']);
///////////////////////////////
/// Utility regex constants ///
///////////////////////////////
const LIB = '(?(DEFINE)
(?<singleQuotedString>\'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\')
(?<doubleQuotedString>"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+")
(?<string>(?&singleQuotedString)|(?&doubleQuotedString))
(?<comment>/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/)
(?<code>\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+})
)';
const PARAMS = '\[(?<params>[^[\]]*+(?:\[(?&params)\][^[\]]*+)*+)\]';
const ARGS = '\((?<args>[^()]*+(?:\((?&args)\)[^()]*+)*+)\)';
///////////////////
/// Main script ///
///////////////////
$tokens = file_get_contents($tokensFile);
foreach ($grammarFileToName as $grammarFile => $name) {
echo "Building temporary $name grammar file.\n";
$grammarCode = file_get_contents($grammarFile);
$grammarCode = str_replace('%tokens', $tokens, $grammarCode);
$grammarCode = resolveNodes($grammarCode);
$grammarCode = resolveMacros($grammarCode);
$grammarCode = resolveStackAccess($grammarCode);
file_put_contents($tmpGrammarFile, $grammarCode);
$additionalArgs = $optionDebug ? '-t -v' : '';
echo "Building $name parser.\n";
$output = execCmd("$kmyacc $additionalArgs -m $skeletonFile -p $name $tmpGrammarFile");
$resultCode = file_get_contents($tmpResultFile);
$resultCode = removeTrailingWhitespace($resultCode);
ensureDirExists($resultDir);
file_put_contents("$resultDir/$name.php", $resultCode);
unlink($tmpResultFile);
echo "Building token definition.\n";
$output = execCmd("$kmyacc -m $tokensTemplate $tmpGrammarFile");
rename($tmpResultFile, $tokensResultsFile);
if (!$optionKeepTmpGrammar) {
unlink($tmpGrammarFile);
}
}
///////////////////////////////
/// Preprocessing functions ///
///////////////////////////////
function resolveNodes($code) {
return preg_replace_callback(
'~\b(?<name>[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~',
function($matches) {
// recurse
$matches['params'] = resolveNodes($matches['params']);
$params = magicSplit(
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
$matches['params']
);
$paramCode = '';
foreach ($params as $param) {
$paramCode .= $param . ', ';
}
return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())';
},
$code
);
}
function resolveMacros($code) {
return preg_replace_callback(
'~\b(?<!::|->)(?!array\()(?<name>[a-z][A-Za-z]++)' . ARGS . '~',
function($matches) {
// recurse
$matches['args'] = resolveMacros($matches['args']);
$name = $matches['name'];
$args = magicSplit(
'(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,',
$matches['args']
);
if ('attributes' === $name) {
assertArgs(0, $args, $name);
return '$this->startAttributeStack[#1] + $this->endAttributes';
}
if ('stackAttributes' === $name) {
assertArgs(1, $args, $name);
return '$this->startAttributeStack[' . $args[0] . ']'
. ' + $this->endAttributeStack[' . $args[0] . ']';
}
if ('init' === $name) {
return '$$ = array(' . implode(', ', $args) . ')';
}
if ('push' === $name) {
assertArgs(2, $args, $name);
return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0];
}
if ('pushNormalizing' === $name) {
assertArgs(2, $args, $name);
return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }'
. ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }';
}
if ('toArray' == $name) {
assertArgs(1, $args, $name);
return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')';
}
if ('parseVar' === $name) {
assertArgs(1, $args, $name);
return 'substr(' . $args[0] . ', 1)';
}
if ('parseEncapsed' === $name) {
assertArgs(3, $args, $name);
return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {'
. ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }';
}
if ('makeNop' === $name) {
assertArgs(3, $args, $name);
return '$startAttributes = ' . $args[1] . ';'
. ' if (isset($startAttributes[\'comments\']))'
. ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }'
. ' else { ' . $args[0] . ' = null; }';
}
if ('makeZeroLengthNop' == $name) {
assertArgs(2, $args, $name);
return '$startAttributes = ' . $args[1] . ';'
. ' if (isset($startAttributes[\'comments\']))'
. ' { ' . $args[0] . ' = new Stmt\Nop($this->createCommentNopAttributes($startAttributes[\'comments\'])); }'
. ' else { ' . $args[0] . ' = null; }';
}
if ('strKind' === $name) {
assertArgs(1, $args, $name);
return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && '
. '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) '
. '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)';
}
if ('prependLeadingComments' === $name) {
assertArgs(1, $args, $name);
return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; '
. 'if (!empty($attrs[\'comments\'])) {'
. '$stmts[0]->setAttribute(\'comments\', '
. 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }';
}
return $matches[0];
},
$code
);
}
function assertArgs($num, $args, $name) {
if ($num != count($args)) {
die('Wrong argument count for ' . $name . '().');
}
}
function resolveStackAccess($code) {
$code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code);
$code = preg_replace('/#(\d+)/', '$$1', $code);
return $code;
}
function removeTrailingWhitespace($code) {
$lines = explode("\n", $code);
$lines = array_map('rtrim', $lines);
return implode("\n", $lines);
}
function ensureDirExists($dir) {
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
}
function execCmd($cmd) {
$output = trim(shell_exec("$cmd 2>&1"));
if ($output !== "") {
echo "> " . $cmd . "\n";
echo $output;
}
return $output;
}
//////////////////////////////
/// Regex helper functions ///
//////////////////////////////
function regex($regex) {
return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~';
}
function magicSplit($regex, $string) {
$pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string);
foreach ($pieces as &$piece) {
$piece = trim($piece);
}
if ($pieces === ['']) {
return [];
}
return $pieces;
}
%pure_parser
%expect 2
%tokens
%%
start:
top_statement_list { $$ = $this->handleNamespaces($1); }
;
top_statement_list_ex:
top_statement_list_ex top_statement { pushNormalizing($1, $2); }
| /* empty */ { init(); }
;
top_statement_list:
top_statement_list_ex
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
reserved_non_modifiers:
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
| T_ENDWHILE | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH
| T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
| T_MATCH
;
semi_reserved:
reserved_non_modifiers
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
;
identifier_ex:
T_STRING { $$ = Node\Identifier[$1]; }
| semi_reserved { $$ = Node\Identifier[$1]; }
;
identifier:
T_STRING { $$ = Node\Identifier[$1]; }
;
reserved_non_modifiers_identifier:
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
;
namespace_declaration_name:
T_STRING { $$ = Name[$1]; }
| semi_reserved { $$ = Name[$1]; }
| T_NAME_QUALIFIED { $$ = Name[$1]; }
;
namespace_name:
T_STRING { $$ = Name[$1]; }
| T_NAME_QUALIFIED { $$ = Name[$1]; }
;
legacy_namespace_name:
namespace_name { $$ = $1; }
| T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; }
;
plain_variable:
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
;
semi:
';' { /* nothing */ }
| error { /* nothing */ }
;
no_comma:
/* empty */ { /* nothing */ }
| ',' { $this->emitError(new Error('A trailing comma is not allowed here', attributes())); }
;
optional_comma:
/* empty */
| ','
;
attribute_decl:
class_name { $$ = Node\Attribute[$1, []]; }
| class_name argument_list { $$ = Node\Attribute[$1, $2]; }
;
attribute_group:
attribute_decl { init($1); }
| attribute_group ',' attribute_decl { push($1, $3); }
;
attribute:
T_ATTRIBUTE attribute_group optional_comma ']' { $$ = Node\AttributeGroup[$2]; }
;
attributes:
attribute { init($1); }
| attributes attribute { push($1, $2); }
;
optional_attributes:
/* empty */ { $$ = []; }
| attributes { $$ = $1; }
;
top_statement:
statement { $$ = $1; }
| function_declaration_statement { $$ = $1; }
| class_declaration_statement { $$ = $1; }
| T_HALT_COMPILER
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
| T_NAMESPACE namespace_declaration_name semi
{ $$ = Stmt\Namespace_[$2, null];
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
$this->checkNamespace($$); }
| T_NAMESPACE namespace_declaration_name '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[$2, $4];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| T_NAMESPACE '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[null, $3];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| T_USE use_declarations semi { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations semi { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration semi { $$ = $1; }
| T_CONST constant_declaration_list semi { $$ = Stmt\Const_[$2]; }
;
use_type:
T_FUNCTION { $$ = Stmt\Use_::TYPE_FUNCTION; }
| T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; }
;
group_use_declaration:
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
;
unprefixed_use_declarations:
non_empty_unprefixed_use_declarations optional_comma { $$ = $1; }
;
non_empty_unprefixed_use_declarations:
non_empty_unprefixed_use_declarations ',' unprefixed_use_declaration
{ push($1, $3); }
| unprefixed_use_declaration { init($1); }
;
use_declarations:
non_empty_use_declarations no_comma { $$ = $1; }
;
non_empty_use_declarations:
non_empty_use_declarations ',' use_declaration { push($1, $3); }
| use_declaration { init($1); }
;
inline_use_declarations:
non_empty_inline_use_declarations optional_comma { $$ = $1; }
;
non_empty_inline_use_declarations:
non_empty_inline_use_declarations ',' inline_use_declaration
{ push($1, $3); }
| inline_use_declaration { init($1); }
;
unprefixed_use_declaration:
namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| namespace_name T_AS identifier
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
use_declaration:
legacy_namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| legacy_namespace_name T_AS identifier
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
inline_use_declaration:
unprefixed_use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; }
| use_type unprefixed_use_declaration { $$ = $2; $$->type = $1; }
;
constant_declaration_list:
non_empty_constant_declaration_list no_comma { $$ = $1; }
;
non_empty_constant_declaration_list:
non_empty_constant_declaration_list ',' constant_declaration
{ push($1, $3); }
| constant_declaration { init($1); }
;
constant_declaration:
identifier '=' expr { $$ = Node\Const_[$1, $3]; }
;
class_const_list:
non_empty_class_const_list no_comma { $$ = $1; }
;
non_empty_class_const_list:
non_empty_class_const_list ',' class_const { push($1, $3); }
| class_const { init($1); }
;
class_const:
identifier_ex '=' expr { $$ = Node\Const_[$1, $3]; }
;
inner_statement_list_ex:
inner_statement_list_ex inner_statement { pushNormalizing($1, $2); }
| /* empty */ { init(); }
;
inner_statement_list:
inner_statement_list_ex
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
inner_statement:
statement { $$ = $1; }
| function_declaration_statement { $$ = $1; }
| class_declaration_statement { $$ = $1; }
| T_HALT_COMPILER
{ throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
;
non_empty_statement:
'{' inner_statement_list '}'
{
if ($2) {
$$ = $2; prependLeadingComments($$);
} else {
makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
if (null === $$) { $$ = array(); }
}
}
| T_IF '(' expr ')' statement elseif_list else_single
{ $$ = Stmt\If_[$3, ['stmts' => toArray($5), 'elseifs' => $6, 'else' => $7]]; }
| T_IF '(' expr ')' ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
{ $$ = Stmt\If_[$3, ['stmts' => $6, 'elseifs' => $7, 'else' => $8]]; }
| T_WHILE '(' expr ')' while_statement { $$ = Stmt\While_[$3, $5]; }
| T_DO statement T_WHILE '(' expr ')' ';' { $$ = Stmt\Do_ [$5, toArray($2)]; }
| T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement
{ $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
| T_SWITCH '(' expr ')' switch_case_list { $$ = Stmt\Switch_[$3, $5]; }
| T_BREAK optional_expr semi { $$ = Stmt\Break_[$2]; }
| T_CONTINUE optional_expr semi { $$ = Stmt\Continue_[$2]; }
| T_RETURN optional_expr semi { $$ = Stmt\Return_[$2]; }
| T_GLOBAL global_var_list semi { $$ = Stmt\Global_[$2]; }
| T_STATIC static_var_list semi { $$ = Stmt\Static_[$2]; }
| T_ECHO expr_list_forbid_comma semi { $$ = Stmt\Echo_[$2]; }
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
| expr semi {
$e = $1;
if ($e instanceof Expr\Throw_) {
// For backwards-compatibility reasons, convert throw in statement position into
// Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_).
$$ = Stmt\Throw_[$e->expr];
} else {
$$ = Stmt\Expression[$e];
}
}
| T_UNSET '(' variables_list ')' semi { $$ = Stmt\Unset_[$3]; }
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
| T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
| T_FOREACH '(' expr error ')' foreach_statement
{ $$ = Stmt\Foreach_[$3, new Expr\Error(stackAttributes(#4)), ['stmts' => $6]]; }
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
| T_TRY '{' inner_statement_list '}' catches optional_finally
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_GOTO identifier semi { $$ = Stmt\Goto_[$2]; }
| identifier ':' { $$ = Stmt\Label[$1]; }
| error { $$ = array(); /* means: no statement */ }
;
statement:
non_empty_statement { $$ = $1; }
| ';'
{ makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
if ($$ === null) $$ = array(); /* means: no statement */ }
;
catches:
/* empty */ { init(); }
| catches catch { push($1, $2); }
;
name_union:
name { init($1); }
| name_union '|' name { push($1, $3); }
;
catch:
T_CATCH '(' name_union optional_plain_variable ')' '{' inner_statement_list '}'
{ $$ = Stmt\Catch_[$3, $4, $7]; }
;
optional_finally:
/* empty */ { $$ = null; }
| T_FINALLY '{' inner_statement_list '}' { $$ = Stmt\Finally_[$3]; }
;
variables_list:
non_empty_variables_list optional_comma { $$ = $1; }
;
non_empty_variables_list:
variable { init($1); }
| non_empty_variables_list ',' variable { push($1, $3); }
;
optional_ref:
/* empty */ { $$ = false; }
| '&' { $$ = true; }
;
optional_ellipsis:
/* empty */ { $$ = false; }
| T_ELLIPSIS { $$ = true; }
;
block_or_error:
'{' inner_statement_list '}' { $$ = $2; }
| error { $$ = []; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
| attributes T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type block_or_error
{ $$ = Stmt\Function_[$4, ['byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
;
class_declaration_statement:
optional_attributes class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$3, ['type' => $2, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]];
$this->checkClass($$, #3); }
| optional_attributes T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$3, ['extends' => $4, 'stmts' => $6, 'attrGroups' => $1]];
$this->checkInterface($$, #3); }
| optional_attributes T_TRAIT identifier '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$3, ['stmts' => $5, 'attrGroups' => $1]]; }
;
class_entry_type:
T_CLASS { $$ = 0; }
| T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; }
;
extends_from:
/* empty */ { $$ = null; }
| T_EXTENDS class_name { $$ = $2; }
;
interface_extends_list:
/* empty */ { $$ = array(); }
| T_EXTENDS class_name_list { $$ = $2; }
;
implements_list:
/* empty */ { $$ = array(); }
| T_IMPLEMENTS class_name_list { $$ = $2; }
;
class_name_list:
non_empty_class_name_list no_comma { $$ = $1; }
;
non_empty_class_name_list:
class_name { init($1); }
| non_empty_class_name_list ',' class_name { push($1, $3); }
;
for_statement:
statement { $$ = toArray($1); }
| ':' inner_statement_list T_ENDFOR ';' { $$ = $2; }
;
foreach_statement:
statement { $$ = toArray($1); }
| ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; }
;
declare_statement:
non_empty_statement { $$ = toArray($1); }
| ';' { $$ = null; }
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
;
declare_list:
non_empty_declare_list no_comma { $$ = $1; }
;
non_empty_declare_list:
declare_list_element { init($1); }
| non_empty_declare_list ',' declare_list_element { push($1, $3); }
;
declare_list_element:
identifier '=' expr { $$ = Stmt\DeclareDeclare[$1, $3]; }
;
switch_case_list:
'{' case_list '}' { $$ = $2; }
| '{' ';' case_list '}' { $$ = $3; }
| ':' case_list T_ENDSWITCH ';' { $$ = $2; }
| ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; }
;
case_list:
/* empty */ { init(); }
| case_list case { push($1, $2); }
;
case:
T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; }
| T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; }
;
case_separator:
':'
| ';'
;
match:
T_MATCH '(' expr ')' '{' match_arm_list '}' { $$ = Expr\Match_[$3, $6]; }
;
match_arm_list:
/* empty */ { $$ = []; }
| non_empty_match_arm_list optional_comma { $$ = $1; }
;
non_empty_match_arm_list:
match_arm { init($1); }
| non_empty_match_arm_list ',' match_arm { push($1, $3); }
;
match_arm:
expr_list_allow_comma T_DOUBLE_ARROW expr { $$ = Node\MatchArm[$1, $3]; }
| T_DEFAULT optional_comma T_DOUBLE_ARROW expr { $$ = Node\MatchArm[null, $4]; }
;
while_statement:
statement { $$ = toArray($1); }
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
;
elseif_list:
/* empty */ { init(); }
| elseif_list elseif { push($1, $2); }
;
elseif:
T_ELSEIF '(' expr ')' statement { $$ = Stmt\ElseIf_[$3, toArray($5)]; }
;
new_elseif_list:
/* empty */ { init(); }
| new_elseif_list new_elseif { push($1, $2); }
;
new_elseif:
T_ELSEIF '(' expr ')' ':' inner_statement_list { $$ = Stmt\ElseIf_[$3, $6]; }
;
else_single:
/* empty */ { $$ = null; }
| T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; }
;
new_else_single:
/* empty */ { $$ = null; }
| T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; }
;
foreach_variable:
variable { $$ = array($1, false); }
| '&' variable { $$ = array($2, true); }
| list_expr { $$ = array($1, false); }
| array_short_syntax { $$ = array($1, false); }
;
parameter_list:
non_empty_parameter_list optional_comma { $$ = $1; }
| /* empty */ { $$ = array(); }
;
non_empty_parameter_list:
parameter { init($1); }
| non_empty_parameter_list ',' parameter { push($1, $3); }
;
optional_visibility_modifier:
/* empty */ { $$ = 0; }
| T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
;
parameter:
optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable
{ $$ = new Node\Param($6, null, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
| optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis plain_variable '=' expr
{ $$ = new Node\Param($6, $8, $3, $4, $5, attributes(), $2, $1);
$this->checkParam($$); }
| optional_attributes optional_visibility_modifier optional_type_without_static optional_ref optional_ellipsis error
{ $$ = new Node\Param(Expr\Error[], null, $3, $4, $5, attributes(), $2, $1); }
;
type_expr:
type { $$ = $1; }
| '?' type { $$ = Node\NullableType[$2]; }
| union_type { $$ = Node\UnionType[$1]; }
;
type:
type_without_static { $$ = $1; }
| T_STATIC { $$ = Node\Name['static']; }
;
type_without_static:
name { $$ = $this->handleBuiltinTypes($1); }
| T_ARRAY { $$ = Node\Identifier['array']; }
| T_CALLABLE { $$ = Node\Identifier['callable']; }
;
union_type:
type '|' type { init($1, $3); }
| union_type '|' type { push($1, $3); }
;
union_type_without_static:
type_without_static '|' type_without_static { init($1, $3); }
| union_type_without_static '|' type_without_static { push($1, $3); }
;
type_expr_without_static:
type_without_static { $$ = $1; }
| '?' type_without_static { $$ = Node\NullableType[$2]; }
| union_type_without_static { $$ = Node\UnionType[$1]; }
;
optional_type_without_static:
/* empty */ { $$ = null; }
| type_expr_without_static { $$ = $1; }
;
optional_return_type:
/* empty */ { $$ = null; }
| ':' type_expr { $$ = $2; }
| ':' error { $$ = null; }
;
argument_list:
'(' ')' { $$ = array(); }
| '(' non_empty_argument_list optional_comma ')' { $$ = $2; }
;
non_empty_argument_list:
argument { init($1); }
| non_empty_argument_list ',' argument { push($1, $3); }
;
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| '&' variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
| identifier_ex ':' expr
{ $$ = new Node\Arg($3, false, false, attributes(), $1); }
;
global_var_list:
non_empty_global_var_list no_comma { $$ = $1; }
;
non_empty_global_var_list:
non_empty_global_var_list ',' global_var { push($1, $3); }
| global_var { init($1); }
;
global_var:
simple_variable { $$ = $1; }
;
static_var_list:
non_empty_static_var_list no_comma { $$ = $1; }
;
non_empty_static_var_list:
non_empty_static_var_list ',' static_var { push($1, $3); }
| static_var { init($1); }
;
static_var:
plain_variable { $$ = Stmt\StaticVar[$1, null]; }
| plain_variable '=' expr { $$ = Stmt\StaticVar[$1, $3]; }
;
class_statement_list_ex:
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
| /* empty */ { init(); }
;
class_statement_list:
class_statement_list_ex
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
class_statement:
optional_attributes variable_modifiers optional_type_without_static property_declaration_list semi
{ $$ = new Stmt\Property($2, $4, attributes(), $3, $1);
$this->checkProperty($$, #2); }
| optional_attributes method_modifiers T_CONST class_const_list semi
{ $$ = new Stmt\ClassConst($4, $2, attributes(), $1);
$this->checkClassConst($$, #2); }
| optional_attributes method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
{ $$ = Stmt\ClassMethod[$5, ['type' => $2, 'byRef' => $4, 'params' => $7, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]];
$this->checkClassMethod($$, #2); }
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
| error { $$ = null; /* will be skipped */ }
;
trait_adaptations:
';' { $$ = array(); }
| '{' trait_adaptation_list '}' { $$ = $2; }
;
trait_adaptation_list:
/* empty */ { init(); }
| trait_adaptation_list trait_adaptation { push($1, $2); }
;
trait_adaptation:
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
| trait_method_reference T_AS member_modifier identifier_ex ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
| trait_method_reference T_AS member_modifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
| trait_method_reference T_AS identifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
;
trait_method_reference_fully_qualified:
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
;
trait_method_reference:
trait_method_reference_fully_qualified { $$ = $1; }
| identifier_ex { $$ = array(null, $1); }
;
method_body:
';' /* abstract method */ { $$ = null; }
| block_or_error { $$ = $1; }
;
variable_modifiers:
non_empty_member_modifiers { $$ = $1; }
| T_VAR { $$ = 0; }
;
method_modifiers:
/* empty */ { $$ = 0; }
| non_empty_member_modifiers { $$ = $1; }
;
non_empty_member_modifiers:
member_modifier { $$ = $1; }
| non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
;
member_modifier:
T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
| T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; }
| T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
;
property_declaration_list:
non_empty_property_declaration_list no_comma { $$ = $1; }
;
non_empty_property_declaration_list:
property_declaration { init($1); }
| non_empty_property_declaration_list ',' property_declaration
{ push($1, $3); }
;
property_decl_name:
T_VARIABLE { $$ = Node\VarLikeIdentifier[parseVar($1)]; }
;
property_declaration:
property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; }
| property_decl_name '=' expr { $$ = Stmt\PropertyProperty[$1, $3]; }
;
expr_list_forbid_comma:
non_empty_expr_list no_comma { $$ = $1; }
;
expr_list_allow_comma:
non_empty_expr_list optional_comma { $$ = $1; }
;
non_empty_expr_list:
non_empty_expr_list ',' expr { push($1, $3); }
| expr { init($1); }
;
for_expr:
/* empty */ { $$ = array(); }
| expr_list_forbid_comma { $$ = $1; }
;
expr:
variable { $$ = $1; }
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
| array_short_syntax '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
| new_expr { $$ = $1; }
| match { $$ = $1; }
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }
| variable T_MUL_EQUAL expr { $$ = Expr\AssignOp\Mul [$1, $3]; }
| variable T_DIV_EQUAL expr { $$ = Expr\AssignOp\Div [$1, $3]; }
| variable T_CONCAT_EQUAL expr { $$ = Expr\AssignOp\Concat [$1, $3]; }
| variable T_MOD_EQUAL expr { $$ = Expr\AssignOp\Mod [$1, $3]; }
| variable T_AND_EQUAL expr { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; }
| variable T_OR_EQUAL expr { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; }
| variable T_XOR_EQUAL expr { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; }
| variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
| variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; }
| variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; }
| variable T_COALESCE_EQUAL expr { $$ = Expr\AssignOp\Coalesce [$1, $3]; }
| variable T_INC { $$ = Expr\PostInc[$1]; }
| T_INC variable { $$ = Expr\PreInc [$2]; }
| variable T_DEC { $$ = Expr\PostDec[$1]; }
| T_DEC variable { $$ = Expr\PreDec [$2]; }
| expr T_BOOLEAN_OR expr { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
| expr T_BOOLEAN_AND expr { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
| expr T_LOGICAL_OR expr { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
| expr '-' expr { $$ = Expr\BinaryOp\Minus [$1, $3]; }
| expr '*' expr { $$ = Expr\BinaryOp\Mul [$1, $3]; }
| expr '/' expr { $$ = Expr\BinaryOp\Div [$1, $3]; }
| expr '%' expr { $$ = Expr\BinaryOp\Mod [$1, $3]; }
| expr T_SL expr { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
| expr T_SR expr { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
| expr T_POW expr { $$ = Expr\BinaryOp\Pow [$1, $3]; }
| '+' expr %prec T_INC { $$ = Expr\UnaryPlus [$2]; }
| '-' expr %prec T_INC { $$ = Expr\UnaryMinus[$2]; }
| '!' expr { $$ = Expr\BooleanNot[$2]; }
| '~' expr { $$ = Expr\BitwiseNot[$2]; }
| expr T_IS_IDENTICAL expr { $$ = Expr\BinaryOp\Identical [$1, $3]; }
| expr T_IS_NOT_IDENTICAL expr { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; }
| expr T_IS_EQUAL expr { $$ = Expr\BinaryOp\Equal [$1, $3]; }
| expr T_IS_NOT_EQUAL expr { $$ = Expr\BinaryOp\NotEqual [$1, $3]; }
| expr T_SPACESHIP expr { $$ = Expr\BinaryOp\Spaceship [$1, $3]; }
| expr '<' expr { $$ = Expr\BinaryOp\Smaller [$1, $3]; }
| expr T_IS_SMALLER_OR_EQUAL expr { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
| expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; }
| expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
| expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; }
| '(' expr ')' { $$ = $2; }
| expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; }
| expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; }
| expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }
| T_ISSET '(' expr_list_allow_comma ')' { $$ = Expr\Isset_[$3]; }
| T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; }
| T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; }
| T_INCLUDE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; }
| T_EVAL '(' expr ')' { $$ = Expr\Eval_[$3]; }
| T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
| T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
| T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; }
| T_DOUBLE_CAST expr
{ $attrs = attributes();
$attrs['kind'] = $this->getFloatCastKind($1);
$$ = new Expr\Cast\Double($2, $attrs); }
| T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; }
| T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; }
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
| T_EXIT exit_expr
{ $attrs = attributes();
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
$$ = new Expr\Exit_($2, $attrs); }
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
| scalar { $$ = $1; }
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
| T_PRINT expr { $$ = Expr\Print_[$2]; }
| T_YIELD { $$ = Expr\Yield_[null, null]; }
| T_YIELD expr { $$ = Expr\Yield_[$2, null]; }
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; }
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
| T_THROW expr { $$ = Expr\Throw_[$2]; }
| T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $2, 'params' => $4, 'returnType' => $6, 'expr' => $8, 'attrGroups' => []]]; }
| T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => []]]; }
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $8, 'attrGroups' => []]]; }
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => []]]; }
| attributes T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => false, 'byRef' => $3, 'params' => $5, 'returnType' => $7, 'expr' => $9, 'attrGroups' => $1]]; }
| attributes T_STATIC T_FN optional_ref '(' parameter_list ')' optional_return_type T_DOUBLE_ARROW expr
{ $$ = Expr\ArrowFunction[['static' => true, 'byRef' => $4, 'params' => $6, 'returnType' => $8, 'expr' => $10, 'attrGroups' => $1]]; }
| attributes T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => false, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $9, 'attrGroups' => $1]]; }
| attributes T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type block_or_error
{ $$ = Expr\Closure[['static' => true, 'byRef' => $4, 'params' => $6, 'uses' => $8, 'returnType' => $9, 'stmts' => $10, 'attrGroups' => $1]]; }
;
anonymous_class:
optional_attributes T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $4, 'implements' => $5, 'stmts' => $7, 'attrGroups' => $1]], $3);
$this->checkClass($$[0], -1); }
;
new_expr:
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
| T_NEW anonymous_class
{ list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
;
lexical_vars:
/* empty */ { $$ = array(); }
| T_USE '(' lexical_var_list ')' { $$ = $3; }
;
lexical_var_list:
non_empty_lexical_var_list optional_comma { $$ = $1; }
;
non_empty_lexical_var_list:
lexical_var { init($1); }
| non_empty_lexical_var_list ',' lexical_var { push($1, $3); }
;
lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
;
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| callable_expr argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM member_name argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }
;
class_name:
T_STATIC { $$ = Name[$1]; }
| name { $$ = $1; }
;
name:
T_STRING { $$ = Name[$1]; }
| T_NAME_QUALIFIED { $$ = Name[$1]; }
| T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; }
| T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; }
;
class_name_reference:
class_name { $$ = $1; }
| new_variable { $$ = $1; }
| '(' expr ')' { $$ = $2; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
;
class_name_or_var:
class_name { $$ = $1; }
| fully_dereferencable { $$ = $1; }
;
exit_expr:
/* empty */ { $$ = null; }
| '(' optional_expr ')' { $$ = $2; }
;
backticks_expr:
/* empty */ { $$ = array(); }
| T_ENCAPSED_AND_WHITESPACE
{ $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`')]); }
| encaps_list { parseEncapsed($1, '`', true); $$ = $1; }
;
ctor_arguments:
/* empty */ { $$ = array(); }
| argument_list { $$ = $1; }
;
constant:
name { $$ = Expr\ConstFetch[$1]; }
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
| T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; }
| T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; }
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
;
class_constant:
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
{ $$ = Expr\ClassConstFetch[$1, $3]; }
/* We interpret an isolated FOO:: as an unfinished class constant fetch. It could also be
an unfinished static property fetch or unfinished scoped call. */
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM error
{ $$ = Expr\ClassConstFetch[$1, new Expr\Error(stackAttributes(#3))]; $this->errorState = 2; }
;
array_short_syntax:
'[' array_pair_list ']'
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT;
$$ = new Expr\Array_($2, $attrs); }
;
dereferencable_scalar:
T_ARRAY '(' array_pair_list ')'
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs); }
| array_short_syntax { $$ = $1; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1), $attrs); }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
;
scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes()); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| dereferencable_scalar { $$ = $1; }
| constant { $$ = $1; }
| class_constant { $$ = $1; }
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
| T_START_HEREDOC T_END_HEREDOC
{ $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), true); }
| T_START_HEREDOC encaps_list T_END_HEREDOC
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
;
optional_expr:
/* empty */ { $$ = null; }
| expr { $$ = $1; }
;
fully_dereferencable:
variable { $$ = $1; }
| '(' expr ')' { $$ = $2; }
| dereferencable_scalar { $$ = $1; }
| class_constant { $$ = $1; }
;
array_object_dereferencable:
fully_dereferencable { $$ = $1; }
| constant { $$ = $1; }
;
callable_expr:
callable_variable { $$ = $1; }
| '(' expr ')' { $$ = $2; }
| dereferencable_scalar { $$ = $1; }
;
callable_variable:
simple_variable { $$ = $1; }
| array_object_dereferencable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| array_object_dereferencable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| function_call { $$ = $1; }
| array_object_dereferencable T_OBJECT_OPERATOR property_name argument_list
{ $$ = Expr\MethodCall[$1, $3, $4]; }
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name argument_list
{ $$ = Expr\NullsafeMethodCall[$1, $3, $4]; }
;
optional_plain_variable:
/* empty */ { $$ = null; }
| plain_variable { $$ = $1; }
;
variable:
callable_variable { $$ = $1; }
| static_member { $$ = $1; }
| array_object_dereferencable T_OBJECT_OPERATOR property_name
{ $$ = Expr\PropertyFetch[$1, $3]; }
| array_object_dereferencable T_NULLSAFE_OBJECT_OPERATOR property_name
{ $$ = Expr\NullsafePropertyFetch[$1, $3]; }
;
simple_variable:
plain_variable { $$ = $1; }
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
| '$' simple_variable { $$ = Expr\Variable[$2]; }
| '$' error { $$ = Expr\Variable[Expr\Error[]]; $this->errorState = 2; }
;
static_member_prop_name:
simple_variable
{ $var = $1->name; $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
;
static_member:
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
;
new_variable:
simple_variable { $$ = $1; }
| new_variable '[' optional_expr ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| new_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| new_variable T_OBJECT_OPERATOR property_name { $$ = Expr\PropertyFetch[$1, $3]; }
| new_variable T_NULLSAFE_OBJECT_OPERATOR property_name { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| class_name T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
| new_variable T_PAAMAYIM_NEKUDOTAYIM static_member_prop_name
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
;
member_name:
identifier_ex { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| simple_variable { $$ = $1; }
;
property_name:
identifier { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| simple_variable { $$ = $1; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
;
list_expr:
T_LIST '(' inner_array_pair_list ')' { $$ = Expr\List_[$3]; }
;
array_pair_list:
inner_array_pair_list
{ $$ = $1; $end = count($$)-1; if ($$[$end] === null) array_pop($$); }
;
comma_or_error:
','
| error
{ /* do nothing -- prevent default action of $$=$1. See #551. */ }
;
inner_array_pair_list:
inner_array_pair_list comma_or_error array_pair { push($1, $3); }
| array_pair { init($1); }
;
array_pair:
expr { $$ = Expr\ArrayItem[$1, null, false]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| expr T_DOUBLE_ARROW list_expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
| /* empty */ { $$ = null; }
;
encaps_list:
encaps_list encaps_var { push($1, $2); }
| encaps_list encaps_string_part { push($1, $2); }
| encaps_var { init($1); }
| encaps_string_part encaps_var { init($1, $2); }
;
encaps_string_part:
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
;
encaps_str_varname:
T_STRING_VARNAME { $$ = Expr\Variable[$1]; }
;
encaps_var:
plain_variable { $$ = $1; }
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| plain_variable T_NULLSAFE_OBJECT_OPERATOR identifier { $$ = Expr\NullsafePropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
{ $$ = Expr\ArrayDimFetch[$2, $4]; }
| T_CURLY_OPEN variable '}' { $$ = $2; }
;
encaps_var_offset:
T_STRING { $$ = Scalar\String_[$1]; }
| T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); }
| '-' T_NUM_STRING { $$ = $this->parseNumString('-' . $2, attributes()); }
| plain_variable { $$ = $1; }
;
%%
/* We currently rely on the token ID mapping to be the same between PHP 5 and PHP 7 - so the same lexer can be used for
* both. This is enforced by sharing this token file. */
%right T_THROW
%left T_INCLUDE T_INCLUDE_ONCE T_EVAL T_REQUIRE T_REQUIRE_ONCE
%left ','
%left T_LOGICAL_OR
%left T_LOGICAL_XOR
%left T_LOGICAL_AND
%right T_PRINT
%right T_YIELD
%right T_DOUBLE_ARROW
%right T_YIELD_FROM
%left '=' T_PLUS_EQUAL T_MINUS_EQUAL T_MUL_EQUAL T_DIV_EQUAL T_CONCAT_EQUAL T_MOD_EQUAL T_AND_EQUAL T_OR_EQUAL T_XOR_EQUAL T_SL_EQUAL T_SR_EQUAL T_POW_EQUAL T_COALESCE_EQUAL
%left '?' ':'
%right T_COALESCE
%left T_BOOLEAN_OR
%left T_BOOLEAN_AND
%left '|'
%left '^'
%left '&'
%nonassoc T_IS_EQUAL T_IS_NOT_EQUAL T_IS_IDENTICAL T_IS_NOT_IDENTICAL T_SPACESHIP
%nonassoc '<' T_IS_SMALLER_OR_EQUAL '>' T_IS_GREATER_OR_EQUAL
%left T_SL T_SR
%left '+' '-' '.'
%left '*' '/' '%'
%right '!'
%nonassoc T_INSTANCEOF
%right '~' T_INC T_DEC T_INT_CAST T_DOUBLE_CAST T_STRING_CAST T_ARRAY_CAST T_OBJECT_CAST T_BOOL_CAST T_UNSET_CAST '@'
%right T_POW
%right '['
%nonassoc T_NEW T_CLONE
%token T_EXIT
%token T_IF
%left T_ELSEIF
%left T_ELSE
%left T_ENDIF
%token T_LNUMBER
%token T_DNUMBER
%token T_STRING
%token T_STRING_VARNAME
%token T_VARIABLE
%token T_NUM_STRING
%token T_INLINE_HTML
%token T_ENCAPSED_AND_WHITESPACE
%token T_CONSTANT_ENCAPSED_STRING
%token T_ECHO
%token T_DO
%token T_WHILE
%token T_ENDWHILE
%token T_FOR
%token T_ENDFOR
%token T_FOREACH
%token T_ENDFOREACH
%token T_DECLARE
%token T_ENDDECLARE
%token T_AS
%token T_SWITCH
%token T_MATCH
%token T_ENDSWITCH
%token T_CASE
%token T_DEFAULT
%token T_BREAK
%token T_CONTINUE
%token T_GOTO
%token T_FUNCTION
%token T_FN
%token T_CONST
%token T_RETURN
%token T_TRY
%token T_CATCH
%token T_FINALLY
%token T_THROW
%token T_USE
%token T_INSTEADOF
%token T_GLOBAL
%right T_STATIC T_ABSTRACT T_FINAL T_PRIVATE T_PROTECTED T_PUBLIC
%token T_VAR
%token T_UNSET
%token T_ISSET
%token T_EMPTY
%token T_HALT_COMPILER
%token T_CLASS
%token T_TRAIT
%token T_INTERFACE
%token T_EXTENDS
%token T_IMPLEMENTS
%token T_OBJECT_OPERATOR
%token T_NULLSAFE_OBJECT_OPERATOR
%token T_DOUBLE_ARROW
%token T_LIST
%token T_ARRAY
%token T_CALLABLE
%token T_CLASS_C
%token T_TRAIT_C
%token T_METHOD_C
%token T_FUNC_C
%token T_LINE
%token T_FILE
%token T_START_HEREDOC
%token T_END_HEREDOC
%token T_DOLLAR_OPEN_CURLY_BRACES
%token T_CURLY_OPEN
%token T_PAAMAYIM_NEKUDOTAYIM
%token T_NAMESPACE
%token T_NS_C
%token T_DIR
%token T_NS_SEPARATOR
%token T_ELLIPSIS
%token T_NAME_FULLY_QUALIFIED
%token T_NAME_QUALIFIED
%token T_NAME_RELATIVE
%token T_ATTRIBUTE<?php
$meta #
#semval($) $this->semValue
#semval($,%t) $this->semValue
#semval(%n) $this->stackPos-(%l-%n)
#semval(%n,%t) $this->stackPos-(%l-%n)
namespace PhpParser\Parser;
#include;
/* GENERATED file based on grammar/tokens.y */
final class Tokens
{
#tokenval
const %s = %n;
#endtokenval
}
<?php
$meta #
#semval($) $this->semValue
#semval($,%t) $this->semValue
#semval(%n) $stackPos-(%l-%n)
#semval(%n,%t) $stackPos-(%l-%n)
namespace PhpParser\Parser;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
#include;
/* This is an automatically GENERATED file, which should not be manually edited.
* Instead edit one of the following:
* * the grammar files grammar/php5.y or grammar/php7.y
* * the skeleton file grammar/parser.template
* * the preprocessing script grammar/rebuildParsers.php
*/
class #(-p) extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = #(YYMAXLEX);
protected $actionTableSize = #(YYLAST);
protected $gotoTableSize = #(YYGLAST);
protected $invalidSymbol = #(YYBADCH);
protected $errorSymbol = #(YYINTERRTOK);
protected $defaultAction = #(YYDEFAULT);
protected $unexpectedTokenRule = #(YYUNEXPECTED);
protected $YY2TBLSTATE = #(YY2TBLSTATE);
protected $numNonLeafStates = #(YYNLSTATES);
protected $symbolToName = array(
#listvar terminals
);
protected $tokenToSymbol = array(
#listvar yytranslate
);
protected $action = array(
#listvar yyaction
);
protected $actionCheck = array(
#listvar yycheck
);
protected $actionBase = array(
#listvar yybase
);
protected $actionDefault = array(
#listvar yydefault
);
protected $goto = array(
#listvar yygoto
);
protected $gotoCheck = array(
#listvar yygcheck
);
protected $gotoBase = array(
#listvar yygbase
);
protected $gotoDefault = array(
#listvar yygdefault
);
protected $ruleToNonTerminal = array(
#listvar yylhs
);
protected $ruleToLength = array(
#listvar yylen
);
#if -t
protected $productions = array(
#production-strings;
);
#endif
protected function initReduceCallbacks() {
$this->reduceCallbacks = [
#reduce
%n => function ($stackPos) {
%b
},
#noact
%n => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
#endreduce
];
}
}
#tailcode;
%pure_parser
%expect 6
%tokens
%%
start:
top_statement_list { $$ = $this->handleNamespaces($1); }
;
top_statement_list_ex:
top_statement_list_ex top_statement { pushNormalizing($1, $2); }
| /* empty */ { init(); }
;
top_statement_list:
top_statement_list_ex
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
reserved_non_modifiers:
T_INCLUDE | T_INCLUDE_ONCE | T_EVAL | T_REQUIRE | T_REQUIRE_ONCE | T_LOGICAL_OR | T_LOGICAL_XOR | T_LOGICAL_AND
| T_INSTANCEOF | T_NEW | T_CLONE | T_EXIT | T_IF | T_ELSEIF | T_ELSE | T_ENDIF | T_ECHO | T_DO | T_WHILE
| T_ENDWHILE | T_FOR | T_ENDFOR | T_FOREACH | T_ENDFOREACH | T_DECLARE | T_ENDDECLARE | T_AS | T_TRY | T_CATCH
| T_FINALLY | T_THROW | T_USE | T_INSTEADOF | T_GLOBAL | T_VAR | T_UNSET | T_ISSET | T_EMPTY | T_CONTINUE | T_GOTO
| T_FUNCTION | T_CONST | T_RETURN | T_PRINT | T_YIELD | T_LIST | T_SWITCH | T_ENDSWITCH | T_CASE | T_DEFAULT
| T_BREAK | T_ARRAY | T_CALLABLE | T_EXTENDS | T_IMPLEMENTS | T_NAMESPACE | T_TRAIT | T_INTERFACE | T_CLASS
| T_CLASS_C | T_TRAIT_C | T_FUNC_C | T_METHOD_C | T_LINE | T_FILE | T_DIR | T_NS_C | T_HALT_COMPILER | T_FN
| T_MATCH
;
semi_reserved:
reserved_non_modifiers
| T_STATIC | T_ABSTRACT | T_FINAL | T_PRIVATE | T_PROTECTED | T_PUBLIC
;
identifier_ex:
T_STRING { $$ = Node\Identifier[$1]; }
| semi_reserved { $$ = Node\Identifier[$1]; }
;
identifier:
T_STRING { $$ = Node\Identifier[$1]; }
;
reserved_non_modifiers_identifier:
reserved_non_modifiers { $$ = Node\Identifier[$1]; }
;
namespace_name:
T_STRING { $$ = Name[$1]; }
| T_NAME_QUALIFIED { $$ = Name[$1]; }
;
legacy_namespace_name:
namespace_name { $$ = $1; }
| T_NAME_FULLY_QUALIFIED { $$ = Name[substr($1, 1)]; }
;
plain_variable:
T_VARIABLE { $$ = Expr\Variable[parseVar($1)]; }
;
top_statement:
statement { $$ = $1; }
| function_declaration_statement { $$ = $1; }
| class_declaration_statement { $$ = $1; }
| T_HALT_COMPILER
{ $$ = Stmt\HaltCompiler[$this->lexer->handleHaltCompiler()]; }
| T_NAMESPACE namespace_name ';'
{ $$ = Stmt\Namespace_[$2, null];
$$->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
$this->checkNamespace($$); }
| T_NAMESPACE namespace_name '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[$2, $4];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| T_NAMESPACE '{' top_statement_list '}'
{ $$ = Stmt\Namespace_[null, $3];
$$->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($$); }
| T_USE use_declarations ';' { $$ = Stmt\Use_[$2, Stmt\Use_::TYPE_NORMAL]; }
| T_USE use_type use_declarations ';' { $$ = Stmt\Use_[$3, $2]; }
| group_use_declaration ';' { $$ = $1; }
| T_CONST constant_declaration_list ';' { $$ = Stmt\Const_[$2]; }
;
use_type:
T_FUNCTION { $$ = Stmt\Use_::TYPE_FUNCTION; }
| T_CONST { $$ = Stmt\Use_::TYPE_CONSTANT; }
;
group_use_declaration:
T_USE use_type legacy_namespace_name T_NS_SEPARATOR '{' unprefixed_use_declarations '}'
{ $$ = Stmt\GroupUse[$3, $6, $2]; }
| T_USE legacy_namespace_name T_NS_SEPARATOR '{' inline_use_declarations '}'
{ $$ = Stmt\GroupUse[$2, $5, Stmt\Use_::TYPE_UNKNOWN]; }
;
unprefixed_use_declarations:
unprefixed_use_declarations ',' unprefixed_use_declaration
{ push($1, $3); }
| unprefixed_use_declaration { init($1); }
;
use_declarations:
use_declarations ',' use_declaration { push($1, $3); }
| use_declaration { init($1); }
;
inline_use_declarations:
inline_use_declarations ',' inline_use_declaration { push($1, $3); }
| inline_use_declaration { init($1); }
;
unprefixed_use_declaration:
namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| namespace_name T_AS identifier
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
use_declaration:
legacy_namespace_name
{ $$ = Stmt\UseUse[$1, null, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #1); }
| legacy_namespace_name T_AS identifier
{ $$ = Stmt\UseUse[$1, $3, Stmt\Use_::TYPE_UNKNOWN]; $this->checkUseUse($$, #3); }
;
inline_use_declaration:
unprefixed_use_declaration { $$ = $1; $$->type = Stmt\Use_::TYPE_NORMAL; }
| use_type unprefixed_use_declaration { $$ = $2; $$->type = $1; }
;
constant_declaration_list:
constant_declaration_list ',' constant_declaration { push($1, $3); }
| constant_declaration { init($1); }
;
constant_declaration:
identifier '=' static_scalar { $$ = Node\Const_[$1, $3]; }
;
class_const_list:
class_const_list ',' class_const { push($1, $3); }
| class_const { init($1); }
;
class_const:
identifier_ex '=' static_scalar { $$ = Node\Const_[$1, $3]; }
;
inner_statement_list_ex:
inner_statement_list_ex inner_statement { pushNormalizing($1, $2); }
| /* empty */ { init(); }
;
inner_statement_list:
inner_statement_list_ex
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
inner_statement:
statement { $$ = $1; }
| function_declaration_statement { $$ = $1; }
| class_declaration_statement { $$ = $1; }
| T_HALT_COMPILER
{ throw new Error('__HALT_COMPILER() can only be used from the outermost scope', attributes()); }
;
non_empty_statement:
'{' inner_statement_list '}'
{
if ($2) {
$$ = $2; prependLeadingComments($$);
} else {
makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
if (null === $$) { $$ = array(); }
}
}
| T_IF parentheses_expr statement elseif_list else_single
{ $$ = Stmt\If_[$2, ['stmts' => toArray($3), 'elseifs' => $4, 'else' => $5]]; }
| T_IF parentheses_expr ':' inner_statement_list new_elseif_list new_else_single T_ENDIF ';'
{ $$ = Stmt\If_[$2, ['stmts' => $4, 'elseifs' => $5, 'else' => $6]]; }
| T_WHILE parentheses_expr while_statement { $$ = Stmt\While_[$2, $3]; }
| T_DO statement T_WHILE parentheses_expr ';' { $$ = Stmt\Do_ [$4, toArray($2)]; }
| T_FOR '(' for_expr ';' for_expr ';' for_expr ')' for_statement
{ $$ = Stmt\For_[['init' => $3, 'cond' => $5, 'loop' => $7, 'stmts' => $9]]; }
| T_SWITCH parentheses_expr switch_case_list { $$ = Stmt\Switch_[$2, $3]; }
| T_BREAK ';' { $$ = Stmt\Break_[null]; }
| T_BREAK expr ';' { $$ = Stmt\Break_[$2]; }
| T_CONTINUE ';' { $$ = Stmt\Continue_[null]; }
| T_CONTINUE expr ';' { $$ = Stmt\Continue_[$2]; }
| T_RETURN ';' { $$ = Stmt\Return_[null]; }
| T_RETURN expr ';' { $$ = Stmt\Return_[$2]; }
| T_GLOBAL global_var_list ';' { $$ = Stmt\Global_[$2]; }
| T_STATIC static_var_list ';' { $$ = Stmt\Static_[$2]; }
| T_ECHO expr_list ';' { $$ = Stmt\Echo_[$2]; }
| T_INLINE_HTML { $$ = Stmt\InlineHTML[$1]; }
| yield_expr ';' { $$ = Stmt\Expression[$1]; }
| expr ';' { $$ = Stmt\Expression[$1]; }
| T_UNSET '(' variables_list ')' ';' { $$ = Stmt\Unset_[$3]; }
| T_FOREACH '(' expr T_AS foreach_variable ')' foreach_statement
{ $$ = Stmt\Foreach_[$3, $5[0], ['keyVar' => null, 'byRef' => $5[1], 'stmts' => $7]]; }
| T_FOREACH '(' expr T_AS variable T_DOUBLE_ARROW foreach_variable ')' foreach_statement
{ $$ = Stmt\Foreach_[$3, $7[0], ['keyVar' => $5, 'byRef' => $7[1], 'stmts' => $9]]; }
| T_DECLARE '(' declare_list ')' declare_statement { $$ = Stmt\Declare_[$3, $5]; }
| T_TRY '{' inner_statement_list '}' catches optional_finally
{ $$ = Stmt\TryCatch[$3, $5, $6]; $this->checkTryCatch($$); }
| T_THROW expr ';' { $$ = Stmt\Throw_[$2]; }
| T_GOTO identifier ';' { $$ = Stmt\Goto_[$2]; }
| identifier ':' { $$ = Stmt\Label[$1]; }
| expr error { $$ = Stmt\Expression[$1]; }
| error { $$ = array(); /* means: no statement */ }
;
statement:
non_empty_statement { $$ = $1; }
| ';'
{ makeNop($$, $this->startAttributeStack[#1], $this->endAttributes);
if ($$ === null) $$ = array(); /* means: no statement */ }
;
catches:
/* empty */ { init(); }
| catches catch { push($1, $2); }
;
catch:
T_CATCH '(' name plain_variable ')' '{' inner_statement_list '}'
{ $$ = Stmt\Catch_[array($3), $4, $7]; }
;
optional_finally:
/* empty */ { $$ = null; }
| T_FINALLY '{' inner_statement_list '}' { $$ = Stmt\Finally_[$3]; }
;
variables_list:
variable { init($1); }
| variables_list ',' variable { push($1, $3); }
;
optional_ref:
/* empty */ { $$ = false; }
| '&' { $$ = true; }
;
optional_ellipsis:
/* empty */ { $$ = false; }
| T_ELLIPSIS { $$ = true; }
;
function_declaration_statement:
T_FUNCTION optional_ref identifier '(' parameter_list ')' optional_return_type '{' inner_statement_list '}'
{ $$ = Stmt\Function_[$3, ['byRef' => $2, 'params' => $5, 'returnType' => $7, 'stmts' => $9]]; }
;
class_declaration_statement:
class_entry_type identifier extends_from implements_list '{' class_statement_list '}'
{ $$ = Stmt\Class_[$2, ['type' => $1, 'extends' => $3, 'implements' => $4, 'stmts' => $6]];
$this->checkClass($$, #2); }
| T_INTERFACE identifier interface_extends_list '{' class_statement_list '}'
{ $$ = Stmt\Interface_[$2, ['extends' => $3, 'stmts' => $5]];
$this->checkInterface($$, #2); }
| T_TRAIT identifier '{' class_statement_list '}'
{ $$ = Stmt\Trait_[$2, ['stmts' => $4]]; }
;
class_entry_type:
T_CLASS { $$ = 0; }
| T_ABSTRACT T_CLASS { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL T_CLASS { $$ = Stmt\Class_::MODIFIER_FINAL; }
;
extends_from:
/* empty */ { $$ = null; }
| T_EXTENDS class_name { $$ = $2; }
;
interface_extends_list:
/* empty */ { $$ = array(); }
| T_EXTENDS class_name_list { $$ = $2; }
;
implements_list:
/* empty */ { $$ = array(); }
| T_IMPLEMENTS class_name_list { $$ = $2; }
;
class_name_list:
class_name { init($1); }
| class_name_list ',' class_name { push($1, $3); }
;
for_statement:
statement { $$ = toArray($1); }
| ':' inner_statement_list T_ENDFOR ';' { $$ = $2; }
;
foreach_statement:
statement { $$ = toArray($1); }
| ':' inner_statement_list T_ENDFOREACH ';' { $$ = $2; }
;
declare_statement:
non_empty_statement { $$ = toArray($1); }
| ';' { $$ = null; }
| ':' inner_statement_list T_ENDDECLARE ';' { $$ = $2; }
;
declare_list:
declare_list_element { init($1); }
| declare_list ',' declare_list_element { push($1, $3); }
;
declare_list_element:
identifier '=' static_scalar { $$ = Stmt\DeclareDeclare[$1, $3]; }
;
switch_case_list:
'{' case_list '}' { $$ = $2; }
| '{' ';' case_list '}' { $$ = $3; }
| ':' case_list T_ENDSWITCH ';' { $$ = $2; }
| ':' ';' case_list T_ENDSWITCH ';' { $$ = $3; }
;
case_list:
/* empty */ { init(); }
| case_list case { push($1, $2); }
;
case:
T_CASE expr case_separator inner_statement_list_ex { $$ = Stmt\Case_[$2, $4]; }
| T_DEFAULT case_separator inner_statement_list_ex { $$ = Stmt\Case_[null, $3]; }
;
case_separator:
':'
| ';'
;
while_statement:
statement { $$ = toArray($1); }
| ':' inner_statement_list T_ENDWHILE ';' { $$ = $2; }
;
elseif_list:
/* empty */ { init(); }
| elseif_list elseif { push($1, $2); }
;
elseif:
T_ELSEIF parentheses_expr statement { $$ = Stmt\ElseIf_[$2, toArray($3)]; }
;
new_elseif_list:
/* empty */ { init(); }
| new_elseif_list new_elseif { push($1, $2); }
;
new_elseif:
T_ELSEIF parentheses_expr ':' inner_statement_list { $$ = Stmt\ElseIf_[$2, $4]; }
;
else_single:
/* empty */ { $$ = null; }
| T_ELSE statement { $$ = Stmt\Else_[toArray($2)]; }
;
new_else_single:
/* empty */ { $$ = null; }
| T_ELSE ':' inner_statement_list { $$ = Stmt\Else_[$3]; }
;
foreach_variable:
variable { $$ = array($1, false); }
| '&' variable { $$ = array($2, true); }
| list_expr { $$ = array($1, false); }
;
parameter_list:
non_empty_parameter_list { $$ = $1; }
| /* empty */ { $$ = array(); }
;
non_empty_parameter_list:
parameter { init($1); }
| non_empty_parameter_list ',' parameter { push($1, $3); }
;
parameter:
optional_param_type optional_ref optional_ellipsis plain_variable
{ $$ = Node\Param[$4, null, $1, $2, $3]; $this->checkParam($$); }
| optional_param_type optional_ref optional_ellipsis plain_variable '=' static_scalar
{ $$ = Node\Param[$4, $6, $1, $2, $3]; $this->checkParam($$); }
;
type:
name { $$ = $1; }
| T_ARRAY { $$ = Node\Identifier['array']; }
| T_CALLABLE { $$ = Node\Identifier['callable']; }
;
optional_param_type:
/* empty */ { $$ = null; }
| type { $$ = $1; }
;
optional_return_type:
/* empty */ { $$ = null; }
| ':' type { $$ = $2; }
;
argument_list:
'(' ')' { $$ = array(); }
| '(' non_empty_argument_list ')' { $$ = $2; }
| '(' yield_expr ')' { $$ = array(Node\Arg[$2, false, false]); }
;
non_empty_argument_list:
argument { init($1); }
| non_empty_argument_list ',' argument { push($1, $3); }
;
argument:
expr { $$ = Node\Arg[$1, false, false]; }
| '&' variable { $$ = Node\Arg[$2, true, false]; }
| T_ELLIPSIS expr { $$ = Node\Arg[$2, false, true]; }
;
global_var_list:
global_var_list ',' global_var { push($1, $3); }
| global_var { init($1); }
;
global_var:
plain_variable { $$ = $1; }
| '$' variable { $$ = Expr\Variable[$2]; }
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
;
static_var_list:
static_var_list ',' static_var { push($1, $3); }
| static_var { init($1); }
;
static_var:
plain_variable { $$ = Stmt\StaticVar[$1, null]; }
| plain_variable '=' static_scalar { $$ = Stmt\StaticVar[$1, $3]; }
;
class_statement_list_ex:
class_statement_list_ex class_statement { if ($2 !== null) { push($1, $2); } }
| /* empty */ { init(); }
;
class_statement_list:
class_statement_list_ex
{ makeZeroLengthNop($nop, $this->lookaheadStartAttributes);
if ($nop !== null) { $1[] = $nop; } $$ = $1; }
;
class_statement:
variable_modifiers property_declaration_list ';'
{ $$ = Stmt\Property[$1, $2]; $this->checkProperty($$, #1); }
| T_CONST class_const_list ';' { $$ = Stmt\ClassConst[$2, 0]; }
| method_modifiers T_FUNCTION optional_ref identifier_ex '(' parameter_list ')' optional_return_type method_body
{ $$ = Stmt\ClassMethod[$4, ['type' => $1, 'byRef' => $3, 'params' => $6, 'returnType' => $8, 'stmts' => $9]];
$this->checkClassMethod($$, #1); }
| T_USE class_name_list trait_adaptations { $$ = Stmt\TraitUse[$2, $3]; }
;
trait_adaptations:
';' { $$ = array(); }
| '{' trait_adaptation_list '}' { $$ = $2; }
;
trait_adaptation_list:
/* empty */ { init(); }
| trait_adaptation_list trait_adaptation { push($1, $2); }
;
trait_adaptation:
trait_method_reference_fully_qualified T_INSTEADOF class_name_list ';'
{ $$ = Stmt\TraitUseAdaptation\Precedence[$1[0], $1[1], $3]; }
| trait_method_reference T_AS member_modifier identifier_ex ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, $4]; }
| trait_method_reference T_AS member_modifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], $3, null]; }
| trait_method_reference T_AS identifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
| trait_method_reference T_AS reserved_non_modifiers_identifier ';'
{ $$ = Stmt\TraitUseAdaptation\Alias[$1[0], $1[1], null, $3]; }
;
trait_method_reference_fully_qualified:
name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = array($1, $3); }
;
trait_method_reference:
trait_method_reference_fully_qualified { $$ = $1; }
| identifier_ex { $$ = array(null, $1); }
;
method_body:
';' /* abstract method */ { $$ = null; }
| '{' inner_statement_list '}' { $$ = $2; }
;
variable_modifiers:
non_empty_member_modifiers { $$ = $1; }
| T_VAR { $$ = 0; }
;
method_modifiers:
/* empty */ { $$ = 0; }
| non_empty_member_modifiers { $$ = $1; }
;
non_empty_member_modifiers:
member_modifier { $$ = $1; }
| non_empty_member_modifiers member_modifier { $this->checkModifier($1, $2, #2); $$ = $1 | $2; }
;
member_modifier:
T_PUBLIC { $$ = Stmt\Class_::MODIFIER_PUBLIC; }
| T_PROTECTED { $$ = Stmt\Class_::MODIFIER_PROTECTED; }
| T_PRIVATE { $$ = Stmt\Class_::MODIFIER_PRIVATE; }
| T_STATIC { $$ = Stmt\Class_::MODIFIER_STATIC; }
| T_ABSTRACT { $$ = Stmt\Class_::MODIFIER_ABSTRACT; }
| T_FINAL { $$ = Stmt\Class_::MODIFIER_FINAL; }
;
property_declaration_list:
property_declaration { init($1); }
| property_declaration_list ',' property_declaration { push($1, $3); }
;
property_decl_name:
T_VARIABLE { $$ = Node\VarLikeIdentifier[parseVar($1)]; }
;
property_declaration:
property_decl_name { $$ = Stmt\PropertyProperty[$1, null]; }
| property_decl_name '=' static_scalar { $$ = Stmt\PropertyProperty[$1, $3]; }
;
expr_list:
expr_list ',' expr { push($1, $3); }
| expr { init($1); }
;
for_expr:
/* empty */ { $$ = array(); }
| expr_list { $$ = $1; }
;
expr:
variable { $$ = $1; }
| list_expr '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' expr { $$ = Expr\Assign[$1, $3]; }
| variable '=' '&' variable { $$ = Expr\AssignRef[$1, $4]; }
| variable '=' '&' new_expr { $$ = Expr\AssignRef[$1, $4]; }
| new_expr { $$ = $1; }
| T_CLONE expr { $$ = Expr\Clone_[$2]; }
| variable T_PLUS_EQUAL expr { $$ = Expr\AssignOp\Plus [$1, $3]; }
| variable T_MINUS_EQUAL expr { $$ = Expr\AssignOp\Minus [$1, $3]; }
| variable T_MUL_EQUAL expr { $$ = Expr\AssignOp\Mul [$1, $3]; }
| variable T_DIV_EQUAL expr { $$ = Expr\AssignOp\Div [$1, $3]; }
| variable T_CONCAT_EQUAL expr { $$ = Expr\AssignOp\Concat [$1, $3]; }
| variable T_MOD_EQUAL expr { $$ = Expr\AssignOp\Mod [$1, $3]; }
| variable T_AND_EQUAL expr { $$ = Expr\AssignOp\BitwiseAnd[$1, $3]; }
| variable T_OR_EQUAL expr { $$ = Expr\AssignOp\BitwiseOr [$1, $3]; }
| variable T_XOR_EQUAL expr { $$ = Expr\AssignOp\BitwiseXor[$1, $3]; }
| variable T_SL_EQUAL expr { $$ = Expr\AssignOp\ShiftLeft [$1, $3]; }
| variable T_SR_EQUAL expr { $$ = Expr\AssignOp\ShiftRight[$1, $3]; }
| variable T_POW_EQUAL expr { $$ = Expr\AssignOp\Pow [$1, $3]; }
| variable T_COALESCE_EQUAL expr { $$ = Expr\AssignOp\Coalesce [$1, $3]; }
| variable T_INC { $$ = Expr\PostInc[$1]; }
| T_INC variable { $$ = Expr\PreInc [$2]; }
| variable T_DEC { $$ = Expr\PostDec[$1]; }
| T_DEC variable { $$ = Expr\PreDec [$2]; }
| expr T_BOOLEAN_OR expr { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
| expr T_BOOLEAN_AND expr { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
| expr T_LOGICAL_OR expr { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
| expr T_LOGICAL_AND expr { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| expr T_LOGICAL_XOR expr { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| expr '|' expr { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| expr '&' expr { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| expr '^' expr { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| expr '.' expr { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| expr '+' expr { $$ = Expr\BinaryOp\Plus [$1, $3]; }
| expr '-' expr { $$ = Expr\BinaryOp\Minus [$1, $3]; }
| expr '*' expr { $$ = Expr\BinaryOp\Mul [$1, $3]; }
| expr '/' expr { $$ = Expr\BinaryOp\Div [$1, $3]; }
| expr '%' expr { $$ = Expr\BinaryOp\Mod [$1, $3]; }
| expr T_SL expr { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
| expr T_SR expr { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
| expr T_POW expr { $$ = Expr\BinaryOp\Pow [$1, $3]; }
| '+' expr %prec T_INC { $$ = Expr\UnaryPlus [$2]; }
| '-' expr %prec T_INC { $$ = Expr\UnaryMinus[$2]; }
| '!' expr { $$ = Expr\BooleanNot[$2]; }
| '~' expr { $$ = Expr\BitwiseNot[$2]; }
| expr T_IS_IDENTICAL expr { $$ = Expr\BinaryOp\Identical [$1, $3]; }
| expr T_IS_NOT_IDENTICAL expr { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; }
| expr T_IS_EQUAL expr { $$ = Expr\BinaryOp\Equal [$1, $3]; }
| expr T_IS_NOT_EQUAL expr { $$ = Expr\BinaryOp\NotEqual [$1, $3]; }
| expr T_SPACESHIP expr { $$ = Expr\BinaryOp\Spaceship [$1, $3]; }
| expr '<' expr { $$ = Expr\BinaryOp\Smaller [$1, $3]; }
| expr T_IS_SMALLER_OR_EQUAL expr { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
| expr '>' expr { $$ = Expr\BinaryOp\Greater [$1, $3]; }
| expr T_IS_GREATER_OR_EQUAL expr { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
| expr T_INSTANCEOF class_name_reference { $$ = Expr\Instanceof_[$1, $3]; }
| parentheses_expr { $$ = $1; }
/* we need a separate '(' new_expr ')' rule to avoid problems caused by a s/r conflict */
| '(' new_expr ')' { $$ = $2; }
| expr '?' expr ':' expr { $$ = Expr\Ternary[$1, $3, $5]; }
| expr '?' ':' expr { $$ = Expr\Ternary[$1, null, $4]; }
| expr T_COALESCE expr { $$ = Expr\BinaryOp\Coalesce[$1, $3]; }
| T_ISSET '(' variables_list ')' { $$ = Expr\Isset_[$3]; }
| T_EMPTY '(' expr ')' { $$ = Expr\Empty_[$3]; }
| T_INCLUDE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE]; }
| T_INCLUDE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_INCLUDE_ONCE]; }
| T_EVAL parentheses_expr { $$ = Expr\Eval_[$2]; }
| T_REQUIRE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE]; }
| T_REQUIRE_ONCE expr { $$ = Expr\Include_[$2, Expr\Include_::TYPE_REQUIRE_ONCE]; }
| T_INT_CAST expr { $$ = Expr\Cast\Int_ [$2]; }
| T_DOUBLE_CAST expr
{ $attrs = attributes();
$attrs['kind'] = $this->getFloatCastKind($1);
$$ = new Expr\Cast\Double($2, $attrs); }
| T_STRING_CAST expr { $$ = Expr\Cast\String_ [$2]; }
| T_ARRAY_CAST expr { $$ = Expr\Cast\Array_ [$2]; }
| T_OBJECT_CAST expr { $$ = Expr\Cast\Object_ [$2]; }
| T_BOOL_CAST expr { $$ = Expr\Cast\Bool_ [$2]; }
| T_UNSET_CAST expr { $$ = Expr\Cast\Unset_ [$2]; }
| T_EXIT exit_expr
{ $attrs = attributes();
$attrs['kind'] = strtolower($1) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
$$ = new Expr\Exit_($2, $attrs); }
| '@' expr { $$ = Expr\ErrorSuppress[$2]; }
| scalar { $$ = $1; }
| array_expr { $$ = $1; }
| scalar_dereference { $$ = $1; }
| '`' backticks_expr '`' { $$ = Expr\ShellExec[$2]; }
| T_PRINT expr { $$ = Expr\Print_[$2]; }
| T_YIELD { $$ = Expr\Yield_[null, null]; }
| T_YIELD_FROM expr { $$ = Expr\YieldFrom[$2]; }
| T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
'{' inner_statement_list '}'
{ $$ = Expr\Closure[['static' => false, 'byRef' => $2, 'params' => $4, 'uses' => $6, 'returnType' => $7, 'stmts' => $9]]; }
| T_STATIC T_FUNCTION optional_ref '(' parameter_list ')' lexical_vars optional_return_type
'{' inner_statement_list '}'
{ $$ = Expr\Closure[['static' => true, 'byRef' => $3, 'params' => $5, 'uses' => $7, 'returnType' => $8, 'stmts' => $10]]; }
;
parentheses_expr:
'(' expr ')' { $$ = $2; }
| '(' yield_expr ')' { $$ = $2; }
;
yield_expr:
T_YIELD expr { $$ = Expr\Yield_[$2, null]; }
| T_YIELD expr T_DOUBLE_ARROW expr { $$ = Expr\Yield_[$4, $2]; }
;
array_expr:
T_ARRAY '(' array_pair_list ')'
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_LONG;
$$ = new Expr\Array_($3, $attrs); }
| '[' array_pair_list ']'
{ $attrs = attributes(); $attrs['kind'] = Expr\Array_::KIND_SHORT;
$$ = new Expr\Array_($2, $attrs); }
;
scalar_dereference:
array_expr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| T_CONSTANT_ENCAPSED_STRING '[' dim_offset ']'
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = Expr\ArrayDimFetch[new Scalar\String_(Scalar\String_::parse($1), $attrs), $3]; }
| constant '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| scalar_dereference '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */
;
anonymous_class:
T_CLASS ctor_arguments extends_from implements_list '{' class_statement_list '}'
{ $$ = array(Stmt\Class_[null, ['type' => 0, 'extends' => $3, 'implements' => $4, 'stmts' => $6]], $2);
$this->checkClass($$[0], -1); }
;
new_expr:
T_NEW class_name_reference ctor_arguments { $$ = Expr\New_[$2, $3]; }
| T_NEW anonymous_class
{ list($class, $ctorArgs) = $2; $$ = Expr\New_[$class, $ctorArgs]; }
;
lexical_vars:
/* empty */ { $$ = array(); }
| T_USE '(' lexical_var_list ')' { $$ = $3; }
;
lexical_var_list:
lexical_var { init($1); }
| lexical_var_list ',' lexical_var { push($1, $3); }
;
lexical_var:
optional_ref plain_variable { $$ = Expr\ClosureUse[$2, $1]; }
;
function_call:
name argument_list { $$ = Expr\FuncCall[$1, $2]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex argument_list
{ $$ = Expr\StaticCall[$1, $3, $4]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '{' expr '}' argument_list
{ $$ = Expr\StaticCall[$1, $4, $6]; }
| static_property argument_list
{ $$ = $this->fixupPhp5StaticPropCall($1, $2, attributes()); }
| variable_without_objects argument_list
{ $$ = Expr\FuncCall[$1, $2]; }
| function_call '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */
;
class_name:
T_STATIC { $$ = Name[$1]; }
| name { $$ = $1; }
;
name:
T_STRING { $$ = Name[$1]; }
| T_NAME_QUALIFIED { $$ = Name[$1]; }
| T_NAME_FULLY_QUALIFIED { $$ = Name\FullyQualified[substr($1, 1)]; }
| T_NAME_RELATIVE { $$ = Name\Relative[substr($1, 10)]; }
;
class_name_reference:
class_name { $$ = $1; }
| dynamic_class_name_reference { $$ = $1; }
;
dynamic_class_name_reference:
object_access_for_dcnr { $$ = $1; }
| base_variable { $$ = $1; }
;
class_name_or_var:
class_name { $$ = $1; }
| reference_variable { $$ = $1; }
;
object_access_for_dcnr:
base_variable T_OBJECT_OPERATOR object_property
{ $$ = Expr\PropertyFetch[$1, $3]; }
| object_access_for_dcnr T_OBJECT_OPERATOR object_property
{ $$ = Expr\PropertyFetch[$1, $3]; }
| object_access_for_dcnr '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| object_access_for_dcnr '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
;
exit_expr:
/* empty */ { $$ = null; }
| '(' ')' { $$ = null; }
| parentheses_expr { $$ = $1; }
;
backticks_expr:
/* empty */ { $$ = array(); }
| T_ENCAPSED_AND_WHITESPACE
{ $$ = array(Scalar\EncapsedStringPart[Scalar\String_::parseEscapeSequences($1, '`', false)]); }
| encaps_list { parseEncapsed($1, '`', false); $$ = $1; }
;
ctor_arguments:
/* empty */ { $$ = array(); }
| argument_list { $$ = $1; }
;
common_scalar:
T_LNUMBER { $$ = $this->parseLNumber($1, attributes(), true); }
| T_DNUMBER { $$ = Scalar\DNumber[Scalar\DNumber::parse($1)]; }
| T_CONSTANT_ENCAPSED_STRING
{ $attrs = attributes(); $attrs['kind'] = strKind($1);
$$ = new Scalar\String_(Scalar\String_::parse($1, false), $attrs); }
| T_LINE { $$ = Scalar\MagicConst\Line[]; }
| T_FILE { $$ = Scalar\MagicConst\File[]; }
| T_DIR { $$ = Scalar\MagicConst\Dir[]; }
| T_CLASS_C { $$ = Scalar\MagicConst\Class_[]; }
| T_TRAIT_C { $$ = Scalar\MagicConst\Trait_[]; }
| T_METHOD_C { $$ = Scalar\MagicConst\Method[]; }
| T_FUNC_C { $$ = Scalar\MagicConst\Function_[]; }
| T_NS_C { $$ = Scalar\MagicConst\Namespace_[]; }
| T_START_HEREDOC T_ENCAPSED_AND_WHITESPACE T_END_HEREDOC
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), false); }
| T_START_HEREDOC T_END_HEREDOC
{ $$ = $this->parseDocString($1, '', $2, attributes(), stackAttributes(#2), false); }
;
static_scalar:
common_scalar { $$ = $1; }
| class_name T_PAAMAYIM_NEKUDOTAYIM identifier_ex { $$ = Expr\ClassConstFetch[$1, $3]; }
| name { $$ = Expr\ConstFetch[$1]; }
| T_ARRAY '(' static_array_pair_list ')' { $$ = Expr\Array_[$3]; }
| '[' static_array_pair_list ']' { $$ = Expr\Array_[$2]; }
| static_operation { $$ = $1; }
;
static_operation:
static_scalar T_BOOLEAN_OR static_scalar { $$ = Expr\BinaryOp\BooleanOr [$1, $3]; }
| static_scalar T_BOOLEAN_AND static_scalar { $$ = Expr\BinaryOp\BooleanAnd[$1, $3]; }
| static_scalar T_LOGICAL_OR static_scalar { $$ = Expr\BinaryOp\LogicalOr [$1, $3]; }
| static_scalar T_LOGICAL_AND static_scalar { $$ = Expr\BinaryOp\LogicalAnd[$1, $3]; }
| static_scalar T_LOGICAL_XOR static_scalar { $$ = Expr\BinaryOp\LogicalXor[$1, $3]; }
| static_scalar '|' static_scalar { $$ = Expr\BinaryOp\BitwiseOr [$1, $3]; }
| static_scalar '&' static_scalar { $$ = Expr\BinaryOp\BitwiseAnd[$1, $3]; }
| static_scalar '^' static_scalar { $$ = Expr\BinaryOp\BitwiseXor[$1, $3]; }
| static_scalar '.' static_scalar { $$ = Expr\BinaryOp\Concat [$1, $3]; }
| static_scalar '+' static_scalar { $$ = Expr\BinaryOp\Plus [$1, $3]; }
| static_scalar '-' static_scalar { $$ = Expr\BinaryOp\Minus [$1, $3]; }
| static_scalar '*' static_scalar { $$ = Expr\BinaryOp\Mul [$1, $3]; }
| static_scalar '/' static_scalar { $$ = Expr\BinaryOp\Div [$1, $3]; }
| static_scalar '%' static_scalar { $$ = Expr\BinaryOp\Mod [$1, $3]; }
| static_scalar T_SL static_scalar { $$ = Expr\BinaryOp\ShiftLeft [$1, $3]; }
| static_scalar T_SR static_scalar { $$ = Expr\BinaryOp\ShiftRight[$1, $3]; }
| static_scalar T_POW static_scalar { $$ = Expr\BinaryOp\Pow [$1, $3]; }
| '+' static_scalar %prec T_INC { $$ = Expr\UnaryPlus [$2]; }
| '-' static_scalar %prec T_INC { $$ = Expr\UnaryMinus[$2]; }
| '!' static_scalar { $$ = Expr\BooleanNot[$2]; }
| '~' static_scalar { $$ = Expr\BitwiseNot[$2]; }
| static_scalar T_IS_IDENTICAL static_scalar { $$ = Expr\BinaryOp\Identical [$1, $3]; }
| static_scalar T_IS_NOT_IDENTICAL static_scalar { $$ = Expr\BinaryOp\NotIdentical [$1, $3]; }
| static_scalar T_IS_EQUAL static_scalar { $$ = Expr\BinaryOp\Equal [$1, $3]; }
| static_scalar T_IS_NOT_EQUAL static_scalar { $$ = Expr\BinaryOp\NotEqual [$1, $3]; }
| static_scalar '<' static_scalar { $$ = Expr\BinaryOp\Smaller [$1, $3]; }
| static_scalar T_IS_SMALLER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\SmallerOrEqual[$1, $3]; }
| static_scalar '>' static_scalar { $$ = Expr\BinaryOp\Greater [$1, $3]; }
| static_scalar T_IS_GREATER_OR_EQUAL static_scalar { $$ = Expr\BinaryOp\GreaterOrEqual[$1, $3]; }
| static_scalar '?' static_scalar ':' static_scalar { $$ = Expr\Ternary[$1, $3, $5]; }
| static_scalar '?' ':' static_scalar { $$ = Expr\Ternary[$1, null, $4]; }
| static_scalar '[' static_scalar ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| '(' static_scalar ')' { $$ = $2; }
;
constant:
name { $$ = Expr\ConstFetch[$1]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM identifier_ex
{ $$ = Expr\ClassConstFetch[$1, $3]; }
;
scalar:
common_scalar { $$ = $1; }
| constant { $$ = $1; }
| '"' encaps_list '"'
{ $attrs = attributes(); $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
parseEncapsed($2, '"', true); $$ = new Scalar\Encapsed($2, $attrs); }
| T_START_HEREDOC encaps_list T_END_HEREDOC
{ $$ = $this->parseDocString($1, $2, $3, attributes(), stackAttributes(#3), true); }
;
static_array_pair_list:
/* empty */ { $$ = array(); }
| non_empty_static_array_pair_list optional_comma { $$ = $1; }
;
optional_comma:
/* empty */
| ','
;
non_empty_static_array_pair_list:
non_empty_static_array_pair_list ',' static_array_pair { push($1, $3); }
| static_array_pair { init($1); }
;
static_array_pair:
static_scalar T_DOUBLE_ARROW static_scalar { $$ = Expr\ArrayItem[$3, $1, false]; }
| static_scalar { $$ = Expr\ArrayItem[$1, null, false]; }
;
variable:
object_access { $$ = $1; }
| base_variable { $$ = $1; }
| function_call { $$ = $1; }
| new_expr_array_deref { $$ = $1; }
;
new_expr_array_deref:
'(' new_expr ')' '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$2, $5]; }
| new_expr_array_deref '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
/* alternative array syntax missing intentionally */
;
object_access:
variable_or_new_expr T_OBJECT_OPERATOR object_property
{ $$ = Expr\PropertyFetch[$1, $3]; }
| variable_or_new_expr T_OBJECT_OPERATOR object_property argument_list
{ $$ = Expr\MethodCall[$1, $3, $4]; }
| object_access argument_list { $$ = Expr\FuncCall[$1, $2]; }
| object_access '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| object_access '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
;
variable_or_new_expr:
variable { $$ = $1; }
| '(' new_expr ')' { $$ = $2; }
;
variable_without_objects:
reference_variable { $$ = $1; }
| '$' variable_without_objects { $$ = Expr\Variable[$2]; }
;
base_variable:
variable_without_objects { $$ = $1; }
| static_property { $$ = $1; }
;
static_property:
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' reference_variable
{ $$ = Expr\StaticPropertyFetch[$1, $4]; }
| static_property_with_arrays { $$ = $1; }
;
static_property_simple_name:
T_VARIABLE
{ $var = parseVar($1); $$ = \is_string($var) ? Node\VarLikeIdentifier[$var] : $var; }
;
static_property_with_arrays:
class_name_or_var T_PAAMAYIM_NEKUDOTAYIM static_property_simple_name
{ $$ = Expr\StaticPropertyFetch[$1, $3]; }
| class_name_or_var T_PAAMAYIM_NEKUDOTAYIM '$' '{' expr '}'
{ $$ = Expr\StaticPropertyFetch[$1, $5]; }
| static_property_with_arrays '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| static_property_with_arrays '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
;
reference_variable:
reference_variable '[' dim_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| reference_variable '{' expr '}' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| plain_variable { $$ = $1; }
| '$' '{' expr '}' { $$ = Expr\Variable[$3]; }
;
dim_offset:
/* empty */ { $$ = null; }
| expr { $$ = $1; }
;
object_property:
identifier { $$ = $1; }
| '{' expr '}' { $$ = $2; }
| variable_without_objects { $$ = $1; }
| error { $$ = Expr\Error[]; $this->errorState = 2; }
;
list_expr:
T_LIST '(' list_expr_elements ')' { $$ = Expr\List_[$3]; }
;
list_expr_elements:
list_expr_elements ',' list_expr_element { push($1, $3); }
| list_expr_element { init($1); }
;
list_expr_element:
variable { $$ = Expr\ArrayItem[$1, null, false]; }
| list_expr { $$ = Expr\ArrayItem[$1, null, false]; }
| /* empty */ { $$ = null; }
;
array_pair_list:
/* empty */ { $$ = array(); }
| non_empty_array_pair_list optional_comma { $$ = $1; }
;
non_empty_array_pair_list:
non_empty_array_pair_list ',' array_pair { push($1, $3); }
| array_pair { init($1); }
;
array_pair:
expr T_DOUBLE_ARROW expr { $$ = Expr\ArrayItem[$3, $1, false]; }
| expr { $$ = Expr\ArrayItem[$1, null, false]; }
| expr T_DOUBLE_ARROW '&' variable { $$ = Expr\ArrayItem[$4, $1, true]; }
| '&' variable { $$ = Expr\ArrayItem[$2, null, true]; }
| T_ELLIPSIS expr { $$ = Expr\ArrayItem[$2, null, false, attributes(), true]; }
;
encaps_list:
encaps_list encaps_var { push($1, $2); }
| encaps_list encaps_string_part { push($1, $2); }
| encaps_var { init($1); }
| encaps_string_part encaps_var { init($1, $2); }
;
encaps_string_part:
T_ENCAPSED_AND_WHITESPACE { $$ = Scalar\EncapsedStringPart[$1]; }
;
encaps_str_varname:
T_STRING_VARNAME { $$ = Expr\Variable[$1]; }
;
encaps_var:
plain_variable { $$ = $1; }
| plain_variable '[' encaps_var_offset ']' { $$ = Expr\ArrayDimFetch[$1, $3]; }
| plain_variable T_OBJECT_OPERATOR identifier { $$ = Expr\PropertyFetch[$1, $3]; }
| T_DOLLAR_OPEN_CURLY_BRACES expr '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES T_STRING_VARNAME '}' { $$ = Expr\Variable[$2]; }
| T_DOLLAR_OPEN_CURLY_BRACES encaps_str_varname '[' expr ']' '}'
{ $$ = Expr\ArrayDimFetch[$2, $4]; }
| T_CURLY_OPEN variable '}' { $$ = $2; }
;
encaps_var_offset:
T_STRING { $$ = Scalar\String_[$1]; }
| T_NUM_STRING { $$ = $this->parseNumString($1, attributes()); }
| plain_variable { $$ = $1; }
;
%%
<?php declare(strict_types=1);
namespace PhpParser;
interface Node
{
/**
* Gets the type of the node.
*
* @return string Type of the node
*/
public function getType() : string;
/**
* Gets the names of the sub nodes.
*
* @return array Names of sub nodes
*/
public function getSubNodeNames() : array;
/**
* Gets line the node started in (alias of getStartLine).
*
* @return int Start line (or -1 if not available)
*/
public function getLine() : int;
/**
* Gets line the node started in.
*
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
*
* @return int Start line (or -1 if not available)
*/
public function getStartLine() : int;
/**
* Gets the line the node ended in.
*
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
*
* @return int End line (or -1 if not available)
*/
public function getEndLine() : int;
/**
* Gets the token offset of the first token that is part of this node.
*
* The offset is an index into the array returned by Lexer::getTokens().
*
* Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default).
*
* @return int Token start position (or -1 if not available)
*/
public function getStartTokenPos() : int;
/**
* Gets the token offset of the last token that is part of this node.
*
* The offset is an index into the array returned by Lexer::getTokens().
*
* Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default).
*
* @return int Token end position (or -1 if not available)
*/
public function getEndTokenPos() : int;
/**
* Gets the file offset of the first character that is part of this node.
*
* Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default).
*
* @return int File start position (or -1 if not available)
*/
public function getStartFilePos() : int;
/**
* Gets the file offset of the last character that is part of this node.
*
* Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default).
*
* @return int File end position (or -1 if not available)
*/
public function getEndFilePos() : int;
/**
* Gets all comments directly preceding this node.
*
* The comments are also available through the "comments" attribute.
*
* @return Comment[]
*/
public function getComments() : array;
/**
* Gets the doc comment of the node.
*
* @return null|Comment\Doc Doc comment object or null
*/
public function getDocComment();
/**
* Sets the doc comment of the node.
*
* This will either replace an existing doc comment or add it to the comments array.
*
* @param Comment\Doc $docComment Doc comment to set
*/
public function setDocComment(Comment\Doc $docComment);
/**
* Sets an attribute on a node.
*
* @param string $key
* @param mixed $value
*/
public function setAttribute(string $key, $value);
/**
* Returns whether an attribute exists.
*
* @param string $key
*
* @return bool
*/
public function hasAttribute(string $key) : bool;
/**
* Returns the value of an attribute.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function getAttribute(string $key, $default = null);
/**
* Returns all the attributes of this node.
*
* @return array
*/
public function getAttributes() : array;
/**
* Replaces all the attributes of this node.
*
* @param array $attributes
*/
public function setAttributes(array $attributes);
}
<?php
namespace PhpParser;
class ConstExprEvaluationException extends \Exception
{}
<?php declare(strict_types=1);
namespace PhpParser;
class JsonDecoder
{
/** @var \ReflectionClass[] Node type to reflection class map */
private $reflectionClassCache;
public function decode(string $json) {
$value = json_decode($json, true);
if (json_last_error()) {
throw new \RuntimeException('JSON decoding error: ' . json_last_error_msg());
}
return $this->decodeRecursive($value);
}
private function decodeRecursive($value) {
if (\is_array($value)) {
if (isset($value['nodeType'])) {
if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') {
return $this->decodeComment($value);
}
return $this->decodeNode($value);
}
return $this->decodeArray($value);
}
return $value;
}
private function decodeArray(array $array) : array {
$decodedArray = [];
foreach ($array as $key => $value) {
$decodedArray[$key] = $this->decodeRecursive($value);
}
return $decodedArray;
}
private function decodeNode(array $value) : Node {
$nodeType = $value['nodeType'];
if (!\is_string($nodeType)) {
throw new \RuntimeException('Node type must be a string');
}
$reflectionClass = $this->reflectionClassFromNodeType($nodeType);
/** @var Node $node */
$node = $reflectionClass->newInstanceWithoutConstructor();
if (isset($value['attributes'])) {
if (!\is_array($value['attributes'])) {
throw new \RuntimeException('Attributes must be an array');
}
$node->setAttributes($this->decodeArray($value['attributes']));
}
foreach ($value as $name => $subNode) {
if ($name === 'nodeType' || $name === 'attributes') {
continue;
}
$node->$name = $this->decodeRecursive($subNode);
}
return $node;
}
private function decodeComment(array $value) : Comment {
$className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class;
if (!isset($value['text'])) {
throw new \RuntimeException('Comment must have text');
}
return new $className(
$value['text'],
$value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1,
$value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1
);
}
private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass {
if (!isset($this->reflectionClassCache[$nodeType])) {
$className = $this->classNameFromNodeType($nodeType);
$this->reflectionClassCache[$nodeType] = new \ReflectionClass($className);
}
return $this->reflectionClassCache[$nodeType];
}
private function classNameFromNodeType(string $nodeType) : string {
$className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\');
if (class_exists($className)) {
return $className;
}
$className .= '_';
if (class_exists($className)) {
return $className;
}
throw new \RuntimeException("Unknown node type \"$nodeType\"");
}
}
<?php
namespace PhpParser;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
/**
* Evaluates constant expressions.
*
* This evaluator is able to evaluate all constant expressions (as defined by PHP), which can be
* evaluated without further context. If a subexpression is not of this type, a user-provided
* fallback evaluator is invoked. To support all constant expressions that are also supported by
* PHP (and not already handled by this class), the fallback evaluator must be able to handle the
* following node types:
*
* * All Scalar\MagicConst\* nodes.
* * Expr\ConstFetch nodes. Only null/false/true are already handled by this class.
* * Expr\ClassConstFetch nodes.
*
* The fallback evaluator should throw ConstExprEvaluationException for nodes it cannot evaluate.
*
* The evaluation is dependent on runtime configuration in two respects: Firstly, floating
* point to string conversions are affected by the precision ini setting. Secondly, they are also
* affected by the LC_NUMERIC locale.
*/
class ConstExprEvaluator
{
private $fallbackEvaluator;
/**
* Create a constant expression evaluator.
*
* The provided fallback evaluator is invoked whenever a subexpression cannot be evaluated. See
* class doc comment for more information.
*
* @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
*/
public function __construct(callable $fallbackEvaluator = null) {
$this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
throw new ConstExprEvaluationException(
"Expression of type {$expr->getType()} cannot be evaluated"
);
};
}
/**
* Silently evaluates a constant expression into a PHP value.
*
* Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException.
* The original source of the exception is available through getPrevious().
*
* If some part of the expression cannot be evaluated, the fallback evaluator passed to the
* constructor will be invoked. By default, if no fallback is provided, an exception of type
* ConstExprEvaluationException is thrown.
*
* See class doc comment for caveats and limitations.
*
* @param Expr $expr Constant expression to evaluate
* @return mixed Result of evaluation
*
* @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred
*/
public function evaluateSilently(Expr $expr) {
set_error_handler(function($num, $str, $file, $line) {
throw new \ErrorException($str, 0, $num, $file, $line);
});
try {
return $this->evaluate($expr);
} catch (\Throwable $e) {
if (!$e instanceof ConstExprEvaluationException) {
$e = new ConstExprEvaluationException(
"An error occurred during constant expression evaluation", 0, $e);
}
throw $e;
} finally {
restore_error_handler();
}
}
/**
* Directly evaluates a constant expression into a PHP value.
*
* May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these
* into a ConstExprEvaluationException.
*
* If some part of the expression cannot be evaluated, the fallback evaluator passed to the
* constructor will be invoked. By default, if no fallback is provided, an exception of type
* ConstExprEvaluationException is thrown.
*
* See class doc comment for caveats and limitations.
*
* @param Expr $expr Constant expression to evaluate
* @return mixed Result of evaluation
*
* @throws ConstExprEvaluationException if the expression cannot be evaluated
*/
public function evaluateDirectly(Expr $expr) {
return $this->evaluate($expr);
}
private function evaluate(Expr $expr) {
if ($expr instanceof Scalar\LNumber
|| $expr instanceof Scalar\DNumber
|| $expr instanceof Scalar\String_
) {
return $expr->value;
}
if ($expr instanceof Expr\Array_) {
return $this->evaluateArray($expr);
}
// Unary operators
if ($expr instanceof Expr\UnaryPlus) {
return +$this->evaluate($expr->expr);
}
if ($expr instanceof Expr\UnaryMinus) {
return -$this->evaluate($expr->expr);
}
if ($expr instanceof Expr\BooleanNot) {
return !$this->evaluate($expr->expr);
}
if ($expr instanceof Expr\BitwiseNot) {
return ~$this->evaluate($expr->expr);
}
if ($expr instanceof Expr\BinaryOp) {
return $this->evaluateBinaryOp($expr);
}
if ($expr instanceof Expr\Ternary) {
return $this->evaluateTernary($expr);
}
if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) {
return $this->evaluate($expr->var)[$this->evaluate($expr->dim)];
}
if ($expr instanceof Expr\ConstFetch) {
return $this->evaluateConstFetch($expr);
}
return ($this->fallbackEvaluator)($expr);
}
private function evaluateArray(Expr\Array_ $expr) {
$array = [];
foreach ($expr->items as $item) {
if (null !== $item->key) {
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
} else {
$array[] = $this->evaluate($item->value);
}
}
return $array;
}
private function evaluateTernary(Expr\Ternary $expr) {
if (null === $expr->if) {
return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else);
}
return $this->evaluate($expr->cond)
? $this->evaluate($expr->if)
: $this->evaluate($expr->else);
}
private function evaluateBinaryOp(Expr\BinaryOp $expr) {
if ($expr instanceof Expr\BinaryOp\Coalesce
&& $expr->left instanceof Expr\ArrayDimFetch
) {
// This needs to be special cased to respect BP_VAR_IS fetch semantics
return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)]
?? $this->evaluate($expr->right);
}
// The evaluate() calls are repeated in each branch, because some of the operators are
// short-circuiting and evaluating the RHS in advance may be illegal in that case
$l = $expr->left;
$r = $expr->right;
switch ($expr->getOperatorSigil()) {
case '&': return $this->evaluate($l) & $this->evaluate($r);
case '|': return $this->evaluate($l) | $this->evaluate($r);
case '^': return $this->evaluate($l) ^ $this->evaluate($r);
case '&&': return $this->evaluate($l) && $this->evaluate($r);
case '||': return $this->evaluate($l) || $this->evaluate($r);
case '??': return $this->evaluate($l) ?? $this->evaluate($r);
case '.': return $this->evaluate($l) . $this->evaluate($r);
case '/': return $this->evaluate($l) / $this->evaluate($r);
case '==': return $this->evaluate($l) == $this->evaluate($r);
case '>': return $this->evaluate($l) > $this->evaluate($r);
case '>=': return $this->evaluate($l) >= $this->evaluate($r);
case '===': return $this->evaluate($l) === $this->evaluate($r);
case 'and': return $this->evaluate($l) and $this->evaluate($r);
case 'or': return $this->evaluate($l) or $this->evaluate($r);
case 'xor': return $this->evaluate($l) xor $this->evaluate($r);
case '-': return $this->evaluate($l) - $this->evaluate($r);
case '%': return $this->evaluate($l) % $this->evaluate($r);
case '*': return $this->evaluate($l) * $this->evaluate($r);
case '!=': return $this->evaluate($l) != $this->evaluate($r);
case '!==': return $this->evaluate($l) !== $this->evaluate($r);
case '+': return $this->evaluate($l) + $this->evaluate($r);
case '**': return $this->evaluate($l) ** $this->evaluate($r);
case '<<': return $this->evaluate($l) << $this->evaluate($r);
case '>>': return $this->evaluate($l) >> $this->evaluate($r);
case '<': return $this->evaluate($l) < $this->evaluate($r);
case '<=': return $this->evaluate($l) <= $this->evaluate($r);
case '<=>': return $this->evaluate($l) <=> $this->evaluate($r);
}
throw new \Exception('Should not happen');
}
private function evaluateConstFetch(Expr\ConstFetch $expr) {
$name = $expr->name->toLowerString();
switch ($name) {
case 'null': return null;
case 'false': return false;
case 'true': return true;
}
return ($this->fallbackEvaluator)($expr);
}
}
<?php declare(strict_types=1);
namespace PhpParser\PrettyPrinter;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Stmt;
use PhpParser\PrettyPrinterAbstract;
class Standard extends PrettyPrinterAbstract
{
// Special nodes
protected function pParam(Node\Param $node) {
return $this->pAttrGroups($node->attrGroups, true)
. $this->pModifiers($node->flags)
. ($node->type ? $this->p($node->type) . ' ' : '')
. ($node->byRef ? '&' : '')
. ($node->variadic ? '...' : '')
. $this->p($node->var)
. ($node->default ? ' = ' . $this->p($node->default) : '');
}
protected function pArg(Node\Arg $node) {
return ($node->name ? $node->name->toString() . ': ' : '')
. ($node->byRef ? '&' : '') . ($node->unpack ? '...' : '')
. $this->p($node->value);
}
protected function pConst(Node\Const_ $node) {
return $node->name . ' = ' . $this->p($node->value);
}
protected function pNullableType(Node\NullableType $node) {
return '?' . $this->p($node->type);
}
protected function pUnionType(Node\UnionType $node) {
return $this->pImplode($node->types, '|');
}
protected function pIdentifier(Node\Identifier $node) {
return $node->name;
}
protected function pVarLikeIdentifier(Node\VarLikeIdentifier $node) {
return '$' . $node->name;
}
protected function pAttribute(Node\Attribute $node) {
return $this->p($node->name)
. ($node->args ? '(' . $this->pCommaSeparated($node->args) . ')' : '');
}
protected function pAttributeGroup(Node\AttributeGroup $node) {
return '#[' . $this->pCommaSeparated($node->attrs) . ']';
}
// Names
protected function pName(Name $node) {
return implode('\\', $node->parts);
}
protected function pName_FullyQualified(Name\FullyQualified $node) {
return '\\' . implode('\\', $node->parts);
}
protected function pName_Relative(Name\Relative $node) {
return 'namespace\\' . implode('\\', $node->parts);
}
// Magic Constants
protected function pScalar_MagicConst_Class(MagicConst\Class_ $node) {
return '__CLASS__';
}
protected function pScalar_MagicConst_Dir(MagicConst\Dir $node) {
return '__DIR__';
}
protected function pScalar_MagicConst_File(MagicConst\File $node) {
return '__FILE__';
}
protected function pScalar_MagicConst_Function(MagicConst\Function_ $node) {
return '__FUNCTION__';
}
protected function pScalar_MagicConst_Line(MagicConst\Line $node) {
return '__LINE__';
}
protected function pScalar_MagicConst_Method(MagicConst\Method $node) {
return '__METHOD__';
}
protected function pScalar_MagicConst_Namespace(MagicConst\Namespace_ $node) {
return '__NAMESPACE__';
}
protected function pScalar_MagicConst_Trait(MagicConst\Trait_ $node) {
return '__TRAIT__';
}
// Scalars
protected function pScalar_String(Scalar\String_ $node) {
$kind = $node->getAttribute('kind', Scalar\String_::KIND_SINGLE_QUOTED);
switch ($kind) {
case Scalar\String_::KIND_NOWDOC:
$label = $node->getAttribute('docLabel');
if ($label && !$this->containsEndLabel($node->value, $label)) {
if ($node->value === '') {
return "<<<'$label'\n$label" . $this->docStringEndToken;
}
return "<<<'$label'\n$node->value\n$label"
. $this->docStringEndToken;
}
/* break missing intentionally */
case Scalar\String_::KIND_SINGLE_QUOTED:
return $this->pSingleQuotedString($node->value);
case Scalar\String_::KIND_HEREDOC:
$label = $node->getAttribute('docLabel');
if ($label && !$this->containsEndLabel($node->value, $label)) {
if ($node->value === '') {
return "<<<$label\n$label" . $this->docStringEndToken;
}
$escaped = $this->escapeString($node->value, null);
return "<<<$label\n" . $escaped . "\n$label"
. $this->docStringEndToken;
}
/* break missing intentionally */
case Scalar\String_::KIND_DOUBLE_QUOTED:
return '"' . $this->escapeString($node->value, '"') . '"';
}
throw new \Exception('Invalid string kind');
}
protected function pScalar_Encapsed(Scalar\Encapsed $node) {
if ($node->getAttribute('kind') === Scalar\String_::KIND_HEREDOC) {
$label = $node->getAttribute('docLabel');
if ($label && !$this->encapsedContainsEndLabel($node->parts, $label)) {
if (count($node->parts) === 1
&& $node->parts[0] instanceof Scalar\EncapsedStringPart
&& $node->parts[0]->value === ''
) {
return "<<<$label\n$label" . $this->docStringEndToken;
}
return "<<<$label\n" . $this->pEncapsList($node->parts, null) . "\n$label"
. $this->docStringEndToken;
}
}
return '"' . $this->pEncapsList($node->parts, '"') . '"';
}
protected function pScalar_LNumber(Scalar\LNumber $node) {
if ($node->value === -\PHP_INT_MAX-1) {
// PHP_INT_MIN cannot be represented as a literal,
// because the sign is not part of the literal
return '(-' . \PHP_INT_MAX . '-1)';
}
$kind = $node->getAttribute('kind', Scalar\LNumber::KIND_DEC);
if (Scalar\LNumber::KIND_DEC === $kind) {
return (string) $node->value;
}
if ($node->value < 0) {
$sign = '-';
$str = (string) -$node->value;
} else {
$sign = '';
$str = (string) $node->value;
}
switch ($kind) {
case Scalar\LNumber::KIND_BIN:
return $sign . '0b' . base_convert($str, 10, 2);
case Scalar\LNumber::KIND_OCT:
return $sign . '0' . base_convert($str, 10, 8);
case Scalar\LNumber::KIND_HEX:
return $sign . '0x' . base_convert($str, 10, 16);
}
throw new \Exception('Invalid number kind');
}
protected function pScalar_DNumber(Scalar\DNumber $node) {
if (!is_finite($node->value)) {
if ($node->value === \INF) {
return '\INF';
} elseif ($node->value === -\INF) {
return '-\INF';
} else {
return '\NAN';
}
}
// Try to find a short full-precision representation
$stringValue = sprintf('%.16G', $node->value);
if ($node->value !== (double) $stringValue) {
$stringValue = sprintf('%.17G', $node->value);
}
// %G is locale dependent and there exists no locale-independent alternative. We don't want
// mess with switching locales here, so let's assume that a comma is the only non-standard
// decimal separator we may encounter...
$stringValue = str_replace(',', '.', $stringValue);
// ensure that number is really printed as float
return preg_match('/^-?[0-9]+$/', $stringValue) ? $stringValue . '.0' : $stringValue;
}
protected function pScalar_EncapsedStringPart(Scalar\EncapsedStringPart $node) {
throw new \LogicException('Cannot directly print EncapsedStringPart');
}
// Assignments
protected function pExpr_Assign(Expr\Assign $node) {
return $this->pInfixOp(Expr\Assign::class, $node->var, ' = ', $node->expr);
}
protected function pExpr_AssignRef(Expr\AssignRef $node) {
return $this->pInfixOp(Expr\AssignRef::class, $node->var, ' =& ', $node->expr);
}
protected function pExpr_AssignOp_Plus(AssignOp\Plus $node) {
return $this->pInfixOp(AssignOp\Plus::class, $node->var, ' += ', $node->expr);
}
protected function pExpr_AssignOp_Minus(AssignOp\Minus $node) {
return $this->pInfixOp(AssignOp\Minus::class, $node->var, ' -= ', $node->expr);
}
protected function pExpr_AssignOp_Mul(AssignOp\Mul $node) {
return $this->pInfixOp(AssignOp\Mul::class, $node->var, ' *= ', $node->expr);
}
protected function pExpr_AssignOp_Div(AssignOp\Div $node) {
return $this->pInfixOp(AssignOp\Div::class, $node->var, ' /= ', $node->expr);
}
protected function pExpr_AssignOp_Concat(AssignOp\Concat $node) {
return $this->pInfixOp(AssignOp\Concat::class, $node->var, ' .= ', $node->expr);
}
protected function pExpr_AssignOp_Mod(AssignOp\Mod $node) {
return $this->pInfixOp(AssignOp\Mod::class, $node->var, ' %= ', $node->expr);
}
protected function pExpr_AssignOp_BitwiseAnd(AssignOp\BitwiseAnd $node) {
return $this->pInfixOp(AssignOp\BitwiseAnd::class, $node->var, ' &= ', $node->expr);
}
protected function pExpr_AssignOp_BitwiseOr(AssignOp\BitwiseOr $node) {
return $this->pInfixOp(AssignOp\BitwiseOr::class, $node->var, ' |= ', $node->expr);
}
protected function pExpr_AssignOp_BitwiseXor(AssignOp\BitwiseXor $node) {
return $this->pInfixOp(AssignOp\BitwiseXor::class, $node->var, ' ^= ', $node->expr);
}
protected function pExpr_AssignOp_ShiftLeft(AssignOp\ShiftLeft $node) {
return $this->pInfixOp(AssignOp\ShiftLeft::class, $node->var, ' <<= ', $node->expr);
}
protected function pExpr_AssignOp_ShiftRight(AssignOp\ShiftRight $node) {
return $this->pInfixOp(AssignOp\ShiftRight::class, $node->var, ' >>= ', $node->expr);
}
protected function pExpr_AssignOp_Pow(AssignOp\Pow $node) {
return $this->pInfixOp(AssignOp\Pow::class, $node->var, ' **= ', $node->expr);
}
protected function pExpr_AssignOp_Coalesce(AssignOp\Coalesce $node) {
return $this->pInfixOp(AssignOp\Coalesce::class, $node->var, ' ??= ', $node->expr);
}
// Binary expressions
protected function pExpr_BinaryOp_Plus(BinaryOp\Plus $node) {
return $this->pInfixOp(BinaryOp\Plus::class, $node->left, ' + ', $node->right);
}
protected function pExpr_BinaryOp_Minus(BinaryOp\Minus $node) {
return $this->pInfixOp(BinaryOp\Minus::class, $node->left, ' - ', $node->right);
}
protected function pExpr_BinaryOp_Mul(BinaryOp\Mul $node) {
return $this->pInfixOp(BinaryOp\Mul::class, $node->left, ' * ', $node->right);
}
protected function pExpr_BinaryOp_Div(BinaryOp\Div $node) {
return $this->pInfixOp(BinaryOp\Div::class, $node->left, ' / ', $node->right);
}
protected function pExpr_BinaryOp_Concat(BinaryOp\Concat $node) {
return $this->pInfixOp(BinaryOp\Concat::class, $node->left, ' . ', $node->right);
}
protected function pExpr_BinaryOp_Mod(BinaryOp\Mod $node) {
return $this->pInfixOp(BinaryOp\Mod::class, $node->left, ' % ', $node->right);
}
protected function pExpr_BinaryOp_BooleanAnd(BinaryOp\BooleanAnd $node) {
return $this->pInfixOp(BinaryOp\BooleanAnd::class, $node->left, ' && ', $node->right);
}
protected function pExpr_BinaryOp_BooleanOr(BinaryOp\BooleanOr $node) {
return $this->pInfixOp(BinaryOp\BooleanOr::class, $node->left, ' || ', $node->right);
}
protected function pExpr_BinaryOp_BitwiseAnd(BinaryOp\BitwiseAnd $node) {
return $this->pInfixOp(BinaryOp\BitwiseAnd::class, $node->left, ' & ', $node->right);
}
protected function pExpr_BinaryOp_BitwiseOr(BinaryOp\BitwiseOr $node) {
return $this->pInfixOp(BinaryOp\BitwiseOr::class, $node->left, ' | ', $node->right);
}
protected function pExpr_BinaryOp_BitwiseXor(BinaryOp\BitwiseXor $node) {
return $this->pInfixOp(BinaryOp\BitwiseXor::class, $node->left, ' ^ ', $node->right);
}
protected function pExpr_BinaryOp_ShiftLeft(BinaryOp\ShiftLeft $node) {
return $this->pInfixOp(BinaryOp\ShiftLeft::class, $node->left, ' << ', $node->right);
}
protected function pExpr_BinaryOp_ShiftRight(BinaryOp\ShiftRight $node) {
return $this->pInfixOp(BinaryOp\ShiftRight::class, $node->left, ' >> ', $node->right);
}
protected function pExpr_BinaryOp_Pow(BinaryOp\Pow $node) {
return $this->pInfixOp(BinaryOp\Pow::class, $node->left, ' ** ', $node->right);
}
protected function pExpr_BinaryOp_LogicalAnd(BinaryOp\LogicalAnd $node) {
return $this->pInfixOp(BinaryOp\LogicalAnd::class, $node->left, ' and ', $node->right);
}
protected function pExpr_BinaryOp_LogicalOr(BinaryOp\LogicalOr $node) {
return $this->pInfixOp(BinaryOp\LogicalOr::class, $node->left, ' or ', $node->right);
}
protected function pExpr_BinaryOp_LogicalXor(BinaryOp\LogicalXor $node) {
return $this->pInfixOp(BinaryOp\LogicalXor::class, $node->left, ' xor ', $node->right);
}
protected function pExpr_BinaryOp_Equal(BinaryOp\Equal $node) {
return $this->pInfixOp(BinaryOp\Equal::class, $node->left, ' == ', $node->right);
}
protected function pExpr_BinaryOp_NotEqual(BinaryOp\NotEqual $node) {
return $this->pInfixOp(BinaryOp\NotEqual::class, $node->left, ' != ', $node->right);
}
protected function pExpr_BinaryOp_Identical(BinaryOp\Identical $node) {
return $this->pInfixOp(BinaryOp\Identical::class, $node->left, ' === ', $node->right);
}
protected function pExpr_BinaryOp_NotIdentical(BinaryOp\NotIdentical $node) {
return $this->pInfixOp(BinaryOp\NotIdentical::class, $node->left, ' !== ', $node->right);
}
protected function pExpr_BinaryOp_Spaceship(BinaryOp\Spaceship $node) {
return $this->pInfixOp(BinaryOp\Spaceship::class, $node->left, ' <=> ', $node->right);
}
protected function pExpr_BinaryOp_Greater(BinaryOp\Greater $node) {
return $this->pInfixOp(BinaryOp\Greater::class, $node->left, ' > ', $node->right);
}
protected function pExpr_BinaryOp_GreaterOrEqual(BinaryOp\GreaterOrEqual $node) {
return $this->pInfixOp(BinaryOp\GreaterOrEqual::class, $node->left, ' >= ', $node->right);
}
protected function pExpr_BinaryOp_Smaller(BinaryOp\Smaller $node) {
return $this->pInfixOp(BinaryOp\Smaller::class, $node->left, ' < ', $node->right);
}
protected function pExpr_BinaryOp_SmallerOrEqual(BinaryOp\SmallerOrEqual $node) {
return $this->pInfixOp(BinaryOp\SmallerOrEqual::class, $node->left, ' <= ', $node->right);
}
protected function pExpr_BinaryOp_Coalesce(BinaryOp\Coalesce $node) {
return $this->pInfixOp(BinaryOp\Coalesce::class, $node->left, ' ?? ', $node->right);
}
protected function pExpr_Instanceof(Expr\Instanceof_ $node) {
list($precedence, $associativity) = $this->precedenceMap[Expr\Instanceof_::class];
return $this->pPrec($node->expr, $precedence, $associativity, -1)
. ' instanceof '
. $this->pNewVariable($node->class);
}
// Unary expressions
protected function pExpr_BooleanNot(Expr\BooleanNot $node) {
return $this->pPrefixOp(Expr\BooleanNot::class, '!', $node->expr);
}
protected function pExpr_BitwiseNot(Expr\BitwiseNot $node) {
return $this->pPrefixOp(Expr\BitwiseNot::class, '~', $node->expr);
}
protected function pExpr_UnaryMinus(Expr\UnaryMinus $node) {
if ($node->expr instanceof Expr\UnaryMinus || $node->expr instanceof Expr\PreDec) {
// Enforce -(-$expr) instead of --$expr
return '-(' . $this->p($node->expr) . ')';
}
return $this->pPrefixOp(Expr\UnaryMinus::class, '-', $node->expr);
}
protected function pExpr_UnaryPlus(Expr\UnaryPlus $node) {
if ($node->expr instanceof Expr\UnaryPlus || $node->expr instanceof Expr\PreInc) {
// Enforce +(+$expr) instead of ++$expr
return '+(' . $this->p($node->expr) . ')';
}
return $this->pPrefixOp(Expr\UnaryPlus::class, '+', $node->expr);
}
protected function pExpr_PreInc(Expr\PreInc $node) {
return $this->pPrefixOp(Expr\PreInc::class, '++', $node->var);
}
protected function pExpr_PreDec(Expr\PreDec $node) {
return $this->pPrefixOp(Expr\PreDec::class, '--', $node->var);
}
protected function pExpr_PostInc(Expr\PostInc $node) {
return $this->pPostfixOp(Expr\PostInc::class, $node->var, '++');
}
protected function pExpr_PostDec(Expr\PostDec $node) {
return $this->pPostfixOp(Expr\PostDec::class, $node->var, '--');
}
protected function pExpr_ErrorSuppress(Expr\ErrorSuppress $node) {
return $this->pPrefixOp(Expr\ErrorSuppress::class, '@', $node->expr);
}
protected function pExpr_YieldFrom(Expr\YieldFrom $node) {
return $this->pPrefixOp(Expr\YieldFrom::class, 'yield from ', $node->expr);
}
protected function pExpr_Print(Expr\Print_ $node) {
return $this->pPrefixOp(Expr\Print_::class, 'print ', $node->expr);
}
// Casts
protected function pExpr_Cast_Int(Cast\Int_ $node) {
return $this->pPrefixOp(Cast\Int_::class, '(int) ', $node->expr);
}
protected function pExpr_Cast_Double(Cast\Double $node) {
$kind = $node->getAttribute('kind', Cast\Double::KIND_DOUBLE);
if ($kind === Cast\Double::KIND_DOUBLE) {
$cast = '(double)';
} elseif ($kind === Cast\Double::KIND_FLOAT) {
$cast = '(float)';
} elseif ($kind === Cast\Double::KIND_REAL) {
$cast = '(real)';
}
return $this->pPrefixOp(Cast\Double::class, $cast . ' ', $node->expr);
}
protected function pExpr_Cast_String(Cast\String_ $node) {
return $this->pPrefixOp(Cast\String_::class, '(string) ', $node->expr);
}
protected function pExpr_Cast_Array(Cast\Array_ $node) {
return $this->pPrefixOp(Cast\Array_::class, '(array) ', $node->expr);
}
protected function pExpr_Cast_Object(Cast\Object_ $node) {
return $this->pPrefixOp(Cast\Object_::class, '(object) ', $node->expr);
}
protected function pExpr_Cast_Bool(Cast\Bool_ $node) {
return $this->pPrefixOp(Cast\Bool_::class, '(bool) ', $node->expr);
}
protected function pExpr_Cast_Unset(Cast\Unset_ $node) {
return $this->pPrefixOp(Cast\Unset_::class, '(unset) ', $node->expr);
}
// Function calls and similar constructs
protected function pExpr_FuncCall(Expr\FuncCall $node) {
return $this->pCallLhs($node->name)
. '(' . $this->pMaybeMultiline($node->args) . ')';
}
protected function pExpr_MethodCall(Expr\MethodCall $node) {
return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name)
. '(' . $this->pMaybeMultiline($node->args) . ')';
}
protected function pExpr_NullsafeMethodCall(Expr\NullsafeMethodCall $node) {
return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name)
. '(' . $this->pMaybeMultiline($node->args) . ')';
}
protected function pExpr_StaticCall(Expr\StaticCall $node) {
return $this->pDereferenceLhs($node->class) . '::'
. ($node->name instanceof Expr
? ($node->name instanceof Expr\Variable
? $this->p($node->name)
: '{' . $this->p($node->name) . '}')
: $node->name)
. '(' . $this->pMaybeMultiline($node->args) . ')';
}
protected function pExpr_Empty(Expr\Empty_ $node) {
return 'empty(' . $this->p($node->expr) . ')';
}
protected function pExpr_Isset(Expr\Isset_ $node) {
return 'isset(' . $this->pCommaSeparated($node->vars) . ')';
}
protected function pExpr_Eval(Expr\Eval_ $node) {
return 'eval(' . $this->p($node->expr) . ')';
}
protected function pExpr_Include(Expr\Include_ $node) {
static $map = [
Expr\Include_::TYPE_INCLUDE => 'include',
Expr\Include_::TYPE_INCLUDE_ONCE => 'include_once',
Expr\Include_::TYPE_REQUIRE => 'require',
Expr\Include_::TYPE_REQUIRE_ONCE => 'require_once',
];
return $map[$node->type] . ' ' . $this->p($node->expr);
}
protected function pExpr_List(Expr\List_ $node) {
return 'list(' . $this->pCommaSeparated($node->items) . ')';
}
// Other
protected function pExpr_Error(Expr\Error $node) {
throw new \LogicException('Cannot pretty-print AST with Error nodes');
}
protected function pExpr_Variable(Expr\Variable $node) {
if ($node->name instanceof Expr) {
return '${' . $this->p($node->name) . '}';
} else {
return '$' . $node->name;
}
}
protected function pExpr_Array(Expr\Array_ $node) {
$syntax = $node->getAttribute('kind',
$this->options['shortArraySyntax'] ? Expr\Array_::KIND_SHORT : Expr\Array_::KIND_LONG);
if ($syntax === Expr\Array_::KIND_SHORT) {
return '[' . $this->pMaybeMultiline($node->items, true) . ']';
} else {
return 'array(' . $this->pMaybeMultiline($node->items, true) . ')';
}
}
protected function pExpr_ArrayItem(Expr\ArrayItem $node) {
return (null !== $node->key ? $this->p($node->key) . ' => ' : '')
. ($node->byRef ? '&' : '')
. ($node->unpack ? '...' : '')
. $this->p($node->value);
}
protected function pExpr_ArrayDimFetch(Expr\ArrayDimFetch $node) {
return $this->pDereferenceLhs($node->var)
. '[' . (null !== $node->dim ? $this->p($node->dim) : '') . ']';
}
protected function pExpr_ConstFetch(Expr\ConstFetch $node) {
return $this->p($node->name);
}
protected function pExpr_ClassConstFetch(Expr\ClassConstFetch $node) {
return $this->pDereferenceLhs($node->class) . '::' . $this->p($node->name);
}
protected function pExpr_PropertyFetch(Expr\PropertyFetch $node) {
return $this->pDereferenceLhs($node->var) . '->' . $this->pObjectProperty($node->name);
}
protected function pExpr_NullsafePropertyFetch(Expr\NullsafePropertyFetch $node) {
return $this->pDereferenceLhs($node->var) . '?->' . $this->pObjectProperty($node->name);
}
protected function pExpr_StaticPropertyFetch(Expr\StaticPropertyFetch $node) {
return $this->pDereferenceLhs($node->class) . '::$' . $this->pObjectProperty($node->name);
}
protected function pExpr_ShellExec(Expr\ShellExec $node) {
return '`' . $this->pEncapsList($node->parts, '`') . '`';
}
protected function pExpr_Closure(Expr\Closure $node) {
return $this->pAttrGroups($node->attrGroups, true)
. ($node->static ? 'static ' : '')
. 'function ' . ($node->byRef ? '&' : '')
. '(' . $this->pCommaSeparated($node->params) . ')'
. (!empty($node->uses) ? ' use(' . $this->pCommaSeparated($node->uses) . ')' : '')
. (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '')
. ' {' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pExpr_Match(Expr\Match_ $node) {
return 'match (' . $this->p($node->cond) . ') {'
. $this->pCommaSeparatedMultiline($node->arms, true)
. $this->nl
. '}';
}
protected function pMatchArm(Node\MatchArm $node) {
return ($node->conds ? $this->pCommaSeparated($node->conds) : 'default')
. ' => ' . $this->p($node->body);
}
protected function pExpr_ArrowFunction(Expr\ArrowFunction $node) {
return $this->pAttrGroups($node->attrGroups, true)
. ($node->static ? 'static ' : '')
. 'fn' . ($node->byRef ? '&' : '')
. '(' . $this->pCommaSeparated($node->params) . ')'
. (null !== $node->returnType ? ': ' . $this->p($node->returnType) : '')
. ' => '
. $this->p($node->expr);
}
protected function pExpr_ClosureUse(Expr\ClosureUse $node) {
return ($node->byRef ? '&' : '') . $this->p($node->var);
}
protected function pExpr_New(Expr\New_ $node) {
if ($node->class instanceof Stmt\Class_) {
$args = $node->args ? '(' . $this->pMaybeMultiline($node->args) . ')' : '';
return 'new ' . $this->pClassCommon($node->class, $args);
}
return 'new ' . $this->pNewVariable($node->class)
. '(' . $this->pMaybeMultiline($node->args) . ')';
}
protected function pExpr_Clone(Expr\Clone_ $node) {
return 'clone ' . $this->p($node->expr);
}
protected function pExpr_Ternary(Expr\Ternary $node) {
// a bit of cheating: we treat the ternary as a binary op where the ?...: part is the operator.
// this is okay because the part between ? and : never needs parentheses.
return $this->pInfixOp(Expr\Ternary::class,
$node->cond, ' ?' . (null !== $node->if ? ' ' . $this->p($node->if) . ' ' : '') . ': ', $node->else
);
}
protected function pExpr_Exit(Expr\Exit_ $node) {
$kind = $node->getAttribute('kind', Expr\Exit_::KIND_DIE);
return ($kind === Expr\Exit_::KIND_EXIT ? 'exit' : 'die')
. (null !== $node->expr ? '(' . $this->p($node->expr) . ')' : '');
}
protected function pExpr_Throw(Expr\Throw_ $node) {
return 'throw ' . $this->p($node->expr);
}
protected function pExpr_Yield(Expr\Yield_ $node) {
if ($node->value === null) {
return 'yield';
} else {
// this is a bit ugly, but currently there is no way to detect whether the parentheses are necessary
return '(yield '
. ($node->key !== null ? $this->p($node->key) . ' => ' : '')
. $this->p($node->value)
. ')';
}
}
// Declarations
protected function pStmt_Namespace(Stmt\Namespace_ $node) {
if ($this->canUseSemicolonNamespaces) {
return 'namespace ' . $this->p($node->name) . ';'
. $this->nl . $this->pStmts($node->stmts, false);
} else {
return 'namespace' . (null !== $node->name ? ' ' . $this->p($node->name) : '')
. ' {' . $this->pStmts($node->stmts) . $this->nl . '}';
}
}
protected function pStmt_Use(Stmt\Use_ $node) {
return 'use ' . $this->pUseType($node->type)
. $this->pCommaSeparated($node->uses) . ';';
}
protected function pStmt_GroupUse(Stmt\GroupUse $node) {
return 'use ' . $this->pUseType($node->type) . $this->pName($node->prefix)
. '\{' . $this->pCommaSeparated($node->uses) . '};';
}
protected function pStmt_UseUse(Stmt\UseUse $node) {
return $this->pUseType($node->type) . $this->p($node->name)
. (null !== $node->alias ? ' as ' . $node->alias : '');
}
protected function pUseType($type) {
return $type === Stmt\Use_::TYPE_FUNCTION ? 'function '
: ($type === Stmt\Use_::TYPE_CONSTANT ? 'const ' : '');
}
protected function pStmt_Interface(Stmt\Interface_ $node) {
return $this->pAttrGroups($node->attrGroups)
. 'interface ' . $node->name
. (!empty($node->extends) ? ' extends ' . $this->pCommaSeparated($node->extends) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Class(Stmt\Class_ $node) {
return $this->pClassCommon($node, ' ' . $node->name);
}
protected function pStmt_Trait(Stmt\Trait_ $node) {
return $this->pAttrGroups($node->attrGroups)
. 'trait ' . $node->name
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_TraitUse(Stmt\TraitUse $node) {
return 'use ' . $this->pCommaSeparated($node->traits)
. (empty($node->adaptations)
? ';'
: ' {' . $this->pStmts($node->adaptations) . $this->nl . '}');
}
protected function pStmt_TraitUseAdaptation_Precedence(Stmt\TraitUseAdaptation\Precedence $node) {
return $this->p($node->trait) . '::' . $node->method
. ' insteadof ' . $this->pCommaSeparated($node->insteadof) . ';';
}
protected function pStmt_TraitUseAdaptation_Alias(Stmt\TraitUseAdaptation\Alias $node) {
return (null !== $node->trait ? $this->p($node->trait) . '::' : '')
. $node->method . ' as'
. (null !== $node->newModifier ? ' ' . rtrim($this->pModifiers($node->newModifier), ' ') : '')
. (null !== $node->newName ? ' ' . $node->newName : '')
. ';';
}
protected function pStmt_Property(Stmt\Property $node) {
return $this->pAttrGroups($node->attrGroups)
. (0 === $node->flags ? 'var ' : $this->pModifiers($node->flags))
. ($node->type ? $this->p($node->type) . ' ' : '')
. $this->pCommaSeparated($node->props) . ';';
}
protected function pStmt_PropertyProperty(Stmt\PropertyProperty $node) {
return '$' . $node->name
. (null !== $node->default ? ' = ' . $this->p($node->default) : '');
}
protected function pStmt_ClassMethod(Stmt\ClassMethod $node) {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pMaybeMultiline($node->params) . ')'
. (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '')
. (null !== $node->stmts
? $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}'
: ';');
}
protected function pStmt_ClassConst(Stmt\ClassConst $node) {
return $this->pAttrGroups($node->attrGroups)
. $this->pModifiers($node->flags)
. 'const ' . $this->pCommaSeparated($node->consts) . ';';
}
protected function pStmt_Function(Stmt\Function_ $node) {
return $this->pAttrGroups($node->attrGroups)
. 'function ' . ($node->byRef ? '&' : '') . $node->name
. '(' . $this->pCommaSeparated($node->params) . ')'
. (null !== $node->returnType ? ' : ' . $this->p($node->returnType) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Const(Stmt\Const_ $node) {
return 'const ' . $this->pCommaSeparated($node->consts) . ';';
}
protected function pStmt_Declare(Stmt\Declare_ $node) {
return 'declare (' . $this->pCommaSeparated($node->declares) . ')'
. (null !== $node->stmts ? ' {' . $this->pStmts($node->stmts) . $this->nl . '}' : ';');
}
protected function pStmt_DeclareDeclare(Stmt\DeclareDeclare $node) {
return $node->key . '=' . $this->p($node->value);
}
// Control flow
protected function pStmt_If(Stmt\If_ $node) {
return 'if (' . $this->p($node->cond) . ') {'
. $this->pStmts($node->stmts) . $this->nl . '}'
. ($node->elseifs ? ' ' . $this->pImplode($node->elseifs, ' ') : '')
. (null !== $node->else ? ' ' . $this->p($node->else) : '');
}
protected function pStmt_ElseIf(Stmt\ElseIf_ $node) {
return 'elseif (' . $this->p($node->cond) . ') {'
. $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Else(Stmt\Else_ $node) {
return 'else {' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_For(Stmt\For_ $node) {
return 'for ('
. $this->pCommaSeparated($node->init) . ';' . (!empty($node->cond) ? ' ' : '')
. $this->pCommaSeparated($node->cond) . ';' . (!empty($node->loop) ? ' ' : '')
. $this->pCommaSeparated($node->loop)
. ') {' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Foreach(Stmt\Foreach_ $node) {
return 'foreach (' . $this->p($node->expr) . ' as '
. (null !== $node->keyVar ? $this->p($node->keyVar) . ' => ' : '')
. ($node->byRef ? '&' : '') . $this->p($node->valueVar) . ') {'
. $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_While(Stmt\While_ $node) {
return 'while (' . $this->p($node->cond) . ') {'
. $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Do(Stmt\Do_ $node) {
return 'do {' . $this->pStmts($node->stmts) . $this->nl
. '} while (' . $this->p($node->cond) . ');';
}
protected function pStmt_Switch(Stmt\Switch_ $node) {
return 'switch (' . $this->p($node->cond) . ') {'
. $this->pStmts($node->cases) . $this->nl . '}';
}
protected function pStmt_Case(Stmt\Case_ $node) {
return (null !== $node->cond ? 'case ' . $this->p($node->cond) : 'default') . ':'
. $this->pStmts($node->stmts);
}
protected function pStmt_TryCatch(Stmt\TryCatch $node) {
return 'try {' . $this->pStmts($node->stmts) . $this->nl . '}'
. ($node->catches ? ' ' . $this->pImplode($node->catches, ' ') : '')
. ($node->finally !== null ? ' ' . $this->p($node->finally) : '');
}
protected function pStmt_Catch(Stmt\Catch_ $node) {
return 'catch (' . $this->pImplode($node->types, '|')
. ($node->var !== null ? ' ' . $this->p($node->var) : '')
. ') {' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Finally(Stmt\Finally_ $node) {
return 'finally {' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pStmt_Break(Stmt\Break_ $node) {
return 'break' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
}
protected function pStmt_Continue(Stmt\Continue_ $node) {
return 'continue' . ($node->num !== null ? ' ' . $this->p($node->num) : '') . ';';
}
protected function pStmt_Return(Stmt\Return_ $node) {
return 'return' . (null !== $node->expr ? ' ' . $this->p($node->expr) : '') . ';';
}
protected function pStmt_Throw(Stmt\Throw_ $node) {
return 'throw ' . $this->p($node->expr) . ';';
}
protected function pStmt_Label(Stmt\Label $node) {
return $node->name . ':';
}
protected function pStmt_Goto(Stmt\Goto_ $node) {
return 'goto ' . $node->name . ';';
}
// Other
protected function pStmt_Expression(Stmt\Expression $node) {
return $this->p($node->expr) . ';';
}
protected function pStmt_Echo(Stmt\Echo_ $node) {
return 'echo ' . $this->pCommaSeparated($node->exprs) . ';';
}
protected function pStmt_Static(Stmt\Static_ $node) {
return 'static ' . $this->pCommaSeparated($node->vars) . ';';
}
protected function pStmt_Global(Stmt\Global_ $node) {
return 'global ' . $this->pCommaSeparated($node->vars) . ';';
}
protected function pStmt_StaticVar(Stmt\StaticVar $node) {
return $this->p($node->var)
. (null !== $node->default ? ' = ' . $this->p($node->default) : '');
}
protected function pStmt_Unset(Stmt\Unset_ $node) {
return 'unset(' . $this->pCommaSeparated($node->vars) . ');';
}
protected function pStmt_InlineHTML(Stmt\InlineHTML $node) {
$newline = $node->getAttribute('hasLeadingNewline', true) ? "\n" : '';
return '?>' . $newline . $node->value . '<?php ';
}
protected function pStmt_HaltCompiler(Stmt\HaltCompiler $node) {
return '__halt_compiler();' . $node->remaining;
}
protected function pStmt_Nop(Stmt\Nop $node) {
return '';
}
// Helpers
protected function pClassCommon(Stmt\Class_ $node, $afterClassToken) {
return $this->pAttrGroups($node->attrGroups, $node->name === null)
. $this->pModifiers($node->flags)
. 'class' . $afterClassToken
. (null !== $node->extends ? ' extends ' . $this->p($node->extends) : '')
. (!empty($node->implements) ? ' implements ' . $this->pCommaSeparated($node->implements) : '')
. $this->nl . '{' . $this->pStmts($node->stmts) . $this->nl . '}';
}
protected function pObjectProperty($node) {
if ($node instanceof Expr) {
return '{' . $this->p($node) . '}';
} else {
return $node;
}
}
protected function pEncapsList(array $encapsList, $quote) {
$return = '';
foreach ($encapsList as $element) {
if ($element instanceof Scalar\EncapsedStringPart) {
$return .= $this->escapeString($element->value, $quote);
} else {
$return .= '{' . $this->p($element) . '}';
}
}
return $return;
}
protected function pSingleQuotedString(string $string) {
return '\'' . addcslashes($string, '\'\\') . '\'';
}
protected function escapeString($string, $quote) {
if (null === $quote) {
// For doc strings, don't escape newlines
$escaped = addcslashes($string, "\t\f\v$\\");
} else {
$escaped = addcslashes($string, "\n\r\t\f\v$" . $quote . "\\");
}
// Escape other control characters
return preg_replace_callback('/([\0-\10\16-\37])(?=([0-7]?))/', function ($matches) {
$oct = decoct(ord($matches[1]));
if ($matches[2] !== '') {
// If there is a trailing digit, use the full three character form
return '\\' . str_pad($oct, 3, '0', \STR_PAD_LEFT);
}
return '\\' . $oct;
}, $escaped);
}
protected function containsEndLabel($string, $label, $atStart = true, $atEnd = true) {
$start = $atStart ? '(?:^|[\r\n])' : '[\r\n]';
$end = $atEnd ? '(?:$|[;\r\n])' : '[;\r\n]';
return false !== strpos($string, $label)
&& preg_match('/' . $start . $label . $end . '/', $string);
}
protected function encapsedContainsEndLabel(array $parts, $label) {
foreach ($parts as $i => $part) {
$atStart = $i === 0;
$atEnd = $i === count($parts) - 1;
if ($part instanceof Scalar\EncapsedStringPart
&& $this->containsEndLabel($part->value, $label, $atStart, $atEnd)
) {
return true;
}
}
return false;
}
protected function pDereferenceLhs(Node $node) {
if (!$this->dereferenceLhsRequiresParens($node)) {
return $this->p($node);
} else {
return '(' . $this->p($node) . ')';
}
}
protected function pCallLhs(Node $node) {
if (!$this->callLhsRequiresParens($node)) {
return $this->p($node);
} else {
return '(' . $this->p($node) . ')';
}
}
protected function pNewVariable(Node $node) {
// TODO: This is not fully accurate.
return $this->pDereferenceLhs($node);
}
/**
* @param Node[] $nodes
* @return bool
*/
protected function hasNodeWithComments(array $nodes) {
foreach ($nodes as $node) {
if ($node && $node->getComments()) {
return true;
}
}
return false;
}
protected function pMaybeMultiline(array $nodes, bool $trailingComma = false) {
if (!$this->hasNodeWithComments($nodes)) {
return $this->pCommaSeparated($nodes);
} else {
return $this->pCommaSeparatedMultiline($nodes, $trailingComma) . $this->nl;
}
}
protected function pAttrGroups(array $nodes, bool $inline = false): string {
$result = '';
$sep = $inline ? ' ' : $this->nl;
foreach ($nodes as $node) {
$result .= $this->p($node) . $sep;
}
return $result;
}
}
<?php declare(strict_types=1);
namespace PhpParser;
interface Builder
{
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node;
}
<?php declare(strict_types=1);
namespace PhpParser;
abstract class NodeAbstract implements Node, \JsonSerializable
{
protected $attributes;
/**
* Creates a Node.
*
* @param array $attributes Array of attributes
*/
public function __construct(array $attributes = []) {
$this->attributes = $attributes;
}
/**
* Gets line the node started in (alias of getStartLine).
*
* @return int Start line (or -1 if not available)
*/
public function getLine() : int {
return $this->attributes['startLine'] ?? -1;
}
/**
* Gets line the node started in.
*
* Requires the 'startLine' attribute to be enabled in the lexer (enabled by default).
*
* @return int Start line (or -1 if not available)
*/
public function getStartLine() : int {
return $this->attributes['startLine'] ?? -1;
}
/**
* Gets the line the node ended in.
*
* Requires the 'endLine' attribute to be enabled in the lexer (enabled by default).
*
* @return int End line (or -1 if not available)
*/
public function getEndLine() : int {
return $this->attributes['endLine'] ?? -1;
}
/**
* Gets the token offset of the first token that is part of this node.
*
* The offset is an index into the array returned by Lexer::getTokens().
*
* Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default).
*
* @return int Token start position (or -1 if not available)
*/
public function getStartTokenPos() : int {
return $this->attributes['startTokenPos'] ?? -1;
}
/**
* Gets the token offset of the last token that is part of this node.
*
* The offset is an index into the array returned by Lexer::getTokens().
*
* Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default).
*
* @return int Token end position (or -1 if not available)
*/
public function getEndTokenPos() : int {
return $this->attributes['endTokenPos'] ?? -1;
}
/**
* Gets the file offset of the first character that is part of this node.
*
* Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default).
*
* @return int File start position (or -1 if not available)
*/
public function getStartFilePos() : int {
return $this->attributes['startFilePos'] ?? -1;
}
/**
* Gets the file offset of the last character that is part of this node.
*
* Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default).
*
* @return int File end position (or -1 if not available)
*/
public function getEndFilePos() : int {
return $this->attributes['endFilePos'] ?? -1;
}
/**
* Gets all comments directly preceding this node.
*
* The comments are also available through the "comments" attribute.
*
* @return Comment[]
*/
public function getComments() : array {
return $this->attributes['comments'] ?? [];
}
/**
* Gets the doc comment of the node.
*
* @return null|Comment\Doc Doc comment object or null
*/
public function getDocComment() {
$comments = $this->getComments();
for ($i = count($comments) - 1; $i >= 0; $i--) {
$comment = $comments[$i];
if ($comment instanceof Comment\Doc) {
return $comment;
}
}
return null;
}
/**
* Sets the doc comment of the node.
*
* This will either replace an existing doc comment or add it to the comments array.
*
* @param Comment\Doc $docComment Doc comment to set
*/
public function setDocComment(Comment\Doc $docComment) {
$comments = $this->getComments();
for ($i = count($comments) - 1; $i >= 0; $i--) {
if ($comments[$i] instanceof Comment\Doc) {
// Replace existing doc comment.
$comments[$i] = $docComment;
$this->setAttribute('comments', $comments);
return;
}
}
// Append new doc comment.
$comments[] = $docComment;
$this->setAttribute('comments', $comments);
}
public function setAttribute(string $key, $value) {
$this->attributes[$key] = $value;
}
public function hasAttribute(string $key) : bool {
return array_key_exists($key, $this->attributes);
}
public function getAttribute(string $key, $default = null) {
if (array_key_exists($key, $this->attributes)) {
return $this->attributes[$key];
}
return $default;
}
public function getAttributes() : array {
return $this->attributes;
}
public function setAttributes(array $attributes) {
$this->attributes = $attributes;
}
/**
* @return array
*/
public function jsonSerialize() : array {
return ['nodeType' => $this->getType()] + get_object_vars($this);
}
}
<?php declare(strict_types=1);
namespace PhpParser\ErrorHandler;
use PhpParser\Error;
use PhpParser\ErrorHandler;
/**
* Error handler that collects all errors into an array.
*
* This allows graceful handling of errors.
*/
class Collecting implements ErrorHandler
{
/** @var Error[] Collected errors */
private $errors = [];
public function handleError(Error $error) {
$this->errors[] = $error;
}
/**
* Get collected errors.
*
* @return Error[]
*/
public function getErrors() : array {
return $this->errors;
}
/**
* Check whether there are any errors.
*
* @return bool
*/
public function hasErrors() : bool {
return !empty($this->errors);
}
/**
* Reset/clear collected errors.
*/
public function clearErrors() {
$this->errors = [];
}
}
<?php declare(strict_types=1);
namespace PhpParser\ErrorHandler;
use PhpParser\Error;
use PhpParser\ErrorHandler;
/**
* Error handler that handles all errors by throwing them.
*
* This is the default strategy used by all components.
*/
class Throwing implements ErrorHandler
{
public function handleError(Error $error) {
throw $error;
}
}
<?php declare(strict_types=1);
namespace PhpParser;
use PhpParser\Node\Expr\Include_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\GroupUse;
use PhpParser\Node\Stmt\Use_;
use PhpParser\Node\Stmt\UseUse;
class NodeDumper
{
private $dumpComments;
private $dumpPositions;
private $code;
/**
* Constructs a NodeDumper.
*
* Supported options:
* * bool dumpComments: Whether comments should be dumped.
* * bool dumpPositions: Whether line/offset information should be dumped. To dump offset
* information, the code needs to be passed to dump().
*
* @param array $options Options (see description)
*/
public function __construct(array $options = []) {
$this->dumpComments = !empty($options['dumpComments']);
$this->dumpPositions = !empty($options['dumpPositions']);
}
/**
* Dumps a node or array.
*
* @param array|Node $node Node or array to dump
* @param string|null $code Code corresponding to dumped AST. This only needs to be passed if
* the dumpPositions option is enabled and the dumping of node offsets
* is desired.
*
* @return string Dumped value
*/
public function dump($node, string $code = null) : string {
$this->code = $code;
return $this->dumpRecursive($node);
}
protected function dumpRecursive($node) {
if ($node instanceof Node) {
$r = $node->getType();
if ($this->dumpPositions && null !== $p = $this->dumpPosition($node)) {
$r .= $p;
}
$r .= '(';
foreach ($node->getSubNodeNames() as $key) {
$r .= "\n " . $key . ': ';
$value = $node->$key;
if (null === $value) {
$r .= 'null';
} elseif (false === $value) {
$r .= 'false';
} elseif (true === $value) {
$r .= 'true';
} elseif (is_scalar($value)) {
if ('flags' === $key || 'newModifier' === $key) {
$r .= $this->dumpFlags($value);
} elseif ('type' === $key && $node instanceof Include_) {
$r .= $this->dumpIncludeType($value);
} elseif ('type' === $key
&& ($node instanceof Use_ || $node instanceof UseUse || $node instanceof GroupUse)) {
$r .= $this->dumpUseType($value);
} else {
$r .= $value;
}
} else {
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
}
}
if ($this->dumpComments && $comments = $node->getComments()) {
$r .= "\n comments: " . str_replace("\n", "\n ", $this->dumpRecursive($comments));
}
} elseif (is_array($node)) {
$r = 'array(';
foreach ($node as $key => $value) {
$r .= "\n " . $key . ': ';
if (null === $value) {
$r .= 'null';
} elseif (false === $value) {
$r .= 'false';
} elseif (true === $value) {
$r .= 'true';
} elseif (is_scalar($value)) {
$r .= $value;
} else {
$r .= str_replace("\n", "\n ", $this->dumpRecursive($value));
}
}
} elseif ($node instanceof Comment) {
return $node->getReformattedText();
} else {
throw new \InvalidArgumentException('Can only dump nodes and arrays.');
}
return $r . "\n)";
}
protected function dumpFlags($flags) {
$strs = [];
if ($flags & Class_::MODIFIER_PUBLIC) {
$strs[] = 'MODIFIER_PUBLIC';
}
if ($flags & Class_::MODIFIER_PROTECTED) {
$strs[] = 'MODIFIER_PROTECTED';
}
if ($flags & Class_::MODIFIER_PRIVATE) {
$strs[] = 'MODIFIER_PRIVATE';
}
if ($flags & Class_::MODIFIER_ABSTRACT) {
$strs[] = 'MODIFIER_ABSTRACT';
}
if ($flags & Class_::MODIFIER_STATIC) {
$strs[] = 'MODIFIER_STATIC';
}
if ($flags & Class_::MODIFIER_FINAL) {
$strs[] = 'MODIFIER_FINAL';
}
if ($strs) {
return implode(' | ', $strs) . ' (' . $flags . ')';
} else {
return $flags;
}
}
protected function dumpIncludeType($type) {
$map = [
Include_::TYPE_INCLUDE => 'TYPE_INCLUDE',
Include_::TYPE_INCLUDE_ONCE => 'TYPE_INCLUDE_ONCE',
Include_::TYPE_REQUIRE => 'TYPE_REQUIRE',
Include_::TYPE_REQUIRE_ONCE => 'TYPE_REQUIRE_ONCE',
];
if (!isset($map[$type])) {
return $type;
}
return $map[$type] . ' (' . $type . ')';
}
protected function dumpUseType($type) {
$map = [
Use_::TYPE_UNKNOWN => 'TYPE_UNKNOWN',
Use_::TYPE_NORMAL => 'TYPE_NORMAL',
Use_::TYPE_FUNCTION => 'TYPE_FUNCTION',
Use_::TYPE_CONSTANT => 'TYPE_CONSTANT',
];
if (!isset($map[$type])) {
return $type;
}
return $map[$type] . ' (' . $type . ')';
}
/**
* Dump node position, if possible.
*
* @param Node $node Node for which to dump position
*
* @return string|null Dump of position, or null if position information not available
*/
protected function dumpPosition(Node $node) {
if (!$node->hasAttribute('startLine') || !$node->hasAttribute('endLine')) {
return null;
}
$start = $node->getStartLine();
$end = $node->getEndLine();
if ($node->hasAttribute('startFilePos') && $node->hasAttribute('endFilePos')
&& null !== $this->code
) {
$start .= ':' . $this->toColumn($this->code, $node->getStartFilePos());
$end .= ':' . $this->toColumn($this->code, $node->getEndFilePos());
}
return "[$start - $end]";
}
// Copied from Error class
private function toColumn($code, $pos) {
if ($pos > strlen($code)) {
throw new \RuntimeException('Invalid position information');
}
$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
if (false === $lineStartPos) {
$lineStartPos = -1;
}
return $pos - $lineStartPos;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
abstract class Expr extends NodeAbstract
{
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
class Label extends Stmt
{
/** @var Identifier Name */
public $name;
/**
* Constructs a label node.
*
* @param string|Identifier $name Name
* @param array $attributes Additional attributes
*/
public function __construct($name, array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}
public function getSubNodeNames() : array {
return ['name'];
}
public function getType() : string {
return 'Stmt_Label';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Trait_ extends ClassLike
{
/**
* Constructs a trait node.
*
* @param string|Node\Identifier $name Name
* @param array $subNodes Array of the following optional subnodes:
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['attrGroups', 'name', 'stmts'];
}
public function getType() : string {
return 'Stmt_Trait';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\UnionType;
class Property extends Node\Stmt
{
/** @var int Modifiers */
public $flags;
/** @var PropertyProperty[] Properties */
public $props;
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
public $type;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a class property list node.
*
* @param int $flags Modifiers
* @param PropertyProperty[] $props Properties
* @param array $attributes Additional attributes
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(int $flags, array $props, array $attributes = [], $type = null, array $attrGroups = []) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->props = $props;
$this->type = \is_string($type) ? new Identifier($type) : $type;
$this->attrGroups = $attrGroups;
}
public function getSubNodeNames() : array {
return ['attrGroups', 'flags', 'type', 'props'];
}
/**
* Whether the property is explicitly or implicitly public.
*
* @return bool
*/
public function isPublic() : bool {
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0;
}
/**
* Whether the property is protected.
*
* @return bool
*/
public function isProtected() : bool {
return (bool) ($this->flags & Class_::MODIFIER_PROTECTED);
}
/**
* Whether the property is private.
*
* @return bool
*/
public function isPrivate() : bool {
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
}
/**
* Whether the property is static.
*
* @return bool
*/
public function isStatic() : bool {
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
}
public function getType() : string {
return 'Stmt_Property';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Error;
use PhpParser\Node;
class Class_ extends ClassLike
{
const MODIFIER_PUBLIC = 1;
const MODIFIER_PROTECTED = 2;
const MODIFIER_PRIVATE = 4;
const MODIFIER_STATIC = 8;
const MODIFIER_ABSTRACT = 16;
const MODIFIER_FINAL = 32;
const VISIBILITY_MODIFIER_MASK = 7; // 1 | 2 | 4
/** @var int Type */
public $flags;
/** @var null|Node\Name Name of extended class */
public $extends;
/** @var Node\Name[] Names of implemented interfaces */
public $implements;
/**
* Constructs a class node.
*
* @param string|Node\Identifier|null $name Name
* @param array $subNodes Array of the following optional subnodes:
* 'flags' => 0 : Flags
* 'extends' => null : Name of extended class
* 'implements' => array(): Names of implemented interfaces
* 'stmts' => array(): Statements
* '$attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->extends = $subNodes['extends'] ?? null;
$this->implements = $subNodes['implements'] ?? [];
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['attrGroups', 'flags', 'name', 'extends', 'implements', 'stmts'];
}
/**
* Whether the class is explicitly abstract.
*
* @return bool
*/
public function isAbstract() : bool {
return (bool) ($this->flags & self::MODIFIER_ABSTRACT);
}
/**
* Whether the class is final.
*
* @return bool
*/
public function isFinal() : bool {
return (bool) ($this->flags & self::MODIFIER_FINAL);
}
/**
* Whether the class is anonymous.
*
* @return bool
*/
public function isAnonymous() : bool {
return null === $this->name;
}
/**
* @internal
*/
public static function verifyModifier($a, $b) {
if ($a & self::VISIBILITY_MODIFIER_MASK && $b & self::VISIBILITY_MODIFIER_MASK) {
throw new Error('Multiple access type modifiers are not allowed');
}
if ($a & self::MODIFIER_ABSTRACT && $b & self::MODIFIER_ABSTRACT) {
throw new Error('Multiple abstract modifiers are not allowed');
}
if ($a & self::MODIFIER_STATIC && $b & self::MODIFIER_STATIC) {
throw new Error('Multiple static modifiers are not allowed');
}
if ($a & self::MODIFIER_FINAL && $b & self::MODIFIER_FINAL) {
throw new Error('Multiple final modifiers are not allowed');
}
if ($a & 48 && $b & 48) {
throw new Error('Cannot use the final modifier on an abstract class member');
}
}
public function getType() : string {
return 'Stmt_Class';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Do_ extends Node\Stmt
{
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\Expr Condition */
public $cond;
/**
* Constructs a do while node.
*
* @param Node\Expr $cond Condition
* @param Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['stmts', 'cond'];
}
public function getType() : string {
return 'Stmt_Do';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Case_ extends Node\Stmt
{
/** @var null|Node\Expr Condition (null for default) */
public $cond;
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs a case node.
*
* @param null|Node\Expr $cond Condition (null for default)
* @param Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct($cond, array $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['cond', 'stmts'];
}
public function getType() : string {
return 'Stmt_Case';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class GroupUse extends Stmt
{
/** @var int Type of group use */
public $type;
/** @var Name Prefix for uses */
public $prefix;
/** @var UseUse[] Uses */
public $uses;
/**
* Constructs a group use node.
*
* @param Name $prefix Prefix for uses
* @param UseUse[] $uses Uses
* @param int $type Type of group use
* @param array $attributes Additional attributes
*/
public function __construct(Name $prefix, array $uses, int $type = Use_::TYPE_NORMAL, array $attributes = []) {
$this->attributes = $attributes;
$this->type = $type;
$this->prefix = $prefix;
$this->uses = $uses;
}
public function getSubNodeNames() : array {
return ['type', 'prefix', 'uses'];
}
public function getType() : string {
return 'Stmt_GroupUse';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
class InlineHTML extends Stmt
{
/** @var string String */
public $value;
/**
* Constructs an inline HTML node.
*
* @param string $value String
* @param array $attributes Additional attributes
*/
public function __construct(string $value, array $attributes = []) {
$this->attributes = $attributes;
$this->value = $value;
}
public function getSubNodeNames() : array {
return ['value'];
}
public function getType() : string {
return 'Stmt_InlineHTML';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
/**
* Represents statements of type "expr;"
*/
class Expression extends Node\Stmt
{
/** @var Node\Expr Expression */
public $expr;
/**
* Constructs an expression statement.
*
* @param Node\Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Stmt_Expression';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
class Use_ extends Stmt
{
/**
* Unknown type. Both Stmt\Use_ / Stmt\GroupUse and Stmt\UseUse have a $type property, one of them will always be
* TYPE_UNKNOWN while the other has one of the three other possible types. For normal use statements the type on the
* Stmt\UseUse is unknown. It's only the other way around for mixed group use declarations.
*/
const TYPE_UNKNOWN = 0;
/** Class or namespace import */
const TYPE_NORMAL = 1;
/** Function import */
const TYPE_FUNCTION = 2;
/** Constant import */
const TYPE_CONSTANT = 3;
/** @var int Type of alias */
public $type;
/** @var UseUse[] Aliases */
public $uses;
/**
* Constructs an alias (use) list node.
*
* @param UseUse[] $uses Aliases
* @param int $type Type of alias
* @param array $attributes Additional attributes
*/
public function __construct(array $uses, int $type = self::TYPE_NORMAL, array $attributes = []) {
$this->attributes = $attributes;
$this->type = $type;
$this->uses = $uses;
}
public function getSubNodeNames() : array {
return ['type', 'uses'];
}
public function getType() : string {
return 'Stmt_Use';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
abstract class TraitUseAdaptation extends Node\Stmt
{
/** @var Node\Name|null Trait name */
public $trait;
/** @var Node\Identifier Method name */
public $method;
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\Identifier;
class UseUse extends Node\Stmt
{
/** @var int One of the Stmt\Use_::TYPE_* constants. Will only differ from TYPE_UNKNOWN for mixed group uses */
public $type;
/** @var Node\Name Namespace, class, function or constant to alias */
public $name;
/** @var Identifier|null Alias */
public $alias;
/**
* Constructs an alias (use) node.
*
* @param Node\Name $name Namespace/Class to alias
* @param null|string|Identifier $alias Alias
* @param int $type Type of the use element (for mixed group use only)
* @param array $attributes Additional attributes
*/
public function __construct(Node\Name $name, $alias = null, int $type = Use_::TYPE_UNKNOWN, array $attributes = []) {
$this->attributes = $attributes;
$this->type = $type;
$this->name = $name;
$this->alias = \is_string($alias) ? new Identifier($alias) : $alias;
}
public function getSubNodeNames() : array {
return ['type', 'name', 'alias'];
}
/**
* Get alias. If not explicitly given this is the last component of the used name.
*
* @return Identifier
*/
public function getAlias() : Identifier {
if (null !== $this->alias) {
return $this->alias;
}
return new Identifier($this->name->getLast());
}
public function getType() : string {
return 'Stmt_UseUse';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class PropertyProperty extends Node\Stmt
{
/** @var Node\VarLikeIdentifier Name */
public $name;
/** @var null|Node\Expr Default */
public $default;
/**
* Constructs a class property node.
*
* @param string|Node\VarLikeIdentifier $name Name
* @param null|Node\Expr $default Default value
* @param array $attributes Additional attributes
*/
public function __construct($name, Node\Expr $default = null, array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Node\VarLikeIdentifier($name) : $name;
$this->default = $default;
}
public function getSubNodeNames() : array {
return ['name', 'default'];
}
public function getType() : string {
return 'Stmt_PropertyProperty';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Declare_ extends Node\Stmt
{
/** @var DeclareDeclare[] List of declares */
public $declares;
/** @var Node\Stmt[]|null Statements */
public $stmts;
/**
* Constructs a declare node.
*
* @param DeclareDeclare[] $declares List of declares
* @param Node\Stmt[]|null $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(array $declares, array $stmts = null, array $attributes = []) {
$this->attributes = $attributes;
$this->declares = $declares;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['declares', 'stmts'];
}
public function getType() : string {
return 'Stmt_Declare';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
class ClassMethod extends Node\Stmt implements FunctionLike
{
/** @var int Flags */
public $flags;
/** @var bool Whether to return by reference */
public $byRef;
/** @var Node\Identifier Name */
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
public $returnType;
/** @var Node\Stmt[]|null Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
private static $magicNames = [
'__construct' => true,
'__destruct' => true,
'__call' => true,
'__callstatic' => true,
'__get' => true,
'__set' => true,
'__isset' => true,
'__unset' => true,
'__sleep' => true,
'__wakeup' => true,
'__tostring' => true,
'__set_state' => true,
'__clone' => true,
'__invoke' => true,
'__debuginfo' => true,
];
/**
* Constructs a class method node.
*
* @param string|Node\Identifier $name Name
* @param array $subNodes Array of the following optional subnodes:
* 'flags => MODIFIER_PUBLIC: Flags
* 'byRef' => false : Whether to return by reference
* 'params' => array() : Parameters
* 'returnType' => null : Return type
* 'stmts' => array() : Statements
* 'attrGroups' => array() : PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->flags = $subNodes['flags'] ?? $subNodes['type'] ?? 0;
$this->byRef = $subNodes['byRef'] ?? false;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->stmts = array_key_exists('stmts', $subNodes) ? $subNodes['stmts'] : [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['attrGroups', 'flags', 'byRef', 'name', 'params', 'returnType', 'stmts'];
}
public function returnsByRef() : bool {
return $this->byRef;
}
public function getParams() : array {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
public function getStmts() {
return $this->stmts;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
/**
* Whether the method is explicitly or implicitly public.
*
* @return bool
*/
public function isPublic() : bool {
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0;
}
/**
* Whether the method is protected.
*
* @return bool
*/
public function isProtected() : bool {
return (bool) ($this->flags & Class_::MODIFIER_PROTECTED);
}
/**
* Whether the method is private.
*
* @return bool
*/
public function isPrivate() : bool {
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
}
/**
* Whether the method is abstract.
*
* @return bool
*/
public function isAbstract() : bool {
return (bool) ($this->flags & Class_::MODIFIER_ABSTRACT);
}
/**
* Whether the method is final.
*
* @return bool
*/
public function isFinal() : bool {
return (bool) ($this->flags & Class_::MODIFIER_FINAL);
}
/**
* Whether the method is static.
*
* @return bool
*/
public function isStatic() : bool {
return (bool) ($this->flags & Class_::MODIFIER_STATIC);
}
/**
* Whether the method is magic.
*
* @return bool
*/
public function isMagic() : bool {
return isset(self::$magicNames[$this->name->toLowerString()]);
}
public function getType() : string {
return 'Stmt_ClassMethod';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class For_ extends Node\Stmt
{
/** @var Node\Expr[] Init expressions */
public $init;
/** @var Node\Expr[] Loop conditions */
public $cond;
/** @var Node\Expr[] Loop expressions */
public $loop;
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs a for loop node.
*
* @param array $subNodes Array of the following optional subnodes:
* 'init' => array(): Init expressions
* 'cond' => array(): Loop conditions
* 'loop' => array(): Loop expressions
* 'stmts' => array(): Statements
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->init = $subNodes['init'] ?? [];
$this->cond = $subNodes['cond'] ?? [];
$this->loop = $subNodes['loop'] ?? [];
$this->stmts = $subNodes['stmts'] ?? [];
}
public function getSubNodeNames() : array {
return ['init', 'cond', 'loop', 'stmts'];
}
public function getType() : string {
return 'Stmt_For';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Interface_ extends ClassLike
{
/** @var Node\Name[] Extended interfaces */
public $extends;
/**
* Constructs a class node.
*
* @param string|Node\Identifier $name Name
* @param array $subNodes Array of the following optional subnodes:
* 'extends' => array(): Name of extended interfaces
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->extends = $subNodes['extends'] ?? [];
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['attrGroups', 'name', 'extends', 'stmts'];
}
public function getType() : string {
return 'Stmt_Interface';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\FunctionLike;
/**
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
*/
class Function_ extends Node\Stmt implements FunctionLike
{
/** @var bool Whether function returns by reference */
public $byRef;
/** @var Node\Identifier Name */
public $name;
/** @var Node\Param[] Parameters */
public $params;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a function node.
*
* @param string|Node\Identifier $name Name
* @param array $subNodes Array of the following optional subnodes:
* 'byRef' => false : Whether to return by reference
* 'params' => array(): Parameters
* 'returnType' => null : Return type
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct($name, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->byRef = $subNodes['byRef'] ?? false;
$this->name = \is_string($name) ? new Node\Identifier($name) : $name;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['attrGroups', 'byRef', 'name', 'params', 'returnType', 'stmts'];
}
public function returnsByRef() : bool {
return $this->byRef;
}
public function getParams() : array {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
/** @return Node\Stmt[] */
public function getStmts() : array {
return $this->stmts;
}
public function getType() : string {
return 'Stmt_Function';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class ElseIf_ extends Node\Stmt
{
/** @var Node\Expr Condition */
public $cond;
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs an elseif node.
*
* @param Node\Expr $cond Condition
* @param Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['cond', 'stmts'];
}
public function getType() : string {
return 'Stmt_ElseIf';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class DeclareDeclare extends Node\Stmt
{
/** @var Node\Identifier Key */
public $key;
/** @var Node\Expr Value */
public $value;
/**
* Constructs a declare key=>value pair node.
*
* @param string|Node\Identifier $key Key
* @param Node\Expr $value Value
* @param array $attributes Additional attributes
*/
public function __construct($key, Node\Expr $value, array $attributes = []) {
$this->attributes = $attributes;
$this->key = \is_string($key) ? new Node\Identifier($key) : $key;
$this->value = $value;
}
public function getSubNodeNames() : array {
return ['key', 'value'];
}
public function getType() : string {
return 'Stmt_DeclareDeclare';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Identifier;
use PhpParser\Node\Stmt;
class Goto_ extends Stmt
{
/** @var Identifier Name of label to jump to */
public $name;
/**
* Constructs a goto node.
*
* @param string|Identifier $name Name of label to jump to
* @param array $attributes Additional attributes
*/
public function __construct($name, array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}
public function getSubNodeNames() : array {
return ['name'];
}
public function getType() : string {
return 'Stmt_Goto';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
class Static_ extends Stmt
{
/** @var StaticVar[] Variable definitions */
public $vars;
/**
* Constructs a static variables list node.
*
* @param StaticVar[] $vars Variable definitions
* @param array $attributes Additional attributes
*/
public function __construct(array $vars, array $attributes = []) {
$this->attributes = $attributes;
$this->vars = $vars;
}
public function getSubNodeNames() : array {
return ['vars'];
}
public function getType() : string {
return 'Stmt_Static';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\Expr;
class StaticVar extends Node\Stmt
{
/** @var Expr\Variable Variable */
public $var;
/** @var null|Node\Expr Default value */
public $default;
/**
* Constructs a static variable node.
*
* @param Expr\Variable $var Name
* @param null|Node\Expr $default Default value
* @param array $attributes Additional attributes
*/
public function __construct(
Expr\Variable $var, Node\Expr $default = null, array $attributes = []
) {
$this->attributes = $attributes;
$this->var = $var;
$this->default = $default;
}
public function getSubNodeNames() : array {
return ['var', 'default'];
}
public function getType() : string {
return 'Stmt_StaticVar';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class If_ extends Node\Stmt
{
/** @var Node\Expr Condition expression */
public $cond;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var ElseIf_[] Elseif clauses */
public $elseifs;
/** @var null|Else_ Else clause */
public $else;
/**
* Constructs an if node.
*
* @param Node\Expr $cond Condition
* @param array $subNodes Array of the following optional subnodes:
* 'stmts' => array(): Statements
* 'elseifs' => array(): Elseif clauses
* 'else' => null : Else clause
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $cond, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->stmts = $subNodes['stmts'] ?? [];
$this->elseifs = $subNodes['elseifs'] ?? [];
$this->else = $subNodes['else'] ?? null;
}
public function getSubNodeNames() : array {
return ['cond', 'stmts', 'elseifs', 'else'];
}
public function getType() : string {
return 'Stmt_If';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Echo_ extends Node\Stmt
{
/** @var Node\Expr[] Expressions */
public $exprs;
/**
* Constructs an echo node.
*
* @param Node\Expr[] $exprs Expressions
* @param array $attributes Additional attributes
*/
public function __construct(array $exprs, array $attributes = []) {
$this->attributes = $attributes;
$this->exprs = $exprs;
}
public function getSubNodeNames() : array {
return ['exprs'];
}
public function getType() : string {
return 'Stmt_Echo';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Global_ extends Node\Stmt
{
/** @var Node\Expr[] Variables */
public $vars;
/**
* Constructs a global variables list node.
*
* @param Node\Expr[] $vars Variables to unset
* @param array $attributes Additional attributes
*/
public function __construct(array $vars, array $attributes = []) {
$this->attributes = $attributes;
$this->vars = $vars;
}
public function getSubNodeNames() : array {
return ['vars'];
}
public function getType() : string {
return 'Stmt_Global';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class TraitUse extends Node\Stmt
{
/** @var Node\Name[] Traits */
public $traits;
/** @var TraitUseAdaptation[] Adaptations */
public $adaptations;
/**
* Constructs a trait use node.
*
* @param Node\Name[] $traits Traits
* @param TraitUseAdaptation[] $adaptations Adaptations
* @param array $attributes Additional attributes
*/
public function __construct(array $traits, array $adaptations = [], array $attributes = []) {
$this->attributes = $attributes;
$this->traits = $traits;
$this->adaptations = $adaptations;
}
public function getSubNodeNames() : array {
return ['traits', 'adaptations'];
}
public function getType() : string {
return 'Stmt_TraitUse';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Const_ extends Node\Stmt
{
/** @var Node\Const_[] Constant declarations */
public $consts;
/**
* Constructs a const list node.
*
* @param Node\Const_[] $consts Constant declarations
* @param array $attributes Additional attributes
*/
public function __construct(array $consts, array $attributes = []) {
$this->attributes = $attributes;
$this->consts = $consts;
}
public function getSubNodeNames() : array {
return ['consts'];
}
public function getType() : string {
return 'Stmt_Const';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Finally_ extends Node\Stmt
{
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs a finally node.
*
* @param Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(array $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['stmts'];
}
public function getType() : string {
return 'Stmt_Finally';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Return_ extends Node\Stmt
{
/** @var null|Node\Expr Expression */
public $expr;
/**
* Constructs a return node.
*
* @param null|Node\Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $expr = null, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Stmt_Return';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Switch_ extends Node\Stmt
{
/** @var Node\Expr Condition */
public $cond;
/** @var Case_[] Case list */
public $cases;
/**
* Constructs a case node.
*
* @param Node\Expr $cond Condition
* @param Case_[] $cases Case list
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $cond, array $cases, array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->cases = $cases;
}
public function getSubNodeNames() : array {
return ['cond', 'cases'];
}
public function getType() : string {
return 'Stmt_Switch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
/**
* @property Node\Name $namespacedName Namespaced name (if using NameResolver)
*/
abstract class ClassLike extends Node\Stmt
{
/** @var Node\Identifier|null Name */
public $name;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* @return TraitUse[]
*/
public function getTraitUses() : array {
$traitUses = [];
foreach ($this->stmts as $stmt) {
if ($stmt instanceof TraitUse) {
$traitUses[] = $stmt;
}
}
return $traitUses;
}
/**
* @return ClassConst[]
*/
public function getConstants() : array {
$constants = [];
foreach ($this->stmts as $stmt) {
if ($stmt instanceof ClassConst) {
$constants[] = $stmt;
}
}
return $constants;
}
/**
* @return Property[]
*/
public function getProperties() : array {
$properties = [];
foreach ($this->stmts as $stmt) {
if ($stmt instanceof Property) {
$properties[] = $stmt;
}
}
return $properties;
}
/**
* Gets property with the given name defined directly in this class/interface/trait.
*
* @param string $name Name of the property
*
* @return Property|null Property node or null if the property does not exist
*/
public function getProperty(string $name) {
foreach ($this->stmts as $stmt) {
if ($stmt instanceof Property) {
foreach ($stmt->props as $prop) {
if ($prop instanceof PropertyProperty && $name === $prop->name->toString()) {
return $stmt;
}
}
}
}
return null;
}
/**
* Gets all methods defined directly in this class/interface/trait
*
* @return ClassMethod[]
*/
public function getMethods() : array {
$methods = [];
foreach ($this->stmts as $stmt) {
if ($stmt instanceof ClassMethod) {
$methods[] = $stmt;
}
}
return $methods;
}
/**
* Gets method with the given name defined directly in this class/interface/trait.
*
* @param string $name Name of the method (compared case-insensitively)
*
* @return ClassMethod|null Method node or null if the method does not exist
*/
public function getMethod(string $name) {
$lowerName = strtolower($name);
foreach ($this->stmts as $stmt) {
if ($stmt instanceof ClassMethod && $lowerName === $stmt->name->toLowerString()) {
return $stmt;
}
}
return null;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
use PhpParser\Node\Expr;
class Catch_ extends Node\Stmt
{
/** @var Node\Name[] Types of exceptions to catch */
public $types;
/** @var Expr\Variable|null Variable for exception */
public $var;
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs a catch node.
*
* @param Node\Name[] $types Types of exceptions to catch
* @param Expr\Variable|null $var Variable for exception
* @param Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(
array $types, Expr\Variable $var = null, array $stmts = [], array $attributes = []
) {
$this->attributes = $attributes;
$this->types = $types;
$this->var = $var;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['types', 'var', 'stmts'];
}
public function getType() : string {
return 'Stmt_Catch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class TryCatch extends Node\Stmt
{
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Catch_[] Catches */
public $catches;
/** @var null|Finally_ Optional finally node */
public $finally;
/**
* Constructs a try catch node.
*
* @param Node\Stmt[] $stmts Statements
* @param Catch_[] $catches Catches
* @param null|Finally_ $finally Optional finally node
* @param array $attributes Additional attributes
*/
public function __construct(array $stmts, array $catches, Finally_ $finally = null, array $attributes = []) {
$this->attributes = $attributes;
$this->stmts = $stmts;
$this->catches = $catches;
$this->finally = $finally;
}
public function getSubNodeNames() : array {
return ['stmts', 'catches', 'finally'];
}
public function getType() : string {
return 'Stmt_TryCatch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Else_ extends Node\Stmt
{
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs an else node.
*
* @param Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(array $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['stmts'];
}
public function getType() : string {
return 'Stmt_Else';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class ClassConst extends Node\Stmt
{
/** @var int Modifiers */
public $flags;
/** @var Node\Const_[] Constant declarations */
public $consts;
/** @var Node\AttributeGroup[] */
public $attrGroups;
/**
* Constructs a class const list node.
*
* @param Node\Const_[] $consts Constant declarations
* @param int $flags Modifiers
* @param array $attributes Additional attributes
* @param Node\AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(
array $consts,
int $flags = 0,
array $attributes = [],
array $attrGroups = []
) {
$this->attributes = $attributes;
$this->flags = $flags;
$this->consts = $consts;
$this->attrGroups = $attrGroups;
}
public function getSubNodeNames() : array {
return ['attrGroups', 'flags', 'consts'];
}
/**
* Whether constant is explicitly or implicitly public.
*
* @return bool
*/
public function isPublic() : bool {
return ($this->flags & Class_::MODIFIER_PUBLIC) !== 0
|| ($this->flags & Class_::VISIBILITY_MODIFIER_MASK) === 0;
}
/**
* Whether constant is protected.
*
* @return bool
*/
public function isProtected() : bool {
return (bool) ($this->flags & Class_::MODIFIER_PROTECTED);
}
/**
* Whether constant is private.
*
* @return bool
*/
public function isPrivate() : bool {
return (bool) ($this->flags & Class_::MODIFIER_PRIVATE);
}
public function getType() : string {
return 'Stmt_ClassConst';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Continue_ extends Node\Stmt
{
/** @var null|Node\Expr Number of loops to continue */
public $num;
/**
* Constructs a continue node.
*
* @param null|Node\Expr $num Number of loops to continue
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $num = null, array $attributes = []) {
$this->attributes = $attributes;
$this->num = $num;
}
public function getSubNodeNames() : array {
return ['num'];
}
public function getType() : string {
return 'Stmt_Continue';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class While_ extends Node\Stmt
{
/** @var Node\Expr Condition */
public $cond;
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs a while node.
*
* @param Node\Expr $cond Condition
* @param Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $cond, array $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['cond', 'stmts'];
}
public function getType() : string {
return 'Stmt_While';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Throw_ extends Node\Stmt
{
/** @var Node\Expr Expression */
public $expr;
/**
* Constructs a legacy throw statement node.
*
* @param Node\Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Stmt_Throw';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Unset_ extends Node\Stmt
{
/** @var Node\Expr[] Variables to unset */
public $vars;
/**
* Constructs an unset node.
*
* @param Node\Expr[] $vars Variables to unset
* @param array $attributes Additional attributes
*/
public function __construct(array $vars, array $attributes = []) {
$this->attributes = $attributes;
$this->vars = $vars;
}
public function getSubNodeNames() : array {
return ['vars'];
}
public function getType() : string {
return 'Stmt_Unset';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Foreach_ extends Node\Stmt
{
/** @var Node\Expr Expression to iterate */
public $expr;
/** @var null|Node\Expr Variable to assign key to */
public $keyVar;
/** @var bool Whether to assign value by reference */
public $byRef;
/** @var Node\Expr Variable to assign value to */
public $valueVar;
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs a foreach node.
*
* @param Node\Expr $expr Expression to iterate
* @param Node\Expr $valueVar Variable to assign value to
* @param array $subNodes Array of the following optional subnodes:
* 'keyVar' => null : Variable to assign key to
* 'byRef' => false : Whether to assign value by reference
* 'stmts' => array(): Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $expr, Node\Expr $valueVar, array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
$this->keyVar = $subNodes['keyVar'] ?? null;
$this->byRef = $subNodes['byRef'] ?? false;
$this->valueVar = $valueVar;
$this->stmts = $subNodes['stmts'] ?? [];
}
public function getSubNodeNames() : array {
return ['expr', 'keyVar', 'byRef', 'valueVar', 'stmts'];
}
public function getType() : string {
return 'Stmt_Foreach';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node\Stmt;
class HaltCompiler extends Stmt
{
/** @var string Remaining text after halt compiler statement. */
public $remaining;
/**
* Constructs a __halt_compiler node.
*
* @param string $remaining Remaining text after halt compiler statement.
* @param array $attributes Additional attributes
*/
public function __construct(string $remaining, array $attributes = []) {
$this->attributes = $attributes;
$this->remaining = $remaining;
}
public function getSubNodeNames() : array {
return ['remaining'];
}
public function getType() : string {
return 'Stmt_HaltCompiler';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Namespace_ extends Node\Stmt
{
/* For use in the "kind" attribute */
const KIND_SEMICOLON = 1;
const KIND_BRACED = 2;
/** @var null|Node\Name Name */
public $name;
/** @var Node\Stmt[] Statements */
public $stmts;
/**
* Constructs a namespace node.
*
* @param null|Node\Name $name Name
* @param null|Node\Stmt[] $stmts Statements
* @param array $attributes Additional attributes
*/
public function __construct(Node\Name $name = null, $stmts = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
$this->stmts = $stmts;
}
public function getSubNodeNames() : array {
return ['name', 'stmts'];
}
public function getType() : string {
return 'Stmt_Namespace';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt\TraitUseAdaptation;
use PhpParser\Node;
class Precedence extends Node\Stmt\TraitUseAdaptation
{
/** @var Node\Name[] Overwritten traits */
public $insteadof;
/**
* Constructs a trait use precedence adaptation node.
*
* @param Node\Name $trait Trait name
* @param string|Node\Identifier $method Method name
* @param Node\Name[] $insteadof Overwritten traits
* @param array $attributes Additional attributes
*/
public function __construct(Node\Name $trait, $method, array $insteadof, array $attributes = []) {
$this->attributes = $attributes;
$this->trait = $trait;
$this->method = \is_string($method) ? new Node\Identifier($method) : $method;
$this->insteadof = $insteadof;
}
public function getSubNodeNames() : array {
return ['trait', 'method', 'insteadof'];
}
public function getType() : string {
return 'Stmt_TraitUseAdaptation_Precedence';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt\TraitUseAdaptation;
use PhpParser\Node;
class Alias extends Node\Stmt\TraitUseAdaptation
{
/** @var null|int New modifier */
public $newModifier;
/** @var null|Node\Identifier New name */
public $newName;
/**
* Constructs a trait use precedence adaptation node.
*
* @param null|Node\Name $trait Trait name
* @param string|Node\Identifier $method Method name
* @param null|int $newModifier New modifier
* @param null|string|Node\Identifier $newName New name
* @param array $attributes Additional attributes
*/
public function __construct($trait, $method, $newModifier, $newName, array $attributes = []) {
$this->attributes = $attributes;
$this->trait = $trait;
$this->method = \is_string($method) ? new Node\Identifier($method) : $method;
$this->newModifier = $newModifier;
$this->newName = \is_string($newName) ? new Node\Identifier($newName) : $newName;
}
public function getSubNodeNames() : array {
return ['trait', 'method', 'newModifier', 'newName'];
}
public function getType() : string {
return 'Stmt_TraitUseAdaptation_Alias';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
/** Nop/empty statement (;). */
class Nop extends Node\Stmt
{
public function getSubNodeNames() : array {
return [];
}
public function getType() : string {
return 'Stmt_Nop';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Stmt;
use PhpParser\Node;
class Break_ extends Node\Stmt
{
/** @var null|Node\Expr Number of loops to break */
public $num;
/**
* Constructs a break node.
*
* @param null|Node\Expr $num Number of loops to break
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $num = null, array $attributes = []) {
$this->attributes = $attributes;
$this->num = $num;
}
public function getSubNodeNames() : array {
return ['num'];
}
public function getType() : string {
return 'Stmt_Break';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Name;
class FullyQualified extends \PhpParser\Node\Name
{
/**
* Checks whether the name is unqualified. (E.g. Name)
*
* @return bool Whether the name is unqualified
*/
public function isUnqualified() : bool {
return false;
}
/**
* Checks whether the name is qualified. (E.g. Name\Name)
*
* @return bool Whether the name is qualified
*/
public function isQualified() : bool {
return false;
}
/**
* Checks whether the name is fully qualified. (E.g. \Name)
*
* @return bool Whether the name is fully qualified
*/
public function isFullyQualified() : bool {
return true;
}
/**
* Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
*
* @return bool Whether the name is relative
*/
public function isRelative() : bool {
return false;
}
public function toCodeString() : string {
return '\\' . $this->toString();
}
public function getType() : string {
return 'Name_FullyQualified';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Name;
class Relative extends \PhpParser\Node\Name
{
/**
* Checks whether the name is unqualified. (E.g. Name)
*
* @return bool Whether the name is unqualified
*/
public function isUnqualified() : bool {
return false;
}
/**
* Checks whether the name is qualified. (E.g. Name\Name)
*
* @return bool Whether the name is qualified
*/
public function isQualified() : bool {
return false;
}
/**
* Checks whether the name is fully qualified. (E.g. \Name)
*
* @return bool Whether the name is fully qualified
*/
public function isFullyQualified() : bool {
return false;
}
/**
* Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
*
* @return bool Whether the name is relative
*/
public function isRelative() : bool {
return true;
}
public function toCodeString() : string {
return 'namespace\\' . $this->toString();
}
public function getType() : string {
return 'Name_Relative';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class Param extends NodeAbstract
{
/** @var null|Identifier|Name|NullableType|UnionType Type declaration */
public $type;
/** @var bool Whether parameter is passed by reference */
public $byRef;
/** @var bool Whether this is a variadic argument */
public $variadic;
/** @var Expr\Variable|Expr\Error Parameter variable */
public $var;
/** @var null|Expr Default value */
public $default;
/** @var int */
public $flags;
/** @var AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a parameter node.
*
* @param Expr\Variable|Expr\Error $var Parameter variable
* @param null|Expr $default Default value
* @param null|string|Identifier|Name|NullableType|UnionType $type Type declaration
* @param bool $byRef Whether is passed by reference
* @param bool $variadic Whether this is a variadic argument
* @param array $attributes Additional attributes
* @param int $flags Optional visibility flags
* @param AttributeGroup[] $attrGroups PHP attribute groups
*/
public function __construct(
$var, Expr $default = null, $type = null,
bool $byRef = false, bool $variadic = false,
array $attributes = [],
int $flags = 0,
array $attrGroups = []
) {
$this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type;
$this->byRef = $byRef;
$this->variadic = $variadic;
$this->var = $var;
$this->default = $default;
$this->flags = $flags;
$this->attrGroups = $attrGroups;
}
public function getSubNodeNames() : array {
return ['attrGroups', 'flags', 'type', 'byRef', 'variadic', 'var', 'default'];
}
public function getType() : string {
return 'Param';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
abstract class Scalar extends Expr
{
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class Name extends NodeAbstract
{
/** @var string[] Parts of the name */
public $parts;
private static $specialClassNames = [
'self' => true,
'parent' => true,
'static' => true,
];
/**
* Constructs a name node.
*
* @param string|string[]|self $name Name as string, part array or Name instance (copy ctor)
* @param array $attributes Additional attributes
*/
public function __construct($name, array $attributes = []) {
$this->attributes = $attributes;
$this->parts = self::prepareName($name);
}
public function getSubNodeNames() : array {
return ['parts'];
}
/**
* Gets the first part of the name, i.e. everything before the first namespace separator.
*
* @return string First part of the name
*/
public function getFirst() : string {
return $this->parts[0];
}
/**
* Gets the last part of the name, i.e. everything after the last namespace separator.
*
* @return string Last part of the name
*/
public function getLast() : string {
return $this->parts[count($this->parts) - 1];
}
/**
* Checks whether the name is unqualified. (E.g. Name)
*
* @return bool Whether the name is unqualified
*/
public function isUnqualified() : bool {
return 1 === count($this->parts);
}
/**
* Checks whether the name is qualified. (E.g. Name\Name)
*
* @return bool Whether the name is qualified
*/
public function isQualified() : bool {
return 1 < count($this->parts);
}
/**
* Checks whether the name is fully qualified. (E.g. \Name)
*
* @return bool Whether the name is fully qualified
*/
public function isFullyQualified() : bool {
return false;
}
/**
* Checks whether the name is explicitly relative to the current namespace. (E.g. namespace\Name)
*
* @return bool Whether the name is relative
*/
public function isRelative() : bool {
return false;
}
/**
* Returns a string representation of the name itself, without taking the name type into
* account (e.g., not including a leading backslash for fully qualified names).
*
* @return string String representation
*/
public function toString() : string {
return implode('\\', $this->parts);
}
/**
* Returns a string representation of the name as it would occur in code (e.g., including
* leading backslash for fully qualified names.
*
* @return string String representation
*/
public function toCodeString() : string {
return $this->toString();
}
/**
* Returns lowercased string representation of the name, without taking the name type into
* account (e.g., no leading backslash for fully qualified names).
*
* @return string Lowercased string representation
*/
public function toLowerString() : string {
return strtolower(implode('\\', $this->parts));
}
/**
* Checks whether the identifier is a special class name (self, parent or static).
*
* @return bool Whether identifier is a special class name
*/
public function isSpecialClassName() : bool {
return count($this->parts) === 1
&& isset(self::$specialClassNames[strtolower($this->parts[0])]);
}
/**
* Returns a string representation of the name by imploding the namespace parts with the
* namespace separator.
*
* @return string String representation
*/
public function __toString() : string {
return implode('\\', $this->parts);
}
/**
* Gets a slice of a name (similar to array_slice).
*
* This method returns a new instance of the same type as the original and with the same
* attributes.
*
* If the slice is empty, null is returned. The null value will be correctly handled in
* concatenations using concat().
*
* Offset and length have the same meaning as in array_slice().
*
* @param int $offset Offset to start the slice at (may be negative)
* @param int|null $length Length of the slice (may be negative)
*
* @return static|null Sliced name
*/
public function slice(int $offset, int $length = null) {
$numParts = count($this->parts);
$realOffset = $offset < 0 ? $offset + $numParts : $offset;
if ($realOffset < 0 || $realOffset > $numParts) {
throw new \OutOfBoundsException(sprintf('Offset %d is out of bounds', $offset));
}
if (null === $length) {
$realLength = $numParts - $realOffset;
} else {
$realLength = $length < 0 ? $length + $numParts - $realOffset : $length;
if ($realLength < 0 || $realLength > $numParts) {
throw new \OutOfBoundsException(sprintf('Length %d is out of bounds', $length));
}
}
if ($realLength === 0) {
// Empty slice is represented as null
return null;
}
return new static(array_slice($this->parts, $realOffset, $realLength), $this->attributes);
}
/**
* Concatenate two names, yielding a new Name instance.
*
* The type of the generated instance depends on which class this method is called on, for
* example Name\FullyQualified::concat() will yield a Name\FullyQualified instance.
*
* If one of the arguments is null, a new instance of the other name will be returned. If both
* arguments are null, null will be returned. As such, writing
* Name::concat($namespace, $shortName)
* where $namespace is a Name node or null will work as expected.
*
* @param string|string[]|self|null $name1 The first name
* @param string|string[]|self|null $name2 The second name
* @param array $attributes Attributes to assign to concatenated name
*
* @return static|null Concatenated name
*/
public static function concat($name1, $name2, array $attributes = []) {
if (null === $name1 && null === $name2) {
return null;
} elseif (null === $name1) {
return new static(self::prepareName($name2), $attributes);
} elseif (null === $name2) {
return new static(self::prepareName($name1), $attributes);
} else {
return new static(
array_merge(self::prepareName($name1), self::prepareName($name2)), $attributes
);
}
}
/**
* Prepares a (string, array or Name node) name for use in name changing methods by converting
* it to an array.
*
* @param string|string[]|self $name Name to prepare
*
* @return string[] Prepared name
*/
private static function prepareName($name) : array {
if (\is_string($name)) {
if ('' === $name) {
throw new \InvalidArgumentException('Name cannot be empty');
}
return explode('\\', $name);
} elseif (\is_array($name)) {
if (empty($name)) {
throw new \InvalidArgumentException('Name cannot be empty');
}
return $name;
} elseif ($name instanceof self) {
return $name->parts;
}
throw new \InvalidArgumentException(
'Expected string, array of parts or Name instance'
);
}
public function getType() : string {
return 'Name';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class UnionType extends NodeAbstract
{
/** @var (Identifier|Name)[] Types */
public $types;
/**
* Constructs a union type.
*
* @param (Identifier|Name)[] $types Types
* @param array $attributes Additional attributes
*/
public function __construct(array $types, array $attributes = []) {
$this->attributes = $attributes;
$this->types = $types;
}
public function getSubNodeNames() : array {
return ['types'];
}
public function getType() : string {
return 'UnionType';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class AttributeGroup extends NodeAbstract
{
/** @var Attribute[] Attributes */
public $attrs;
/**
* @param Attribute[] $attrs PHP attributes
* @param array $attributes Additional node attributes
*/
public function __construct(array $attrs, array $attributes = []) {
$this->attributes = $attributes;
$this->attrs = $attrs;
}
public function getSubNodeNames() : array {
return ['attrs'];
}
public function getType() : string {
return 'AttributeGroup';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* @property Name $namespacedName Namespaced name (for global constants, if using NameResolver)
*/
class Const_ extends NodeAbstract
{
/** @var Identifier Name */
public $name;
/** @var Expr Value */
public $value;
/**
* Constructs a const node for use in class const and const statements.
*
* @param string|Identifier $name Name
* @param Expr $value Value
* @param array $attributes Additional attributes
*/
public function __construct($name, Expr $value, array $attributes = []) {
$this->attributes = $attributes;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->value = $value;
}
public function getSubNodeNames() : array {
return ['name', 'value'];
}
public function getType() : string {
return 'Const';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
abstract class Stmt extends NodeAbstract
{
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
/**
* Represents a name that is written in source code with a leading dollar,
* but is not a proper variable. The leading dollar is not stored as part of the name.
*
* Examples: Names in property declarations are formatted as variables. Names in static property
* lookups are also formatted as variables.
*/
class VarLikeIdentifier extends Identifier
{
public function getType() : string {
return 'VarLikeIdentifier';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Scalar;
class EncapsedStringPart extends Scalar
{
/** @var string String value */
public $value;
/**
* Constructs a node representing a string part of an encapsed string.
*
* @param string $value String value
* @param array $attributes Additional attributes
*/
public function __construct(string $value, array $attributes = []) {
$this->attributes = $attributes;
$this->value = $value;
}
public function getSubNodeNames() : array {
return ['value'];
}
public function getType() : string {
return 'Scalar_EncapsedStringPart';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Scalar;
abstract class MagicConst extends Scalar
{
/**
* Constructs a magic constant node.
*
* @param array $attributes Additional attributes
*/
public function __construct(array $attributes = []) {
$this->attributes = $attributes;
}
public function getSubNodeNames() : array {
return [];
}
/**
* Get name of magic constant.
*
* @return string Name of magic constant
*/
abstract public function getName() : string;
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Error;
use PhpParser\Node\Scalar;
class LNumber extends Scalar
{
/* For use in "kind" attribute */
const KIND_BIN = 2;
const KIND_OCT = 8;
const KIND_DEC = 10;
const KIND_HEX = 16;
/** @var int Number value */
public $value;
/**
* Constructs an integer number scalar node.
*
* @param int $value Value of the number
* @param array $attributes Additional attributes
*/
public function __construct(int $value, array $attributes = []) {
$this->attributes = $attributes;
$this->value = $value;
}
public function getSubNodeNames() : array {
return ['value'];
}
/**
* Constructs an LNumber node from a string number literal.
*
* @param string $str String number literal (decimal, octal, hex or binary)
* @param array $attributes Additional attributes
* @param bool $allowInvalidOctal Whether to allow invalid octal numbers (PHP 5)
*
* @return LNumber The constructed LNumber, including kind attribute
*/
public static function fromString(string $str, array $attributes = [], bool $allowInvalidOctal = false) : LNumber {
$str = str_replace('_', '', $str);
if ('0' !== $str[0] || '0' === $str) {
$attributes['kind'] = LNumber::KIND_DEC;
return new LNumber((int) $str, $attributes);
}
if ('x' === $str[1] || 'X' === $str[1]) {
$attributes['kind'] = LNumber::KIND_HEX;
return new LNumber(hexdec($str), $attributes);
}
if ('b' === $str[1] || 'B' === $str[1]) {
$attributes['kind'] = LNumber::KIND_BIN;
return new LNumber(bindec($str), $attributes);
}
if (!$allowInvalidOctal && strpbrk($str, '89')) {
throw new Error('Invalid numeric literal', $attributes);
}
// use intval instead of octdec to get proper cutting behavior with malformed numbers
$attributes['kind'] = LNumber::KIND_OCT;
return new LNumber(intval($str, 8), $attributes);
}
public function getType() : string {
return 'Scalar_LNumber';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Expr;
use PhpParser\Node\Scalar;
class Encapsed extends Scalar
{
/** @var Expr[] list of string parts */
public $parts;
/**
* Constructs an encapsed string node.
*
* @param Expr[] $parts Encaps list
* @param array $attributes Additional attributes
*/
public function __construct(array $parts, array $attributes = []) {
$this->attributes = $attributes;
$this->parts = $parts;
}
public function getSubNodeNames() : array {
return ['parts'];
}
public function getType() : string {
return 'Scalar_Encapsed';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Node\Scalar;
class DNumber extends Scalar
{
/** @var float Number value */
public $value;
/**
* Constructs a float number scalar node.
*
* @param float $value Value of the number
* @param array $attributes Additional attributes
*/
public function __construct(float $value, array $attributes = []) {
$this->attributes = $attributes;
$this->value = $value;
}
public function getSubNodeNames() : array {
return ['value'];
}
/**
* @internal
*
* Parses a DNUMBER token like PHP would.
*
* @param string $str A string number
*
* @return float The parsed number
*/
public static function parse(string $str) : float {
$str = str_replace('_', '', $str);
// if string contains any of .eE just cast it to float
if (false !== strpbrk($str, '.eE')) {
return (float) $str;
}
// otherwise it's an integer notation that overflowed into a float
// if it starts with 0 it's one of the special integer notations
if ('0' === $str[0]) {
// hex
if ('x' === $str[1] || 'X' === $str[1]) {
return hexdec($str);
}
// bin
if ('b' === $str[1] || 'B' === $str[1]) {
return bindec($str);
}
// oct
// substr($str, 0, strcspn($str, '89')) cuts the string at the first invalid digit (8 or 9)
// so that only the digits before that are used
return octdec(substr($str, 0, strcspn($str, '89')));
}
// dec
return (float) $str;
}
public function getType() : string {
return 'Scalar_DNumber';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar;
use PhpParser\Error;
use PhpParser\Node\Scalar;
class String_ extends Scalar
{
/* For use in "kind" attribute */
const KIND_SINGLE_QUOTED = 1;
const KIND_DOUBLE_QUOTED = 2;
const KIND_HEREDOC = 3;
const KIND_NOWDOC = 4;
/** @var string String value */
public $value;
protected static $replacements = [
'\\' => '\\',
'$' => '$',
'n' => "\n",
'r' => "\r",
't' => "\t",
'f' => "\f",
'v' => "\v",
'e' => "\x1B",
];
/**
* Constructs a string scalar node.
*
* @param string $value Value of the string
* @param array $attributes Additional attributes
*/
public function __construct(string $value, array $attributes = []) {
$this->attributes = $attributes;
$this->value = $value;
}
public function getSubNodeNames() : array {
return ['value'];
}
/**
* @internal
*
* Parses a string token.
*
* @param string $str String token content
* @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
*
* @return string The parsed string
*/
public static function parse(string $str, bool $parseUnicodeEscape = true) : string {
$bLength = 0;
if ('b' === $str[0] || 'B' === $str[0]) {
$bLength = 1;
}
if ('\'' === $str[$bLength]) {
return str_replace(
['\\\\', '\\\''],
['\\', '\''],
substr($str, $bLength + 1, -1)
);
} else {
return self::parseEscapeSequences(
substr($str, $bLength + 1, -1), '"', $parseUnicodeEscape
);
}
}
/**
* @internal
*
* Parses escape sequences in strings (all string types apart from single quoted).
*
* @param string $str String without quotes
* @param null|string $quote Quote type
* @param bool $parseUnicodeEscape Whether to parse PHP 7 \u escapes
*
* @return string String with escape sequences parsed
*/
public static function parseEscapeSequences(string $str, $quote, bool $parseUnicodeEscape = true) : string {
if (null !== $quote) {
$str = str_replace('\\' . $quote, $quote, $str);
}
$extra = '';
if ($parseUnicodeEscape) {
$extra = '|u\{([0-9a-fA-F]+)\}';
}
return preg_replace_callback(
'~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3}' . $extra . ')~',
function($matches) {
$str = $matches[1];
if (isset(self::$replacements[$str])) {
return self::$replacements[$str];
} elseif ('x' === $str[0] || 'X' === $str[0]) {
return chr(hexdec(substr($str, 1)));
} elseif ('u' === $str[0]) {
return self::codePointToUtf8(hexdec($matches[2]));
} else {
return chr(octdec($str));
}
},
$str
);
}
/**
* Converts a Unicode code point to its UTF-8 encoded representation.
*
* @param int $num Code point
*
* @return string UTF-8 representation of code point
*/
private static function codePointToUtf8(int $num) : string {
if ($num <= 0x7F) {
return chr($num);
}
if ($num <= 0x7FF) {
return chr(($num>>6) + 0xC0) . chr(($num&0x3F) + 0x80);
}
if ($num <= 0xFFFF) {
return chr(($num>>12) + 0xE0) . chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
}
if ($num <= 0x1FFFFF) {
return chr(($num>>18) + 0xF0) . chr((($num>>12)&0x3F) + 0x80)
. chr((($num>>6)&0x3F) + 0x80) . chr(($num&0x3F) + 0x80);
}
throw new Error('Invalid UTF-8 codepoint escape sequence: Codepoint too large');
}
public function getType() : string {
return 'Scalar_String';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Trait_ extends MagicConst
{
public function getName() : string {
return '__TRAIT__';
}
public function getType() : string {
return 'Scalar_MagicConst_Trait';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Class_ extends MagicConst
{
public function getName() : string {
return '__CLASS__';
}
public function getType() : string {
return 'Scalar_MagicConst_Class';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Method extends MagicConst
{
public function getName() : string {
return '__METHOD__';
}
public function getType() : string {
return 'Scalar_MagicConst_Method';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Function_ extends MagicConst
{
public function getName() : string {
return '__FUNCTION__';
}
public function getType() : string {
return 'Scalar_MagicConst_Function';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Line extends MagicConst
{
public function getName() : string {
return '__LINE__';
}
public function getType() : string {
return 'Scalar_MagicConst_Line';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Dir extends MagicConst
{
public function getName() : string {
return '__DIR__';
}
public function getType() : string {
return 'Scalar_MagicConst_Dir';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class Namespace_ extends MagicConst
{
public function getName() : string {
return '__NAMESPACE__';
}
public function getType() : string {
return 'Scalar_MagicConst_Namespace';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Scalar\MagicConst;
use PhpParser\Node\Scalar\MagicConst;
class File extends MagicConst
{
public function getName() : string {
return '__FILE__';
}
public function getType() : string {
return 'Scalar_MagicConst_File';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
class Instanceof_ extends Expr
{
/** @var Expr Expression */
public $expr;
/** @var Name|Expr Class name */
public $class;
/**
* Constructs an instanceof check node.
*
* @param Expr $expr Expression
* @param Name|Expr $class Class name
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, $class, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
$this->class = $class;
}
public function getSubNodeNames() : array {
return ['expr', 'class'];
}
public function getType() : string {
return 'Expr_Instanceof';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class UnaryMinus extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs a unary minus node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_UnaryMinus';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
abstract class Cast extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs a cast node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
class StaticCall extends Expr
{
/** @var Node\Name|Expr Class name */
public $class;
/** @var Identifier|Expr Method name */
public $name;
/** @var Node\Arg[] Arguments */
public $args;
/**
* Constructs a static method call node.
*
* @param Node\Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->class = $class;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['class', 'name', 'args'];
}
public function getType() : string {
return 'Expr_StaticCall';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class BooleanNot extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs a boolean not node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_BooleanNot';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Include_ extends Expr
{
const TYPE_INCLUDE = 1;
const TYPE_INCLUDE_ONCE = 2;
const TYPE_REQUIRE = 3;
const TYPE_REQUIRE_ONCE = 4;
/** @var Expr Expression */
public $expr;
/** @var int Type of include */
public $type;
/**
* Constructs an include node.
*
* @param Expr $expr Expression
* @param int $type Type of include
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, int $type, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
$this->type = $type;
}
public function getSubNodeNames() : array {
return ['expr', 'type'];
}
public function getType() : string {
return 'Expr_Include';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class ArrayItem extends Expr
{
/** @var null|Expr Key */
public $key;
/** @var Expr Value */
public $value;
/** @var bool Whether to assign by reference */
public $byRef;
/** @var bool Whether to unpack the argument */
public $unpack;
/**
* Constructs an array item node.
*
* @param Expr $value Value
* @param null|Expr $key Key
* @param bool $byRef Whether to assign by reference
* @param array $attributes Additional attributes
*/
public function __construct(Expr $value, Expr $key = null, bool $byRef = false, array $attributes = [], bool $unpack = false) {
$this->attributes = $attributes;
$this->key = $key;
$this->value = $value;
$this->byRef = $byRef;
$this->unpack = $unpack;
}
public function getSubNodeNames() : array {
return ['key', 'value', 'byRef', 'unpack'];
}
public function getType() : string {
return 'Expr_ArrayItem';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Concat extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Concat';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Mul extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Mul';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class ShiftLeft extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_ShiftLeft';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Div extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Div';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class BitwiseOr extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_BitwiseOr';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Minus extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Minus';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Mod extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Mod';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class BitwiseXor extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_BitwiseXor';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Plus extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Plus';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Pow extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Pow';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class BitwiseAnd extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_BitwiseAnd';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class ShiftRight extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_ShiftRight';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\AssignOp;
class Coalesce extends AssignOp
{
public function getType() : string {
return 'Expr_AssignOp_Coalesce';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class PostDec extends Expr
{
/** @var Expr Variable */
public $var;
/**
* Constructs a post decrement node.
*
* @param Expr $var Variable
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
}
public function getSubNodeNames() : array {
return ['var'];
}
public function getType() : string {
return 'Expr_PostDec';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
abstract class BinaryOp extends Expr
{
/** @var Expr The left hand side expression */
public $left;
/** @var Expr The right hand side expression */
public $right;
/**
* Constructs a binary operator node.
*
* @param Expr $left The left hand side expression
* @param Expr $right The right hand side expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $left, Expr $right, array $attributes = []) {
$this->attributes = $attributes;
$this->left = $left;
$this->right = $right;
}
public function getSubNodeNames() : array {
return ['left', 'right'];
}
/**
* Get the operator sigil for this binary operation.
*
* In the case there are multiple possible sigils for an operator, this method does not
* necessarily return the one used in the parsed code.
*
* @return string
*/
abstract public function getOperatorSigil() : string;
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
abstract class AssignOp extends Expr
{
/** @var Expr Variable */
public $var;
/** @var Expr Expression */
public $expr;
/**
* Constructs a compound assignment operation node.
*
* @param Expr $var Variable
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['var', 'expr'];
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class BitwiseNot extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs a bitwise not node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_BitwiseNot';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
class FuncCall extends Expr
{
/** @var Node\Name|Expr Function name */
public $name;
/** @var Node\Arg[] Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr $name Function name
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['name', 'args'];
}
public function getType() : string {
return 'Expr_FuncCall';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Isset_ extends Expr
{
/** @var Expr[] Variables */
public $vars;
/**
* Constructs an array node.
*
* @param Expr[] $vars Variables
* @param array $attributes Additional attributes
*/
public function __construct(array $vars, array $attributes = []) {
$this->attributes = $attributes;
$this->vars = $vars;
}
public function getSubNodeNames() : array {
return ['vars'];
}
public function getType() : string {
return 'Expr_Isset';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Print_ extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs an print() node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_Print';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
class PropertyFetch extends Expr
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Property name */
public $name;
/**
* Constructs a function call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Property name
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}
public function getSubNodeNames() : array {
return ['var', 'name'];
}
public function getType() : string {
return 'Expr_PropertyFetch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Assign extends Expr
{
/** @var Expr Variable */
public $var;
/** @var Expr Expression */
public $expr;
/**
* Constructs an assignment node.
*
* @param Expr $var Variable
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['var', 'expr'];
}
public function getType() : string {
return 'Expr_Assign';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;
class Closure extends Expr implements FunctionLike
{
/** @var bool Whether the closure is static */
public $static;
/** @var bool Whether to return by reference */
public $byRef;
/** @var Node\Param[] Parameters */
public $params;
/** @var ClosureUse[] use()s */
public $uses;
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType Return type */
public $returnType;
/** @var Node\Stmt[] Statements */
public $stmts;
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/**
* Constructs a lambda function node.
*
* @param array $subNodes Array of the following optional subnodes:
* 'static' => false : Whether the closure is static
* 'byRef' => false : Whether to return by reference
* 'params' => array(): Parameters
* 'uses' => array(): use()s
* 'returnType' => null : Return type
* 'stmts' => array(): Statements
* 'attrGroups' => array(): PHP attributes groups
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->static = $subNodes['static'] ?? false;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$this->uses = $subNodes['uses'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->stmts = $subNodes['stmts'] ?? [];
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['attrGroups', 'static', 'byRef', 'params', 'uses', 'returnType', 'stmts'];
}
public function returnsByRef() : bool {
return $this->byRef;
}
public function getParams() : array {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
/** @return Node\Stmt[] */
public function getStmts() : array {
return $this->stmts;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
public function getType() : string {
return 'Expr_Closure';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Yield_ extends Expr
{
/** @var null|Expr Key expression */
public $key;
/** @var null|Expr Value expression */
public $value;
/**
* Constructs a yield expression node.
*
* @param null|Expr $value Value expression
* @param null|Expr $key Key expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $value = null, Expr $key = null, array $attributes = []) {
$this->attributes = $attributes;
$this->key = $key;
$this->value = $value;
}
public function getSubNodeNames() : array {
return ['key', 'value'];
}
public function getType() : string {
return 'Expr_Yield';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Eval_ extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs an eval() node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_Eval';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
class ClassConstFetch extends Expr
{
/** @var Name|Expr Class name */
public $class;
/** @var Identifier|Error Constant name */
public $name;
/**
* Constructs a class const fetch node.
*
* @param Name|Expr $class Class name
* @param string|Identifier|Error $name Constant name
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $attributes = []) {
$this->attributes = $attributes;
$this->class = $class;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}
public function getSubNodeNames() : array {
return ['class', 'name'];
}
public function getType() : string {
return 'Expr_ClassConstFetch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Array_ extends Expr
{
// For use in "kind" attribute
const KIND_LONG = 1; // array() syntax
const KIND_SHORT = 2; // [] syntax
/** @var (ArrayItem|null)[] Items */
public $items;
/**
* Constructs an array node.
*
* @param (ArrayItem|null)[] $items Items of the array
* @param array $attributes Additional attributes
*/
public function __construct(array $items = [], array $attributes = []) {
$this->attributes = $attributes;
$this->items = $items;
}
public function getSubNodeNames() : array {
return ['items'];
}
public function getType() : string {
return 'Expr_Array';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
class NullsafePropertyFetch extends Expr
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Property name */
public $name;
/**
* Constructs a nullsafe property fetch node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Property name
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
}
public function getSubNodeNames() : array {
return ['var', 'name'];
}
public function getType() : string {
return 'Expr_NullsafePropertyFetch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
class ConstFetch extends Expr
{
/** @var Name Constant name */
public $name;
/**
* Constructs a const fetch node.
*
* @param Name $name Constant name
* @param array $attributes Additional attributes
*/
public function __construct(Name $name, array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
}
public function getSubNodeNames() : array {
return ['name'];
}
public function getType() : string {
return 'Expr_ConstFetch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class UnaryPlus extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs a unary plus node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_UnaryPlus';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Smaller extends BinaryOp
{
public function getOperatorSigil() : string {
return '<';
}
public function getType() : string {
return 'Expr_BinaryOp_Smaller';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class LogicalXor extends BinaryOp
{
public function getOperatorSigil() : string {
return 'xor';
}
public function getType() : string {
return 'Expr_BinaryOp_LogicalXor';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Concat extends BinaryOp
{
public function getOperatorSigil() : string {
return '.';
}
public function getType() : string {
return 'Expr_BinaryOp_Concat';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class BooleanOr extends BinaryOp
{
public function getOperatorSigil() : string {
return '||';
}
public function getType() : string {
return 'Expr_BinaryOp_BooleanOr';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Mul extends BinaryOp
{
public function getOperatorSigil() : string {
return '*';
}
public function getType() : string {
return 'Expr_BinaryOp_Mul';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class ShiftLeft extends BinaryOp
{
public function getOperatorSigil() : string {
return '<<';
}
public function getType() : string {
return 'Expr_BinaryOp_ShiftLeft';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Div extends BinaryOp
{
public function getOperatorSigil() : string {
return '/';
}
public function getType() : string {
return 'Expr_BinaryOp_Div';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class BitwiseOr extends BinaryOp
{
public function getOperatorSigil() : string {
return '|';
}
public function getType() : string {
return 'Expr_BinaryOp_BitwiseOr';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Equal extends BinaryOp
{
public function getOperatorSigil() : string {
return '==';
}
public function getType() : string {
return 'Expr_BinaryOp_Equal';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class BooleanAnd extends BinaryOp
{
public function getOperatorSigil() : string {
return '&&';
}
public function getType() : string {
return 'Expr_BinaryOp_BooleanAnd';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Minus extends BinaryOp
{
public function getOperatorSigil() : string {
return '-';
}
public function getType() : string {
return 'Expr_BinaryOp_Minus';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Mod extends BinaryOp
{
public function getOperatorSigil() : string {
return '%';
}
public function getType() : string {
return 'Expr_BinaryOp_Mod';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class BitwiseXor extends BinaryOp
{
public function getOperatorSigil() : string {
return '^';
}
public function getType() : string {
return 'Expr_BinaryOp_BitwiseXor';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Identical extends BinaryOp
{
public function getOperatorSigil() : string {
return '===';
}
public function getType() : string {
return 'Expr_BinaryOp_Identical';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Plus extends BinaryOp
{
public function getOperatorSigil() : string {
return '+';
}
public function getType() : string {
return 'Expr_BinaryOp_Plus';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class NotEqual extends BinaryOp
{
public function getOperatorSigil() : string {
return '!=';
}
public function getType() : string {
return 'Expr_BinaryOp_NotEqual';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class LogicalOr extends BinaryOp
{
public function getOperatorSigil() : string {
return 'or';
}
public function getType() : string {
return 'Expr_BinaryOp_LogicalOr';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Pow extends BinaryOp
{
public function getOperatorSigil() : string {
return '**';
}
public function getType() : string {
return 'Expr_BinaryOp_Pow';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class GreaterOrEqual extends BinaryOp
{
public function getOperatorSigil() : string {
return '>=';
}
public function getType() : string {
return 'Expr_BinaryOp_GreaterOrEqual';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class LogicalAnd extends BinaryOp
{
public function getOperatorSigil() : string {
return 'and';
}
public function getType() : string {
return 'Expr_BinaryOp_LogicalAnd';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class NotIdentical extends BinaryOp
{
public function getOperatorSigil() : string {
return '!==';
}
public function getType() : string {
return 'Expr_BinaryOp_NotIdentical';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class BitwiseAnd extends BinaryOp
{
public function getOperatorSigil() : string {
return '&';
}
public function getType() : string {
return 'Expr_BinaryOp_BitwiseAnd';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class ShiftRight extends BinaryOp
{
public function getOperatorSigil() : string {
return '>>';
}
public function getType() : string {
return 'Expr_BinaryOp_ShiftRight';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Coalesce extends BinaryOp
{
public function getOperatorSigil() : string {
return '??';
}
public function getType() : string {
return 'Expr_BinaryOp_Coalesce';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Greater extends BinaryOp
{
public function getOperatorSigil() : string {
return '>';
}
public function getType() : string {
return 'Expr_BinaryOp_Greater';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class SmallerOrEqual extends BinaryOp
{
public function getOperatorSigil() : string {
return '<=';
}
public function getType() : string {
return 'Expr_BinaryOp_SmallerOrEqual';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\BinaryOp;
class Spaceship extends BinaryOp
{
public function getOperatorSigil() : string {
return '<=>';
}
public function getType() : string {
return 'Expr_BinaryOp_Spaceship';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\VarLikeIdentifier;
class StaticPropertyFetch extends Expr
{
/** @var Name|Expr Class name */
public $class;
/** @var VarLikeIdentifier|Expr Property name */
public $name;
/**
* Constructs a static property fetch node.
*
* @param Name|Expr $class Class name
* @param string|VarLikeIdentifier|Expr $name Property name
* @param array $attributes Additional attributes
*/
public function __construct($class, $name, array $attributes = []) {
$this->attributes = $attributes;
$this->class = $class;
$this->name = \is_string($name) ? new VarLikeIdentifier($name) : $name;
}
public function getSubNodeNames() : array {
return ['class', 'name'];
}
public function getType() : string {
return 'Expr_StaticPropertyFetch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\MatchArm;
class Match_ extends Node\Expr
{
/** @var Node\Expr */
public $cond;
/** @var MatchArm[] */
public $arms;
/**
* @param MatchArm[] $arms
*/
public function __construct(Node\Expr $cond, array $arms = [], array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->arms = $arms;
}
public function getSubNodeNames() : array {
return ['cond', 'arms'];
}
public function getType() : string {
return 'Expr_Match';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class PreInc extends Expr
{
/** @var Expr Variable */
public $var;
/**
* Constructs a pre increment node.
*
* @param Expr $var Variable
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
}
public function getSubNodeNames() : array {
return ['var'];
}
public function getType() : string {
return 'Expr_PreInc';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
class NullsafeMethodCall extends Expr
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
public $args;
/**
* Constructs a nullsafe method call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['var', 'name', 'args'];
}
public function getType() : string {
return 'Expr_NullsafeMethodCall';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
/**
* Error node used during parsing with error recovery.
*
* An error node may be placed at a position where an expression is required, but an error occurred.
* Error nodes will not be present if the parser is run in throwOnError mode (the default).
*/
class Error extends Expr
{
/**
* Constructs an error node.
*
* @param array $attributes Additional attributes
*/
public function __construct(array $attributes = []) {
$this->attributes = $attributes;
}
public function getSubNodeNames() : array {
return [];
}
public function getType() : string {
return 'Expr_Error';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class ArrayDimFetch extends Expr
{
/** @var Expr Variable */
public $var;
/** @var null|Expr Array index / dim */
public $dim;
/**
* Constructs an array index fetch node.
*
* @param Expr $var Variable
* @param null|Expr $dim Array index / dim
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $dim = null, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->dim = $dim;
}
public function getSubNodeNames() : array {
return ['var', 'dim'];
}
public function getType() : string {
return 'Expr_ArrayDimFetch';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Empty_ extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs an empty() node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_Empty';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class YieldFrom extends Expr
{
/** @var Expr Expression to yield from */
public $expr;
/**
* Constructs an "yield from" node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_YieldFrom';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\FunctionLike;
class ArrowFunction extends Expr implements FunctionLike
{
/** @var bool */
public $static;
/** @var bool */
public $byRef;
/** @var Node\Param[] */
public $params = [];
/** @var null|Node\Identifier|Node\Name|Node\NullableType|Node\UnionType */
public $returnType;
/** @var Expr */
public $expr;
/** @var Node\AttributeGroup[] */
public $attrGroups;
/**
* @param array $subNodes Array of the following optional subnodes:
* 'static' => false : Whether the closure is static
* 'byRef' => false : Whether to return by reference
* 'params' => array() : Parameters
* 'returnType' => null : Return type
* 'expr' => Expr : Expression body
* 'attrGroups' => array() : PHP attribute groups
* @param array $attributes Additional attributes
*/
public function __construct(array $subNodes = [], array $attributes = []) {
$this->attributes = $attributes;
$this->static = $subNodes['static'] ?? false;
$this->byRef = $subNodes['byRef'] ?? false;
$this->params = $subNodes['params'] ?? [];
$returnType = $subNodes['returnType'] ?? null;
$this->returnType = \is_string($returnType) ? new Node\Identifier($returnType) : $returnType;
$this->expr = $subNodes['expr'] ?? null;
$this->attrGroups = $subNodes['attrGroups'] ?? [];
}
public function getSubNodeNames() : array {
return ['attrGroups', 'static', 'byRef', 'params', 'returnType', 'expr'];
}
public function returnsByRef() : bool {
return $this->byRef;
}
public function getParams() : array {
return $this->params;
}
public function getReturnType() {
return $this->returnType;
}
public function getAttrGroups() : array {
return $this->attrGroups;
}
/**
* @return Node\Stmt\Return_[]
*/
public function getStmts() : array {
return [new Node\Stmt\Return_($this->expr)];
}
public function getType() : string {
return 'Expr_ArrowFunction';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
class Throw_ extends Node\Expr
{
/** @var Node\Expr Expression */
public $expr;
/**
* Constructs a throw expression node.
*
* @param Node\Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Node\Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_Throw';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class PreDec extends Expr
{
/** @var Expr Variable */
public $var;
/**
* Constructs a pre decrement node.
*
* @param Expr $var Variable
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
}
public function getSubNodeNames() : array {
return ['var'];
}
public function getType() : string {
return 'Expr_PreDec';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class ClosureUse extends Expr
{
/** @var Expr\Variable Variable to use */
public $var;
/** @var bool Whether to use by reference */
public $byRef;
/**
* Constructs a closure use node.
*
* @param Expr\Variable $var Variable to use
* @param bool $byRef Whether to use by reference
* @param array $attributes Additional attributes
*/
public function __construct(Expr\Variable $var, bool $byRef = false, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->byRef = $byRef;
}
public function getSubNodeNames() : array {
return ['var', 'byRef'];
}
public function getType() : string {
return 'Expr_ClosureUse';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Exit_ extends Expr
{
/* For use in "kind" attribute */
const KIND_EXIT = 1;
const KIND_DIE = 2;
/** @var null|Expr Expression */
public $expr;
/**
* Constructs an exit() node.
*
* @param null|Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr = null, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_Exit';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Variable extends Expr
{
/** @var string|Expr Name */
public $name;
/**
* Constructs a variable node.
*
* @param string|Expr $name Name
* @param array $attributes Additional attributes
*/
public function __construct($name, array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
}
public function getSubNodeNames() : array {
return ['name'];
}
public function getType() : string {
return 'Expr_Variable';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class ShellExec extends Expr
{
/** @var array Encapsed string array */
public $parts;
/**
* Constructs a shell exec (backtick) node.
*
* @param array $parts Encapsed string array
* @param array $attributes Additional attributes
*/
public function __construct(array $parts, array $attributes = []) {
$this->attributes = $attributes;
$this->parts = $parts;
}
public function getSubNodeNames() : array {
return ['parts'];
}
public function getType() : string {
return 'Expr_ShellExec';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class PostInc extends Expr
{
/** @var Expr Variable */
public $var;
/**
* Constructs a post increment node.
*
* @param Expr $var Variable
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
}
public function getSubNodeNames() : array {
return ['var'];
}
public function getType() : string {
return 'Expr_PostInc';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class ErrorSuppress extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs an error suppress node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_ErrorSuppress';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class List_ extends Expr
{
/** @var (ArrayItem|null)[] List of items to assign to */
public $items;
/**
* Constructs a list() destructuring node.
*
* @param (ArrayItem|null)[] $items List of items to assign to
* @param array $attributes Additional attributes
*/
public function __construct(array $items, array $attributes = []) {
$this->attributes = $attributes;
$this->items = $items;
}
public function getSubNodeNames() : array {
return ['items'];
}
public function getType() : string {
return 'Expr_List';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Clone_ extends Expr
{
/** @var Expr Expression */
public $expr;
/**
* Constructs a clone node.
*
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['expr'];
}
public function getType() : string {
return 'Expr_Clone';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class Ternary extends Expr
{
/** @var Expr Condition */
public $cond;
/** @var null|Expr Expression for true */
public $if;
/** @var Expr Expression for false */
public $else;
/**
* Constructs a ternary operator node.
*
* @param Expr $cond Condition
* @param null|Expr $if Expression for true
* @param Expr $else Expression for false
* @param array $attributes Additional attributes
*/
public function __construct(Expr $cond, $if, Expr $else, array $attributes = []) {
$this->attributes = $attributes;
$this->cond = $cond;
$this->if = $if;
$this->else = $else;
}
public function getSubNodeNames() : array {
return ['cond', 'if', 'else'];
}
public function getType() : string {
return 'Expr_Ternary';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Bool_ extends Cast
{
public function getType() : string {
return 'Expr_Cast_Bool';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Int_ extends Cast
{
public function getType() : string {
return 'Expr_Cast_Int';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Double extends Cast
{
// For use in "kind" attribute
const KIND_DOUBLE = 1; // "double" syntax
const KIND_FLOAT = 2; // "float" syntax
const KIND_REAL = 3; // "real" syntax
public function getType() : string {
return 'Expr_Cast_Double';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Object_ extends Cast
{
public function getType() : string {
return 'Expr_Cast_Object';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Array_ extends Cast
{
public function getType() : string {
return 'Expr_Cast_Array';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class String_ extends Cast
{
public function getType() : string {
return 'Expr_Cast_String';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr\Cast;
use PhpParser\Node\Expr\Cast;
class Unset_ extends Cast
{
public function getType() : string {
return 'Expr_Cast_Unset';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Expr;
class AssignRef extends Expr
{
/** @var Expr Variable reference is assigned to */
public $var;
/** @var Expr Variable which is referenced */
public $expr;
/**
* Constructs an assignment node.
*
* @param Expr $var Variable
* @param Expr $expr Expression
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, Expr $expr, array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->expr = $expr;
}
public function getSubNodeNames() : array {
return ['var', 'expr'];
}
public function getType() : string {
return 'Expr_AssignRef';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node;
use PhpParser\Node\Expr;
class New_ extends Expr
{
/** @var Node\Name|Expr|Node\Stmt\Class_ Class name */
public $class;
/** @var Node\Arg[] Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Node\Name|Expr|Node\Stmt\Class_ $class Class name (or class node for anonymous classes)
* @param Node\Arg[] $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct($class, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->class = $class;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['class', 'args'];
}
public function getType() : string {
return 'Expr_New';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node\Expr;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
class MethodCall extends Expr
{
/** @var Expr Variable holding object */
public $var;
/** @var Identifier|Expr Method name */
public $name;
/** @var Arg[] Arguments */
public $args;
/**
* Constructs a function call node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Method name
* @param Arg[] $args Arguments
* @param array $attributes Additional attributes
*/
public function __construct(Expr $var, $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->var = $var;
$this->name = \is_string($name) ? new Identifier($name) : $name;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['var', 'name', 'args'];
}
public function getType() : string {
return 'Expr_MethodCall';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class MatchArm extends NodeAbstract
{
/** @var null|Node\Expr[] */
public $conds;
/** @var Node\Expr */
public $body;
/**
* @param null|Node\Expr[] $conds
*/
public function __construct($conds, Node\Expr $body, array $attributes = []) {
$this->conds = $conds;
$this->body = $body;
$this->attributes = $attributes;
}
public function getSubNodeNames() : array {
return ['conds', 'body'];
}
public function getType() : string {
return 'MatchArm';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
/**
* Represents a non-namespaced name. Namespaced names are represented using Name nodes.
*/
class Identifier extends NodeAbstract
{
/** @var string Identifier as string */
public $name;
private static $specialClassNames = [
'self' => true,
'parent' => true,
'static' => true,
];
/**
* Constructs an identifier node.
*
* @param string $name Identifier as string
* @param array $attributes Additional attributes
*/
public function __construct(string $name, array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
}
public function getSubNodeNames() : array {
return ['name'];
}
/**
* Get identifier as string.
*
* @return string Identifier as string.
*/
public function toString() : string {
return $this->name;
}
/**
* Get lowercased identifier as string.
*
* @return string Lowercased identifier as string
*/
public function toLowerString() : string {
return strtolower($this->name);
}
/**
* Checks whether the identifier is a special class name (self, parent or static).
*
* @return bool Whether identifier is a special class name
*/
public function isSpecialClassName() : bool {
return isset(self::$specialClassNames[strtolower($this->name)]);
}
/**
* Get identifier as string.
*
* @return string Identifier as string
*/
public function __toString() : string {
return $this->name;
}
public function getType() : string {
return 'Identifier';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class NullableType extends NodeAbstract
{
/** @var Identifier|Name Type */
public $type;
/**
* Constructs a nullable type (wrapping another type).
*
* @param string|Identifier|Name $type Type
* @param array $attributes Additional attributes
*/
public function __construct($type, array $attributes = []) {
$this->attributes = $attributes;
$this->type = \is_string($type) ? new Identifier($type) : $type;
}
public function getSubNodeNames() : array {
return ['type'];
}
public function getType() : string {
return 'NullableType';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
use PhpParser\NodeAbstract;
class Attribute extends NodeAbstract
{
/** @var Name Attribute name */
public $name;
/** @var Arg[] Attribute arguments */
public $args;
/**
* @param Node\Name $name Attribute name
* @param Arg[] $args Attribute arguments
* @param array $attributes Additional node attributes
*/
public function __construct(Name $name, array $args = [], array $attributes = []) {
$this->attributes = $attributes;
$this->name = $name;
$this->args = $args;
}
public function getSubNodeNames() : array {
return ['name', 'args'];
}
public function getType() : string {
return 'Attribute';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\Node;
interface FunctionLike extends Node
{
/**
* Whether to return by reference
*
* @return bool
*/
public function returnsByRef() : bool;
/**
* List of parameters
*
* @return Param[]
*/
public function getParams() : array;
/**
* Get the declared return type or null
*
* @return null|Identifier|Name|NullableType|UnionType
*/
public function getReturnType();
/**
* The function body
*
* @return Stmt[]|null
*/
public function getStmts();
/**
* Get PHP attribute groups.
*
* @return AttributeGroup[]
*/
public function getAttrGroups() : array;
}
<?php declare(strict_types=1);
namespace PhpParser\Node;
use PhpParser\NodeAbstract;
class Arg extends NodeAbstract
{
/** @var Identifier|null Parameter name (for named parameters) */
public $name;
/** @var Expr Value to pass */
public $value;
/** @var bool Whether to pass by ref */
public $byRef;
/** @var bool Whether to unpack the argument */
public $unpack;
/**
* Constructs a function call argument node.
*
* @param Expr $value Value to pass
* @param bool $byRef Whether to pass by ref
* @param bool $unpack Whether to unpack the argument
* @param array $attributes Additional attributes
* @param Identifier|null $name Parameter name (for named parameters)
*/
public function __construct(
Expr $value, bool $byRef = false, bool $unpack = false, array $attributes = [],
Identifier $name = null
) {
$this->attributes = $attributes;
$this->name = $name;
$this->value = $value;
$this->byRef = $byRef;
$this->unpack = $unpack;
}
public function getSubNodeNames() : array {
return ['name', 'value', 'byRef', 'unpack'];
}
public function getType() : string {
return 'Arg';
}
}
<?php declare(strict_types=1);
namespace PhpParser\Internal;
/**
* @internal
*/
class DiffElem
{
const TYPE_KEEP = 0;
const TYPE_REMOVE = 1;
const TYPE_ADD = 2;
const TYPE_REPLACE = 3;
/** @var int One of the TYPE_* constants */
public $type;
/** @var mixed Is null for add operations */
public $old;
/** @var mixed Is null for remove operations */
public $new;
public function __construct(int $type, $old, $new) {
$this->type = $type;
$this->old = $old;
$this->new = $new;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Internal;
/**
* Implements the Myers diff algorithm.
*
* Myers, Eugene W. "An O (ND) difference algorithm and its variations."
* Algorithmica 1.1 (1986): 251-266.
*
* @internal
*/
class Differ
{
private $isEqual;
/**
* Create differ over the given equality relation.
*
* @param callable $isEqual Equality relation with signature function($a, $b) : bool
*/
public function __construct(callable $isEqual) {
$this->isEqual = $isEqual;
}
/**
* Calculate diff (edit script) from $old to $new.
*
* @param array $old Original array
* @param array $new New array
*
* @return DiffElem[] Diff (edit script)
*/
public function diff(array $old, array $new) {
list($trace, $x, $y) = $this->calculateTrace($old, $new);
return $this->extractDiff($trace, $x, $y, $old, $new);
}
/**
* Calculate diff, including "replace" operations.
*
* If a sequence of remove operations is followed by the same number of add operations, these
* will be coalesced into replace operations.
*
* @param array $old Original array
* @param array $new New array
*
* @return DiffElem[] Diff (edit script), including replace operations
*/
public function diffWithReplacements(array $old, array $new) {
return $this->coalesceReplacements($this->diff($old, $new));
}
private function calculateTrace(array $a, array $b) {
$n = \count($a);
$m = \count($b);
$max = $n + $m;
$v = [1 => 0];
$trace = [];
for ($d = 0; $d <= $max; $d++) {
$trace[] = $v;
for ($k = -$d; $k <= $d; $k += 2) {
if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
$x = $v[$k+1];
} else {
$x = $v[$k-1] + 1;
}
$y = $x - $k;
while ($x < $n && $y < $m && ($this->isEqual)($a[$x], $b[$y])) {
$x++;
$y++;
}
$v[$k] = $x;
if ($x >= $n && $y >= $m) {
return [$trace, $x, $y];
}
}
}
throw new \Exception('Should not happen');
}
private function extractDiff(array $trace, int $x, int $y, array $a, array $b) {
$result = [];
for ($d = \count($trace) - 1; $d >= 0; $d--) {
$v = $trace[$d];
$k = $x - $y;
if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
$prevK = $k + 1;
} else {
$prevK = $k - 1;
}
$prevX = $v[$prevK];
$prevY = $prevX - $prevK;
while ($x > $prevX && $y > $prevY) {
$result[] = new DiffElem(DiffElem::TYPE_KEEP, $a[$x-1], $b[$y-1]);
$x--;
$y--;
}
if ($d === 0) {
break;
}
while ($x > $prevX) {
$result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x-1], null);
$x--;
}
while ($y > $prevY) {
$result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y-1]);
$y--;
}
}
return array_reverse($result);
}
/**
* Coalesce equal-length sequences of remove+add into a replace operation.
*
* @param DiffElem[] $diff
* @return DiffElem[]
*/
private function coalesceReplacements(array $diff) {
$newDiff = [];
$c = \count($diff);
for ($i = 0; $i < $c; $i++) {
$diffType = $diff[$i]->type;
if ($diffType !== DiffElem::TYPE_REMOVE) {
$newDiff[] = $diff[$i];
continue;
}
$j = $i;
while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) {
$j++;
}
$k = $j;
while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) {
$k++;
}
if ($j - $i === $k - $j) {
$len = $j - $i;
for ($n = 0; $n < $len; $n++) {
$newDiff[] = new DiffElem(
DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new
);
}
} else {
for (; $i < $k; $i++) {
$newDiff[] = $diff[$i];
}
}
$i = $k - 1;
}
return $newDiff;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Internal;
/**
* Provides operations on token streams, for use by pretty printer.
*
* @internal
*/
class TokenStream
{
/** @var array Tokens (in token_get_all format) */
private $tokens;
/** @var int[] Map from position to indentation */
private $indentMap;
/**
* Create token stream instance.
*
* @param array $tokens Tokens in token_get_all() format
*/
public function __construct(array $tokens) {
$this->tokens = $tokens;
$this->indentMap = $this->calcIndentMap();
}
/**
* Whether the given position is immediately surrounded by parenthesis.
*
* @param int $startPos Start position
* @param int $endPos End position
*
* @return bool
*/
public function haveParens(int $startPos, int $endPos) : bool {
return $this->haveTokenImmediatelyBefore($startPos, '(')
&& $this->haveTokenImmediatelyAfter($endPos, ')');
}
/**
* Whether the given position is immediately surrounded by braces.
*
* @param int $startPos Start position
* @param int $endPos End position
*
* @return bool
*/
public function haveBraces(int $startPos, int $endPos) : bool {
return ($this->haveTokenImmediatelyBefore($startPos, '{')
|| $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN))
&& $this->haveTokenImmediatelyAfter($endPos, '}');
}
/**
* Check whether the position is directly preceded by a certain token type.
*
* During this check whitespace and comments are skipped.
*
* @param int $pos Position before which the token should occur
* @param int|string $expectedTokenType Token to check for
*
* @return bool Whether the expected token was found
*/
public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType) : bool {
$tokens = $this->tokens;
$pos--;
for (; $pos >= 0; $pos--) {
$tokenType = $tokens[$pos][0];
if ($tokenType === $expectedTokenType) {
return true;
}
if ($tokenType !== \T_WHITESPACE
&& $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) {
break;
}
}
return false;
}
/**
* Check whether the position is directly followed by a certain token type.
*
* During this check whitespace and comments are skipped.
*
* @param int $pos Position after which the token should occur
* @param int|string $expectedTokenType Token to check for
*
* @return bool Whether the expected token was found
*/
public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType) : bool {
$tokens = $this->tokens;
$pos++;
for (; $pos < \count($tokens); $pos++) {
$tokenType = $tokens[$pos][0];
if ($tokenType === $expectedTokenType) {
return true;
}
if ($tokenType !== \T_WHITESPACE
&& $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) {
break;
}
}
return false;
}
public function skipLeft(int $pos, $skipTokenType) {
$tokens = $this->tokens;
$pos = $this->skipLeftWhitespace($pos);
if ($skipTokenType === \T_WHITESPACE) {
return $pos;
}
if ($tokens[$pos][0] !== $skipTokenType) {
// Shouldn't happen. The skip token MUST be there
throw new \Exception('Encountered unexpected token');
}
$pos--;
return $this->skipLeftWhitespace($pos);
}
public function skipRight(int $pos, $skipTokenType) {
$tokens = $this->tokens;
$pos = $this->skipRightWhitespace($pos);
if ($skipTokenType === \T_WHITESPACE) {
return $pos;
}
if ($tokens[$pos][0] !== $skipTokenType) {
// Shouldn't happen. The skip token MUST be there
throw new \Exception('Encountered unexpected token');
}
$pos++;
return $this->skipRightWhitespace($pos);
}
/**
* Return first non-whitespace token position smaller or equal to passed position.
*
* @param int $pos Token position
* @return int Non-whitespace token position
*/
public function skipLeftWhitespace(int $pos) {
$tokens = $this->tokens;
for (; $pos >= 0; $pos--) {
$type = $tokens[$pos][0];
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
break;
}
}
return $pos;
}
/**
* Return first non-whitespace position greater or equal to passed position.
*
* @param int $pos Token position
* @return int Non-whitespace token position
*/
public function skipRightWhitespace(int $pos) {
$tokens = $this->tokens;
for ($count = \count($tokens); $pos < $count; $pos++) {
$type = $tokens[$pos][0];
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
break;
}
}
return $pos;
}
public function findRight(int $pos, $findTokenType) {
$tokens = $this->tokens;
for ($count = \count($tokens); $pos < $count; $pos++) {
$type = $tokens[$pos][0];
if ($type === $findTokenType) {
return $pos;
}
}
return -1;
}
/**
* Whether the given position range contains a certain token type.
*
* @param int $startPos Starting position (inclusive)
* @param int $endPos Ending position (exclusive)
* @param int|string $tokenType Token type to look for
* @return bool Whether the token occurs in the given range
*/
public function haveTokenInRange(int $startPos, int $endPos, $tokenType) {
$tokens = $this->tokens;
for ($pos = $startPos; $pos < $endPos; $pos++) {
if ($tokens[$pos][0] === $tokenType) {
return true;
}
}
return false;
}
public function haveBracesInRange(int $startPos, int $endPos) {
return $this->haveTokenInRange($startPos, $endPos, '{')
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|| $this->haveTokenInRange($startPos, $endPos, '}');
}
/**
* Get indentation before token position.
*
* @param int $pos Token position
*
* @return int Indentation depth (in spaces)
*/
public function getIndentationBefore(int $pos) : int {
return $this->indentMap[$pos];
}
/**
* Get the code corresponding to a token offset range, optionally adjusted for indentation.
*
* @param int $from Token start position (inclusive)
* @param int $to Token end position (exclusive)
* @param int $indent By how much the code should be indented (can be negative as well)
*
* @return string Code corresponding to token range, adjusted for indentation
*/
public function getTokenCode(int $from, int $to, int $indent) : string {
$tokens = $this->tokens;
$result = '';
for ($pos = $from; $pos < $to; $pos++) {
$token = $tokens[$pos];
if (\is_array($token)) {
$type = $token[0];
$content = $token[1];
if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) {
$result .= $content;
} else {
// TODO Handle non-space indentation
if ($indent < 0) {
$result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $content);
} elseif ($indent > 0) {
$result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $content);
} else {
$result .= $content;
}
}
} else {
$result .= $token;
}
}
return $result;
}
/**
* Precalculate the indentation at every token position.
*
* @return int[] Token position to indentation map
*/
private function calcIndentMap() {
$indentMap = [];
$indent = 0;
foreach ($this->tokens as $token) {
$indentMap[] = $indent;
if ($token[0] === \T_WHITESPACE) {
$content = $token[1];
$newlinePos = \strrpos($content, "\n");
if (false !== $newlinePos) {
$indent = \strlen($content) - $newlinePos - 1;
}
}
}
// Add a sentinel for one past end of the file
$indentMap[] = $indent;
return $indentMap;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Internal;
use PhpParser\Node;
use PhpParser\Node\Expr;
/**
* This node is used internally by the format-preserving pretty printer to print anonymous classes.
*
* The normal anonymous class structure violates assumptions about the order of token offsets.
* Namely, the constructor arguments are part of the Expr\New_ node and follow the class node, even
* though they are actually interleaved with them. This special node type is used temporarily to
* restore a sane token offset order.
*
* @internal
*/
class PrintableNewAnonClassNode extends Expr
{
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Arg[] Arguments */
public $args;
/** @var null|Node\Name Name of extended class */
public $extends;
/** @var Node\Name[] Names of implemented interfaces */
public $implements;
/** @var Node\Stmt[] Statements */
public $stmts;
public function __construct(
array $attrGroups, array $args, Node\Name $extends = null, array $implements,
array $stmts, array $attributes
) {
parent::__construct($attributes);
$this->attrGroups = $attrGroups;
$this->args = $args;
$this->extends = $extends;
$this->implements = $implements;
$this->stmts = $stmts;
}
public static function fromNewNode(Expr\New_ $newNode) {
$class = $newNode->class;
assert($class instanceof Node\Stmt\Class_);
// We don't assert that $class->name is null here, to allow consumers to assign unique names
// to anonymous classes for their own purposes. We simplify ignore the name here.
return new self(
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
$class->stmts, $newNode->getAttributes()
);
}
public function getType() : string {
return 'Expr_PrintableNewAnonClass';
}
public function getSubNodeNames() : array {
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
}
}
<?php declare(strict_types=1);
namespace PhpParser;
use PhpParser\Parser\Tokens;
class Lexer
{
protected $code;
protected $tokens;
protected $pos;
protected $line;
protected $filePos;
protected $prevCloseTagHasNewline;
protected $tokenMap;
protected $dropTokens;
protected $identifierTokens;
private $attributeStartLineUsed;
private $attributeEndLineUsed;
private $attributeStartTokenPosUsed;
private $attributeEndTokenPosUsed;
private $attributeStartFilePosUsed;
private $attributeEndFilePosUsed;
private $attributeCommentsUsed;
/**
* Creates a Lexer.
*
* @param array $options Options array. Currently only the 'usedAttributes' option is supported,
* which is an array of attributes to add to the AST nodes. Possible
* attributes are: 'comments', 'startLine', 'endLine', 'startTokenPos',
* 'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the
* first three. For more info see getNextToken() docs.
*/
public function __construct(array $options = []) {
// Create Map from internal tokens to PhpParser tokens.
$this->defineCompatibilityTokens();
$this->tokenMap = $this->createTokenMap();
$this->identifierTokens = $this->createIdentifierTokenMap();
// map of tokens to drop while lexing (the map is only used for isset lookup,
// that's why the value is simply set to 1; the value is never actually used.)
$this->dropTokens = array_fill_keys(
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1
);
$defaultAttributes = ['comments', 'startLine', 'endLine'];
$usedAttributes = array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, true);
// Create individual boolean properties to make these checks faster.
$this->attributeStartLineUsed = isset($usedAttributes['startLine']);
$this->attributeEndLineUsed = isset($usedAttributes['endLine']);
$this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']);
$this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']);
$this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']);
$this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']);
$this->attributeCommentsUsed = isset($usedAttributes['comments']);
}
/**
* Initializes the lexer for lexing the provided source code.
*
* This function does not throw if lexing errors occur. Instead, errors may be retrieved using
* the getErrors() method.
*
* @param string $code The source code to lex
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
* ErrorHandler\Throwing
*/
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing();
}
$this->code = $code; // keep the code around for __halt_compiler() handling
$this->pos = -1;
$this->line = 1;
$this->filePos = 0;
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
// This ensures proper composability, because having a newline is the "safe" assumption.
$this->prevCloseTagHasNewline = true;
$scream = ini_set('xdebug.scream', '0');
$this->tokens = @token_get_all($code);
$this->postprocessTokens($errorHandler);
if (false !== $scream) {
ini_set('xdebug.scream', $scream);
}
}
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
$tokens = [];
for ($i = $start; $i < $end; $i++) {
$chr = $this->code[$i];
if ($chr === "\0") {
// PHP cuts error message after null byte, so need special case
$errorMsg = 'Unexpected null byte';
} else {
$errorMsg = sprintf(
'Unexpected character "%s" (ASCII %d)', $chr, ord($chr)
);
}
$tokens[] = [\T_BAD_CHARACTER, $chr, $line];
$errorHandler->handleError(new Error($errorMsg, [
'startLine' => $line,
'endLine' => $line,
'startFilePos' => $i,
'endFilePos' => $i,
]));
}
return $tokens;
}
/**
* Check whether comment token is unterminated.
*
* @return bool
*/
private function isUnterminatedComment($token) : bool {
return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT)
&& substr($token[1], 0, 2) === '/*'
&& substr($token[1], -2) !== '*/';
}
protected function postprocessTokens(ErrorHandler $errorHandler) {
// PHP's error handling for token_get_all() is rather bad, so if we want detailed
// error information we need to compute it ourselves. Invalid character errors are
// detected by finding "gaps" in the token array. Unterminated comments are detected
// by checking if a trailing comment has a "*/" at the end.
//
// Additionally, we canonicalize to the PHP 8 comment format here, which does not include
// the trailing whitespace anymore.
//
// We also canonicalize to the PHP 8 T_NAME_* tokens.
$filePos = 0;
$line = 1;
$numTokens = \count($this->tokens);
for ($i = 0; $i < $numTokens; $i++) {
$token = $this->tokens[$i];
// Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token.
// In this case we only need to emit an error.
if ($token[0] === \T_BAD_CHARACTER) {
$this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler);
}
if ($token[0] === \T_COMMENT && substr($token[1], 0, 2) !== '/*'
&& preg_match('/(\r\n|\n|\r)$/D', $token[1], $matches)) {
$trailingNewline = $matches[0];
$token[1] = substr($token[1], 0, -strlen($trailingNewline));
$this->tokens[$i] = $token;
if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) {
// Move trailing newline into following T_WHITESPACE token, if it already exists.
$this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1];
$this->tokens[$i + 1][2]--;
} else {
// Otherwise, we need to create a new T_WHITESPACE token.
array_splice($this->tokens, $i + 1, 0, [
[\T_WHITESPACE, $trailingNewline, $line],
]);
$numTokens++;
}
}
// Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING
// into a single token.
if (\is_array($token)
&& ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) {
$lastWasSeparator = $token[0] === \T_NS_SEPARATOR;
$text = $token[1];
for ($j = $i + 1; isset($this->tokens[$j]); $j++) {
if ($lastWasSeparator) {
if (!isset($this->identifierTokens[$this->tokens[$j][0]])) {
break;
}
$lastWasSeparator = false;
} else {
if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) {
break;
}
$lastWasSeparator = true;
}
$text .= $this->tokens[$j][1];
}
if ($lastWasSeparator) {
// Trailing separator is not part of the name.
$j--;
$text = substr($text, 0, -1);
}
if ($j > $i + 1) {
if ($token[0] === \T_NS_SEPARATOR) {
$type = \T_NAME_FULLY_QUALIFIED;
} else if ($token[0] === \T_NAMESPACE) {
$type = \T_NAME_RELATIVE;
} else {
$type = \T_NAME_QUALIFIED;
}
$token = [$type, $text, $line];
array_splice($this->tokens, $i, $j - $i, [$token]);
$numTokens -= $j - $i - 1;
}
}
$tokenValue = \is_string($token) ? $token : $token[1];
$tokenLen = \strlen($tokenValue);
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
// Something is missing, must be an invalid character
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
$badCharTokens = $this->handleInvalidCharacterRange(
$filePos, $nextFilePos, $line, $errorHandler);
$filePos = (int) $nextFilePos;
array_splice($this->tokens, $i, 0, $badCharTokens);
$numTokens += \count($badCharTokens);
$i += \count($badCharTokens);
}
$filePos += $tokenLen;
$line += substr_count($tokenValue, "\n");
}
if ($filePos !== \strlen($this->code)) {
if (substr($this->code, $filePos, 2) === '/*') {
// Unlike PHP, HHVM will drop unterminated comments entirely
$comment = substr($this->code, $filePos);
$errorHandler->handleError(new Error('Unterminated comment', [
'startLine' => $line,
'endLine' => $line + substr_count($comment, "\n"),
'startFilePos' => $filePos,
'endFilePos' => $filePos + \strlen($comment),
]));
// Emulate the PHP behavior
$isDocComment = isset($comment[3]) && $comment[3] === '*';
$this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line];
} else {
// Invalid characters at the end of the input
$badCharTokens = $this->handleInvalidCharacterRange(
$filePos, \strlen($this->code), $line, $errorHandler);
$this->tokens = array_merge($this->tokens, $badCharTokens);
}
return;
}
if (count($this->tokens) > 0) {
// Check for unterminated comment
$lastToken = $this->tokens[count($this->tokens) - 1];
if ($this->isUnterminatedComment($lastToken)) {
$errorHandler->handleError(new Error('Unterminated comment', [
'startLine' => $line - substr_count($lastToken[1], "\n"),
'endLine' => $line,
'startFilePos' => $filePos - \strlen($lastToken[1]),
'endFilePos' => $filePos,
]));
}
}
}
/**
* Fetches the next token.
*
* The available attributes are determined by the 'usedAttributes' option, which can
* be specified in the constructor. The following attributes are supported:
*
* * 'comments' => Array of PhpParser\Comment or PhpParser\Comment\Doc instances,
* representing all comments that occurred between the previous
* non-discarded token and the current one.
* * 'startLine' => Line in which the node starts.
* * 'endLine' => Line in which the node ends.
* * 'startTokenPos' => Offset into the token array of the first token in the node.
* * 'endTokenPos' => Offset into the token array of the last token in the node.
* * 'startFilePos' => Offset into the code string of the first character that is part of the node.
* * 'endFilePos' => Offset into the code string of the last character that is part of the node.
*
* @param mixed $value Variable to store token content in
* @param mixed $startAttributes Variable to store start attributes in
* @param mixed $endAttributes Variable to store end attributes in
*
* @return int Token id
*/
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {
$startAttributes = [];
$endAttributes = [];
while (1) {
if (isset($this->tokens[++$this->pos])) {
$token = $this->tokens[$this->pos];
} else {
// EOF token with ID 0
$token = "\0";
}
if ($this->attributeStartLineUsed) {
$startAttributes['startLine'] = $this->line;
}
if ($this->attributeStartTokenPosUsed) {
$startAttributes['startTokenPos'] = $this->pos;
}
if ($this->attributeStartFilePosUsed) {
$startAttributes['startFilePos'] = $this->filePos;
}
if (\is_string($token)) {
$value = $token;
if (isset($token[1])) {
// bug in token_get_all
$this->filePos += 2;
$id = ord('"');
} else {
$this->filePos += 1;
$id = ord($token);
}
} elseif (!isset($this->dropTokens[$token[0]])) {
$value = $token[1];
$id = $this->tokenMap[$token[0]];
if (\T_CLOSE_TAG === $token[0]) {
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n");
} elseif (\T_INLINE_HTML === $token[0]) {
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
}
$this->line += substr_count($value, "\n");
$this->filePos += \strlen($value);
} else {
$origLine = $this->line;
$origFilePos = $this->filePos;
$this->line += substr_count($token[1], "\n");
$this->filePos += \strlen($token[1]);
if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) {
if ($this->attributeCommentsUsed) {
$comment = \T_DOC_COMMENT === $token[0]
? new Comment\Doc($token[1],
$origLine, $origFilePos, $this->pos,
$this->line, $this->filePos - 1, $this->pos)
: new Comment($token[1],
$origLine, $origFilePos, $this->pos,
$this->line, $this->filePos - 1, $this->pos);
$startAttributes['comments'][] = $comment;
}
}
continue;
}
if ($this->attributeEndLineUsed) {
$endAttributes['endLine'] = $this->line;
}
if ($this->attributeEndTokenPosUsed) {
$endAttributes['endTokenPos'] = $this->pos;
}
if ($this->attributeEndFilePosUsed) {
$endAttributes['endFilePos'] = $this->filePos - 1;
}
return $id;
}
throw new \RuntimeException('Reached end of lexer loop');
}
/**
* Returns the token array for current code.
*
* The token array is in the same format as provided by the
* token_get_all() function and does not discard tokens (i.e.
* whitespace and comments are included). The token position
* attributes are against this token array.
*
* @return array Array of tokens in token_get_all() format
*/
public function getTokens() : array {
return $this->tokens;
}
/**
* Handles __halt_compiler() by returning the text after it.
*
* @return string Remaining text
*/
public function handleHaltCompiler() : string {
// text after T_HALT_COMPILER, still including ();
$textAfter = substr($this->code, $this->filePos);
// ensure that it is followed by ();
// this simplifies the situation, by not allowing any comments
// in between of the tokens.
if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
throw new Error('__HALT_COMPILER must be followed by "();"');
}
// prevent the lexer from returning any further tokens
$this->pos = count($this->tokens);
// return with (); removed
return substr($textAfter, strlen($matches[0]));
}
private function defineCompatibilityTokens() {
static $compatTokensDefined = false;
if ($compatTokensDefined) {
return;
}
$compatTokens = [
// PHP 7.4
'T_BAD_CHARACTER',
'T_FN',
'T_COALESCE_EQUAL',
// PHP 8.0
'T_NAME_QUALIFIED',
'T_NAME_FULLY_QUALIFIED',
'T_NAME_RELATIVE',
'T_MATCH',
'T_NULLSAFE_OBJECT_OPERATOR',
'T_ATTRIBUTE',
];
// PHP-Parser might be used together with another library that also emulates some or all
// of these tokens. Perform a sanity-check that all already defined tokens have been
// assigned a unique ID.
$usedTokenIds = [];
foreach ($compatTokens as $token) {
if (\defined($token)) {
$tokenId = \constant($token);
$clashingToken = $usedTokenIds[$tokenId] ?? null;
if ($clashingToken !== null) {
throw new \Error(sprintf(
'Token %s has same ID as token %s, ' .
'you may be using a library with broken token emulation',
$token, $clashingToken
));
}
$usedTokenIds[$tokenId] = $token;
}
}
// Now define any tokens that have not yet been emulated. Try to assign IDs from -1
// downwards, but skip any IDs that may already be in use.
$newTokenId = -1;
foreach ($compatTokens as $token) {
if (!\defined($token)) {
while (isset($usedTokenIds[$newTokenId])) {
$newTokenId--;
}
\define($token, $newTokenId);
$newTokenId--;
}
}
$compatTokensDefined = true;
}
/**
* Creates the token map.
*
* The token map maps the PHP internal token identifiers
* to the identifiers used by the Parser. Additionally it
* maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'.
*
* @return array The token map
*/
protected function createTokenMap() : array {
$tokenMap = [];
// 256 is the minimum possible token number, as everything below
// it is an ASCII value
for ($i = 256; $i < 1000; ++$i) {
if (\T_DOUBLE_COLON === $i) {
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
$tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM;
} elseif(\T_OPEN_TAG_WITH_ECHO === $i) {
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[$i] = Tokens::T_ECHO;
} elseif(\T_CLOSE_TAG === $i) {
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[$i] = ord(';');
} elseif ('UNKNOWN' !== $name = token_name($i)) {
if ('T_HASHBANG' === $name) {
// HHVM uses a special token for #! hashbang lines
$tokenMap[$i] = Tokens::T_INLINE_HTML;
} elseif (defined($name = Tokens::class . '::' . $name)) {
// Other tokens can be mapped directly
$tokenMap[$i] = constant($name);
}
}
}
// HHVM uses a special token for numbers that overflow to double
if (defined('T_ONUMBER')) {
$tokenMap[\T_ONUMBER] = Tokens::T_DNUMBER;
}
// HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
if (defined('T_COMPILER_HALT_OFFSET')) {
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
}
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
$tokenMap[\T_FN] = Tokens::T_FN;
$tokenMap[\T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL;
$tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED;
$tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED;
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
return $tokenMap;
}
private function createIdentifierTokenMap(): array {
// Based on semi_reserved production.
return array_fill_keys([
\T_STRING,
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC,
\T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND,
\T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE,
\T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH,
\T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO,
\T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT,
\T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS,
\T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN,
\T_MATCH,
], true);
}
}
<?php declare(strict_types=1);
namespace PhpParser;
class ParserFactory
{
const PREFER_PHP7 = 1;
const PREFER_PHP5 = 2;
const ONLY_PHP7 = 3;
const ONLY_PHP5 = 4;
/**
* Creates a Parser instance, according to the provided kind.
*
* @param int $kind One of ::PREFER_PHP7, ::PREFER_PHP5, ::ONLY_PHP7 or ::ONLY_PHP5
* @param Lexer|null $lexer Lexer to use. Defaults to emulative lexer when not specified
* @param array $parserOptions Parser options. See ParserAbstract::__construct() argument
*
* @return Parser The parser instance
*/
public function create(int $kind, Lexer $lexer = null, array $parserOptions = []) : Parser {
if (null === $lexer) {
$lexer = new Lexer\Emulative();
}
switch ($kind) {
case self::PREFER_PHP7:
return new Parser\Multiple([
new Parser\Php7($lexer, $parserOptions), new Parser\Php5($lexer, $parserOptions)
]);
case self::PREFER_PHP5:
return new Parser\Multiple([
new Parser\Php5($lexer, $parserOptions), new Parser\Php7($lexer, $parserOptions)
]);
case self::ONLY_PHP7:
return new Parser\Php7($lexer, $parserOptions);
case self::ONLY_PHP5:
return new Parser\Php5($lexer, $parserOptions);
default:
throw new \LogicException(
'Kind must be one of ::PREFER_PHP7, ::PREFER_PHP5, ::ONLY_PHP7 or ::ONLY_PHP5'
);
}
}
}
<?php declare(strict_types=1);
namespace PhpParser;
interface NodeVisitor
{
/**
* Called once before traversal.
*
* Return value semantics:
* * null: $nodes stays as-is
* * otherwise: $nodes is set to the return value
*
* @param Node[] $nodes Array of nodes
*
* @return null|Node[] Array of nodes
*/
public function beforeTraverse(array $nodes);
/**
* Called when entering a node.
*
* Return value semantics:
* * null
* => $node stays as-is
* * NodeTraverser::DONT_TRAVERSE_CHILDREN
* => Children of $node are not traversed. $node stays as-is
* * NodeTraverser::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * otherwise
* => $node is set to the return value
*
* @param Node $node Node
*
* @return null|int|Node Replacement node (or special return value)
*/
public function enterNode(Node $node);
/**
* Called when leaving a node.
*
* Return value semantics:
* * null
* => $node stays as-is
* * NodeTraverser::REMOVE_NODE
* => $node is removed from the parent array
* * NodeTraverser::STOP_TRAVERSAL
* => Traversal is aborted. $node stays as-is
* * array (of Nodes)
* => The return value is merged into the parent array (at the position of the $node)
* * otherwise
* => $node is set to the return value
*
* @param Node $node Node
*
* @return null|int|Node|Node[] Replacement node (or special return value)
*/
public function leaveNode(Node $node);
/**
* Called once after traversal.
*
* Return value semantics:
* * null: $nodes stays as-is
* * otherwise: $nodes is set to the return value
*
* @param Node[] $nodes Array of nodes
*
* @return null|Node[] Array of nodes
*/
public function afterTraverse(array $nodes);
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node\Stmt;
class Trait_ extends Declaration
{
protected $name;
protected $uses = [];
protected $properties = [];
protected $methods = [];
/**
* Creates an interface builder.
*
* @param string $name Name of the interface
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
if ($stmt instanceof Stmt\Property) {
$this->properties[] = $stmt;
} elseif ($stmt instanceof Stmt\ClassMethod) {
$this->methods[] = $stmt;
} elseif ($stmt instanceof Stmt\TraitUse) {
$this->uses[] = $stmt;
} else {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
return $this;
}
/**
* Returns the built trait node.
*
* @return Stmt\Trait_ The built interface node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Trait_(
$this->name, [
'stmts' => array_merge($this->uses, $this->properties, $this->methods)
], $this->attributes
);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt;
class Property implements PhpParser\Builder
{
protected $name;
protected $flags = 0;
protected $default = null;
protected $attributes = [];
/** @var null|Identifier|Name|NullableType */
protected $type;
/**
* Creates a property builder.
*
* @param string $name Name of the property
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Makes the property public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Makes the property protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Makes the property private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Makes the property static.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeStatic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC);
return $this;
}
/**
* Sets default value for the property.
*
* @param mixed $value Default value to use
*
* @return $this The builder instance (for fluid interface)
*/
public function setDefault($value) {
$this->default = BuilderHelpers::normalizeValue($value);
return $this;
}
/**
* Sets doc comment for the property.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Sets the property type for PHP 7.4+.
*
* @param string|Name|NullableType|Identifier $type
*
* @return $this
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Property The built property node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Property(
$this->flags !== 0 ? $this->flags : Stmt\Class_::MODIFIER_PUBLIC,
[
new Stmt\PropertyProperty($this->name, $this->default)
],
$this->attributes,
$this->type
);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class Class_ extends Declaration
{
protected $name;
protected $extends = null;
protected $implements = [];
protected $flags = 0;
protected $uses = [];
protected $constants = [];
protected $properties = [];
protected $methods = [];
/**
* Creates a class builder.
*
* @param string $name Name of the class
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Extends a class.
*
* @param Name|string $class Name of class to extend
*
* @return $this The builder instance (for fluid interface)
*/
public function extend($class) {
$this->extends = BuilderHelpers::normalizeName($class);
return $this;
}
/**
* Implements one or more interfaces.
*
* @param Name|string ...$interfaces Names of interfaces to implement
*
* @return $this The builder instance (for fluid interface)
*/
public function implement(...$interfaces) {
foreach ($interfaces as $interface) {
$this->implements[] = BuilderHelpers::normalizeName($interface);
}
return $this;
}
/**
* Makes the class abstract.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
return $this;
}
/**
* Makes the class final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
$targets = [
Stmt\TraitUse::class => &$this->uses,
Stmt\ClassConst::class => &$this->constants,
Stmt\Property::class => &$this->properties,
Stmt\ClassMethod::class => &$this->methods,
];
$class = \get_class($stmt);
if (!isset($targets[$class])) {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
$targets[$class][] = $stmt;
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Class_ The built class node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Class_($this->name, [
'flags' => $this->flags,
'extends' => $this->extends,
'implements' => $this->implements,
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
], $this->attributes);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class Method extends FunctionLike
{
protected $name;
protected $flags = 0;
/** @var array|null */
protected $stmts = [];
/**
* Creates a method builder.
*
* @param string $name Name of the method
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Makes the method public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Makes the method protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Makes the method private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Makes the method static.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeStatic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC);
return $this;
}
/**
* Makes the method abstract.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
if (!empty($this->stmts)) {
throw new \LogicException('Cannot make method with statements abstract');
}
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
$this->stmts = null; // abstract methods don't have statements
return $this;
}
/**
* Makes the method final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
/**
* Adds a statement.
*
* @param Node|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
if (null === $this->stmts) {
throw new \LogicException('Cannot add statements to an abstract method');
}
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
return $this;
}
/**
* Returns the built method node.
*
* @return Stmt\ClassMethod The built method node
*/
public function getNode() : Node {
return new Stmt\ClassMethod($this->name, [
'flags' => $this->flags,
'byRef' => $this->returnByRef,
'params' => $this->params,
'returnType' => $this->returnType,
'stmts' => $this->stmts,
], $this->attributes);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\Builder;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class Use_ implements Builder
{
protected $name;
protected $type;
protected $alias = null;
/**
* Creates a name use (alias) builder.
*
* @param Node\Name|string $name Name of the entity (namespace, class, function, constant) to alias
* @param int $type One of the Stmt\Use_::TYPE_* constants
*/
public function __construct($name, int $type) {
$this->name = BuilderHelpers::normalizeName($name);
$this->type = $type;
}
/**
* Sets alias for used name.
*
* @param string $alias Alias to use (last component of full name by default)
*
* @return $this The builder instance (for fluid interface)
*/
public function as(string $alias) {
$this->alias = $alias;
return $this;
}
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node {
return new Stmt\Use_([
new Stmt\UseUse($this->name, $this->alias)
], $this->type);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
class Param implements PhpParser\Builder
{
protected $name;
protected $default = null;
/** @var Node\Identifier|Node\Name|Node\NullableType|null */
protected $type = null;
protected $byRef = false;
protected $variadic = false;
/**
* Creates a parameter builder.
*
* @param string $name Name of the parameter
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Sets default value for the parameter.
*
* @param mixed $value Default value to use
*
* @return $this The builder instance (for fluid interface)
*/
public function setDefault($value) {
$this->default = BuilderHelpers::normalizeValue($value);
return $this;
}
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
if ($this->type == 'void') {
throw new \LogicException('Parameter type cannot be void');
}
return $this;
}
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\NullableType|Node\UnionType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*
* @deprecated Use setType() instead
*/
public function setTypeHint($type) {
return $this->setType($type);
}
/**
* Make the parameter accept the value by reference.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeByRef() {
$this->byRef = true;
return $this;
}
/**
* Make the parameter variadic
*
* @return $this The builder instance (for fluid interface)
*/
public function makeVariadic() {
$this->variadic = true;
return $this;
}
/**
* Returns the built parameter node.
*
* @return Node\Param The built parameter node
*/
public function getNode() : Node {
return new Node\Param(
new Node\Expr\Variable($this->name),
$this->default, $this->type, $this->byRef, $this->variadic
);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\Builder;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class TraitUseAdaptation implements Builder
{
const TYPE_UNDEFINED = 0;
const TYPE_ALIAS = 1;
const TYPE_PRECEDENCE = 2;
/** @var int Type of building adaptation */
protected $type;
protected $trait;
protected $method;
protected $modifier = null;
protected $alias = null;
protected $insteadof = [];
/**
* Creates a trait use adaptation builder.
*
* @param Node\Name|string|null $trait Name of adaptated trait
* @param Node\Identifier|string $method Name of adaptated method
*/
public function __construct($trait, $method) {
$this->type = self::TYPE_UNDEFINED;
$this->trait = is_null($trait)? null: BuilderHelpers::normalizeName($trait);
$this->method = BuilderHelpers::normalizeIdentifier($method);
}
/**
* Sets alias of method.
*
* @param Node\Identifier|string $alias Alias for adaptated method
*
* @return $this The builder instance (for fluid interface)
*/
public function as($alias) {
if ($this->type === self::TYPE_UNDEFINED) {
$this->type = self::TYPE_ALIAS;
}
if ($this->type !== self::TYPE_ALIAS) {
throw new \LogicException('Cannot set alias for not alias adaptation buider');
}
$this->alias = $alias;
return $this;
}
/**
* Sets adaptated method public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Sets adaptated method protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Sets adaptated method private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Adds overwritten traits.
*
* @param Node\Name|string ...$traits Traits for overwrite
*
* @return $this The builder instance (for fluid interface)
*/
public function insteadof(...$traits) {
if ($this->type === self::TYPE_UNDEFINED) {
if (is_null($this->trait)) {
throw new \LogicException('Precedence adaptation must have trait');
}
$this->type = self::TYPE_PRECEDENCE;
}
if ($this->type !== self::TYPE_PRECEDENCE) {
throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider');
}
foreach ($traits as $trait) {
$this->insteadof[] = BuilderHelpers::normalizeName($trait);
}
return $this;
}
protected function setModifier(int $modifier) {
if ($this->type === self::TYPE_UNDEFINED) {
$this->type = self::TYPE_ALIAS;
}
if ($this->type !== self::TYPE_ALIAS) {
throw new \LogicException('Cannot set access modifier for not alias adaptation buider');
}
if (is_null($this->modifier)) {
$this->modifier = $modifier;
} else {
throw new \LogicException('Multiple access type modifiers are not allowed');
}
}
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node {
switch ($this->type) {
case self::TYPE_ALIAS:
return new Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias);
case self::TYPE_PRECEDENCE:
return new Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof);
default:
throw new \LogicException('Type of adaptation is not defined');
}
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
abstract class Declaration implements PhpParser\Builder
{
protected $attributes = [];
abstract public function addStmt($stmt);
/**
* Adds multiple statements.
*
* @param array $stmts The statements to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmts(array $stmts) {
foreach ($stmts as $stmt) {
$this->addStmt($stmt);
}
return $this;
}
/**
* Sets doc comment for the declaration.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes['comments'] = [
BuilderHelpers::normalizeDocComment($docComment)
];
return $this;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node\Name;
use PhpParser\Node\Stmt;
class Interface_ extends Declaration
{
protected $name;
protected $extends = [];
protected $constants = [];
protected $methods = [];
/**
* Creates an interface builder.
*
* @param string $name Name of the interface
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Extends one or more interfaces.
*
* @param Name|string ...$interfaces Names of interfaces to extend
*
* @return $this The builder instance (for fluid interface)
*/
public function extend(...$interfaces) {
foreach ($interfaces as $interface) {
$this->extends[] = BuilderHelpers::normalizeName($interface);
}
return $this;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
if ($stmt instanceof Stmt\ClassConst) {
$this->constants[] = $stmt;
} elseif ($stmt instanceof Stmt\ClassMethod) {
// we erase all statements in the body of an interface method
$stmt->stmts = null;
$this->methods[] = $stmt;
} else {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
return $this;
}
/**
* Returns the built interface node.
*
* @return Stmt\Interface_ The built interface node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Interface_($this->name, [
'extends' => $this->extends,
'stmts' => array_merge($this->constants, $this->methods),
], $this->attributes);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class Function_ extends FunctionLike
{
protected $name;
protected $stmts = [];
/**
* Creates a function builder.
*
* @param string $name Name of the function
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Adds a statement.
*
* @param Node|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
return $this;
}
/**
* Returns the built function node.
*
* @return Stmt\Function_ The built function node
*/
public function getNode() : Node {
return new Stmt\Function_($this->name, [
'byRef' => $this->returnByRef,
'params' => $this->params,
'returnType' => $this->returnType,
'stmts' => $this->stmts,
], $this->attributes);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\Builder;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class TraitUse implements Builder
{
protected $traits = [];
protected $adaptations = [];
/**
* Creates a trait use builder.
*
* @param Node\Name|string ...$traits Names of used traits
*/
public function __construct(...$traits) {
foreach ($traits as $trait) {
$this->and($trait);
}
}
/**
* Adds used trait.
*
* @param Node\Name|string $trait Trait name
*
* @return $this The builder instance (for fluid interface)
*/
public function and($trait) {
$this->traits[] = BuilderHelpers::normalizeName($trait);
return $this;
}
/**
* Adds trait adaptation.
*
* @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation
*
* @return $this The builder instance (for fluid interface)
*/
public function with($adaptation) {
$adaptation = BuilderHelpers::normalizeNode($adaptation);
if (!$adaptation instanceof Stmt\TraitUseAdaptation) {
throw new \LogicException('Adaptation must have type TraitUseAdaptation');
}
$this->adaptations[] = $adaptation;
return $this;
}
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node {
return new Stmt\TraitUse($this->traits, $this->adaptations);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
use PhpParser\Node\Stmt;
class Namespace_ extends Declaration
{
private $name;
private $stmts = [];
/**
* Creates a namespace builder.
*
* @param Node\Name|string|null $name Name of the namespace
*/
public function __construct($name) {
$this->name = null !== $name ? BuilderHelpers::normalizeName($name) : null;
}
/**
* Adds a statement.
*
* @param Node|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
return $this;
}
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node {
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
}
}
<?php declare(strict_types=1);
namespace PhpParser\Builder;
use PhpParser\BuilderHelpers;
use PhpParser\Node;
abstract class FunctionLike extends Declaration
{
protected $returnByRef = false;
protected $params = [];
/** @var string|Node\Name|Node\NullableType|null */
protected $returnType = null;
/**
* Make the function return by reference.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeReturnByRef() {
$this->returnByRef = true;
return $this;
}
/**
* Adds a parameter.
*
* @param Node\Param|Param $param The parameter to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addParam($param) {
$param = BuilderHelpers::normalizeNode($param);
if (!$param instanceof Node\Param) {
throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType()));
}
$this->params[] = $param;
return $this;
}
/**
* Adds multiple parameters.
*
* @param array $params The parameters to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addParams(array $params) {
foreach ($params as $param) {
$this->addParam($param);
}
return $this;
}
/**
* Sets the return type for PHP 7.
*
* @param string|Node\Name|Node\NullableType $type One of array, callable, string, int, float,
* bool, iterable, or a class/interface name.
*
* @return $this The builder instance (for fluid interface)
*/
public function setReturnType($type) {
$this->returnType = BuilderHelpers::normalizeType($type);
return $this;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Comment;
class Doc extends \PhpParser\Comment
{
}
<?php declare(strict_types=1);
namespace PhpParser;
use PhpParser\NodeVisitor\FindingVisitor;
use PhpParser\NodeVisitor\FirstFindingVisitor;
class NodeFinder
{
/**
* Find all nodes satisfying a filter callback.
*
* @param Node|Node[] $nodes Single node or array of nodes to search in
* @param callable $filter Filter callback: function(Node $node) : bool
*
* @return Node[] Found nodes satisfying the filter callback
*/
public function find($nodes, callable $filter) : array {
if (!is_array($nodes)) {
$nodes = [$nodes];
}
$visitor = new FindingVisitor($filter);
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$traverser->traverse($nodes);
return $visitor->getFoundNodes();
}
/**
* Find all nodes that are instances of a certain class.
*
* @param Node|Node[] $nodes Single node or array of nodes to search in
* @param string $class Class name
*
* @return Node[] Found nodes (all instances of $class)
*/
public function findInstanceOf($nodes, string $class) : array {
return $this->find($nodes, function ($node) use ($class) {
return $node instanceof $class;
});
}
/**
* Find first node satisfying a filter callback.
*
* @param Node|Node[] $nodes Single node or array of nodes to search in
* @param callable $filter Filter callback: function(Node $node) : bool
*
* @return null|Node Found node (or null if none found)
*/
public function findFirst($nodes, callable $filter) {
if (!is_array($nodes)) {
$nodes = [$nodes];
}
$visitor = new FirstFindingVisitor($filter);
$traverser = new NodeTraverser;
$traverser->addVisitor($visitor);
$traverser->traverse($nodes);
return $visitor->getFoundNode();
}
/**
* Find first node that is an instance of a certain class.
*
* @param Node|Node[] $nodes Single node or array of nodes to search in
* @param string $class Class name
*
* @return null|Node Found node, which is an instance of $class (or null if none found)
*/
public function findFirstInstanceOf($nodes, string $class) {
return $this->findFirst($nodes, function ($node) use ($class) {
return $node instanceof $class;
});
}
}
<?php declare(strict_types=1);
namespace PhpParser;
use PhpParser\Internal\DiffElem;
use PhpParser\Internal\PrintableNewAnonClassNode;
use PhpParser\Internal\TokenStream;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\AssignOp;
use PhpParser\Node\Expr\BinaryOp;
use PhpParser\Node\Expr\Cast;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
abstract class PrettyPrinterAbstract
{
const FIXUP_PREC_LEFT = 0; // LHS operand affected by precedence
const FIXUP_PREC_RIGHT = 1; // RHS operand affected by precedence
const FIXUP_CALL_LHS = 2; // LHS of call
const FIXUP_DEREF_LHS = 3; // LHS of dereferencing operation
const FIXUP_BRACED_NAME = 4; // Name operand that may require bracing
const FIXUP_VAR_BRACED_NAME = 5; // Name operand that may require ${} bracing
const FIXUP_ENCAPSED = 6; // Encapsed string part
protected $precedenceMap = [
// [precedence, associativity]
// where for precedence -1 is %left, 0 is %nonassoc and 1 is %right
BinaryOp\Pow::class => [ 0, 1],
Expr\BitwiseNot::class => [ 10, 1],
Expr\PreInc::class => [ 10, 1],
Expr\PreDec::class => [ 10, 1],
Expr\PostInc::class => [ 10, -1],
Expr\PostDec::class => [ 10, -1],
Expr\UnaryPlus::class => [ 10, 1],
Expr\UnaryMinus::class => [ 10, 1],
Cast\Int_::class => [ 10, 1],
Cast\Double::class => [ 10, 1],
Cast\String_::class => [ 10, 1],
Cast\Array_::class => [ 10, 1],
Cast\Object_::class => [ 10, 1],
Cast\Bool_::class => [ 10, 1],
Cast\Unset_::class => [ 10, 1],
Expr\ErrorSuppress::class => [ 10, 1],
Expr\Instanceof_::class => [ 20, 0],
Expr\BooleanNot::class => [ 30, 1],
BinaryOp\Mul::class => [ 40, -1],
BinaryOp\Div::class => [ 40, -1],
BinaryOp\Mod::class => [ 40, -1],
BinaryOp\Plus::class => [ 50, -1],
BinaryOp\Minus::class => [ 50, -1],
BinaryOp\Concat::class => [ 50, -1],
BinaryOp\ShiftLeft::class => [ 60, -1],
BinaryOp\ShiftRight::class => [ 60, -1],
BinaryOp\Smaller::class => [ 70, 0],
BinaryOp\SmallerOrEqual::class => [ 70, 0],
BinaryOp\Greater::class => [ 70, 0],
BinaryOp\GreaterOrEqual::class => [ 70, 0],
BinaryOp\Equal::class => [ 80, 0],
BinaryOp\NotEqual::class => [ 80, 0],
BinaryOp\Identical::class => [ 80, 0],
BinaryOp\NotIdentical::class => [ 80, 0],
BinaryOp\Spaceship::class => [ 80, 0],
BinaryOp\BitwiseAnd::class => [ 90, -1],
BinaryOp\BitwiseXor::class => [100, -1],
BinaryOp\BitwiseOr::class => [110, -1],
BinaryOp\BooleanAnd::class => [120, -1],
BinaryOp\BooleanOr::class => [130, -1],
BinaryOp\Coalesce::class => [140, 1],
Expr\Ternary::class => [150, 0],
// parser uses %left for assignments, but they really behave as %right
Expr\Assign::class => [160, 1],
Expr\AssignRef::class => [160, 1],
AssignOp\Plus::class => [160, 1],
AssignOp\Minus::class => [160, 1],
AssignOp\Mul::class => [160, 1],
AssignOp\Div::class => [160, 1],
AssignOp\Concat::class => [160, 1],
AssignOp\Mod::class => [160, 1],
AssignOp\BitwiseAnd::class => [160, 1],
AssignOp\BitwiseOr::class => [160, 1],
AssignOp\BitwiseXor::class => [160, 1],
AssignOp\ShiftLeft::class => [160, 1],
AssignOp\ShiftRight::class => [160, 1],
AssignOp\Pow::class => [160, 1],
AssignOp\Coalesce::class => [160, 1],
Expr\YieldFrom::class => [165, 1],
Expr\Print_::class => [168, 1],
BinaryOp\LogicalAnd::class => [170, -1],
BinaryOp\LogicalXor::class => [180, -1],
BinaryOp\LogicalOr::class => [190, -1],
Expr\Include_::class => [200, -1],
];
/** @var int Current indentation level. */
protected $indentLevel;
/** @var string Newline including current indentation. */
protected $nl;
/** @var string Token placed at end of doc string to ensure it is followed by a newline. */
protected $docStringEndToken;
/** @var bool Whether semicolon namespaces can be used (i.e. no global namespace is used) */
protected $canUseSemicolonNamespaces;
/** @var array Pretty printer options */
protected $options;
/** @var TokenStream Original tokens for use in format-preserving pretty print */
protected $origTokens;
/** @var Internal\Differ Differ for node lists */
protected $nodeListDiffer;
/** @var bool[] Map determining whether a certain character is a label character */
protected $labelCharMap;
/**
* @var int[][] Map from token classes and subnode names to FIXUP_* constants. This is used
* during format-preserving prints to place additional parens/braces if necessary.
*/
protected $fixupMap;
/**
* @var int[][] Map from "{$node->getType()}->{$subNode}" to ['left' => $l, 'right' => $r],
* where $l and $r specify the token type that needs to be stripped when removing
* this node.
*/
protected $removalMap;
/**
* @var mixed[] Map from "{$node->getType()}->{$subNode}" to [$find, $beforeToken, $extraLeft, $extraRight].
* $find is an optional token after which the insertion occurs. $extraLeft/Right
* are optionally added before/after the main insertions.
*/
protected $insertionMap;
/**
* @var string[] Map From "{$node->getType()}->{$subNode}" to string that should be inserted
* between elements of this list subnode.
*/
protected $listInsertionMap;
protected $emptyListInsertionMap;
/** @var int[] Map from "{$node->getType()}->{$subNode}" to token before which the modifiers
* should be reprinted. */
protected $modifierChangeMap;
/**
* Creates a pretty printer instance using the given options.
*
* Supported options:
* * bool $shortArraySyntax = false: Whether to use [] instead of array() as the default array
* syntax, if the node does not specify a format.
*
* @param array $options Dictionary of formatting options
*/
public function __construct(array $options = []) {
$this->docStringEndToken = '_DOC_STRING_END_' . mt_rand();
$defaultOptions = ['shortArraySyntax' => false];
$this->options = $options + $defaultOptions;
}
/**
* Reset pretty printing state.
*/
protected function resetState() {
$this->indentLevel = 0;
$this->nl = "\n";
$this->origTokens = null;
}
/**
* Set indentation level
*
* @param int $level Level in number of spaces
*/
protected function setIndentLevel(int $level) {
$this->indentLevel = $level;
$this->nl = "\n" . \str_repeat(' ', $level);
}
/**
* Increase indentation level.
*/
protected function indent() {
$this->indentLevel += 4;
$this->nl .= ' ';
}
/**
* Decrease indentation level.
*/
protected function outdent() {
assert($this->indentLevel >= 4);
$this->indentLevel -= 4;
$this->nl = "\n" . str_repeat(' ', $this->indentLevel);
}
/**
* Pretty prints an array of statements.
*
* @param Node[] $stmts Array of statements
*
* @return string Pretty printed statements
*/
public function prettyPrint(array $stmts) : string {
$this->resetState();
$this->preprocessNodes($stmts);
return ltrim($this->handleMagicTokens($this->pStmts($stmts, false)));
}
/**
* Pretty prints an expression.
*
* @param Expr $node Expression node
*
* @return string Pretty printed node
*/
public function prettyPrintExpr(Expr $node) : string {
$this->resetState();
return $this->handleMagicTokens($this->p($node));
}
/**
* Pretty prints a file of statements (includes the opening <?php tag if it is required).
*
* @param Node[] $stmts Array of statements
*
* @return string Pretty printed statements
*/
public function prettyPrintFile(array $stmts) : string {
if (!$stmts) {
return "<?php\n\n";
}
$p = "<?php\n\n" . $this->prettyPrint($stmts);
if ($stmts[0] instanceof Stmt\InlineHTML) {
$p = preg_replace('/^<\?php\s+\?>\n?/', '', $p);
}
if ($stmts[count($stmts) - 1] instanceof Stmt\InlineHTML) {
$p = preg_replace('/<\?php$/', '', rtrim($p));
}
return $p;
}
/**
* Preprocesses the top-level nodes to initialize pretty printer state.
*
* @param Node[] $nodes Array of nodes
*/
protected function preprocessNodes(array $nodes) {
/* We can use semicolon-namespaces unless there is a global namespace declaration */
$this->canUseSemicolonNamespaces = true;
foreach ($nodes as $node) {
if ($node instanceof Stmt\Namespace_ && null === $node->name) {
$this->canUseSemicolonNamespaces = false;
break;
}
}
}
/**
* Handles (and removes) no-indent and doc-string-end tokens.
*
* @param string $str
* @return string
*/
protected function handleMagicTokens(string $str) : string {
// Replace doc-string-end tokens with nothing or a newline
$str = str_replace($this->docStringEndToken . ";\n", ";\n", $str);
$str = str_replace($this->docStringEndToken, "\n", $str);
return $str;
}
/**
* Pretty prints an array of nodes (statements) and indents them optionally.
*
* @param Node[] $nodes Array of nodes
* @param bool $indent Whether to indent the printed nodes
*
* @return string Pretty printed statements
*/
protected function pStmts(array $nodes, bool $indent = true) : string {
if ($indent) {
$this->indent();
}
$result = '';
foreach ($nodes as $node) {
$comments = $node->getComments();
if ($comments) {
$result .= $this->nl . $this->pComments($comments);
if ($node instanceof Stmt\Nop) {
continue;
}
}
$result .= $this->nl . $this->p($node);
}
if ($indent) {
$this->outdent();
}
return $result;
}
/**
* Pretty-print an infix operation while taking precedence into account.
*
* @param string $class Node class of operator
* @param Node $leftNode Left-hand side node
* @param string $operatorString String representation of the operator
* @param Node $rightNode Right-hand side node
*
* @return string Pretty printed infix operation
*/
protected function pInfixOp(string $class, Node $leftNode, string $operatorString, Node $rightNode) : string {
list($precedence, $associativity) = $this->precedenceMap[$class];
return $this->pPrec($leftNode, $precedence, $associativity, -1)
. $operatorString
. $this->pPrec($rightNode, $precedence, $associativity, 1);
}
/**
* Pretty-print a prefix operation while taking precedence into account.
*
* @param string $class Node class of operator
* @param string $operatorString String representation of the operator
* @param Node $node Node
*
* @return string Pretty printed prefix operation
*/
protected function pPrefixOp(string $class, string $operatorString, Node $node) : string {
list($precedence, $associativity) = $this->precedenceMap[$class];
return $operatorString . $this->pPrec($node, $precedence, $associativity, 1);
}
/**
* Pretty-print a postfix operation while taking precedence into account.
*
* @param string $class Node class of operator
* @param string $operatorString String representation of the operator
* @param Node $node Node
*
* @return string Pretty printed postfix operation
*/
protected function pPostfixOp(string $class, Node $node, string $operatorString) : string {
list($precedence, $associativity) = $this->precedenceMap[$class];
return $this->pPrec($node, $precedence, $associativity, -1) . $operatorString;
}
/**
* Prints an expression node with the least amount of parentheses necessary to preserve the meaning.
*
* @param Node $node Node to pretty print
* @param int $parentPrecedence Precedence of the parent operator
* @param int $parentAssociativity Associativity of parent operator
* (-1 is left, 0 is nonassoc, 1 is right)
* @param int $childPosition Position of the node relative to the operator
* (-1 is left, 1 is right)
*
* @return string The pretty printed node
*/
protected function pPrec(Node $node, int $parentPrecedence, int $parentAssociativity, int $childPosition) : string {
$class = \get_class($node);
if (isset($this->precedenceMap[$class])) {
$childPrecedence = $this->precedenceMap[$class][0];
if ($childPrecedence > $parentPrecedence
|| ($parentPrecedence === $childPrecedence && $parentAssociativity !== $childPosition)
) {
return '(' . $this->p($node) . ')';
}
}
return $this->p($node);
}
/**
* Pretty prints an array of nodes and implodes the printed values.
*
* @param Node[] $nodes Array of Nodes to be printed
* @param string $glue Character to implode with
*
* @return string Imploded pretty printed nodes
*/
protected function pImplode(array $nodes, string $glue = '') : string {
$pNodes = [];
foreach ($nodes as $node) {
if (null === $node) {
$pNodes[] = '';
} else {
$pNodes[] = $this->p($node);
}
}
return implode($glue, $pNodes);
}
/**
* Pretty prints an array of nodes and implodes the printed values with commas.
*
* @param Node[] $nodes Array of Nodes to be printed
*
* @return string Comma separated pretty printed nodes
*/
protected function pCommaSeparated(array $nodes) : string {
return $this->pImplode($nodes, ', ');
}
/**
* Pretty prints a comma-separated list of nodes in multiline style, including comments.
*
* The result includes a leading newline and one level of indentation (same as pStmts).
*
* @param Node[] $nodes Array of Nodes to be printed
* @param bool $trailingComma Whether to use a trailing comma
*
* @return string Comma separated pretty printed nodes in multiline style
*/
protected function pCommaSeparatedMultiline(array $nodes, bool $trailingComma) : string {
$this->indent();
$result = '';
$lastIdx = count($nodes) - 1;
foreach ($nodes as $idx => $node) {
if ($node !== null) {
$comments = $node->getComments();
if ($comments) {
$result .= $this->nl . $this->pComments($comments);
}
$result .= $this->nl . $this->p($node);
} else {
$result .= $this->nl;
}
if ($trailingComma || $idx !== $lastIdx) {
$result .= ',';
}
}
$this->outdent();
return $result;
}
/**
* Prints reformatted text of the passed comments.
*
* @param Comment[] $comments List of comments
*
* @return string Reformatted text of comments
*/
protected function pComments(array $comments) : string {
$formattedComments = [];
foreach ($comments as $comment) {
$formattedComments[] = str_replace("\n", $this->nl, $comment->getReformattedText());
}
return implode($this->nl, $formattedComments);
}
/**
* Perform a format-preserving pretty print of an AST.
*
* The format preservation is best effort. For some changes to the AST the formatting will not
* be preserved (at least not locally).
*
* In order to use this method a number of prerequisites must be satisfied:
* * The startTokenPos and endTokenPos attributes in the lexer must be enabled.
* * The CloningVisitor must be run on the AST prior to modification.
* * The original tokens must be provided, using the getTokens() method on the lexer.
*
* @param Node[] $stmts Modified AST with links to original AST
* @param Node[] $origStmts Original AST with token offset information
* @param array $origTokens Tokens of the original code
*
* @return string
*/
public function printFormatPreserving(array $stmts, array $origStmts, array $origTokens) : string {
$this->initializeNodeListDiffer();
$this->initializeLabelCharMap();
$this->initializeFixupMap();
$this->initializeRemovalMap();
$this->initializeInsertionMap();
$this->initializeListInsertionMap();
$this->initializeEmptyListInsertionMap();
$this->initializeModifierChangeMap();
$this->resetState();
$this->origTokens = new TokenStream($origTokens);
$this->preprocessNodes($stmts);
$pos = 0;
$result = $this->pArray($stmts, $origStmts, $pos, 0, 'File', 'stmts', null);
if (null !== $result) {
$result .= $this->origTokens->getTokenCode($pos, count($origTokens), 0);
} else {
// Fallback
// TODO Add <?php properly
$result = "<?php\n" . $this->pStmts($stmts, false);
}
return ltrim($this->handleMagicTokens($result));
}
protected function pFallback(Node $node) {
return $this->{'p' . $node->getType()}($node);
}
/**
* Pretty prints a node.
*
* This method also handles formatting preservation for nodes.
*
* @param Node $node Node to be pretty printed
* @param bool $parentFormatPreserved Whether parent node has preserved formatting
*
* @return string Pretty printed node
*/
protected function p(Node $node, $parentFormatPreserved = false) : string {
// No orig tokens means this is a normal pretty print without preservation of formatting
if (!$this->origTokens) {
return $this->{'p' . $node->getType()}($node);
}
/** @var Node $origNode */
$origNode = $node->getAttribute('origNode');
if (null === $origNode) {
return $this->pFallback($node);
}
$class = \get_class($node);
\assert($class === \get_class($origNode));
$startPos = $origNode->getStartTokenPos();
$endPos = $origNode->getEndTokenPos();
\assert($startPos >= 0 && $endPos >= 0);
$fallbackNode = $node;
if ($node instanceof Expr\New_ && $node->class instanceof Stmt\Class_) {
// Normalize node structure of anonymous classes
$node = PrintableNewAnonClassNode::fromNewNode($node);
$origNode = PrintableNewAnonClassNode::fromNewNode($origNode);
}
// InlineHTML node does not contain closing and opening PHP tags. If the parent formatting
// is not preserved, then we need to use the fallback code to make sure the tags are
// printed.
if ($node instanceof Stmt\InlineHTML && !$parentFormatPreserved) {
return $this->pFallback($fallbackNode);
}
$indentAdjustment = $this->indentLevel - $this->origTokens->getIndentationBefore($startPos);
$type = $node->getType();
$fixupInfo = $this->fixupMap[$class] ?? null;
$result = '';
$pos = $startPos;
foreach ($node->getSubNodeNames() as $subNodeName) {
$subNode = $node->$subNodeName;
$origSubNode = $origNode->$subNodeName;
if ((!$subNode instanceof Node && $subNode !== null)
|| (!$origSubNode instanceof Node && $origSubNode !== null)
) {
if ($subNode === $origSubNode) {
// Unchanged, can reuse old code
continue;
}
if (is_array($subNode) && is_array($origSubNode)) {
// Array subnode changed, we might be able to reconstruct it
$listResult = $this->pArray(
$subNode, $origSubNode, $pos, $indentAdjustment, $type, $subNodeName,
$fixupInfo[$subNodeName] ?? null
);
if (null === $listResult) {
return $this->pFallback($fallbackNode);
}
$result .= $listResult;
continue;
}
if (is_int($subNode) && is_int($origSubNode)) {
// Check if this is a modifier change
$key = $type . '->' . $subNodeName;
if (!isset($this->modifierChangeMap[$key])) {
return $this->pFallback($fallbackNode);
}
$findToken = $this->modifierChangeMap[$key];
$result .= $this->pModifiers($subNode);
$pos = $this->origTokens->findRight($pos, $findToken);
continue;
}
// If a non-node, non-array subnode changed, we don't be able to do a partial
// reconstructions, as we don't have enough offset information. Pretty print the
// whole node instead.
return $this->pFallback($fallbackNode);
}
$extraLeft = '';
$extraRight = '';
if ($origSubNode !== null) {
$subStartPos = $origSubNode->getStartTokenPos();
$subEndPos = $origSubNode->getEndTokenPos();
\assert($subStartPos >= 0 && $subEndPos >= 0);
} else {
if ($subNode === null) {
// Both null, nothing to do
continue;
}
// A node has been inserted, check if we have insertion information for it
$key = $type . '->' . $subNodeName;
if (!isset($this->insertionMap[$key])) {
return $this->pFallback($fallbackNode);
}
list($findToken, $beforeToken, $extraLeft, $extraRight) = $this->insertionMap[$key];
if (null !== $findToken) {
$subStartPos = $this->origTokens->findRight($pos, $findToken)
+ (int) !$beforeToken;
} else {
$subStartPos = $pos;
}
if (null === $extraLeft && null !== $extraRight) {
// If inserting on the right only, skipping whitespace looks better
$subStartPos = $this->origTokens->skipRightWhitespace($subStartPos);
}
$subEndPos = $subStartPos - 1;
}
if (null === $subNode) {
// A node has been removed, check if we have removal information for it
$key = $type . '->' . $subNodeName;
if (!isset($this->removalMap[$key])) {
return $this->pFallback($fallbackNode);
}
// Adjust positions to account for additional tokens that must be skipped
$removalInfo = $this->removalMap[$key];
if (isset($removalInfo['left'])) {
$subStartPos = $this->origTokens->skipLeft($subStartPos - 1, $removalInfo['left']) + 1;
}
if (isset($removalInfo['right'])) {
$subEndPos = $this->origTokens->skipRight($subEndPos + 1, $removalInfo['right']) - 1;
}
}
$result .= $this->origTokens->getTokenCode($pos, $subStartPos, $indentAdjustment);
if (null !== $subNode) {
$result .= $extraLeft;
$origIndentLevel = $this->indentLevel;
$this->setIndentLevel($this->origTokens->getIndentationBefore($subStartPos) + $indentAdjustment);
// If it's the same node that was previously in this position, it certainly doesn't
// need fixup. It's important to check this here, because our fixup checks are more
// conservative than strictly necessary.
if (isset($fixupInfo[$subNodeName])
&& $subNode->getAttribute('origNode') !== $origSubNode
) {
$fixup = $fixupInfo[$subNodeName];
$res = $this->pFixup($fixup, $subNode, $class, $subStartPos, $subEndPos);
} else {
$res = $this->p($subNode, true);
}
$this->safeAppend($result, $res);
$this->setIndentLevel($origIndentLevel);
$result .= $extraRight;
}
$pos = $subEndPos + 1;
}
$result .= $this->origTokens->getTokenCode($pos, $endPos + 1, $indentAdjustment);
return $result;
}
/**
* Perform a format-preserving pretty print of an array.
*
* @param array $nodes New nodes
* @param array $origNodes Original nodes
* @param int $pos Current token position (updated by reference)
* @param int $indentAdjustment Adjustment for indentation
* @param string $parentNodeType Type of the containing node.
* @param string $subNodeName Name of array subnode.
* @param null|int $fixup Fixup information for array item nodes
*
* @return null|string Result of pretty print or null if cannot preserve formatting
*/
protected function pArray(
array $nodes, array $origNodes, int &$pos, int $indentAdjustment,
string $parentNodeType, string $subNodeName, $fixup
) {
$diff = $this->nodeListDiffer->diffWithReplacements($origNodes, $nodes);
$mapKey = $parentNodeType . '->' . $subNodeName;
$insertStr = $this->listInsertionMap[$mapKey] ?? null;
$isStmtList = $subNodeName === 'stmts';
$beforeFirstKeepOrReplace = true;
$skipRemovedNode = false;
$delayedAdd = [];
$lastElemIndentLevel = $this->indentLevel;
$insertNewline = false;
if ($insertStr === "\n") {
$insertStr = '';
$insertNewline = true;
}
if ($isStmtList && \count($origNodes) === 1 && \count($nodes) !== 1) {
$startPos = $origNodes[0]->getStartTokenPos();
$endPos = $origNodes[0]->getEndTokenPos();
\assert($startPos >= 0 && $endPos >= 0);
if (!$this->origTokens->haveBraces($startPos, $endPos)) {
// This was a single statement without braces, but either additional statements
// have been added, or the single statement has been removed. This requires the
// addition of braces. For now fall back.
// TODO: Try to preserve formatting
return null;
}
}
$result = '';
foreach ($diff as $i => $diffElem) {
$diffType = $diffElem->type;
/** @var Node|null $arrItem */
$arrItem = $diffElem->new;
/** @var Node|null $origArrItem */
$origArrItem = $diffElem->old;
if ($diffType === DiffElem::TYPE_KEEP || $diffType === DiffElem::TYPE_REPLACE) {
$beforeFirstKeepOrReplace = false;
if ($origArrItem === null || $arrItem === null) {
// We can only handle the case where both are null
if ($origArrItem === $arrItem) {
continue;
}
return null;
}
if (!$arrItem instanceof Node || !$origArrItem instanceof Node) {
// We can only deal with nodes. This can occur for Names, which use string arrays.
return null;
}
$itemStartPos = $origArrItem->getStartTokenPos();
$itemEndPos = $origArrItem->getEndTokenPos();
\assert($itemStartPos >= 0 && $itemEndPos >= 0 && $itemStartPos >= $pos);
$origIndentLevel = $this->indentLevel;
$lastElemIndentLevel = $this->origTokens->getIndentationBefore($itemStartPos) + $indentAdjustment;
$this->setIndentLevel($lastElemIndentLevel);
$comments = $arrItem->getComments();
$origComments = $origArrItem->getComments();
$commentStartPos = $origComments ? $origComments[0]->getStartTokenPos() : $itemStartPos;
\assert($commentStartPos >= 0);
if ($commentStartPos < $pos) {
// Comments may be assigned to multiple nodes if they start at the same position.
// Make sure we don't try to print them multiple times.
$commentStartPos = $itemStartPos;
}
if ($skipRemovedNode) {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
$this->setIndentLevel($origIndentLevel);
return null;
}
} else {
$result .= $this->origTokens->getTokenCode(
$pos, $commentStartPos, $indentAdjustment);
}
if (!empty($delayedAdd)) {
/** @var Node $delayedAddNode */
foreach ($delayedAdd as $delayedAddNode) {
if ($insertNewline) {
$delayedAddComments = $delayedAddNode->getComments();
if ($delayedAddComments) {
$result .= $this->pComments($delayedAddComments) . $this->nl;
}
}
$this->safeAppend($result, $this->p($delayedAddNode, true));
if ($insertNewline) {
$result .= $insertStr . $this->nl;
} else {
$result .= $insertStr;
}
}
$delayedAdd = [];
}
if ($comments !== $origComments) {
if ($comments) {
$result .= $this->pComments($comments) . $this->nl;
}
} else {
$result .= $this->origTokens->getTokenCode(
$commentStartPos, $itemStartPos, $indentAdjustment);
}
// If we had to remove anything, we have done so now.
$skipRemovedNode = false;
} elseif ($diffType === DiffElem::TYPE_ADD) {
if (null === $insertStr) {
// We don't have insertion information for this list type
return null;
}
if ($insertStr === ', ' && $this->isMultiline($origNodes)) {
$insertStr = ',';
$insertNewline = true;
}
if ($beforeFirstKeepOrReplace) {
// Will be inserted at the next "replace" or "keep" element
$delayedAdd[] = $arrItem;
continue;
}
$itemStartPos = $pos;
$itemEndPos = $pos - 1;
$origIndentLevel = $this->indentLevel;
$this->setIndentLevel($lastElemIndentLevel);
if ($insertNewline) {
$comments = $arrItem->getComments();
if ($comments) {
$result .= $this->nl . $this->pComments($comments);
}
$result .= $insertStr . $this->nl;
} else {
$result .= $insertStr;
}
} elseif ($diffType === DiffElem::TYPE_REMOVE) {
if (!$origArrItem instanceof Node) {
// We only support removal for nodes
return null;
}
$itemStartPos = $origArrItem->getStartTokenPos();
$itemEndPos = $origArrItem->getEndTokenPos();
\assert($itemStartPos >= 0 && $itemEndPos >= 0);
// Consider comments part of the node.
$origComments = $origArrItem->getComments();
if ($origComments) {
$itemStartPos = $origComments[0]->getStartTokenPos();
}
if ($i === 0) {
// If we're removing from the start, keep the tokens before the node and drop those after it,
// instead of the other way around.
$result .= $this->origTokens->getTokenCode(
$pos, $itemStartPos, $indentAdjustment);
$skipRemovedNode = true;
} else {
if ($isStmtList && $this->origTokens->haveBracesInRange($pos, $itemStartPos)) {
// We'd remove the brace of a code block.
// TODO: Preserve formatting.
return null;
}
}
$pos = $itemEndPos + 1;
continue;
} else {
throw new \Exception("Shouldn't happen");
}
if (null !== $fixup && $arrItem->getAttribute('origNode') !== $origArrItem) {
$res = $this->pFixup($fixup, $arrItem, null, $itemStartPos, $itemEndPos);
} else {
$res = $this->p($arrItem, true);
}
$this->safeAppend($result, $res);
$this->setIndentLevel($origIndentLevel);
$pos = $itemEndPos + 1;
}
if ($skipRemovedNode) {
// TODO: Support removing single node.
return null;
}
if (!empty($delayedAdd)) {
if (!isset($this->emptyListInsertionMap[$mapKey])) {
return null;
}
list($findToken, $extraLeft, $extraRight) = $this->emptyListInsertionMap[$mapKey];
if (null !== $findToken) {
$insertPos = $this->origTokens->findRight($pos, $findToken) + 1;
$result .= $this->origTokens->getTokenCode($pos, $insertPos, $indentAdjustment);
$pos = $insertPos;
}
$first = true;
$result .= $extraLeft;
foreach ($delayedAdd as $delayedAddNode) {
if (!$first) {
$result .= $insertStr;
}
$result .= $this->p($delayedAddNode, true);
$first = false;
}
$result .= $extraRight;
}
return $result;
}
/**
* Print node with fixups.
*
* Fixups here refer to the addition of extra parentheses, braces or other characters, that
* are required to preserve program semantics in a certain context (e.g. to maintain precedence
* or because only certain expressions are allowed in certain places).
*
* @param int $fixup Fixup type
* @param Node $subNode Subnode to print
* @param string|null $parentClass Class of parent node
* @param int $subStartPos Original start pos of subnode
* @param int $subEndPos Original end pos of subnode
*
* @return string Result of fixed-up print of subnode
*/
protected function pFixup(int $fixup, Node $subNode, $parentClass, int $subStartPos, int $subEndPos) : string {
switch ($fixup) {
case self::FIXUP_PREC_LEFT:
case self::FIXUP_PREC_RIGHT:
if (!$this->origTokens->haveParens($subStartPos, $subEndPos)) {
list($precedence, $associativity) = $this->precedenceMap[$parentClass];
return $this->pPrec($subNode, $precedence, $associativity,
$fixup === self::FIXUP_PREC_LEFT ? -1 : 1);
}
break;
case self::FIXUP_CALL_LHS:
if ($this->callLhsRequiresParens($subNode)
&& !$this->origTokens->haveParens($subStartPos, $subEndPos)
) {
return '(' . $this->p($subNode) . ')';
}
break;
case self::FIXUP_DEREF_LHS:
if ($this->dereferenceLhsRequiresParens($subNode)
&& !$this->origTokens->haveParens($subStartPos, $subEndPos)
) {
return '(' . $this->p($subNode) . ')';
}
break;
case self::FIXUP_BRACED_NAME:
case self::FIXUP_VAR_BRACED_NAME:
if ($subNode instanceof Expr
&& !$this->origTokens->haveBraces($subStartPos, $subEndPos)
) {
return ($fixup === self::FIXUP_VAR_BRACED_NAME ? '$' : '')
. '{' . $this->p($subNode) . '}';
}
break;
case self::FIXUP_ENCAPSED:
if (!$subNode instanceof Scalar\EncapsedStringPart
&& !$this->origTokens->haveBraces($subStartPos, $subEndPos)
) {
return '{' . $this->p($subNode) . '}';
}
break;
default:
throw new \Exception('Cannot happen');
}
// Nothing special to do
return $this->p($subNode);
}
/**
* Appends to a string, ensuring whitespace between label characters.
*
* Example: "echo" and "$x" result in "echo$x", but "echo" and "x" result in "echo x".
* Without safeAppend the result would be "echox", which does not preserve semantics.
*
* @param string $str
* @param string $append
*/
protected function safeAppend(string &$str, string $append) {
if ($str === "") {
$str = $append;
return;
}
if ($append === "") {
return;
}
if (!$this->labelCharMap[$append[0]]
|| !$this->labelCharMap[$str[\strlen($str) - 1]]) {
$str .= $append;
} else {
$str .= " " . $append;
}
}
/**
* Determines whether the LHS of a call must be wrapped in parenthesis.
*
* @param Node $node LHS of a call
*
* @return bool Whether parentheses are required
*/
protected function callLhsRequiresParens(Node $node) : bool {
return !($node instanceof Node\Name
|| $node instanceof Expr\Variable
|| $node instanceof Expr\ArrayDimFetch
|| $node instanceof Expr\FuncCall
|| $node instanceof Expr\MethodCall
|| $node instanceof Expr\NullsafeMethodCall
|| $node instanceof Expr\StaticCall
|| $node instanceof Expr\Array_);
}
/**
* Determines whether the LHS of a dereferencing operation must be wrapped in parenthesis.
*
* @param Node $node LHS of dereferencing operation
*
* @return bool Whether parentheses are required
*/
protected function dereferenceLhsRequiresParens(Node $node) : bool {
return !($node instanceof Expr\Variable
|| $node instanceof Node\Name
|| $node instanceof Expr\ArrayDimFetch
|| $node instanceof Expr\PropertyFetch
|| $node instanceof Expr\NullsafePropertyFetch
|| $node instanceof Expr\StaticPropertyFetch
|| $node instanceof Expr\FuncCall
|| $node instanceof Expr\MethodCall
|| $node instanceof Expr\NullsafeMethodCall
|| $node instanceof Expr\StaticCall
|| $node instanceof Expr\Array_
|| $node instanceof Scalar\String_
|| $node instanceof Expr\ConstFetch
|| $node instanceof Expr\ClassConstFetch);
}
/**
* Print modifiers, including trailing whitespace.
*
* @param int $modifiers Modifier mask to print
*
* @return string Printed modifiers
*/
protected function pModifiers(int $modifiers) {
return ($modifiers & Stmt\Class_::MODIFIER_PUBLIC ? 'public ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_PROTECTED ? 'protected ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_PRIVATE ? 'private ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_STATIC ? 'static ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_ABSTRACT ? 'abstract ' : '')
. ($modifiers & Stmt\Class_::MODIFIER_FINAL ? 'final ' : '');
}
/**
* Determine whether a list of nodes uses multiline formatting.
*
* @param (Node|null)[] $nodes Node list
*
* @return bool Whether multiline formatting is used
*/
protected function isMultiline(array $nodes) : bool {
if (\count($nodes) < 2) {
return false;
}
$pos = -1;
foreach ($nodes as $node) {
if (null === $node) {
continue;
}
$endPos = $node->getEndTokenPos() + 1;
if ($pos >= 0) {
$text = $this->origTokens->getTokenCode($pos, $endPos, 0);
if (false === strpos($text, "\n")) {
// We require that a newline is present between *every* item. If the formatting
// is inconsistent, with only some items having newlines, we don't consider it
// as multiline
return false;
}
}
$pos = $endPos;
}
return true;
}
/**
* Lazily initializes label char map.
*
* The label char map determines whether a certain character may occur in a label.
*/
protected function initializeLabelCharMap() {
if ($this->labelCharMap) return;
$this->labelCharMap = [];
for ($i = 0; $i < 256; $i++) {
// Since PHP 7.1 The lower range is 0x80. However, we also want to support code for
// older versions.
$this->labelCharMap[chr($i)] = $i >= 0x7f || ctype_alnum($i);
}
}
/**
* Lazily initializes node list differ.
*
* The node list differ is used to determine differences between two array subnodes.
*/
protected function initializeNodeListDiffer() {
if ($this->nodeListDiffer) return;
$this->nodeListDiffer = new Internal\Differ(function ($a, $b) {
if ($a instanceof Node && $b instanceof Node) {
return $a === $b->getAttribute('origNode');
}
// Can happen for array destructuring
return $a === null && $b === null;
});
}
/**
* Lazily initializes fixup map.
*
* The fixup map is used to determine whether a certain subnode of a certain node may require
* some kind of "fixup" operation, e.g. the addition of parenthesis or braces.
*/
protected function initializeFixupMap() {
if ($this->fixupMap) return;
$this->fixupMap = [
Expr\PreInc::class => ['var' => self::FIXUP_PREC_RIGHT],
Expr\PreDec::class => ['var' => self::FIXUP_PREC_RIGHT],
Expr\PostInc::class => ['var' => self::FIXUP_PREC_LEFT],
Expr\PostDec::class => ['var' => self::FIXUP_PREC_LEFT],
Expr\Instanceof_::class => [
'expr' => self::FIXUP_PREC_LEFT,
'class' => self::FIXUP_PREC_RIGHT, // TODO: FIXUP_NEW_VARIABLE
],
Expr\Ternary::class => [
'cond' => self::FIXUP_PREC_LEFT,
'else' => self::FIXUP_PREC_RIGHT,
],
Expr\FuncCall::class => ['name' => self::FIXUP_CALL_LHS],
Expr\StaticCall::class => ['class' => self::FIXUP_DEREF_LHS],
Expr\ArrayDimFetch::class => ['var' => self::FIXUP_DEREF_LHS],
Expr\ClassConstFetch::class => ['var' => self::FIXUP_DEREF_LHS],
Expr\New_::class => ['class' => self::FIXUP_DEREF_LHS], // TODO: FIXUP_NEW_VARIABLE
Expr\MethodCall::class => [
'var' => self::FIXUP_DEREF_LHS,
'name' => self::FIXUP_BRACED_NAME,
],
Expr\NullsafeMethodCall::class => [
'var' => self::FIXUP_DEREF_LHS,
'name' => self::FIXUP_BRACED_NAME,
],
Expr\StaticPropertyFetch::class => [
'class' => self::FIXUP_DEREF_LHS,
'name' => self::FIXUP_VAR_BRACED_NAME,
],
Expr\PropertyFetch::class => [
'var' => self::FIXUP_DEREF_LHS,
'name' => self::FIXUP_BRACED_NAME,
],
Expr\NullsafePropertyFetch::class => [
'var' => self::FIXUP_DEREF_LHS,
'name' => self::FIXUP_BRACED_NAME,
],
Scalar\Encapsed::class => [
'parts' => self::FIXUP_ENCAPSED,
],
];
$binaryOps = [
BinaryOp\Pow::class, BinaryOp\Mul::class, BinaryOp\Div::class, BinaryOp\Mod::class,
BinaryOp\Plus::class, BinaryOp\Minus::class, BinaryOp\Concat::class,
BinaryOp\ShiftLeft::class, BinaryOp\ShiftRight::class, BinaryOp\Smaller::class,
BinaryOp\SmallerOrEqual::class, BinaryOp\Greater::class, BinaryOp\GreaterOrEqual::class,
BinaryOp\Equal::class, BinaryOp\NotEqual::class, BinaryOp\Identical::class,
BinaryOp\NotIdentical::class, BinaryOp\Spaceship::class, BinaryOp\BitwiseAnd::class,
BinaryOp\BitwiseXor::class, BinaryOp\BitwiseOr::class, BinaryOp\BooleanAnd::class,
BinaryOp\BooleanOr::class, BinaryOp\Coalesce::class, BinaryOp\LogicalAnd::class,
BinaryOp\LogicalXor::class, BinaryOp\LogicalOr::class,
];
foreach ($binaryOps as $binaryOp) {
$this->fixupMap[$binaryOp] = [
'left' => self::FIXUP_PREC_LEFT,
'right' => self::FIXUP_PREC_RIGHT
];
}
$assignOps = [
Expr\Assign::class, Expr\AssignRef::class, AssignOp\Plus::class, AssignOp\Minus::class,
AssignOp\Mul::class, AssignOp\Div::class, AssignOp\Concat::class, AssignOp\Mod::class,
AssignOp\BitwiseAnd::class, AssignOp\BitwiseOr::class, AssignOp\BitwiseXor::class,
AssignOp\ShiftLeft::class, AssignOp\ShiftRight::class, AssignOp\Pow::class, AssignOp\Coalesce::class
];
foreach ($assignOps as $assignOp) {
$this->fixupMap[$assignOp] = [
'var' => self::FIXUP_PREC_LEFT,
'expr' => self::FIXUP_PREC_RIGHT,
];
}
$prefixOps = [
Expr\BitwiseNot::class, Expr\BooleanNot::class, Expr\UnaryPlus::class, Expr\UnaryMinus::class,
Cast\Int_::class, Cast\Double::class, Cast\String_::class, Cast\Array_::class,
Cast\Object_::class, Cast\Bool_::class, Cast\Unset_::class, Expr\ErrorSuppress::class,
Expr\YieldFrom::class, Expr\Print_::class, Expr\Include_::class,
];
foreach ($prefixOps as $prefixOp) {
$this->fixupMap[$prefixOp] = ['expr' => self::FIXUP_PREC_RIGHT];
}
}
/**
* Lazily initializes the removal map.
*
* The removal map is used to determine which additional tokens should be returned when a
* certain node is replaced by null.
*/
protected function initializeRemovalMap() {
if ($this->removalMap) return;
$stripBoth = ['left' => \T_WHITESPACE, 'right' => \T_WHITESPACE];
$stripLeft = ['left' => \T_WHITESPACE];
$stripRight = ['right' => \T_WHITESPACE];
$stripDoubleArrow = ['right' => \T_DOUBLE_ARROW];
$stripColon = ['left' => ':'];
$stripEquals = ['left' => '='];
$this->removalMap = [
'Expr_ArrayDimFetch->dim' => $stripBoth,
'Expr_ArrayItem->key' => $stripDoubleArrow,
'Expr_ArrowFunction->returnType' => $stripColon,
'Expr_Closure->returnType' => $stripColon,
'Expr_Exit->expr' => $stripBoth,
'Expr_Ternary->if' => $stripBoth,
'Expr_Yield->key' => $stripDoubleArrow,
'Expr_Yield->value' => $stripBoth,
'Param->type' => $stripRight,
'Param->default' => $stripEquals,
'Stmt_Break->num' => $stripBoth,
'Stmt_Catch->var' => $stripLeft,
'Stmt_ClassMethod->returnType' => $stripColon,
'Stmt_Class->extends' => ['left' => \T_EXTENDS],
'Expr_PrintableNewAnonClass->extends' => ['left' => \T_EXTENDS],
'Stmt_Continue->num' => $stripBoth,
'Stmt_Foreach->keyVar' => $stripDoubleArrow,
'Stmt_Function->returnType' => $stripColon,
'Stmt_If->else' => $stripLeft,
'Stmt_Namespace->name' => $stripLeft,
'Stmt_Property->type' => $stripRight,
'Stmt_PropertyProperty->default' => $stripEquals,
'Stmt_Return->expr' => $stripBoth,
'Stmt_StaticVar->default' => $stripEquals,
'Stmt_TraitUseAdaptation_Alias->newName' => $stripLeft,
'Stmt_TryCatch->finally' => $stripLeft,
// 'Stmt_Case->cond': Replace with "default"
// 'Stmt_Class->name': Unclear what to do
// 'Stmt_Declare->stmts': Not a plain node
// 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a plain node
];
}
protected function initializeInsertionMap() {
if ($this->insertionMap) return;
// TODO: "yield" where both key and value are inserted doesn't work
// [$find, $beforeToken, $extraLeft, $extraRight]
$this->insertionMap = [
'Expr_ArrayDimFetch->dim' => ['[', false, null, null],
'Expr_ArrayItem->key' => [null, false, null, ' => '],
'Expr_ArrowFunction->returnType' => [')', false, ' : ', null],
'Expr_Closure->returnType' => [')', false, ' : ', null],
'Expr_Ternary->if' => ['?', false, ' ', ' '],
'Expr_Yield->key' => [\T_YIELD, false, null, ' => '],
'Expr_Yield->value' => [\T_YIELD, false, ' ', null],
'Param->type' => [null, false, null, ' '],
'Param->default' => [null, false, ' = ', null],
'Stmt_Break->num' => [\T_BREAK, false, ' ', null],
'Stmt_Catch->var' => [null, false, ' ', null],
'Stmt_ClassMethod->returnType' => [')', false, ' : ', null],
'Stmt_Class->extends' => [null, false, ' extends ', null],
'Expr_PrintableNewAnonClass->extends' => [null, ' extends ', null],
'Stmt_Continue->num' => [\T_CONTINUE, false, ' ', null],
'Stmt_Foreach->keyVar' => [\T_AS, false, null, ' => '],
'Stmt_Function->returnType' => [')', false, ' : ', null],
'Stmt_If->else' => [null, false, ' ', null],
'Stmt_Namespace->name' => [\T_NAMESPACE, false, ' ', null],
'Stmt_Property->type' => [\T_VARIABLE, true, null, ' '],
'Stmt_PropertyProperty->default' => [null, false, ' = ', null],
'Stmt_Return->expr' => [\T_RETURN, false, ' ', null],
'Stmt_StaticVar->default' => [null, false, ' = ', null],
//'Stmt_TraitUseAdaptation_Alias->newName' => [T_AS, false, ' ', null], // TODO
'Stmt_TryCatch->finally' => [null, false, ' ', null],
// 'Expr_Exit->expr': Complicated due to optional ()
// 'Stmt_Case->cond': Conversion from default to case
// 'Stmt_Class->name': Unclear
// 'Stmt_Declare->stmts': Not a proper node
// 'Stmt_TraitUseAdaptation_Alias->newModifier': Not a proper node
];
}
protected function initializeListInsertionMap() {
if ($this->listInsertionMap) return;
$this->listInsertionMap = [
// special
//'Expr_ShellExec->parts' => '', // TODO These need to be treated more carefully
//'Scalar_Encapsed->parts' => '',
'Stmt_Catch->types' => '|',
'UnionType->types' => '|',
'Stmt_If->elseifs' => ' ',
'Stmt_TryCatch->catches' => ' ',
// comma-separated lists
'Expr_Array->items' => ', ',
'Expr_ArrowFunction->params' => ', ',
'Expr_Closure->params' => ', ',
'Expr_Closure->uses' => ', ',
'Expr_FuncCall->args' => ', ',
'Expr_Isset->vars' => ', ',
'Expr_List->items' => ', ',
'Expr_MethodCall->args' => ', ',
'Expr_NullsafeMethodCall->args' => ', ',
'Expr_New->args' => ', ',
'Expr_PrintableNewAnonClass->args' => ', ',
'Expr_StaticCall->args' => ', ',
'Stmt_ClassConst->consts' => ', ',
'Stmt_ClassMethod->params' => ', ',
'Stmt_Class->implements' => ', ',
'Expr_PrintableNewAnonClass->implements' => ', ',
'Stmt_Const->consts' => ', ',
'Stmt_Declare->declares' => ', ',
'Stmt_Echo->exprs' => ', ',
'Stmt_For->init' => ', ',
'Stmt_For->cond' => ', ',
'Stmt_For->loop' => ', ',
'Stmt_Function->params' => ', ',
'Stmt_Global->vars' => ', ',
'Stmt_GroupUse->uses' => ', ',
'Stmt_Interface->extends' => ', ',
'Stmt_Match->arms' => ', ',
'Stmt_Property->props' => ', ',
'Stmt_StaticVar->vars' => ', ',
'Stmt_TraitUse->traits' => ', ',
'Stmt_TraitUseAdaptation_Precedence->insteadof' => ', ',
'Stmt_Unset->vars' => ', ',
'Stmt_Use->uses' => ', ',
'MatchArm->conds' => ', ',
'AttributeGroup->attrs' => ', ',
// statement lists
'Expr_Closure->stmts' => "\n",
'Stmt_Case->stmts' => "\n",
'Stmt_Catch->stmts' => "\n",
'Stmt_Class->stmts' => "\n",
'Expr_PrintableNewAnonClass->stmts' => "\n",
'Stmt_Interface->stmts' => "\n",
'Stmt_Trait->stmts' => "\n",
'Stmt_ClassMethod->stmts' => "\n",
'Stmt_Declare->stmts' => "\n",
'Stmt_Do->stmts' => "\n",
'Stmt_ElseIf->stmts' => "\n",
'Stmt_Else->stmts' => "\n",
'Stmt_Finally->stmts' => "\n",
'Stmt_Foreach->stmts' => "\n",
'Stmt_For->stmts' => "\n",
'Stmt_Function->stmts' => "\n",
'Stmt_If->stmts' => "\n",
'Stmt_Namespace->stmts' => "\n",
'Stmt_Class->attrGroups' => "\n",
'Stmt_Interface->attrGroups' => "\n",
'Stmt_Trait->attrGroups' => "\n",
'Stmt_Function->attrGroups' => "\n",
'Stmt_ClassMethod->attrGroups' => "\n",
'Stmt_ClassConst->attrGroups' => "\n",
'Stmt_Property->attrGroups' => "\n",
'Expr_PrintableNewAnonClass->attrGroups' => ' ',
'Expr_Closure->attrGroups' => ' ',
'Expr_ArrowFunction->attrGroups' => ' ',
'Param->attrGroups' => ' ',
'Stmt_Switch->cases' => "\n",
'Stmt_TraitUse->adaptations' => "\n",
'Stmt_TryCatch->stmts' => "\n",
'Stmt_While->stmts' => "\n",
// dummy for top-level context
'File->stmts' => "\n",
];
}
protected function initializeEmptyListInsertionMap() {
if ($this->emptyListInsertionMap) return;
// TODO Insertion into empty statement lists.
// [$find, $extraLeft, $extraRight]
$this->emptyListInsertionMap = [
'Expr_ArrowFunction->params' => ['(', '', ''],
'Expr_Closure->uses' => [')', ' use(', ')'],
'Expr_Closure->params' => ['(', '', ''],
'Expr_FuncCall->args' => ['(', '', ''],
'Expr_MethodCall->args' => ['(', '', ''],
'Expr_NullsafeMethodCall->args' => ['(', '', ''],
'Expr_New->args' => ['(', '', ''],
'Expr_PrintableNewAnonClass->args' => ['(', '', ''],
'Expr_PrintableNewAnonClass->implements' => [null, ' implements ', ''],
'Expr_StaticCall->args' => ['(', '', ''],
'Stmt_Class->implements' => [null, ' implements ', ''],
'Stmt_ClassMethod->params' => ['(', '', ''],
'Stmt_Interface->extends' => [null, ' extends ', ''],
'Stmt_Function->params' => ['(', '', ''],
/* These cannot be empty to start with:
* Expr_Isset->vars
* Stmt_Catch->types
* Stmt_Const->consts
* Stmt_ClassConst->consts
* Stmt_Declare->declares
* Stmt_Echo->exprs
* Stmt_Global->vars
* Stmt_GroupUse->uses
* Stmt_Property->props
* Stmt_StaticVar->vars
* Stmt_TraitUse->traits
* Stmt_TraitUseAdaptation_Precedence->insteadof
* Stmt_Unset->vars
* Stmt_Use->uses
* UnionType->types
*/
/* TODO
* Stmt_If->elseifs
* Stmt_TryCatch->catches
* Expr_Array->items
* Expr_List->items
* Stmt_For->init
* Stmt_For->cond
* Stmt_For->loop
*/
];
}
protected function initializeModifierChangeMap() {
if ($this->modifierChangeMap) return;
$this->modifierChangeMap = [
'Stmt_ClassConst->flags' => \T_CONST,
'Stmt_ClassMethod->flags' => \T_FUNCTION,
'Stmt_Class->flags' => \T_CLASS,
'Stmt_Property->flags' => \T_VARIABLE,
'Param->flags' => \T_VARIABLE,
//'Stmt_TraitUseAdaptation_Alias->newModifier' => 0, // TODO
];
// List of integer subnodes that are not modifiers:
// Expr_Include->type
// Stmt_GroupUse->type
// Stmt_Use->type
// Stmt_UseUse->type
}
}
<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
/**
* Visitor that connects a child node to its parent node
* as well as its sibling nodes.
*
* On the child node, the parent node can be accessed through
* <code>$node->getAttribute('parent')</code>, the previous
* node can be accessed through <code>$node->getAttribute('previous')</code>,
* and the next node can be accessed through <code>$node->getAttribute('next')</code>.
*/
final class NodeConnectingVisitor extends NodeVisitorAbstract
{
/**
* @var Node[]
*/
private $stack = [];
/**
* @var ?Node
*/
private $previous;
public function beforeTraverse(array $nodes) {
$this->stack = [];
$this->previous = null;
}
public function enterNode(Node $node) {
if (!empty($this->stack)) {
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
}
if ($this->previous !== null && $this->previous->getAttribute('parent') === $node->getAttribute('parent')) {
$node->setAttribute('previous', $this->previous);
$this->previous->setAttribute('next', $node);
}
$this->stack[] = $node;
}
public function leaveNode(Node $node) {
$this->previous = $node;
array_pop($this->stack);
}
}
<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
/**
* Visitor cloning all nodes and linking to the original nodes using an attribute.
*
* This visitor is required to perform format-preserving pretty prints.
*/
class CloningVisitor extends NodeVisitorAbstract
{
public function enterNode(Node $origNode) {
$node = clone $origNode;
$node->setAttribute('origNode', $origNode);
return $node;
}
}
<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
/**
* This visitor can be used to find and collect all nodes satisfying some criterion determined by
* a filter callback.
*/
class FindingVisitor extends NodeVisitorAbstract
{
/** @var callable Filter callback */
protected $filterCallback;
/** @var Node[] Found nodes */
protected $foundNodes;
public function __construct(callable $filterCallback) {
$this->filterCallback = $filterCallback;
}
/**
* Get found nodes satisfying the filter callback.
*
* Nodes are returned in pre-order.
*
* @return Node[] Found nodes
*/
public function getFoundNodes() : array {
return $this->foundNodes;
}
public function beforeTraverse(array $nodes) {
$this->foundNodes = [];
return null;
}
public function enterNode(Node $node) {
$filterCallback = $this->filterCallback;
if ($filterCallback($node)) {
$this->foundNodes[] = $node;
}
return null;
}
}
<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
/**
* This visitor can be used to find the first node satisfying some criterion determined by
* a filter callback.
*/
class FirstFindingVisitor extends NodeVisitorAbstract
{
/** @var callable Filter callback */
protected $filterCallback;
/** @var null|Node Found node */
protected $foundNode;
public function __construct(callable $filterCallback) {
$this->filterCallback = $filterCallback;
}
/**
* Get found node satisfying the filter callback.
*
* Returns null if no node satisfies the filter callback.
*
* @return null|Node Found node (or null if not found)
*/
public function getFoundNode() {
return $this->foundNode;
}
public function beforeTraverse(array $nodes) {
$this->foundNode = null;
return null;
}
public function enterNode(Node $node) {
$filterCallback = $this->filterCallback;
if ($filterCallback($node)) {
$this->foundNode = $node;
return NodeTraverser::STOP_TRAVERSAL;
}
return null;
}
}
<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use PhpParser\ErrorHandler;
use PhpParser\NameContext;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
use PhpParser\NodeVisitorAbstract;
class NameResolver extends NodeVisitorAbstract
{
/** @var NameContext Naming context */
protected $nameContext;
/** @var bool Whether to preserve original names */
protected $preserveOriginalNames;
/** @var bool Whether to replace resolved nodes in place, or to add resolvedNode attributes */
protected $replaceNodes;
/**
* Constructs a name resolution visitor.
*
* Options:
* * preserveOriginalNames (default false): An "originalName" attribute will be added to
* all name nodes that underwent resolution.
* * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
* resolvedName attribute is added. (Names that cannot be statically resolved receive a
* namespacedName attribute, as usual.)
*
* @param ErrorHandler|null $errorHandler Error handler
* @param array $options Options
*/
public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
$this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
$this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false;
$this->replaceNodes = $options['replaceNodes'] ?? true;
}
/**
* Get name resolution context.
*
* @return NameContext
*/
public function getNameContext() : NameContext {
return $this->nameContext;
}
public function beforeTraverse(array $nodes) {
$this->nameContext->startNamespace();
return null;
}
public function enterNode(Node $node) {
if ($node instanceof Stmt\Namespace_) {
$this->nameContext->startNamespace($node->name);
} elseif ($node instanceof Stmt\Use_) {
foreach ($node->uses as $use) {
$this->addAlias($use, $node->type, null);
}
} elseif ($node instanceof Stmt\GroupUse) {
foreach ($node->uses as $use) {
$this->addAlias($use, $node->type, $node->prefix);
}
} elseif ($node instanceof Stmt\Class_) {
if (null !== $node->extends) {
$node->extends = $this->resolveClassName($node->extends);
}
foreach ($node->implements as &$interface) {
$interface = $this->resolveClassName($interface);
}
$this->resolveAttrGroups($node);
if (null !== $node->name) {
$this->addNamespacedName($node);
}
} elseif ($node instanceof Stmt\Interface_) {
foreach ($node->extends as &$interface) {
$interface = $this->resolveClassName($interface);
}
$this->resolveAttrGroups($node);
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\Trait_) {
$this->resolveAttrGroups($node);
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\Function_) {
$this->resolveSignature($node);
$this->resolveAttrGroups($node);
$this->addNamespacedName($node);
} elseif ($node instanceof Stmt\ClassMethod
|| $node instanceof Expr\Closure
|| $node instanceof Expr\ArrowFunction
) {
$this->resolveSignature($node);
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\Property) {
if (null !== $node->type) {
$node->type = $this->resolveType($node->type);
}
$this->resolveAttrGroups($node);
} elseif ($node instanceof Stmt\Const_) {
foreach ($node->consts as $const) {
$this->addNamespacedName($const);
}
} else if ($node instanceof Stmt\ClassConst) {
$this->resolveAttrGroups($node);
} elseif ($node instanceof Expr\StaticCall
|| $node instanceof Expr\StaticPropertyFetch
|| $node instanceof Expr\ClassConstFetch
|| $node instanceof Expr\New_
|| $node instanceof Expr\Instanceof_
) {
if ($node->class instanceof Name) {
$node->class = $this->resolveClassName($node->class);
}
} elseif ($node instanceof Stmt\Catch_) {
foreach ($node->types as &$type) {
$type = $this->resolveClassName($type);
}
} elseif ($node instanceof Expr\FuncCall) {
if ($node->name instanceof Name) {
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
}
} elseif ($node instanceof Expr\ConstFetch) {
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
} elseif ($node instanceof Stmt\TraitUse) {
foreach ($node->traits as &$trait) {
$trait = $this->resolveClassName($trait);
}
foreach ($node->adaptations as $adaptation) {
if (null !== $adaptation->trait) {
$adaptation->trait = $this->resolveClassName($adaptation->trait);
}
if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
foreach ($adaptation->insteadof as &$insteadof) {
$insteadof = $this->resolveClassName($insteadof);
}
}
}
}
return null;
}
private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
// Add prefix for group uses
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
// Type is determined either by individual element or whole use declaration
$type |= $use->type;
$this->nameContext->addAlias(
$name, (string) $use->getAlias(), $type, $use->getAttributes()
);
}
/** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
private function resolveSignature($node) {
foreach ($node->params as $param) {
$param->type = $this->resolveType($param->type);
$this->resolveAttrGroups($param);
}
$node->returnType = $this->resolveType($node->returnType);
}
private function resolveType($node) {
if ($node instanceof Name) {
return $this->resolveClassName($node);
}
if ($node instanceof Node\NullableType) {
$node->type = $this->resolveType($node->type);
return $node;
}
if ($node instanceof Node\UnionType) {
foreach ($node->types as &$type) {
$type = $this->resolveType($type);
}
return $node;
}
return $node;
}
/**
* Resolve name, according to name resolver options.
*
* @param Name $name Function or constant name to resolve
* @param int $type One of Stmt\Use_::TYPE_*
*
* @return Name Resolved name, or original name with attribute
*/
protected function resolveName(Name $name, int $type) : Name {
if (!$this->replaceNodes) {
$resolvedName = $this->nameContext->getResolvedName($name, $type);
if (null !== $resolvedName) {
$name->setAttribute('resolvedName', $resolvedName);
} else {
$name->setAttribute('namespacedName', FullyQualified::concat(
$this->nameContext->getNamespace(), $name, $name->getAttributes()));
}
return $name;
}
if ($this->preserveOriginalNames) {
// Save the original name
$originalName = $name;
$name = clone $originalName;
$name->setAttribute('originalName', $originalName);
}
$resolvedName = $this->nameContext->getResolvedName($name, $type);
if (null !== $resolvedName) {
return $resolvedName;
}
// unqualified names inside a namespace cannot be resolved at compile-time
// add the namespaced version of the name as an attribute
$name->setAttribute('namespacedName', FullyQualified::concat(
$this->nameContext->getNamespace(), $name, $name->getAttributes()));
return $name;
}
protected function resolveClassName(Name $name) {
return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
}
protected function addNamespacedName(Node $node) {
$node->namespacedName = Name::concat(
$this->nameContext->getNamespace(), (string) $node->name);
}
protected function resolveAttrGroups(Node $node)
{
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
$attr->name = $this->resolveClassName($attr->name);
}
}
}
}
<?php declare(strict_types=1);
namespace PhpParser\NodeVisitor;
use function array_pop;
use function count;
use PhpParser\Node;
use PhpParser\NodeVisitorAbstract;
/**
* Visitor that connects a child node to its parent node.
*
* On the child node, the parent node can be accessed through
* <code>$node->getAttribute('parent')</code>.
*/
final class ParentConnectingVisitor extends NodeVisitorAbstract
{
/**
* @var Node[]
*/
private $stack = [];
public function beforeTraverse(array $nodes)
{
$this->stack = [];
}
public function enterNode(Node $node)
{
if (!empty($this->stack)) {
$node->setAttribute('parent', $this->stack[count($this->stack) - 1]);
}
$this->stack[] = $node;
}
public function leaveNode(Node $node)
{
array_pop($this->stack);
}
}
<?php declare(strict_types=1);
namespace PhpParser;
class NodeTraverser implements NodeTraverserInterface
{
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will still be called on the current
* node and leaveNode() will also be invoked for the current node.
*/
const DONT_TRAVERSE_CHILDREN = 1;
/**
* If NodeVisitor::enterNode() or NodeVisitor::leaveNode() returns
* STOP_TRAVERSAL, traversal is aborted.
*
* The afterTraverse() method will still be invoked.
*/
const STOP_TRAVERSAL = 2;
/**
* If NodeVisitor::leaveNode() returns REMOVE_NODE for a node that occurs
* in an array, it will be removed from the array.
*
* For subsequent visitors leaveNode() will still be invoked for the
* removed node.
*/
const REMOVE_NODE = 3;
/**
* If NodeVisitor::enterNode() returns DONT_TRAVERSE_CURRENT_AND_CHILDREN, child nodes
* of the current node will not be traversed for any visitors.
*
* For subsequent visitors enterNode() will not be called as well.
* leaveNode() will be invoked for visitors that has enterNode() method invoked.
*/
const DONT_TRAVERSE_CURRENT_AND_CHILDREN = 4;
/** @var NodeVisitor[] Visitors */
protected $visitors = [];
/** @var bool Whether traversal should be stopped */
protected $stopTraversal;
public function __construct() {
// for BC
}
/**
* Adds a visitor.
*
* @param NodeVisitor $visitor Visitor to add
*/
public function addVisitor(NodeVisitor $visitor) {
$this->visitors[] = $visitor;
}
/**
* Removes an added visitor.
*
* @param NodeVisitor $visitor
*/
public function removeVisitor(NodeVisitor $visitor) {
foreach ($this->visitors as $index => $storedVisitor) {
if ($storedVisitor === $visitor) {
unset($this->visitors[$index]);
break;
}
}
}
/**
* Traverses an array of nodes using the registered visitors.
*
* @param Node[] $nodes Array of nodes
*
* @return Node[] Traversed array of nodes
*/
public function traverse(array $nodes) : array {
$this->stopTraversal = false;
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->beforeTraverse($nodes)) {
$nodes = $return;
}
}
$nodes = $this->traverseArray($nodes);
foreach ($this->visitors as $visitor) {
if (null !== $return = $visitor->afterTraverse($nodes)) {
$nodes = $return;
}
}
return $nodes;
}
/**
* Recursively traverse a node.
*
* @param Node $node Node to traverse.
*
* @return Node Result of traversal (may be original node or new one)
*/
protected function traverseNode(Node $node) : Node {
foreach ($node->getSubNodeNames() as $name) {
$subNode =& $node->$name;
if (\is_array($subNode)) {
$subNode = $this->traverseArray($subNode);
if ($this->stopTraversal) {
break;
}
} elseif ($subNode instanceof Node) {
$traverseChildren = true;
$breakVisitorIndex = null;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $return;
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
$breakVisitorIndex = $visitorIndex;
break;
} elseif (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
if ($traverseChildren) {
$subNode = $this->traverseNode($subNode);
if ($this->stopTraversal) {
break;
}
}
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->leaveNode($subNode);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($subNode, $return);
$subNode = $return;
} elseif (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (\is_array($return)) {
throw new \LogicException(
'leaveNode() may only return an array ' .
'if the parent structure is an array'
);
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
if ($breakVisitorIndex === $visitorIndex) {
break;
}
}
}
}
return $node;
}
/**
* Recursively traverse array (usually of nodes).
*
* @param array $nodes Array to traverse
*
* @return array Result of traversal (may be original array or changed one)
*/
protected function traverseArray(array $nodes) : array {
$doNodes = [];
foreach ($nodes as $i => &$node) {
if ($node instanceof Node) {
$traverseChildren = true;
$breakVisitorIndex = null;
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->enterNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$node = $return;
} elseif (self::DONT_TRAVERSE_CHILDREN === $return) {
$traverseChildren = false;
} elseif (self::DONT_TRAVERSE_CURRENT_AND_CHILDREN === $return) {
$traverseChildren = false;
$breakVisitorIndex = $visitorIndex;
break;
} elseif (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} else {
throw new \LogicException(
'enterNode() returned invalid value of type ' . gettype($return)
);
}
}
}
if ($traverseChildren) {
$node = $this->traverseNode($node);
if ($this->stopTraversal) {
break;
}
}
foreach ($this->visitors as $visitorIndex => $visitor) {
$return = $visitor->leaveNode($node);
if (null !== $return) {
if ($return instanceof Node) {
$this->ensureReplacementReasonable($node, $return);
$node = $return;
} elseif (\is_array($return)) {
$doNodes[] = [$i, $return];
break;
} elseif (self::REMOVE_NODE === $return) {
$doNodes[] = [$i, []];
break;
} elseif (self::STOP_TRAVERSAL === $return) {
$this->stopTraversal = true;
break 2;
} elseif (false === $return) {
throw new \LogicException(
'bool(false) return from leaveNode() no longer supported. ' .
'Return NodeTraverser::REMOVE_NODE instead'
);
} else {
throw new \LogicException(
'leaveNode() returned invalid value of type ' . gettype($return)
);
}
}
if ($breakVisitorIndex === $visitorIndex) {
break;
}
}
} elseif (\is_array($node)) {
throw new \LogicException('Invalid node structure: Contains nested arrays');
}
}
if (!empty($doNodes)) {
while (list($i, $replace) = array_pop($doNodes)) {
array_splice($nodes, $i, 1, $replace);
}
}
return $nodes;
}
private function ensureReplacementReasonable($old, $new) {
if ($old instanceof Node\Stmt && $new instanceof Node\Expr) {
throw new \LogicException(
"Trying to replace statement ({$old->getType()}) " .
"with expression ({$new->getType()}). Are you missing a " .
"Stmt_Expression wrapper?"
);
}
if ($old instanceof Node\Expr && $new instanceof Node\Stmt) {
throw new \LogicException(
"Trying to replace expression ({$old->getType()}) " .
"with statement ({$new->getType()})"
);
}
}
}
<?php declare(strict_types=1);
namespace PhpParser;
interface NodeTraverserInterface
{
/**
* Adds a visitor.
*
* @param NodeVisitor $visitor Visitor to add
*/
public function addVisitor(NodeVisitor $visitor);
/**
* Removes an added visitor.
*
* @param NodeVisitor $visitor
*/
public function removeVisitor(NodeVisitor $visitor);
/**
* Traverses an array of nodes using the registered visitors.
*
* @param Node[] $nodes Array of nodes
*
* @return Node[] Traversed array of nodes
*/
public function traverse(array $nodes) : array;
}
<?php declare(strict_types=1);
namespace PhpParser;
use PhpParser\Node\Expr;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\NullableType;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
use PhpParser\Node\UnionType;
/**
* This class defines helpers used in the implementation of builders. Don't use it directly.
*
* @internal
*/
final class BuilderHelpers
{
/**
* Normalizes a node: Converts builder objects to nodes.
*
* @param Node|Builder $node The node to normalize
*
* @return Node The normalized node
*/
public static function normalizeNode($node) : Node {
if ($node instanceof Builder) {
return $node->getNode();
} elseif ($node instanceof Node) {
return $node;
}
throw new \LogicException('Expected node or builder object');
}
/**
* Normalizes a node to a statement.
*
* Expressions are wrapped in a Stmt\Expression node.
*
* @param Node|Builder $node The node to normalize
*
* @return Stmt The normalized statement node
*/
public static function normalizeStmt($node) : Stmt {
$node = self::normalizeNode($node);
if ($node instanceof Stmt) {
return $node;
}
if ($node instanceof Expr) {
return new Stmt\Expression($node);
}
throw new \LogicException('Expected statement or expression node');
}
/**
* Normalizes strings to Identifier.
*
* @param string|Identifier $name The identifier to normalize
*
* @return Identifier The normalized identifier
*/
public static function normalizeIdentifier($name) : Identifier {
if ($name instanceof Identifier) {
return $name;
}
if (\is_string($name)) {
return new Identifier($name);
}
throw new \LogicException('Expected string or instance of Node\Identifier');
}
/**
* Normalizes strings to Identifier, also allowing expressions.
*
* @param string|Identifier|Expr $name The identifier to normalize
*
* @return Identifier|Expr The normalized identifier or expression
*/
public static function normalizeIdentifierOrExpr($name) {
if ($name instanceof Identifier || $name instanceof Expr) {
return $name;
}
if (\is_string($name)) {
return new Identifier($name);
}
throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr');
}
/**
* Normalizes a name: Converts string names to Name nodes.
*
* @param Name|string $name The name to normalize
*
* @return Name The normalized name
*/
public static function normalizeName($name) : Name {
return self::normalizeNameCommon($name, false);
}
/**
* Normalizes a name: Converts string names to Name nodes, while also allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
*
* @return Name|Expr The normalized name or expression
*/
public static function normalizeNameOrExpr($name) {
return self::normalizeNameCommon($name, true);
}
/**
* Normalizes a name: Converts string names to Name nodes, optionally allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
* @param bool $allowExpr Whether to also allow expressions
*
* @return Name|Expr The normalized name, or expression (if allowed)
*/
private static function normalizeNameCommon($name, bool $allowExpr) {
if ($name instanceof Name) {
return $name;
} elseif (is_string($name)) {
if (!$name) {
throw new \LogicException('Name cannot be empty');
}
if ($name[0] === '\\') {
return new Name\FullyQualified(substr($name, 1));
} elseif (0 === strpos($name, 'namespace\\')) {
return new Name\Relative(substr($name, strlen('namespace\\')));
} else {
return new Name($name);
}
}
if ($allowExpr) {
if ($name instanceof Expr) {
return $name;
}
throw new \LogicException(
'Name must be a string or an instance of Node\Name or Node\Expr'
);
} else {
throw new \LogicException('Name must be a string or an instance of Node\Name');
}
}
/**
* Normalizes a type: Converts plain-text type names into proper AST representation.
*
* In particular, builtin types become Identifiers, custom types become Names and nullables
* are wrapped in NullableType nodes.
*
* @param string|Name|Identifier|NullableType|UnionType $type The type to normalize
*
* @return Name|Identifier|NullableType|UnionType The normalized type
*/
public static function normalizeType($type) {
if (!is_string($type)) {
if (
!$type instanceof Name && !$type instanceof Identifier &&
!$type instanceof NullableType && !$type instanceof UnionType
) {
throw new \LogicException(
'Type must be a string, or an instance of Name, Identifier, NullableType or UnionType'
);
}
return $type;
}
$nullable = false;
if (strlen($type) > 0 && $type[0] === '?') {
$nullable = true;
$type = substr($type, 1);
}
$builtinTypes = [
'array', 'callable', 'string', 'int', 'float', 'bool', 'iterable', 'void', 'object', 'mixed'
];
$lowerType = strtolower($type);
if (in_array($lowerType, $builtinTypes)) {
$type = new Identifier($lowerType);
} else {
$type = self::normalizeName($type);
}
if ($nullable && (string) $type === 'void') {
throw new \LogicException('void type cannot be nullable');
}
if ($nullable && (string) $type === 'mixed') {
throw new \LogicException('mixed type cannot be nullable');
}
return $nullable ? new NullableType($type) : $type;
}
/**
* Normalizes a value: Converts nulls, booleans, integers,
* floats, strings and arrays into their respective nodes
*
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
*
* @return Expr The normalized value
*/
public static function normalizeValue($value) : Expr {
if ($value instanceof Node\Expr) {
return $value;
} elseif (is_null($value)) {
return new Expr\ConstFetch(
new Name('null')
);
} elseif (is_bool($value)) {
return new Expr\ConstFetch(
new Name($value ? 'true' : 'false')
);
} elseif (is_int($value)) {
return new Scalar\LNumber($value);
} elseif (is_float($value)) {
return new Scalar\DNumber($value);
} elseif (is_string($value)) {
return new Scalar\String_($value);
} elseif (is_array($value)) {
$items = [];
$lastKey = -1;
foreach ($value as $itemKey => $itemValue) {
// for consecutive, numeric keys don't generate keys
if (null !== $lastKey && ++$lastKey === $itemKey) {
$items[] = new Expr\ArrayItem(
self::normalizeValue($itemValue)
);
} else {
$lastKey = null;
$items[] = new Expr\ArrayItem(
self::normalizeValue($itemValue),
self::normalizeValue($itemKey)
);
}
}
return new Expr\Array_($items);
} else {
throw new \LogicException('Invalid value');
}
}
/**
* Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc.
*
* @param Comment\Doc|string $docComment The doc comment to normalize
*
* @return Comment\Doc The normalized doc comment
*/
public static function normalizeDocComment($docComment) : Comment\Doc {
if ($docComment instanceof Comment\Doc) {
return $docComment;
} elseif (is_string($docComment)) {
return new Comment\Doc($docComment);
} else {
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
}
}
/**
* Adds a modifier and returns new modifier bitmask.
*
* @param int $modifiers Existing modifiers
* @param int $modifier Modifier to set
*
* @return int New modifiers
*/
public static function addModifier(int $modifiers, int $modifier) : int {
Stmt\Class_::verifyModifier($modifiers, $modifier);
return $modifiers | $modifier;
}
}
<?php declare(strict_types=1);
namespace PhpParser;
interface Parser
{
/**
* Parses PHP code into a node tree.
*
* @param string $code The source code to parse
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
* to ErrorHandler\Throwing.
*
* @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and
* the parser was unable to recover from an error).
*/
public function parse(string $code, ErrorHandler $errorHandler = null);
}
<?php declare(strict_types=1);
namespace PhpParser;
class Comment implements \JsonSerializable
{
protected $text;
protected $startLine;
protected $startFilePos;
protected $startTokenPos;
protected $endLine;
protected $endFilePos;
protected $endTokenPos;
/**
* Constructs a comment node.
*
* @param string $text Comment text (including comment delimiters like /*)
* @param int $startLine Line number the comment started on
* @param int $startFilePos File offset the comment started on
* @param int $startTokenPos Token offset the comment started on
*/
public function __construct(
string $text,
int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1,
int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1
) {
$this->text = $text;
$this->startLine = $startLine;
$this->startFilePos = $startFilePos;
$this->startTokenPos = $startTokenPos;
$this->endLine = $endLine;
$this->endFilePos = $endFilePos;
$this->endTokenPos = $endTokenPos;
}
/**
* Gets the comment text.
*
* @return string The comment text (including comment delimiters like /*)
*/
public function getText() : string {
return $this->text;
}
/**
* Gets the line number the comment started on.
*
* @return int Line number (or -1 if not available)
*/
public function getStartLine() : int {
return $this->startLine;
}
/**
* Gets the file offset the comment started on.
*
* @return int File offset (or -1 if not available)
*/
public function getStartFilePos() : int {
return $this->startFilePos;
}
/**
* Gets the token offset the comment started on.
*
* @return int Token offset (or -1 if not available)
*/
public function getStartTokenPos() : int {
return $this->startTokenPos;
}
/**
* Gets the line number the comment ends on.
*
* @return int Line number (or -1 if not available)
*/
public function getEndLine() : int {
return $this->endLine;
}
/**
* Gets the file offset the comment ends on.
*
* @return int File offset (or -1 if not available)
*/
public function getEndFilePos() : int {
return $this->endFilePos;
}
/**
* Gets the token offset the comment ends on.
*
* @return int Token offset (or -1 if not available)
*/
public function getEndTokenPos() : int {
return $this->endTokenPos;
}
/**
* Gets the line number the comment started on.
*
* @deprecated Use getStartLine() instead
*
* @return int Line number
*/
public function getLine() : int {
return $this->startLine;
}
/**
* Gets the file offset the comment started on.
*
* @deprecated Use getStartFilePos() instead
*
* @return int File offset
*/
public function getFilePos() : int {
return $this->startFilePos;
}
/**
* Gets the token offset the comment started on.
*
* @deprecated Use getStartTokenPos() instead
*
* @return int Token offset
*/
public function getTokenPos() : int {
return $this->startTokenPos;
}
/**
* Gets the comment text.
*
* @return string The comment text (including comment delimiters like /*)
*/
public function __toString() : string {
return $this->text;
}
/**
* Gets the reformatted comment text.
*
* "Reformatted" here means that we try to clean up the whitespace at the
* starts of the lines. This is necessary because we receive the comments
* without trailing whitespace on the first line, but with trailing whitespace
* on all subsequent lines.
*
* @return mixed|string
*/
public function getReformattedText() {
$text = trim($this->text);
$newlinePos = strpos($text, "\n");
if (false === $newlinePos) {
// Single line comments don't need further processing
return $text;
} elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
// Multi line comment of the type
//
// /*
// * Some text.
// * Some more text.
// */
//
// is handled by replacing the whitespace sequences before the * by a single space
return preg_replace('(^\s+\*)m', ' *', $this->text);
} elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
// Multi line comment of the type
//
// /*
// Some text.
// Some more text.
// */
//
// is handled by removing the whitespace sequence on the line before the closing
// */ on all lines. So if the last line is " */", then " " is removed at the
// start of all lines.
return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
} elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
// Multi line comment of the type
//
// /* Some text.
// Some more text.
// Indented text.
// Even more text. */
//
// is handled by removing the difference between the shortest whitespace prefix on all
// lines and the length of the "/* " opening sequence.
$prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1));
$removeLen = $prefixLen - strlen($matches[0]);
return preg_replace('(^\s{' . $removeLen . '})m', '', $text);
}
// No idea how to format this comment, so simply return as is
return $text;
}
/**
* Get length of shortest whitespace prefix (at the start of a line).
*
* If there is a line with no prefix whitespace, 0 is a valid return value.
*
* @param string $str String to check
* @return int Length in characters. Tabs count as single characters.
*/
private function getShortestWhitespacePrefixLen(string $str) : int {
$lines = explode("\n", $str);
$shortestPrefixLen = \INF;
foreach ($lines as $line) {
preg_match('(^\s*)', $line, $matches);
$prefixLen = strlen($matches[0]);
if ($prefixLen < $shortestPrefixLen) {
$shortestPrefixLen = $prefixLen;
}
}
return $shortestPrefixLen;
}
/**
* @return array
* @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
*/
public function jsonSerialize() : array {
// Technically not a node, but we make it look like one anyway
$type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment';
return [
'nodeType' => $type,
'text' => $this->text,
// TODO: Rename these to include "start".
'line' => $this->startLine,
'filePos' => $this->startFilePos,
'tokenPos' => $this->startTokenPos,
'endLine' => $this->endLine,
'endFilePos' => $this->endFilePos,
'endTokenPos' => $this->endTokenPos,
];
}
}
<?php declare(strict_types=1);
namespace PhpParser;
class Error extends \RuntimeException
{
protected $rawMessage;
protected $attributes;
/**
* Creates an Exception signifying a parse error.
*
* @param string $message Error message
* @param array|int $attributes Attributes of node/token where error occurred
* (or start line of error -- deprecated)
*/
public function __construct(string $message, $attributes = []) {
$this->rawMessage = $message;
if (is_array($attributes)) {
$this->attributes = $attributes;
} else {
$this->attributes = ['startLine' => $attributes];
}
$this->updateMessage();
}
/**
* Gets the error message
*
* @return string Error message
*/
public function getRawMessage() : string {
return $this->rawMessage;
}
/**
* Gets the line the error starts in.
*
* @return int Error start line
*/
public function getStartLine() : int {
return $this->attributes['startLine'] ?? -1;
}
/**
* Gets the line the error ends in.
*
* @return int Error end line
*/
public function getEndLine() : int {
return $this->attributes['endLine'] ?? -1;
}
/**
* Gets the attributes of the node/token the error occurred at.
*
* @return array
*/
public function getAttributes() : array {
return $this->attributes;
}
/**
* Sets the attributes of the node/token the error occurred at.
*
* @param array $attributes
*/
public function setAttributes(array $attributes) {
$this->attributes = $attributes;
$this->updateMessage();
}
/**
* Sets the line of the PHP file the error occurred in.
*
* @param string $message Error message
*/
public function setRawMessage(string $message) {
$this->rawMessage = $message;
$this->updateMessage();
}
/**
* Sets the line the error starts in.
*
* @param int $line Error start line
*/
public function setStartLine(int $line) {
$this->attributes['startLine'] = $line;
$this->updateMessage();
}
/**
* Returns whether the error has start and end column information.
*
* For column information enable the startFilePos and endFilePos in the lexer options.
*
* @return bool
*/
public function hasColumnInfo() : bool {
return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']);
}
/**
* Gets the start column (1-based) into the line where the error started.
*
* @param string $code Source code of the file
* @return int
*/
public function getStartColumn(string $code) : int {
if (!$this->hasColumnInfo()) {
throw new \RuntimeException('Error does not have column information');
}
return $this->toColumn($code, $this->attributes['startFilePos']);
}
/**
* Gets the end column (1-based) into the line where the error ended.
*
* @param string $code Source code of the file
* @return int
*/
public function getEndColumn(string $code) : int {
if (!$this->hasColumnInfo()) {
throw new \RuntimeException('Error does not have column information');
}
return $this->toColumn($code, $this->attributes['endFilePos']);
}
/**
* Formats message including line and column information.
*
* @param string $code Source code associated with the error, for calculation of the columns
*
* @return string Formatted message
*/
public function getMessageWithColumnInfo(string $code) : string {
return sprintf(
'%s from %d:%d to %d:%d', $this->getRawMessage(),
$this->getStartLine(), $this->getStartColumn($code),
$this->getEndLine(), $this->getEndColumn($code)
);
}
/**
* Converts a file offset into a column.
*
* @param string $code Source code that $pos indexes into
* @param int $pos 0-based position in $code
*
* @return int 1-based column (relative to start of line)
*/
private function toColumn(string $code, int $pos) : int {
if ($pos > strlen($code)) {
throw new \RuntimeException('Invalid position information');
}
$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
if (false === $lineStartPos) {
$lineStartPos = -1;
}
return $pos - $lineStartPos;
}
/**
* Updates the exception message after a change to rawMessage or rawLine.
*/
protected function updateMessage() {
$this->message = $this->rawMessage;
if (-1 === $this->getStartLine()) {
$this->message .= ' on unknown line';
} else {
$this->message .= ' on line ' . $this->getStartLine();
}
}
}
<?php declare(strict_types=1);
namespace PhpParser;
interface ErrorHandler
{
/**
* Handle an error generated during lexing, parsing or some other operation.
*
* @param Error $error The error that needs to be handled
*/
public function handleError(Error $error);
}
<?php
namespace PhpParser\Parser;
/* GENERATED file based on grammar/tokens.y */
final class Tokens
{
const YYERRTOK = 256;
const T_THROW = 257;
const T_INCLUDE = 258;
const T_INCLUDE_ONCE = 259;
const T_EVAL = 260;
const T_REQUIRE = 261;
const T_REQUIRE_ONCE = 262;
const T_LOGICAL_OR = 263;
const T_LOGICAL_XOR = 264;
const T_LOGICAL_AND = 265;
const T_PRINT = 266;
const T_YIELD = 267;
const T_DOUBLE_ARROW = 268;
const T_YIELD_FROM = 269;
const T_PLUS_EQUAL = 270;
const T_MINUS_EQUAL = 271;
const T_MUL_EQUAL = 272;
const T_DIV_EQUAL = 273;
const T_CONCAT_EQUAL = 274;
const T_MOD_EQUAL = 275;
const T_AND_EQUAL = 276;
const T_OR_EQUAL = 277;
const T_XOR_EQUAL = 278;
const T_SL_EQUAL = 279;
const T_SR_EQUAL = 280;
const T_POW_EQUAL = 281;
const T_COALESCE_EQUAL = 282;
const T_COALESCE = 283;
const T_BOOLEAN_OR = 284;
const T_BOOLEAN_AND = 285;
const T_IS_EQUAL = 286;
const T_IS_NOT_EQUAL = 287;
const T_IS_IDENTICAL = 288;
const T_IS_NOT_IDENTICAL = 289;
const T_SPACESHIP = 290;
const T_IS_SMALLER_OR_EQUAL = 291;
const T_IS_GREATER_OR_EQUAL = 292;
const T_SL = 293;
const T_SR = 294;
const T_INSTANCEOF = 295;
const T_INC = 296;
const T_DEC = 297;
const T_INT_CAST = 298;
const T_DOUBLE_CAST = 299;
const T_STRING_CAST = 300;
const T_ARRAY_CAST = 301;
const T_OBJECT_CAST = 302;
const T_BOOL_CAST = 303;
const T_UNSET_CAST = 304;
const T_POW = 305;
const T_NEW = 306;
const T_CLONE = 307;
const T_EXIT = 308;
const T_IF = 309;
const T_ELSEIF = 310;
const T_ELSE = 311;
const T_ENDIF = 312;
const T_LNUMBER = 313;
const T_DNUMBER = 314;
const T_STRING = 315;
const T_STRING_VARNAME = 316;
const T_VARIABLE = 317;
const T_NUM_STRING = 318;
const T_INLINE_HTML = 319;
const T_ENCAPSED_AND_WHITESPACE = 320;
const T_CONSTANT_ENCAPSED_STRING = 321;
const T_ECHO = 322;
const T_DO = 323;
const T_WHILE = 324;
const T_ENDWHILE = 325;
const T_FOR = 326;
const T_ENDFOR = 327;
const T_FOREACH = 328;
const T_ENDFOREACH = 329;
const T_DECLARE = 330;
const T_ENDDECLARE = 331;
const T_AS = 332;
const T_SWITCH = 333;
const T_MATCH = 334;
const T_ENDSWITCH = 335;
const T_CASE = 336;
const T_DEFAULT = 337;
const T_BREAK = 338;
const T_CONTINUE = 339;
const T_GOTO = 340;
const T_FUNCTION = 341;
const T_FN = 342;
const T_CONST = 343;
const T_RETURN = 344;
const T_TRY = 345;
const T_CATCH = 346;
const T_FINALLY = 347;
const T_USE = 348;
const T_INSTEADOF = 349;
const T_GLOBAL = 350;
const T_STATIC = 351;
const T_ABSTRACT = 352;
const T_FINAL = 353;
const T_PRIVATE = 354;
const T_PROTECTED = 355;
const T_PUBLIC = 356;
const T_VAR = 357;
const T_UNSET = 358;
const T_ISSET = 359;
const T_EMPTY = 360;
const T_HALT_COMPILER = 361;
const T_CLASS = 362;
const T_TRAIT = 363;
const T_INTERFACE = 364;
const T_EXTENDS = 365;
const T_IMPLEMENTS = 366;
const T_OBJECT_OPERATOR = 367;
const T_NULLSAFE_OBJECT_OPERATOR = 368;
const T_LIST = 369;
const T_ARRAY = 370;
const T_CALLABLE = 371;
const T_CLASS_C = 372;
const T_TRAIT_C = 373;
const T_METHOD_C = 374;
const T_FUNC_C = 375;
const T_LINE = 376;
const T_FILE = 377;
const T_START_HEREDOC = 378;
const T_END_HEREDOC = 379;
const T_DOLLAR_OPEN_CURLY_BRACES = 380;
const T_CURLY_OPEN = 381;
const T_PAAMAYIM_NEKUDOTAYIM = 382;
const T_NAMESPACE = 383;
const T_NS_C = 384;
const T_DIR = 385;
const T_NS_SEPARATOR = 386;
const T_ELLIPSIS = 387;
const T_NAME_FULLY_QUALIFIED = 388;
const T_NAME_QUALIFIED = 389;
const T_NAME_RELATIVE = 390;
const T_ATTRIBUTE = 391;
}
<?php
namespace PhpParser\Parser;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
/* This is an automatically GENERATED file, which should not be manually edited.
* Instead edit one of the following:
* * the grammar files grammar/php5.y or grammar/php7.y
* * the skeleton file grammar/parser.template
* * the preprocessing script grammar/rebuildParsers.php
*/
class Php5 extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = 392;
protected $actionTableSize = 1069;
protected $gotoTableSize = 580;
protected $invalidSymbol = 165;
protected $errorSymbol = 1;
protected $defaultAction = -32766;
protected $unexpectedTokenRule = 32767;
protected $YY2TBLSTATE = 405;
protected $numNonLeafStates = 658;
protected $symbolToName = array(
"EOF",
"error",
"T_THROW",
"T_INCLUDE",
"T_INCLUDE_ONCE",
"T_EVAL",
"T_REQUIRE",
"T_REQUIRE_ONCE",
"','",
"T_LOGICAL_OR",
"T_LOGICAL_XOR",
"T_LOGICAL_AND",
"T_PRINT",
"T_YIELD",
"T_DOUBLE_ARROW",
"T_YIELD_FROM",
"'='",
"T_PLUS_EQUAL",
"T_MINUS_EQUAL",
"T_MUL_EQUAL",
"T_DIV_EQUAL",
"T_CONCAT_EQUAL",
"T_MOD_EQUAL",
"T_AND_EQUAL",
"T_OR_EQUAL",
"T_XOR_EQUAL",
"T_SL_EQUAL",
"T_SR_EQUAL",
"T_POW_EQUAL",
"T_COALESCE_EQUAL",
"'?'",
"':'",
"T_COALESCE",
"T_BOOLEAN_OR",
"T_BOOLEAN_AND",
"'|'",
"'^'",
"'&'",
"T_IS_EQUAL",
"T_IS_NOT_EQUAL",
"T_IS_IDENTICAL",
"T_IS_NOT_IDENTICAL",
"T_SPACESHIP",
"'<'",
"T_IS_SMALLER_OR_EQUAL",
"'>'",
"T_IS_GREATER_OR_EQUAL",
"T_SL",
"T_SR",
"'+'",
"'-'",
"'.'",
"'*'",
"'/'",
"'%'",
"'!'",
"T_INSTANCEOF",
"'~'",
"T_INC",
"T_DEC",
"T_INT_CAST",
"T_DOUBLE_CAST",
"T_STRING_CAST",
"T_ARRAY_CAST",
"T_OBJECT_CAST",
"T_BOOL_CAST",
"T_UNSET_CAST",
"'@'",
"T_POW",
"'['",
"T_NEW",
"T_CLONE",
"T_EXIT",
"T_IF",
"T_ELSEIF",
"T_ELSE",
"T_ENDIF",
"T_LNUMBER",
"T_DNUMBER",
"T_STRING",
"T_STRING_VARNAME",
"T_VARIABLE",
"T_NUM_STRING",
"T_INLINE_HTML",
"T_ENCAPSED_AND_WHITESPACE",
"T_CONSTANT_ENCAPSED_STRING",
"T_ECHO",
"T_DO",
"T_WHILE",
"T_ENDWHILE",
"T_FOR",
"T_ENDFOR",
"T_FOREACH",
"T_ENDFOREACH",
"T_DECLARE",
"T_ENDDECLARE",
"T_AS",
"T_SWITCH",
"T_MATCH",
"T_ENDSWITCH",
"T_CASE",
"T_DEFAULT",
"T_BREAK",
"T_CONTINUE",
"T_GOTO",
"T_FUNCTION",
"T_FN",
"T_CONST",
"T_RETURN",
"T_TRY",
"T_CATCH",
"T_FINALLY",
"T_USE",
"T_INSTEADOF",
"T_GLOBAL",
"T_STATIC",
"T_ABSTRACT",
"T_FINAL",
"T_PRIVATE",
"T_PROTECTED",
"T_PUBLIC",
"T_VAR",
"T_UNSET",
"T_ISSET",
"T_EMPTY",
"T_HALT_COMPILER",
"T_CLASS",
"T_TRAIT",
"T_INTERFACE",
"T_EXTENDS",
"T_IMPLEMENTS",
"T_OBJECT_OPERATOR",
"T_LIST",
"T_ARRAY",
"T_CALLABLE",
"T_CLASS_C",
"T_TRAIT_C",
"T_METHOD_C",
"T_FUNC_C",
"T_LINE",
"T_FILE",
"T_START_HEREDOC",
"T_END_HEREDOC",
"T_DOLLAR_OPEN_CURLY_BRACES",
"T_CURLY_OPEN",
"T_PAAMAYIM_NEKUDOTAYIM",
"T_NAMESPACE",
"T_NS_C",
"T_DIR",
"T_NS_SEPARATOR",
"T_ELLIPSIS",
"T_NAME_FULLY_QUALIFIED",
"T_NAME_QUALIFIED",
"T_NAME_RELATIVE",
"';'",
"'{'",
"'}'",
"'('",
"')'",
"'$'",
"'`'",
"']'",
"'\"'",
"T_NULLSAFE_OBJECT_OPERATOR",
"T_ATTRIBUTE"
);
protected $tokenToSymbol = array(
0, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 55, 162, 165, 159, 54, 37, 165,
157, 158, 52, 49, 8, 50, 51, 53, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 31, 154,
43, 16, 45, 30, 67, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 69, 165, 161, 36, 165, 160, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 155, 35, 156, 57, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 1, 2, 3, 4,
5, 6, 7, 9, 10, 11, 12, 13, 14, 15,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 32, 33, 34, 38, 39, 40, 41,
42, 44, 46, 47, 48, 56, 58, 59, 60, 61,
62, 63, 64, 65, 66, 68, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
124, 125, 126, 127, 128, 129, 130, 131, 163, 132,
133, 134, 135, 136, 137, 138, 139, 140, 141, 142,
143, 144, 145, 146, 147, 148, 149, 150, 151, 152,
153, 164
);
protected $action = array(
693, 663, 664, 665, 666, 667, 282, 668, 669, 670,
706, 707, 221, 222, 223, 224, 225, 226, 227, 228,
229, 0, 230, 231, 232, 233, 234, 235, 236, 237,
238, 239, 240, 241,-32766,-32766,-32766,-32766,-32766,-32766,
-32766,-32766,-32767,-32767,-32767,-32767, 27, 242, 243,-32766,
-32766,-32766,-32766,-32766, 671,-32766, 333,-32766,-32766,-32766,
-32766,-32766,-32766,-32767,-32767,-32767,-32767,-32767, 672, 673,
674, 675, 676, 677, 678, 1034, 816, 740, 941, 942,
943, 940, 939, 938, 679, 680, 681, 682, 683, 684,
685, 686, 687, 688, 689, 709, 732, 710, 711, 712,
713, 701, 702, 703, 731, 704, 705, 690, 691, 692,
694, 695, 696, 734, 735, 736, 737, 738, 739, 697,
698, 699, 700, 730, 721, 719, 720, 716, 717, 437,
708, 714, 715, 722, 723, 725, 724, 726, 727, 55,
56, 417, 57, 58, 718, 729, 728, 28, 59, 60,
-220, 61,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766,
-32766, 36,-32767,-32767,-32767,-32767, 1034, 35, 106, 107,
108, 109, 110, 111, 112, 113, 114, 115, 116, 117,
118,-32766,-32766,-32766,-32766, 62, 63, 1034, 125, 285,
292, 64, 748, 65, 290, 291, 66, 67, 68, 69,
70, 71, 72, 73, 763, 25, 298, 74, 409, 973,
975, 294, 294, 1086, 1087, 1064, 796, 748, 218, 219,
220, 465,-32766,-32766,-32766, 742, 864, 817, 54, 807,
9,-32766,-32766,-32766, 760, 320, 761, 410, 10, 202,
246, 428, 209,-32766, 933,-32766,-32766,-32766,-32766,-32766,
-32766, 488,-32766, 438,-32766,-32766,-32766,-32766,-32766, 473,
474, 941, 942, 943, 940, 939, 938,-32766, 475, 476,
337, 1092, 1093, 1094, 1095, 1089, 1090, 315, 1214, -255,
747, 1215, -505, 1096, 1091, 888, 889, 1066, 1065, 1067,
218, 219, 220, 41, 414, 337, 330, 895, 332, 418,
-126, -126, -126, 75, 52, 464, -4, 817, 54, 805,
-224, 202, 40, 21, 419, -126, 466, -126, 467, -126,
468, -126, 359, 420, 128, 128, 748, 1171, 31, 32,
421, 422, 1034, 894, 33, 469,-32766,-32766,-32766, 1186,
351, 352, 470, 471,-32766,-32766,-32766, 309, 472, 865,
323, 788, 835, 423, 424,-32767,-32767,-32767,-32767, 97,
98, 99, 100, 101, 615,-32766, 313,-32766,-32766,-32766,
-32766, 354, 1185, 1171, 218, 219, 220, 475, 748, 418,
819, 629, -126, 297, 915, 464, 817, 54,-32766, 805,
124, 748, 40, 21, 419, 202, 466, 48, 467, 534,
468, 129, 429, 420, 337, 341, 888, 889, 31, 32,
421, 422, 416, 405, 33, 469,-32766,-32766, 311, 298,
351, 352, 470, 471,-32766,-32766,-32766, 748, 472, 412,
748, 752, 835, 423, 424, 338, 1066, 1065, 1067, 219,
220, 919, 1136, 296, 20,-32766, 576,-32766,-32766,-32766,
742, 341, 342, 413, 429, 1064, 337, 512, 418, 202,
819, 629, -4, 1034, 464, 817, 54, 49, 805, 337,
762, 40, 21, 419, 51, 466, 1034, 467, 475, 468,
340, 748, 420, 120, -205, -205, -205, 31, 32, 421,
422, 1062,-32766, 33, 469,-32766,-32766,-32766, 744, 351,
352, 470, 471, 429, 1098, 337, 429, 472, 337, 1034,
788, 835, 423, 424, 415, 1098,-32766, 802,-32766,-32766,
102, 103, 104, 1137, 303, 202, 130, 1066, 1065, 1067,
337, 123, 239, 240, 241, 748, 105, 418, 1205, 819,
629, -205, 440, 464,-32766,-32766,-32766, 805, 242, 243,
40, 21, 419, 121, 466, 126, 467, 429, 468, 337,
122, 420, 1052, -204, -204, -204, 31, 32, 421, 422,
1034, 745, 33, 469, 220, 759, 817, 54, 351, 352,
470, 471, 218, 219, 220, 119, 472, 244, 127, 788,
835, 423, 424, 202,-32766,-32766,-32766, 30, 293, 803,
79, 80, 81, 202, 798, 210, 632, 99, 100, 101,
236, 237, 238, 817, 54,-32766, 211, 800, 819, 629,
-204, 34, 1034, 82, 83, 84, 85, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
99, 100, 101, 102, 103, 104, 286, 303, 418, 1034,
817, 54,-32766,-32766, 464, 218, 219, 220, 805, 105,
914, 40, 21, 419, 78, 466, 212, 467, 337, 468,
133, 247, 420, 295, 567, 248, 202, 31, 32, 421,
633, 242, 243, 33, 469, 418, 249, 817, 54, 351,
352, 464, 760, -84, 761, 805, 310, 472, 40, 21,
419,-32766, 466, 640, 467, 643, 468, 447, 22, 420,
815, 452, 584, 132, 31, 32, 421, 637, 134, 364,
33, 469, 418, 303, 817, 54, 351, 352, 464, 819,
629, 828, 805, 43, 472, 40, 21, 419, 44, 466,
45, 467, 46, 468, 591, 592, 420, 753, 635, 930,
649, 31, 32, 421, 641, 918, 657, 33, 469, 418,
105, 817, 54, 351, 352, 464, 819, 629, 47, 805,
50, 472, 40, 21, 419, 53, 466, 131, 467, 298,
468, 599, 742, 420,-32766, -274, 516, 570, 31, 32,
421, 646, 748, 946, 33, 469, 418, 589, 436,-32766,
351, 352, 464, 819, 629, 623, 805, 836, 472, 40,
21, 419, 611, 466, -82, 467, 603, 468, 11, 573,
420, 439, 456, 281, 318, 31, 32, 421, 588, 432,
321, 33, 469, 418, -414, 458, 322, 351, 352, 464,
851, 629, 837, 805, -505, 472, 40, 21, 419, 654,
466, 38, 467, 24, 468, 0, 0, 420, 319, 0,
-405, 0, 31, 32, 421, 245, 312, 314, 33, 469,
-506, 0, 0, 1097, 351, 352, 1143, 819, 629, 0,
0, 527, 472, 213, 214, 6, 7, 12, 14, 215,
363, 216, -415, 558, 789, -221, 830, 0, 0, 747,
0, 0, 0, 207, 39, 652, 653, 758, 806, 814,
793, 1086, 1087, 808, 819, 629, 213, 214, 867, 1088,
858, 859, 215, 791, 216, 852, 849, 847, 925, 926,
923, 813, 797, 799, 801, 804, 207, 922, 756, 757,
924, 287, 78, 331, 1086, 1087, 353, 630, 634, 636,
638, 639, 1088, 642, 644, 645, 647, 648, 631, 1142,
1211, 1213, 755, 834, 754, 833, 1212, 554, 832, 1092,
1093, 1094, 1095, 1089, 1090, 388, 1048, 824, 1036, 831,
1037, 1096, 1091, 822, 931, 856, 857, 451, 1210, 1179,
0, 217, 1177, 1162, 1175, 1077, 906, 1183, 1173, 0,
554, 26, 1092, 1093, 1094, 1095, 1089, 1090, 388, 29,
37, 42, 76, 77, 1096, 1091, 208, 284, 288, 289,
304, 305, 306, 307, 217, 335, 406, 408, 0, -220,
16, 17, 18, 383, 448, 455, 457, 462, 548, 620,
1039, 1042, 896, 1102, 1038, 1014, 559, 1013, 1079, 0,
0, -424, 1032, 0, 1043, 1045, 1044, 1047, 1046, 1061,
1176, 1161, 1157, 1174, 1076, 1208, 1103, 1156, 595
);
protected $actionCheck = array(
2, 3, 4, 5, 6, 7, 14, 9, 10, 11,
12, 13, 33, 34, 35, 36, 37, 38, 39, 40,
41, 0, 43, 44, 45, 46, 47, 48, 49, 50,
51, 52, 53, 54, 9, 10, 11, 33, 34, 35,
36, 37, 38, 39, 40, 41, 8, 68, 69, 33,
34, 35, 36, 37, 56, 30, 8, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 70, 71,
72, 73, 74, 75, 76, 13, 1, 79, 115, 116,
117, 118, 119, 120, 86, 87, 88, 89, 90, 91,
92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
122, 123, 124, 125, 126, 127, 128, 129, 130, 31,
132, 133, 134, 135, 136, 137, 138, 139, 140, 3,
4, 5, 6, 7, 146, 147, 148, 8, 12, 13,
158, 15, 33, 34, 35, 36, 37, 38, 39, 40,
41, 14, 43, 44, 45, 46, 13, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28,
29, 33, 34, 35, 36, 49, 50, 13, 8, 8,
37, 55, 81, 57, 58, 59, 60, 61, 62, 63,
64, 65, 66, 67, 156, 69, 70, 71, 72, 58,
59, 37, 37, 77, 78, 79, 154, 81, 9, 10,
11, 85, 9, 10, 11, 79, 31, 1, 2, 154,
107, 9, 10, 11, 105, 112, 107, 126, 8, 30,
31, 105, 8, 30, 121, 32, 33, 34, 35, 36,
37, 115, 30, 155, 32, 33, 34, 35, 36, 123,
124, 115, 116, 117, 118, 119, 120, 115, 132, 133,
159, 135, 136, 137, 138, 139, 140, 141, 79, 156,
151, 82, 131, 147, 148, 133, 134, 151, 152, 153,
9, 10, 11, 157, 8, 159, 160, 158, 162, 73,
74, 75, 76, 150, 69, 79, 0, 1, 2, 83,
158, 30, 86, 87, 88, 89, 90, 91, 92, 93,
94, 95, 8, 97, 150, 150, 81, 81, 102, 103,
104, 105, 13, 158, 108, 109, 9, 10, 11, 158,
114, 115, 116, 117, 9, 10, 11, 8, 122, 154,
8, 125, 126, 127, 128, 43, 44, 45, 46, 47,
48, 49, 50, 51, 79, 30, 131, 32, 33, 34,
35, 8, 1, 81, 9, 10, 11, 132, 81, 73,
154, 155, 156, 37, 154, 79, 1, 2, 115, 83,
155, 81, 86, 87, 88, 30, 90, 69, 92, 80,
94, 155, 157, 97, 159, 159, 133, 134, 102, 103,
104, 105, 8, 107, 108, 109, 9, 10, 112, 70,
114, 115, 116, 117, 9, 10, 11, 81, 122, 8,
81, 125, 126, 127, 128, 8, 151, 152, 153, 10,
11, 156, 161, 8, 158, 30, 84, 32, 33, 34,
79, 159, 146, 8, 157, 79, 159, 84, 73, 30,
154, 155, 156, 13, 79, 1, 2, 69, 83, 159,
156, 86, 87, 88, 69, 90, 13, 92, 132, 94,
69, 81, 97, 155, 99, 100, 101, 102, 103, 104,
105, 115, 9, 108, 109, 9, 10, 11, 79, 114,
115, 116, 117, 157, 142, 159, 157, 122, 159, 13,
125, 126, 127, 128, 8, 142, 30, 154, 32, 33,
52, 53, 54, 158, 56, 30, 155, 151, 152, 153,
159, 14, 52, 53, 54, 81, 68, 73, 84, 154,
155, 156, 131, 79, 33, 34, 35, 83, 68, 69,
86, 87, 88, 155, 90, 155, 92, 157, 94, 159,
155, 97, 158, 99, 100, 101, 102, 103, 104, 105,
13, 152, 108, 109, 11, 154, 1, 2, 114, 115,
116, 117, 9, 10, 11, 16, 122, 14, 31, 125,
126, 127, 128, 30, 9, 10, 11, 143, 144, 154,
9, 10, 11, 30, 154, 16, 31, 49, 50, 51,
49, 50, 51, 1, 2, 30, 16, 154, 154, 155,
156, 30, 13, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
49, 50, 51, 52, 53, 54, 37, 56, 73, 13,
1, 2, 33, 34, 79, 9, 10, 11, 83, 68,
154, 86, 87, 88, 155, 90, 16, 92, 159, 94,
155, 16, 97, 37, 159, 16, 30, 102, 103, 104,
31, 68, 69, 108, 109, 73, 16, 1, 2, 114,
115, 79, 105, 31, 107, 83, 31, 122, 86, 87,
88, 33, 90, 31, 92, 31, 94, 74, 75, 97,
31, 74, 75, 31, 102, 103, 104, 31, 100, 101,
108, 109, 73, 56, 1, 2, 114, 115, 79, 154,
155, 37, 83, 69, 122, 86, 87, 88, 69, 90,
69, 92, 69, 94, 110, 111, 97, 154, 155, 154,
155, 102, 103, 104, 31, 154, 155, 108, 109, 73,
68, 1, 2, 114, 115, 79, 154, 155, 69, 83,
69, 122, 86, 87, 88, 69, 90, 69, 92, 70,
94, 76, 79, 97, 84, 81, 84, 89, 102, 103,
104, 31, 81, 81, 108, 109, 73, 112, 88, 115,
114, 115, 79, 154, 155, 91, 83, 126, 122, 86,
87, 88, 93, 90, 96, 92, 95, 94, 96, 99,
97, 96, 96, 96, 129, 102, 103, 104, 99, 105,
113, 108, 109, 73, 145, 105, 129, 114, 115, 79,
154, 155, 126, 83, 131, 122, 86, 87, 88, 156,
90, 154, 92, 157, 94, -1, -1, 97, 130, -1,
145, -1, 102, 103, 104, 31, 131, 131, 108, 109,
131, -1, -1, 142, 114, 115, 142, 154, 155, -1,
-1, 149, 122, 49, 50, 145, 145, 145, 145, 55,
145, 57, 145, 149, 156, 158, 150, -1, -1, 151,
-1, -1, -1, 69, 154, 154, 154, 154, 154, 154,
154, 77, 78, 154, 154, 155, 49, 50, 154, 85,
154, 154, 55, 154, 57, 154, 154, 154, 154, 154,
154, 154, 154, 154, 154, 154, 69, 154, 154, 154,
154, 159, 155, 155, 77, 78, 155, 155, 155, 155,
155, 155, 85, 155, 155, 155, 155, 155, 155, 162,
156, 156, 156, 156, 156, 156, 156, 133, 156, 135,
136, 137, 138, 139, 140, 141, 156, 156, 156, 156,
156, 147, 148, 156, 156, 156, 156, 156, 156, 156,
-1, 157, 156, 156, 156, 156, 156, 156, 156, -1,
133, 157, 135, 136, 137, 138, 139, 140, 141, 157,
157, 157, 157, 157, 147, 148, 157, 157, 157, 157,
157, 157, 157, 157, 157, 157, 157, 157, -1, 158,
158, 158, 158, 158, 158, 158, 158, 158, 158, 158,
158, 158, 158, 158, 158, 158, 158, 158, 158, -1,
-1, 160, 160, -1, 161, 161, 161, 161, 161, 161,
161, 161, 161, 161, 161, 161, 161, 161, 161
);
protected $actionBase = array(
0, 226, 306, 385, 464, 285, 246, 246, 786, -2,
-2, 146, -2, -2, -2, 649, 723, 760, 723, 575,
686, 612, 612, 612, 175, 153, 153, 153, 174, 890,
319, 62, 450, 463, 557, 609, 636, 496, 496, 496,
496, 136, 136, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 496, 496, 496, 496, 496,
496, 496, 496, 496, 496, 195, 75, 777, 517, 147,
778, 779, 780, 886, 727, 887, 832, 833, 682, 836,
837, 838, 839, 840, 831, 841, 907, 842, 591, 591,
591, 591, 591, 591, 591, 591, 591, 591, 591, 591,
483, 573, 365, 209, 281, 407, 646, 646, 646, 646,
646, 646, 646, 327, 327, 327, 327, 327, 327, 327,
327, 327, 327, 327, 327, 327, 327, 327, 327, 327,
327, 429, 834, 585, 585, 585, 563, 867, 867, 867,
867, 867, 867, 867, 867, 867, 867, 867, 867, 867,
867, 867, 867, 867, 867, 867, 867, 867, 867, 867,
867, 867, 867, 867, 867, 867, 867, 867, 867, 867,
867, 867, 867, 867, 867, 867, 867, 867, 867, 867,
495, 486, -21, -21, 415, 668, 335, 619, 222, 511,
213, 25, 25, 25, 25, 25, 148, 16, 4, 4,
4, 4, 151, 312, 312, 312, 312, 119, 119, 119,
119, 346, 346, 123, 245, 245, 349, 400, 297, 297,
297, 297, 297, 297, 297, 297, 297, 297, 111, 558,
558, 561, 561, 310, 152, 152, 152, 152, 704, 273,
273, 129, 371, 371, 371, 373, 734, 797, 376, 376,
376, 376, 376, 376, 468, 468, 468, 480, 480, 480,
702, 587, 454, 587, 454, 684, 748, 509, 748, 700,
199, 515, 803, 398, 720, 829, 729, 830, 601, 747,
235, 782, 724, 419, 782, 633, 637, 634, 419, 419,
715, 98, 863, 292, 195, 595, 405, 667, 781, 421,
732, 784, 363, 445, 411, 593, 328, 286, 744, 785,
888, 889, 181, 739, 667, 667, 667, 139, 362, 328,
-8, 613, 613, 613, 613, 48, 613, 613, 613, 613,
314, 230, 506, 404, 783, 703, 703, 712, 694, 852,
696, 696, 703, 711, 703, 712, 694, 854, 854, 854,
854, 703, 694, 703, 703, 703, 696, 696, 694, 709,
696, 38, 694, 695, 707, 707, 854, 751, 752, 703,
703, 728, 696, 696, 696, 728, 694, 854, 685, 746,
234, 696, 854, 665, 711, 665, 703, 685, 694, 665,
711, 711, 665, 21, 662, 664, 853, 855, 869, 792,
681, 716, 861, 862, 856, 860, 844, 679, 753, 754,
569, 669, 671, 673, 699, 740, 701, 735, 724, 692,
692, 692, 713, 741, 713, 692, 692, 692, 692, 692,
692, 692, 692, 893, 689, 745, 736, 710, 755, 589,
600, 793, 731, 738, 882, 875, 891, 892, 863, 880,
713, 894, 697, 180, 650, 864, 693, 788, 713, 865,
713, 794, 713, 883, 804, 708, 805, 806, 692, 884,
895, 896, 897, 898, 899, 900, 901, 902, 706, 903,
756, 698, 876, 339, 859, 715, 742, 725, 791, 759,
807, 342, 904, 808, 713, 713, 795, 787, 713, 796,
764, 750, 872, 766, 877, 905, 731, 726, 878, 713,
730, 809, 906, 342, 672, 705, 737, 721, 767, 870,
885, 868, 798, 655, 659, 810, 812, 820, 674, 769,
873, 874, 871, 771, 799, 670, 800, 719, 821, 801,
866, 772, 822, 823, 881, 718, 743, 717, 722, 714,
802, 824, 879, 773, 774, 775, 827, 776, 828, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 136,
136, 136, 136, -2, -2, -2, -2, 0, 0, -2,
0, 0, 0, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 0,
0, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 136, 136, 136, 136, 136, 136, 136,
136, 136, 136, 591, 591, 591, 591, 591, 591, 591,
591, 591, 591, 591, 591, 591, 591, 591, 591, 591,
591, 591, 591, 591, 591, 591, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 591, -21,
-21, -21, -21, 591, -21, -21, -21, -21, -21, -21,
-21, 591, 591, 591, 591, 591, 591, 591, 591, 591,
591, 591, 591, 591, 591, 591, 591, 591, 591, -21,
376, 591, 591, 591, -21, 376, 376, 376, 376, 376,
376, 376, 376, 376, 376, 376, 376, 376, 376, 376,
376, 376, 376, 376, 376, 376, 376, 376, 376, 376,
376, 376, 376, 376, 376, 376, 376, 376, 376, 376,
376, 376, 376, 376, 376, 376, 376, 376, -21, 591,
0, 0, 591, -21, 591, -21, 591, -21, 591, 591,
591, 591, 591, 591, -21, -21, -21, -21, -21, -21,
0, 468, 468, 468, 468, -21, -21, -21, -21, 376,
376, -37, 376, 376, 376, 376, 376, 376, 376, 376,
376, 376, 376, 376, 376, 376, 376, 468, 468, 480,
480, 376, 376, 376, 376, 376, -37, 376, 376, 419,
711, 711, 711, 454, 454, 454, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 454, 419,
0, 419, 0, 376, 419, 711, 419, 454, 711, 711,
419, 696, 618, 618, 618, 618, 342, 328, 0, 711,
711, 0, 711, 0, 0, 0, 0, 0, 696, 0,
703, 0, 0, 0, 0, 692, 180, 0, 725, 427,
0, 0, 0, 0, 0, 0, 725, 427, 435, 435,
0, 706, 692, 692, 692, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 342
);
protected $actionDefault = array(
3,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767, 534, 534, 489,32767,32767,
32767,32767,32767,32767,32767,32767,32767, 293, 293, 293,
32767,32767,32767, 522, 522, 522, 522, 522, 522, 522,
522, 522, 522, 522,32767,32767,32767,32767,32767,32767,
376,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767, 382, 539,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767, 357, 358,
360, 361, 292, 542, 523, 241, 383, 538, 291, 243,
321, 493,32767,32767,32767, 323, 120, 252, 197, 492,
123, 290, 228, 375, 377, 322, 297, 302, 303, 304,
305, 306, 307, 308, 309, 310, 311, 312, 313, 314,
296, 449,32767, 354, 353, 352, 451, 486, 486, 489,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
450, 319, 477, 476, 320, 447, 324, 448, 326, 452,
325, 342, 343, 340, 341, 344, 454, 453, 470, 471,
468, 469, 295, 345, 346, 347, 348, 472, 473, 474,
475,32767,32767, 276, 533, 533,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767, 333,
334, 461, 462,32767, 232, 232, 232, 232, 277, 232,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767, 328, 329, 327, 456, 457, 455,
423,32767,32767,32767, 425,32767,32767,32767,32767,32767,
32767,32767,32767, 494,32767,32767,32767,32767,32767, 507,
412,32767, 404,32767,32767, 216, 218, 165,32767,32767,
480,32767,32767,32767,32767,32767, 512, 338,32767,32767,
114,32767,32767,32767, 549,32767, 507,32767, 114,32767,
32767,32767,32767, 351, 330, 331, 332,32767,32767, 511,
505, 464, 465, 466, 467,32767, 458, 459, 460, 463,
32767,32767,32767,32767,32767,32767,32767,32767, 169, 420,
426, 426,32767,32767,32767,32767, 169,32767,32767,32767,
32767,32767, 169,32767,32767,32767, 510, 509, 169,32767,
405, 488, 169, 182, 180, 180,32767, 202, 202,32767,
32767, 184, 481, 500,32767, 184, 169,32767, 393, 171,
488,32767,32767, 234,32767, 234,32767, 393, 169, 234,
32767,32767, 234,32767, 406, 430,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767, 372, 373, 483, 496,32767, 497,32767, 404, 336,
337, 339, 316,32767, 318, 362, 363, 364, 365, 366,
367, 368, 370,32767, 410,32767, 413,32767,32767,32767,
251,32767, 547,32767,32767, 300, 547,32767,32767,32767,
541,32767,32767, 294,32767,32767,32767,32767, 247,32767,
167,32767, 531,32767, 548,32767, 505,32767, 335,32767,
32767,32767,32767,32767,32767,32767,32767,32767, 506,32767,
32767,32767,32767, 223,32767, 443,32767, 114,32767,32767,
32767, 183,32767,32767, 298, 242,32767,32767, 540,32767,
32767,32767,32767,32767,32767,32767,32767, 112,32767, 168,
32767,32767,32767, 185,32767,32767, 505,32767,32767,32767,
32767,32767,32767,32767, 289,32767,32767,32767,32767,32767,
32767,32767, 505,32767,32767, 227,32767,32767,32767,32767,
32767,32767,32767,32767,32767, 406,32767, 270,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767, 125,
125, 3, 125, 125, 254, 3, 254, 125, 254, 254,
125, 125, 125, 125, 125, 125, 125, 125, 125, 125,
210, 213, 202, 202, 162, 125, 125, 262
);
protected $goto = array(
165, 139, 139, 139, 165, 143, 146, 140, 141, 142,
148, 186, 167, 162, 162, 162, 162, 143, 143, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
137, 158, 159, 160, 161, 183, 138, 184, 489, 490,
367, 491, 495, 496, 497, 498, 499, 500, 501, 502,
959, 163, 144, 145, 147, 170, 175, 185, 203, 251,
254, 256, 258, 260, 261, 262, 263, 264, 265, 273,
274, 275, 276, 299, 300, 324, 325, 326, 384, 385,
386, 538, 187, 188, 189, 190, 191, 192, 193, 194,
195, 196, 197, 198, 199, 200, 149, 150, 151, 166,
152, 168, 153, 204, 169, 154, 155, 156, 205, 157,
135, 616, 556, 574, 578, 622, 624, 556, 556, 556,
556, 556, 556, 556, 556, 556, 556, 556, 556, 556,
556, 556, 556, 556, 556, 556, 556, 556, 556, 556,
556, 556, 556, 556, 556, 556, 556, 556, 556, 556,
556, 556, 556, 556, 556, 556, 556, 556, 556, 556,
1099, 515, 345, 571, 600, 1099, 1099, 1099, 1099, 1099,
1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099,
1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099,
1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099,
1099, 1099, 1099, 1099, 1099, 1099, 1099, 1099, 504, 1202,
1202, 1075, 1074, 504, 540, 541, 542, 543, 544, 545,
546, 547, 549, 582, 3, 4, 173, 1202, 844, 844,
844, 844, 839, 845, 176, 177, 178, 391, 392, 393,
394, 172, 201, 206, 250, 255, 257, 259, 266, 267,
268, 269, 270, 271, 277, 278, 279, 280, 301, 302,
327, 328, 329, 396, 397, 398, 399, 174, 179, 252,
253, 180, 181, 182, 493, 493, 750, 493, 493, 493,
493, 493, 493, 493, 493, 493, 493, 493, 493, 493,
493, 505, 929, 442, 444, 627, 505, 751, 779, 1100,
610, 927, 880, 880, 765, 1190, 1190, 1168, 555, 775,
764, 743, 1168, 555, 555, 555, 555, 555, 555, 555,
555, 555, 555, 555, 555, 555, 555, 555, 555, 555,
555, 555, 555, 555, 555, 555, 555, 555, 555, 555,
555, 555, 555, 555, 555, 555, 555, 555, 555, 555,
555, 555, 555, 555, 555, 555, 390, 602, 746, 532,
532, 564, 528, 530, 530, 492, 494, 520, 536, 565,
568, 579, 586, 810, 606, 506, 346, 347, 609, 850,
506, 365, 537, 746, 533, 746, 563, 430, 430, 375,
430, 430, 430, 430, 430, 430, 430, 430, 430, 430,
430, 430, 430, 430, 1063, 581, 957, 596, 597, 1063,
887, 887, 887, 887, 1160, 887, 887, 1182, 1182, 1182,
376, 376, 376, 749, 1063, 1063, 1063, 1063, 1063, 1063,
334, 1056, 317, 374, 374, 374, 866, 848, 846, 848,
650, 461, 507, 875, 870, 376, 1194, 368, 374, 389,
374, 898, 374, 1080, 583, 348, 404, 374, 1216, 590,
601, 1017, 19, 15, 361, 1148, 1187, 525, 936, 904,
510, 526, 904, 651, 551, 381, 1201, 1201, 587, 1007,
550, 877, 607, 608, 873, 612, 613, 619, 621, 626,
628, 23, 884, 937, 1201, 336, 598, 1059, 1060, 1204,
378, 1056, 557, 539, 893, 768, 766, 379, 514, 902,
509, 524, 655, 1057, 1159, 1057, 776, 509, 1167, 524,
514, 514, 1058, 1167, 1049, 907, 508, 1054, 511, 433,
434, 510, 1184, 1184, 1184, 854, 445, 945, 569, 1145,
459, 362, 0, 0, 773, 1209, 0, 518, 0, 519,
0, 529, 0, 0, 0, 0, 0, 1166, 0, 0,
0, 771, 0, 0, 0, 449, 0, 0, 0, 0,
0, 0, 605, 0, 0, 0, 0, 13, 1055, 614
);
protected $gotoCheck = array(
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 56, 66, 59, 59, 59, 8, 66, 66, 66,
66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
124, 99, 69, 39, 39, 124, 124, 124, 124, 124,
124, 124, 124, 124, 124, 124, 124, 124, 124, 124,
124, 124, 124, 124, 124, 124, 124, 124, 124, 124,
124, 124, 124, 124, 124, 124, 124, 124, 124, 124,
124, 124, 124, 124, 124, 124, 124, 124, 66, 140,
140, 122, 122, 66, 108, 108, 108, 108, 108, 108,
108, 108, 108, 108, 29, 29, 26, 140, 66, 66,
66, 66, 66, 66, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 26, 26, 26, 26, 26, 26,
26, 26, 26, 26, 115, 115, 14, 115, 115, 115,
115, 115, 115, 115, 115, 115, 115, 115, 115, 115,
115, 115, 7, 7, 7, 7, 115, 15, 28, 7,
7, 7, 74, 74, 22, 74, 74, 116, 56, 22,
22, 5, 116, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 56, 50, 50, 10, 50,
50, 50, 50, 50, 50, 50, 50, 50, 50, 50,
50, 50, 50, 49, 60, 120, 69, 69, 60, 32,
120, 60, 2, 10, 107, 10, 2, 56, 56, 10,
56, 56, 56, 56, 56, 56, 56, 56, 56, 56,
56, 56, 56, 56, 56, 64, 99, 64, 64, 56,
56, 56, 56, 56, 79, 56, 56, 8, 8, 8,
121, 121, 121, 13, 56, 56, 56, 56, 56, 56,
123, 79, 123, 12, 12, 12, 13, 13, 13, 13,
13, 56, 13, 13, 13, 121, 138, 45, 12, 121,
12, 81, 12, 33, 67, 67, 67, 12, 12, 125,
48, 33, 33, 33, 33, 129, 136, 8, 95, 12,
12, 31, 12, 31, 31, 47, 139, 139, 31, 100,
33, 31, 31, 31, 31, 31, 31, 31, 31, 31,
31, 33, 76, 95, 139, 17, 33, 79, 79, 139,
11, 79, 11, 46, 78, 24, 23, 16, 46, 82,
8, 8, 71, 79, 79, 79, 25, 8, 117, 8,
46, 46, 79, 117, 111, 83, 8, 113, 8, 8,
8, 12, 117, 117, 117, 68, 62, 97, 63, 128,
106, 57, -1, -1, 8, 8, -1, 57, -1, 99,
-1, 57, -1, -1, -1, -1, -1, 117, -1, -1,
-1, 8, -1, -1, -1, 57, -1, -1, -1, -1,
-1, -1, 12, -1, -1, -1, -1, 57, 12, 12
);
protected $gotoBase = array(
0, 0, -249, 0, 0, 300, 0, 287, 105, 0,
47, 164, 118, 421, 274, 295, 171, 184, 0, 0,
0, 0, -49, 168, 172, 104, 24, 0, 288, -431,
0, -159, 359, 44, 0, 0, 0, 0, 0, 125,
0, 0, -24, 0, 0, 407, 479, 186, 178, 355,
75, 0, 0, 0, 0, 0, 106, 119, 0, -192,
-81, 0, 101, 93, -231, 0, -90, 135, 121, -276,
0, 148, 0, 0, 21, 0, 183, 0, 194, 71,
0, 423, 155, 112, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 185, 0, 122, 0, 120,
176, 0, 0, 0, 0, 0, 83, 358, 170, 0,
0, 113, 0, 111, 0, -7, 9, 220, 0, 0,
77, 108, -102, 100, -42, 251, 0, 0, 89, 256,
0, 0, 0, 0, 0, 0, 181, 0, 419, 160,
-107, 0, 0
);
protected $gotoDefault = array(
-32768, 463, 659, 2, 660, 733, 741, 593, 477, 625,
577, 370, 1178, 785, 786, 787, 371, 358, 478, 369,
400, 395, 774, 767, 769, 777, 171, 401, 780, 1,
782, 513, 818, 1008, 355, 790, 356, 585, 792, 522,
794, 795, 136, 372, 373, 523, 479, 380, 572, 809,
272, 377, 811, 357, 812, 821, 360, 460, 454, 552,
604, 425, 441, 566, 560, 531, 1072, 561, 853, 344,
861, 656, 869, 872, 480, 553, 883, 446, 891, 1085,
387, 897, 903, 908, 283, 911, 407, 402, 580, 916,
917, 5, 921, 617, 618, 8, 308, 944, 594, 958,
411, 1027, 1029, 481, 482, 517, 453, 503, 521, 483,
1050, 435, 403, 1053, 484, 485, 426, 427, 1069, 350,
1153, 349, 443, 316, 1140, 575, 1104, 450, 1193, 1149,
343, 486, 487, 366, 1172, 382, 1188, 431, 1195, 1203,
339, 535, 562
);
protected $ruleToNonTerminal = array(
0, 1, 3, 3, 2, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 6, 6, 6, 6, 6,
6, 6, 7, 7, 8, 9, 10, 10, 11, 11,
12, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 17, 17, 18, 18, 20, 20, 16, 16,
21, 21, 22, 22, 23, 23, 24, 24, 19, 19,
25, 27, 27, 28, 29, 29, 31, 30, 30, 30,
30, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
13, 13, 53, 53, 55, 54, 54, 47, 47, 57,
57, 58, 58, 14, 15, 15, 15, 61, 61, 61,
62, 62, 65, 65, 63, 63, 67, 67, 40, 40,
49, 49, 52, 52, 52, 51, 51, 68, 41, 41,
41, 41, 69, 69, 70, 70, 71, 71, 38, 38,
34, 34, 72, 36, 36, 73, 35, 35, 37, 37,
48, 48, 48, 59, 59, 75, 75, 76, 76, 78,
78, 78, 77, 77, 60, 60, 79, 79, 79, 80,
80, 81, 81, 81, 43, 43, 82, 82, 82, 44,
44, 83, 83, 84, 84, 64, 85, 85, 85, 85,
90, 90, 91, 91, 92, 92, 92, 92, 92, 93,
94, 94, 89, 89, 86, 86, 88, 88, 96, 96,
95, 95, 95, 95, 95, 95, 87, 87, 98, 97,
97, 45, 45, 39, 39, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
33, 33, 46, 46, 103, 103, 104, 104, 104, 104,
110, 99, 99, 106, 106, 112, 112, 113, 114, 114,
114, 114, 114, 114, 66, 66, 56, 56, 56, 56,
100, 100, 118, 118, 115, 115, 119, 119, 119, 119,
101, 101, 101, 105, 105, 105, 111, 111, 124, 124,
124, 124, 124, 124, 124, 124, 124, 124, 124, 124,
124, 26, 26, 26, 26, 26, 26, 126, 126, 126,
126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
126, 126, 126, 126, 126, 126, 126, 126, 126, 126,
109, 109, 102, 102, 102, 102, 125, 125, 128, 128,
127, 127, 129, 129, 50, 50, 50, 50, 131, 131,
130, 130, 130, 130, 130, 132, 132, 117, 117, 120,
120, 116, 116, 134, 133, 133, 133, 133, 121, 121,
121, 121, 108, 108, 122, 122, 122, 122, 74, 135,
135, 136, 136, 136, 107, 107, 137, 137, 138, 138,
138, 138, 138, 123, 123, 123, 123, 140, 141, 139,
139, 139, 139, 139, 139, 139, 142, 142, 142
);
protected $ruleToLength = array(
1, 1, 2, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 3, 5, 4, 3, 4,
2, 3, 1, 1, 7, 6, 3, 1, 3, 1,
3, 1, 1, 3, 1, 3, 1, 2, 3, 1,
3, 3, 1, 3, 2, 0, 1, 1, 1, 1,
1, 3, 5, 8, 3, 5, 9, 3, 2, 3,
2, 3, 2, 3, 3, 3, 3, 1, 2, 2,
5, 7, 9, 5, 6, 3, 3, 2, 2, 1,
1, 1, 0, 2, 8, 0, 4, 1, 3, 0,
1, 0, 1, 10, 7, 6, 5, 1, 2, 2,
0, 2, 0, 2, 0, 2, 1, 3, 1, 4,
1, 4, 1, 1, 4, 1, 3, 3, 3, 4,
4, 5, 0, 2, 4, 3, 1, 1, 1, 4,
0, 2, 3, 0, 2, 4, 0, 2, 0, 3,
1, 2, 1, 1, 0, 1, 3, 4, 6, 1,
1, 1, 0, 1, 0, 2, 2, 3, 3, 1,
3, 1, 2, 2, 3, 1, 1, 2, 4, 3,
1, 1, 3, 2, 0, 1, 3, 3, 9, 3,
1, 3, 0, 2, 4, 5, 4, 4, 4, 3,
1, 1, 1, 3, 1, 1, 0, 1, 1, 2,
1, 1, 1, 1, 1, 1, 1, 3, 1, 1,
3, 3, 1, 0, 1, 1, 3, 3, 4, 4,
1, 2, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 2, 2, 2, 2, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
1, 3, 5, 4, 3, 4, 4, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 1, 1, 1, 3, 2, 1, 2, 10, 11,
3, 3, 2, 4, 4, 3, 4, 4, 4, 4,
7, 3, 2, 0, 4, 1, 3, 2, 2, 4,
6, 2, 2, 4, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 3, 3, 4, 4,
0, 2, 1, 0, 1, 1, 0, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 3,
2, 1, 3, 1, 4, 3, 1, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 2, 2, 2, 2, 3, 3,
3, 3, 3, 3, 3, 3, 5, 4, 4, 3,
1, 3, 1, 1, 3, 3, 0, 2, 0, 1,
3, 1, 3, 1, 1, 1, 1, 1, 6, 4,
3, 4, 2, 4, 4, 1, 3, 1, 2, 1,
1, 4, 1, 1, 3, 6, 4, 4, 4, 4,
1, 4, 0, 1, 1, 3, 1, 1, 4, 3,
1, 1, 1, 0, 0, 2, 3, 1, 3, 1,
4, 2, 2, 2, 2, 1, 2, 1, 1, 1,
4, 3, 3, 3, 6, 3, 1, 1, 1
);
protected function initReduceCallbacks() {
$this->reduceCallbacks = [
0 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
1 => function ($stackPos) {
$this->semValue = $this->handleNamespaces($this->semStack[$stackPos-(1-1)]);
},
2 => function ($stackPos) {
if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; };
},
3 => function ($stackPos) {
$this->semValue = array();
},
4 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
5 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
6 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
7 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
8 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
9 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
10 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
11 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
12 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
13 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
14 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
15 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
16 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
17 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
18 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
19 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
20 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
21 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
22 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
23 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
24 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
25 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
26 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
27 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
28 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
29 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
30 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
31 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
32 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
33 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
34 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
35 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
36 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
37 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
38 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
39 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
40 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
41 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
42 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
43 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
44 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
45 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
46 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
47 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
48 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
49 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
50 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
51 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
52 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
53 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
54 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
55 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
56 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
57 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
58 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
59 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
60 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
61 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
62 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
63 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
64 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
65 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
66 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
67 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
68 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
69 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
70 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
71 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
72 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
73 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
74 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
75 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
76 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
77 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
78 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
79 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
80 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
81 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
82 => function ($stackPos) {
$this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
83 => function ($stackPos) {
$this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
84 => function ($stackPos) {
$this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
85 => function ($stackPos) {
$this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
86 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
87 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
88 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
89 => function ($stackPos) {
$this->semValue = new Name(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
90 => function ($stackPos) {
$this->semValue = new Expr\Variable(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
91 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
92 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
93 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
94 => function ($stackPos) {
$this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
95 => function ($stackPos) {
$this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(3-2)], null, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
$this->checkNamespace($this->semValue);
},
96 => function ($stackPos) {
$this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($this->semValue);
},
97 => function ($stackPos) {
$this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($this->semValue);
},
98 => function ($stackPos) {
$this->semValue = new Stmt\Use_($this->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
99 => function ($stackPos) {
$this->semValue = new Stmt\Use_($this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
100 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
101 => function ($stackPos) {
$this->semValue = new Stmt\Const_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
102 => function ($stackPos) {
$this->semValue = Stmt\Use_::TYPE_FUNCTION;
},
103 => function ($stackPos) {
$this->semValue = Stmt\Use_::TYPE_CONSTANT;
},
104 => function ($stackPos) {
$this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->semStack[$stackPos-(7-2)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
},
105 => function ($stackPos) {
$this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
106 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
107 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
108 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
109 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
110 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
111 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
112 => function ($stackPos) {
$this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1));
},
113 => function ($stackPos) {
$this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3));
},
114 => function ($stackPos) {
$this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1));
},
115 => function ($stackPos) {
$this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3));
},
116 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL;
},
117 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)]; $this->semValue->type = $this->semStack[$stackPos-(2-1)];
},
118 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
119 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
120 => function ($stackPos) {
$this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
121 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
122 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
123 => function ($stackPos) {
$this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
124 => function ($stackPos) {
if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; };
},
125 => function ($stackPos) {
$this->semValue = array();
},
126 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
127 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
128 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
129 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
130 => function ($stackPos) {
throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
131 => function ($stackPos) {
if ($this->semStack[$stackPos-(3-2)]) {
$this->semValue = $this->semStack[$stackPos-(3-2)]; $attrs = $this->startAttributeStack[$stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments'])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); };
} else {
$startAttributes = $this->startAttributeStack[$stackPos-(3-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; };
if (null === $this->semValue) { $this->semValue = array(); }
}
},
132 => function ($stackPos) {
$this->semValue = new Stmt\If_($this->semStack[$stackPos-(5-2)], ['stmts' => is_array($this->semStack[$stackPos-(5-3)]) ? $this->semStack[$stackPos-(5-3)] : array($this->semStack[$stackPos-(5-3)]), 'elseifs' => $this->semStack[$stackPos-(5-4)], 'else' => $this->semStack[$stackPos-(5-5)]], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
133 => function ($stackPos) {
$this->semValue = new Stmt\If_($this->semStack[$stackPos-(8-2)], ['stmts' => $this->semStack[$stackPos-(8-4)], 'elseifs' => $this->semStack[$stackPos-(8-5)], 'else' => $this->semStack[$stackPos-(8-6)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes);
},
134 => function ($stackPos) {
$this->semValue = new Stmt\While_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
135 => function ($stackPos) {
$this->semValue = new Stmt\Do_($this->semStack[$stackPos-(5-4)], is_array($this->semStack[$stackPos-(5-2)]) ? $this->semStack[$stackPos-(5-2)] : array($this->semStack[$stackPos-(5-2)]), $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
136 => function ($stackPos) {
$this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos-(9-3)], 'cond' => $this->semStack[$stackPos-(9-5)], 'loop' => $this->semStack[$stackPos-(9-7)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
137 => function ($stackPos) {
$this->semValue = new Stmt\Switch_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
138 => function ($stackPos) {
$this->semValue = new Stmt\Break_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
139 => function ($stackPos) {
$this->semValue = new Stmt\Break_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
140 => function ($stackPos) {
$this->semValue = new Stmt\Continue_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
141 => function ($stackPos) {
$this->semValue = new Stmt\Continue_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
142 => function ($stackPos) {
$this->semValue = new Stmt\Return_(null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
143 => function ($stackPos) {
$this->semValue = new Stmt\Return_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
144 => function ($stackPos) {
$this->semValue = new Stmt\Global_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
145 => function ($stackPos) {
$this->semValue = new Stmt\Static_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
146 => function ($stackPos) {
$this->semValue = new Stmt\Echo_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
147 => function ($stackPos) {
$this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
148 => function ($stackPos) {
$this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
149 => function ($stackPos) {
$this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
150 => function ($stackPos) {
$this->semValue = new Stmt\Unset_($this->semStack[$stackPos-(5-3)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
151 => function ($stackPos) {
$this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos-(7-5)][1], 'stmts' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
},
152 => function ($stackPos) {
$this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(9-3)], $this->semStack[$stackPos-(9-7)][0], ['keyVar' => $this->semStack[$stackPos-(9-5)], 'byRef' => $this->semStack[$stackPos-(9-7)][1], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
153 => function ($stackPos) {
$this->semValue = new Stmt\Declare_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
154 => function ($stackPos) {
$this->semValue = new Stmt\TryCatch($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-5)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkTryCatch($this->semValue);
},
155 => function ($stackPos) {
$this->semValue = new Stmt\Throw_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
156 => function ($stackPos) {
$this->semValue = new Stmt\Goto_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
157 => function ($stackPos) {
$this->semValue = new Stmt\Label($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
158 => function ($stackPos) {
$this->semValue = new Stmt\Expression($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
159 => function ($stackPos) {
$this->semValue = array(); /* means: no statement */
},
160 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
161 => function ($stackPos) {
$startAttributes = $this->startAttributeStack[$stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; };
if ($this->semValue === null) $this->semValue = array(); /* means: no statement */
},
162 => function ($stackPos) {
$this->semValue = array();
},
163 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
164 => function ($stackPos) {
$this->semValue = new Stmt\Catch_(array($this->semStack[$stackPos-(8-3)]), $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-7)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes);
},
165 => function ($stackPos) {
$this->semValue = null;
},
166 => function ($stackPos) {
$this->semValue = new Stmt\Finally_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
167 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
168 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
169 => function ($stackPos) {
$this->semValue = false;
},
170 => function ($stackPos) {
$this->semValue = true;
},
171 => function ($stackPos) {
$this->semValue = false;
},
172 => function ($stackPos) {
$this->semValue = true;
},
173 => function ($stackPos) {
$this->semValue = new Stmt\Function_($this->semStack[$stackPos-(10-3)], ['byRef' => $this->semStack[$stackPos-(10-2)], 'params' => $this->semStack[$stackPos-(10-5)], 'returnType' => $this->semStack[$stackPos-(10-7)], 'stmts' => $this->semStack[$stackPos-(10-9)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes);
},
174 => function ($stackPos) {
$this->semValue = new Stmt\Class_($this->semStack[$stackPos-(7-2)], ['type' => $this->semStack[$stackPos-(7-1)], 'extends' => $this->semStack[$stackPos-(7-3)], 'implements' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
$this->checkClass($this->semValue, $stackPos-(7-2));
},
175 => function ($stackPos) {
$this->semValue = new Stmt\Interface_($this->semStack[$stackPos-(6-2)], ['extends' => $this->semStack[$stackPos-(6-3)], 'stmts' => $this->semStack[$stackPos-(6-5)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
$this->checkInterface($this->semValue, $stackPos-(6-2));
},
176 => function ($stackPos) {
$this->semValue = new Stmt\Trait_($this->semStack[$stackPos-(5-2)], ['stmts' => $this->semStack[$stackPos-(5-4)]], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
177 => function ($stackPos) {
$this->semValue = 0;
},
178 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
},
179 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_FINAL;
},
180 => function ($stackPos) {
$this->semValue = null;
},
181 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)];
},
182 => function ($stackPos) {
$this->semValue = array();
},
183 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)];
},
184 => function ($stackPos) {
$this->semValue = array();
},
185 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)];
},
186 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
187 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
188 => function ($stackPos) {
$this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]);
},
189 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
190 => function ($stackPos) {
$this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]);
},
191 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
192 => function ($stackPos) {
$this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]);
},
193 => function ($stackPos) {
$this->semValue = null;
},
194 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
195 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
196 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
197 => function ($stackPos) {
$this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
198 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
199 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-3)];
},
200 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
201 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(5-3)];
},
202 => function ($stackPos) {
$this->semValue = array();
},
203 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
204 => function ($stackPos) {
$this->semValue = new Stmt\Case_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
205 => function ($stackPos) {
$this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
206 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
207 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
208 => function ($stackPos) {
$this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]);
},
209 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
210 => function ($stackPos) {
$this->semValue = array();
},
211 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
212 => function ($stackPos) {
$this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(3-2)], is_array($this->semStack[$stackPos-(3-3)]) ? $this->semStack[$stackPos-(3-3)] : array($this->semStack[$stackPos-(3-3)]), $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
213 => function ($stackPos) {
$this->semValue = array();
},
214 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
215 => function ($stackPos) {
$this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
216 => function ($stackPos) {
$this->semValue = null;
},
217 => function ($stackPos) {
$this->semValue = new Stmt\Else_(is_array($this->semStack[$stackPos-(2-2)]) ? $this->semStack[$stackPos-(2-2)] : array($this->semStack[$stackPos-(2-2)]), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
218 => function ($stackPos) {
$this->semValue = null;
},
219 => function ($stackPos) {
$this->semValue = new Stmt\Else_($this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
220 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)], false);
},
221 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(2-2)], true);
},
222 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)], false);
},
223 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
224 => function ($stackPos) {
$this->semValue = array();
},
225 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
226 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
227 => function ($stackPos) {
$this->semValue = new Node\Param($this->semStack[$stackPos-(4-4)], null, $this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes); $this->checkParam($this->semValue);
},
228 => function ($stackPos) {
$this->semValue = new Node\Param($this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-6)], $this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-3)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkParam($this->semValue);
},
229 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
230 => function ($stackPos) {
$this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
231 => function ($stackPos) {
$this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
232 => function ($stackPos) {
$this->semValue = null;
},
233 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
234 => function ($stackPos) {
$this->semValue = null;
},
235 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)];
},
236 => function ($stackPos) {
$this->semValue = array();
},
237 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
238 => function ($stackPos) {
$this->semValue = array(new Node\Arg($this->semStack[$stackPos-(3-2)], false, false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes));
},
239 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
240 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
241 => function ($stackPos) {
$this->semValue = new Node\Arg($this->semStack[$stackPos-(1-1)], false, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
242 => function ($stackPos) {
$this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], true, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
243 => function ($stackPos) {
$this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], false, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
244 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
245 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
246 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
247 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
248 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
249 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
250 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
251 => function ($stackPos) {
$this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
252 => function ($stackPos) {
$this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
253 => function ($stackPos) {
if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }
},
254 => function ($stackPos) {
$this->semValue = array();
},
255 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
256 => function ($stackPos) {
$this->semValue = new Stmt\Property($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkProperty($this->semValue, $stackPos-(3-1));
},
257 => function ($stackPos) {
$this->semValue = new Stmt\ClassConst($this->semStack[$stackPos-(3-2)], 0, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
258 => function ($stackPos) {
$this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos-(9-4)], ['type' => $this->semStack[$stackPos-(9-1)], 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-6)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
$this->checkClassMethod($this->semValue, $stackPos-(9-1));
},
259 => function ($stackPos) {
$this->semValue = new Stmt\TraitUse($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
260 => function ($stackPos) {
$this->semValue = array();
},
261 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
262 => function ($stackPos) {
$this->semValue = array();
},
263 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
264 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
265 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(5-1)][0], $this->semStack[$stackPos-(5-1)][1], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
266 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], null, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
267 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
268 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
269 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]);
},
270 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
271 => function ($stackPos) {
$this->semValue = array(null, $this->semStack[$stackPos-(1-1)]);
},
272 => function ($stackPos) {
$this->semValue = null;
},
273 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
274 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
275 => function ($stackPos) {
$this->semValue = 0;
},
276 => function ($stackPos) {
$this->semValue = 0;
},
277 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
278 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
279 => function ($stackPos) {
$this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)];
},
280 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PUBLIC;
},
281 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PROTECTED;
},
282 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PRIVATE;
},
283 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_STATIC;
},
284 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
},
285 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_FINAL;
},
286 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
287 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
288 => function ($stackPos) {
$this->semValue = new Node\VarLikeIdentifier(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
289 => function ($stackPos) {
$this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
290 => function ($stackPos) {
$this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
291 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
292 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
293 => function ($stackPos) {
$this->semValue = array();
},
294 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
295 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
296 => function ($stackPos) {
$this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
297 => function ($stackPos) {
$this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
298 => function ($stackPos) {
$this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
299 => function ($stackPos) {
$this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
300 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
301 => function ($stackPos) {
$this->semValue = new Expr\Clone_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
302 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
303 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
304 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
305 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
306 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
307 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
308 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
309 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
310 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
311 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
312 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
313 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
314 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
315 => function ($stackPos) {
$this->semValue = new Expr\PostInc($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
316 => function ($stackPos) {
$this->semValue = new Expr\PreInc($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
317 => function ($stackPos) {
$this->semValue = new Expr\PostDec($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
318 => function ($stackPos) {
$this->semValue = new Expr\PreDec($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
319 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
320 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
321 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
322 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
323 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
324 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
325 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
326 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
327 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
328 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
329 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
330 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
331 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
332 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
333 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
334 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
335 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
336 => function ($stackPos) {
$this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
337 => function ($stackPos) {
$this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
338 => function ($stackPos) {
$this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
339 => function ($stackPos) {
$this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
340 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
341 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
342 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
343 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
344 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
345 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
346 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
347 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
348 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
349 => function ($stackPos) {
$this->semValue = new Expr\Instanceof_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
350 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
351 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
352 => function ($stackPos) {
$this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
353 => function ($stackPos) {
$this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
354 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
355 => function ($stackPos) {
$this->semValue = new Expr\Isset_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
356 => function ($stackPos) {
$this->semValue = new Expr\Empty_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
357 => function ($stackPos) {
$this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
358 => function ($stackPos) {
$this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
359 => function ($stackPos) {
$this->semValue = new Expr\Eval_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
360 => function ($stackPos) {
$this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
361 => function ($stackPos) {
$this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
362 => function ($stackPos) {
$this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
363 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes;
$attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos-(2-1)]);
$this->semValue = new Expr\Cast\Double($this->semStack[$stackPos-(2-2)], $attrs);
},
364 => function ($stackPos) {
$this->semValue = new Expr\Cast\String_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
365 => function ($stackPos) {
$this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
366 => function ($stackPos) {
$this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
367 => function ($stackPos) {
$this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
368 => function ($stackPos) {
$this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
369 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes;
$attrs['kind'] = strtolower($this->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
$this->semValue = new Expr\Exit_($this->semStack[$stackPos-(2-2)], $attrs);
},
370 => function ($stackPos) {
$this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
371 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
372 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
373 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
374 => function ($stackPos) {
$this->semValue = new Expr\ShellExec($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
375 => function ($stackPos) {
$this->semValue = new Expr\Print_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
376 => function ($stackPos) {
$this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
377 => function ($stackPos) {
$this->semValue = new Expr\YieldFrom($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
378 => function ($stackPos) {
$this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(10-2)], 'params' => $this->semStack[$stackPos-(10-4)], 'uses' => $this->semStack[$stackPos-(10-6)], 'returnType' => $this->semStack[$stackPos-(10-7)], 'stmts' => $this->semStack[$stackPos-(10-9)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes);
},
379 => function ($stackPos) {
$this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(11-3)], 'params' => $this->semStack[$stackPos-(11-5)], 'uses' => $this->semStack[$stackPos-(11-7)], 'returnType' => $this->semStack[$stackPos-(11-8)], 'stmts' => $this->semStack[$stackPos-(11-10)]], $this->startAttributeStack[$stackPos-(11-1)] + $this->endAttributes);
},
380 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
381 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
382 => function ($stackPos) {
$this->semValue = new Expr\Yield_($this->semStack[$stackPos-(2-2)], null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
383 => function ($stackPos) {
$this->semValue = new Expr\Yield_($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
384 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG;
$this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $attrs);
},
385 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT;
$this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $attrs);
},
386 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
387 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(4-1)][0] === "'" || ($this->semStack[$stackPos-(4-1)][1] === "'" && ($this->semStack[$stackPos-(4-1)][0] === 'b' || $this->semStack[$stackPos-(4-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
$this->semValue = new Expr\ArrayDimFetch(new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(4-1)]), $attrs), $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
388 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
389 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
390 => function ($stackPos) {
$this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos-(7-3)], 'implements' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes), $this->semStack[$stackPos-(7-2)]);
$this->checkClass($this->semValue[0], -1);
},
391 => function ($stackPos) {
$this->semValue = new Expr\New_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
392 => function ($stackPos) {
list($class, $ctorArgs) = $this->semStack[$stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
393 => function ($stackPos) {
$this->semValue = array();
},
394 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-3)];
},
395 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
396 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
397 => function ($stackPos) {
$this->semValue = new Expr\ClosureUse($this->semStack[$stackPos-(2-2)], $this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
398 => function ($stackPos) {
$this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
399 => function ($stackPos) {
$this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
400 => function ($stackPos) {
$this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
401 => function ($stackPos) {
$this->semValue = $this->fixupPhp5StaticPropCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
402 => function ($stackPos) {
$this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
403 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
404 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
405 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
406 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
407 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
408 => function ($stackPos) {
$this->semValue = new Name\FullyQualified(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
409 => function ($stackPos) {
$this->semValue = new Name\Relative(substr($this->semStack[$stackPos-(1-1)], 10), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
410 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
411 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
412 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
413 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
414 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
415 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
416 => function ($stackPos) {
$this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
417 => function ($stackPos) {
$this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
418 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
419 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
420 => function ($stackPos) {
$this->semValue = null;
},
421 => function ($stackPos) {
$this->semValue = null;
},
422 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
423 => function ($stackPos) {
$this->semValue = array();
},
424 => function ($stackPos) {
$this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos-(1-1)], '`', false), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes));
},
425 => function ($stackPos) {
foreach ($this->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', false); } }; $this->semValue = $this->semStack[$stackPos-(1-1)];
},
426 => function ($stackPos) {
$this->semValue = array();
},
427 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
428 => function ($stackPos) {
$this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes, true);
},
429 => function ($stackPos) {
$this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos-(1-1)]), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
430 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(1-1)][0] === "'" || ($this->semStack[$stackPos-(1-1)][1] === "'" && ($this->semStack[$stackPos-(1-1)][0] === 'b' || $this->semStack[$stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
$this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(1-1)], false), $attrs);
},
431 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
432 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
433 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
434 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
435 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
436 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
437 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
438 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
439 => function ($stackPos) {
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], false);
},
440 => function ($stackPos) {
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], false);
},
441 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
442 => function ($stackPos) {
$this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
443 => function ($stackPos) {
$this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
444 => function ($stackPos) {
$this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
445 => function ($stackPos) {
$this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
446 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
447 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
448 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
449 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
450 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
451 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
452 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
453 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
454 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
455 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
456 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
457 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
458 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
459 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
460 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
461 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
462 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
463 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
464 => function ($stackPos) {
$this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
465 => function ($stackPos) {
$this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
466 => function ($stackPos) {
$this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
467 => function ($stackPos) {
$this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
468 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
469 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
470 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
471 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
472 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
473 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
474 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
475 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
476 => function ($stackPos) {
$this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
477 => function ($stackPos) {
$this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
478 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
479 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
480 => function ($stackPos) {
$this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
481 => function ($stackPos) {
$this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
482 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
483 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
484 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
},
485 => function ($stackPos) {
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
486 => function ($stackPos) {
$this->semValue = array();
},
487 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
488 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
489 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
490 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
491 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
492 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
493 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
494 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
495 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
496 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
497 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
498 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
499 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
500 => function ($stackPos) {
$this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
501 => function ($stackPos) {
$this->semValue = new Expr\MethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
502 => function ($stackPos) {
$this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
503 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
504 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
505 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
506 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
507 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
508 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
509 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
510 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
511 => function ($stackPos) {
$this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
512 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
513 => function ($stackPos) {
$var = substr($this->semStack[$stackPos-(1-1)], 1); $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes) : $var;
},
514 => function ($stackPos) {
$this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
515 => function ($stackPos) {
$this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(6-1)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
516 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
517 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
518 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
519 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
520 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
521 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
522 => function ($stackPos) {
$this->semValue = null;
},
523 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
524 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
525 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
526 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
527 => function ($stackPos) {
$this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2;
},
528 => function ($stackPos) {
$this->semValue = new Expr\List_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
529 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
530 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
531 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
532 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
533 => function ($stackPos) {
$this->semValue = null;
},
534 => function ($stackPos) {
$this->semValue = array();
},
535 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
536 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
537 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
538 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
539 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
540 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-1)], true, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
541 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
542 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
543 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
544 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
545 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
546 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]);
},
547 => function ($stackPos) {
$this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
548 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
549 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
550 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
551 => function ($stackPos) {
$this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
552 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
553 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
554 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-4)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
555 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
556 => function ($stackPos) {
$this->semValue = new Scalar\String_($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
557 => function ($stackPos) {
$this->semValue = $this->parseNumString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
558 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
];
}
}
<?php
namespace PhpParser\Parser;
use PhpParser\Error;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar;
use PhpParser\Node\Stmt;
/* This is an automatically GENERATED file, which should not be manually edited.
* Instead edit one of the following:
* * the grammar files grammar/php5.y or grammar/php7.y
* * the skeleton file grammar/parser.template
* * the preprocessing script grammar/rebuildParsers.php
*/
class Php7 extends \PhpParser\ParserAbstract
{
protected $tokenToSymbolMapSize = 392;
protected $actionTableSize = 1162;
protected $gotoTableSize = 588;
protected $invalidSymbol = 165;
protected $errorSymbol = 1;
protected $defaultAction = -32766;
protected $unexpectedTokenRule = 32767;
protected $YY2TBLSTATE = 397;
protected $numNonLeafStates = 688;
protected $symbolToName = array(
"EOF",
"error",
"T_THROW",
"T_INCLUDE",
"T_INCLUDE_ONCE",
"T_EVAL",
"T_REQUIRE",
"T_REQUIRE_ONCE",
"','",
"T_LOGICAL_OR",
"T_LOGICAL_XOR",
"T_LOGICAL_AND",
"T_PRINT",
"T_YIELD",
"T_DOUBLE_ARROW",
"T_YIELD_FROM",
"'='",
"T_PLUS_EQUAL",
"T_MINUS_EQUAL",
"T_MUL_EQUAL",
"T_DIV_EQUAL",
"T_CONCAT_EQUAL",
"T_MOD_EQUAL",
"T_AND_EQUAL",
"T_OR_EQUAL",
"T_XOR_EQUAL",
"T_SL_EQUAL",
"T_SR_EQUAL",
"T_POW_EQUAL",
"T_COALESCE_EQUAL",
"'?'",
"':'",
"T_COALESCE",
"T_BOOLEAN_OR",
"T_BOOLEAN_AND",
"'|'",
"'^'",
"'&'",
"T_IS_EQUAL",
"T_IS_NOT_EQUAL",
"T_IS_IDENTICAL",
"T_IS_NOT_IDENTICAL",
"T_SPACESHIP",
"'<'",
"T_IS_SMALLER_OR_EQUAL",
"'>'",
"T_IS_GREATER_OR_EQUAL",
"T_SL",
"T_SR",
"'+'",
"'-'",
"'.'",
"'*'",
"'/'",
"'%'",
"'!'",
"T_INSTANCEOF",
"'~'",
"T_INC",
"T_DEC",
"T_INT_CAST",
"T_DOUBLE_CAST",
"T_STRING_CAST",
"T_ARRAY_CAST",
"T_OBJECT_CAST",
"T_BOOL_CAST",
"T_UNSET_CAST",
"'@'",
"T_POW",
"'['",
"T_NEW",
"T_CLONE",
"T_EXIT",
"T_IF",
"T_ELSEIF",
"T_ELSE",
"T_ENDIF",
"T_LNUMBER",
"T_DNUMBER",
"T_STRING",
"T_STRING_VARNAME",
"T_VARIABLE",
"T_NUM_STRING",
"T_INLINE_HTML",
"T_ENCAPSED_AND_WHITESPACE",
"T_CONSTANT_ENCAPSED_STRING",
"T_ECHO",
"T_DO",
"T_WHILE",
"T_ENDWHILE",
"T_FOR",
"T_ENDFOR",
"T_FOREACH",
"T_ENDFOREACH",
"T_DECLARE",
"T_ENDDECLARE",
"T_AS",
"T_SWITCH",
"T_MATCH",
"T_ENDSWITCH",
"T_CASE",
"T_DEFAULT",
"T_BREAK",
"T_CONTINUE",
"T_GOTO",
"T_FUNCTION",
"T_FN",
"T_CONST",
"T_RETURN",
"T_TRY",
"T_CATCH",
"T_FINALLY",
"T_USE",
"T_INSTEADOF",
"T_GLOBAL",
"T_STATIC",
"T_ABSTRACT",
"T_FINAL",
"T_PRIVATE",
"T_PROTECTED",
"T_PUBLIC",
"T_VAR",
"T_UNSET",
"T_ISSET",
"T_EMPTY",
"T_HALT_COMPILER",
"T_CLASS",
"T_TRAIT",
"T_INTERFACE",
"T_EXTENDS",
"T_IMPLEMENTS",
"T_OBJECT_OPERATOR",
"T_NULLSAFE_OBJECT_OPERATOR",
"T_LIST",
"T_ARRAY",
"T_CALLABLE",
"T_CLASS_C",
"T_TRAIT_C",
"T_METHOD_C",
"T_FUNC_C",
"T_LINE",
"T_FILE",
"T_START_HEREDOC",
"T_END_HEREDOC",
"T_DOLLAR_OPEN_CURLY_BRACES",
"T_CURLY_OPEN",
"T_PAAMAYIM_NEKUDOTAYIM",
"T_NAMESPACE",
"T_NS_C",
"T_DIR",
"T_NS_SEPARATOR",
"T_ELLIPSIS",
"T_NAME_FULLY_QUALIFIED",
"T_NAME_QUALIFIED",
"T_NAME_RELATIVE",
"T_ATTRIBUTE",
"';'",
"']'",
"'{'",
"'}'",
"'('",
"')'",
"'`'",
"'\"'",
"'$'"
);
protected $tokenToSymbol = array(
0, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 55, 163, 165, 164, 54, 37, 165,
160, 161, 52, 49, 8, 50, 51, 53, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 31, 156,
43, 16, 45, 30, 67, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 69, 165, 157, 36, 165, 162, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 158, 35, 159, 57, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
165, 165, 165, 165, 165, 165, 1, 2, 3, 4,
5, 6, 7, 9, 10, 11, 12, 13, 14, 15,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
27, 28, 29, 32, 33, 34, 38, 39, 40, 41,
42, 44, 46, 47, 48, 56, 58, 59, 60, 61,
62, 63, 64, 65, 66, 68, 70, 71, 72, 73,
74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93,
94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
114, 115, 116, 117, 118, 119, 120, 121, 122, 123,
124, 125, 126, 127, 128, 129, 130, 131, 132, 133,
134, 135, 136, 137, 138, 139, 140, 141, 142, 143,
144, 145, 146, 147, 148, 149, 150, 151, 152, 153,
154, 155
);
protected $action = array(
130, 131, 132, 552, 133, 134, 0, 698, 699, 700,
135, 36, 883, 528, 529,-32766, 1212,-32766,-32766,-32766,
-551, 1145, 772, 889, 430, 431, 1232, -551,-32766,-32766,
-32766, -293,-32766, 1231,-32766, 245,-32766, -189,-32766,-32766,
-32766,-32766,-32766, 458,-32766,-32766,-32766,-32766,-32766,-32766,
-32766,-32766, 124, 783, 701, 777,-32766, 388, 1024, 1025,
1026, 1023, 1022, 1021,-32766, 428, 429, 955, 261, 136,
372, 705, 706, 707, 708, 391, -188, 397, 1024, 1025,
1026, 1023, 1022, 1021, 709, 710, 711, 712, 713, 714,
715, 716, 717, 718, 719, 739, 553, 740, 741, 742,
743, 731, 732, 373, 374, 734, 735, 720, 721, 722,
724, 725, 726, 333, 765, 766, 767, 768, 769, 727,
728, 554, 555, 760, 751, 749, 750, 746, 747, 778,
2, 556, 557, 745, 558, 559, 560, 561, 562, 563,
-542, -548, 19, -502, -542, 748, 564, 565, -548, 137,
-32766,-32766,-32766, 130, 131, 132, 552, 133, 134, 976,
698, 699, 700, 135, 36,-32766,-32766,-32766,-32766, 675,
-32766,-32766,-32766, 80, 1145, 544, -551,-32766,-32766, 309,
-551,-32766,-32766,-32766, -293,-32766,-32766,-32766, 245,-32766,
-189,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766,-32766,
31, 433, 429,-32766,-32766, -502, -502, 701, 782,-32766,
388, 391,-32766,-32766,-32766, 235, 126,-32766, -82, 142,
-502, 261, 136, 372, 705, 706, 707, 708, 247, -188,
397, 292, -502,-32766, -508,-32766,-32766, 709, 710, 711,
712, 713, 714, 715, 716, 717, 718, 719, 739, 553,
740, 741, 742, 743, 731, 732, 373, 374, 734, 735,
720, 721, 722, 724, 725, 726, 333, 765, 766, 767,
768, 769, 727, 728, 554, 555, 760, 751, 749, 750,
746, 747, 294, -82, 556, 557, 745, 558, 559, 560,
561, 562, 563, 310, 81, 82, 83, -548, 748, 564,
565, -548, 137, 723, 693, 694, 695, 696, 697,-32766,
698, 699, 700, 736, 737, 33, 307, 84, 85, 86,
87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
322, 263,-32766,-32766,-32766, 104, 105, 106, 346, 263,
952, 951, 950, 107, 350, 438, 439, 701,-32766,-32766,
-32766, 107, -253,-32766, 355,-32766,-32766,-32766,-32766,-32766,
-32766, 702, 703, 704, 705, 706, 707, 708, 452,-32766,
770,-32766,-32766,-32766,-32766,-32766, 357, 709, 710, 711,
712, 713, 714, 715, 716, 717, 718, 719, 739, 762,
740, 741, 742, 743, 731, 732, 733, 761, 734, 735,
720, 721, 722, 724, 725, 726, 764, 765, 766, 767,
768, 769, 727, 728, 729, 730, 760, 751, 749, 750,
746, 747, 619, 24, 738, 744, 745, 752, 753, 755,
754, 756, 757, 524,-32766,-32766,-32766, 574, 748, 759,
758, 48, 49, 50, 483, 51, 52, 147, 397, 580,
408, 53, 54, 409, 55,-32766, 975,-32766,-32766,-32766,
-32766,-32766,-32766,-32767,-32767,-32767,-32767,-32767, 865,-32767,
-32767,-32767,-32767, 99, 100, 101, 102, 103, 1257, 410,
1172, 1258, 411, 1145, 865, 271, 634, 635, 56, 57,
148, 808, 1184, 809, 58, 453, 59, 240, 241, 60,
61, 62, 63, 64, 65, 66, 67, 787, 26, 262,
68, 412, 484, 121, 667,-32766, 1178, 1179, 485, 1143,
781, 1147, 1146, 1148, 1176, 40, 23, 486, 1002, 487,
150, 488, 234, 489, 962, 963, 490, 491, 780, 423,
424, 42, 43, 413, 417, 415, 865, 44, 492, 151,
855, 920, 248, 345, 321, 1152, 1147, 1146, 1148, 122,
781, 493, 494, 495, 152, -330, 855, -330, 127, -505,
960, 154, 496, 497, 35, 1166, 1167, 1168, 1169, 1163,
1164, 280, 146, 377, 26, -14, 128, 1170, 1165, 962,
963, 1147, 1146, 1148, 281, 141, 781, -501, 155, 69,
1176, 305, 306, 309, 34, 108, 109, 110, 111, 112,
113, 114, 115, 116, 117, 118, 119, 120, 156, -149,
-149, -149, 478, 867, 18, 662, 1152, 1152, 855, 440,
441, -505, -505, 157, -149, 781, -149, 138, -149, 867,
-149, 662, 808, 309, 809, 242, 1060, 1062, 496, 497,
414, 1166, 1167, 1168, 1169, 1163, 1164, -500, -505, -501,
-501, -107, -107, 1170, 1165, -503, 1228, 865, 611, 612,
841, -107, -107, -107, -501, 71, 921, -84, 306, 309,
-107,-32766, -76, 1001, -49, 686, -501, 1145, -507, -73,
-71, 774, -70, -69,-32766,-32766,-32766, 668,-32766, -68,
-32766, 867,-32766, 662, -149,-32766, 781, 781, -67, 281,
-32766,-32766,-32766, -66, 73, -65,-32766,-32766, 309, -500,
-500, 129,-32766, 388, -64, -45,-32766, -503, -503, -16,
-32766, 145, 1145, 264, -500, 676, 865, 679, 864,-32766,
-32766,-32766, -503,-32766, 772,-32766, -500,-32766, 144, 855,
-32766, 272, -107, 273, -503,-32766,-32766,-32766, 879, 72,
244,-32766,-32766,-32766, 275, 776, 669,-32766, 388, 1145,
664, 865, -500, 274, 276,-32766,-32766,-32766,-32766, 315,
-32766, 281,-32766, 263,-32766, 73, 73,-32766, 107, 309,
309, 143,-32766,-32766,-32766, 642,-32766, 246,-32766,-32766,
532, 671, 1145, 772,-32766, 388, -4, 865, 1259,-32766,
-32766,-32766,-32766,-32766, 781,-32766,-32766,-32766, 855, 1030,
-32766, 865, 867, 139, 662,-32766,-32766,-32766, 655, 309,
865,-32766,-32766,-32766, -500, -500, 526,-32766, 388, 1145,
101, 102, 103, 620, 637,-32766,-32766,-32766,-32766, -500,
-32766, 960,-32766, 855,-32766, 20, 865,-32766,-32766, 625,
677, -500,-32766,-32766,-32766, 435,-32766, 463,-32766,-32766,
962, 963, 1145, 626,-32766, 388, 638, 962, 963,-32766,
-32766,-32766,-32766,-32766, 609,-32766, 289,-32766, 46, 855,
-32766, 906, 407, 662,-32766,-32766,-32766,-32766, 287, 1016,
1183,-32766,-32766, 855, 286, 293, 781,-32766, 388, 1247,
890, 414, 855, 402, 891,-32766, 881, 538, 279, -231,
-231, -231, -107, -107, 1000, 414, 867, 26, 662, 1185,
578, 800, -107, -107, -107, -466, -107, -107, 855, 781,
47, -456, 7, 1176, 22, 841, -107, -107, -107, 348,
282, 283, 780, 9, -230, -230, -230, 281, 1173, -536,
414, 38, 867, 39, 662, -4, 683, 684, 846, 32,
243, -107, -107, 930, 907, 680, 867, 123, 662, -231,
841, -107, -107, -107, 914, 867, 904, 662, 915, 844,
902, 1005, 497, 1008, 1166, 1167, 1168, 1169, 1163, 1164,
1009, 1006, 284, 285, 1007, 1013, 1170, 1165, 792, 1198,
1216, 867, 30, 662, -230, 304, 1250, 349, 71, 614,
842, 306, 309, 347, 663, 666, 670, 672, -107, 125,
-107, 673, 674, 678, 665, 288, 1254, 1256, -107, -107,
-107, -107, -107, -107, -107, 803, 802, 811, 888, 922,
810, 1255, 887, 886, 1131, 874, 882, 872, 912, 913,
1253, 1210, 1199, 1217, 1223, 1226, 0, -534, -508, -507,
-506, 1, 27, 28, 37, 41, 45, 70, 74, 75,
76, 77, -307, -256, 78, 79, 140, 149, 153, 239,
311, 334, 335, 336, 337, 338, 339, 340, 341, 342,
343, 344, 403, 404, 0, -254, -253, 12, 13, 14,
15, 17, 376, 454, 455, 462, 465, 466, 467, 468,
472, 473, 474, 481, 649, 1156, 1099, 1174, 977, 1135,
-258, -99, 11, 16, 25, 278, 375, 571, 575, 601,
654, 1103, 1151, 1100, 1229, 0, -470, 1116, 0, 1177,
0, 309
);
protected $actionCheck = array(
2, 3, 4, 5, 6, 7, 0, 9, 10, 11,
12, 13, 1, 116, 117, 73, 1, 9, 10, 11,
1, 79, 79, 126, 127, 128, 1, 8, 86, 87,
88, 8, 90, 8, 92, 37, 94, 8, 30, 97,
32, 33, 34, 101, 102, 103, 104, 9, 10, 11,
108, 109, 14, 1, 56, 79, 114, 115, 115, 116,
117, 118, 119, 120, 122, 105, 106, 1, 70, 71,
72, 73, 74, 75, 76, 115, 8, 79, 115, 116,
117, 118, 119, 120, 86, 87, 88, 89, 90, 91,
92, 93, 94, 95, 96, 97, 98, 99, 100, 101,
102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
122, 123, 124, 125, 126, 127, 128, 129, 130, 153,
8, 133, 134, 135, 136, 137, 138, 139, 140, 141,
157, 1, 8, 69, 161, 147, 148, 149, 8, 151,
9, 10, 11, 2, 3, 4, 5, 6, 7, 161,
9, 10, 11, 12, 13, 9, 10, 11, 73, 158,
9, 10, 11, 158, 79, 80, 157, 9, 10, 164,
161, 86, 87, 88, 161, 90, 30, 92, 37, 94,
161, 30, 97, 32, 33, 34, 35, 102, 103, 104,
8, 105, 106, 108, 109, 131, 132, 56, 156, 114,
115, 115, 9, 10, 11, 14, 8, 122, 31, 8,
146, 70, 71, 72, 73, 74, 75, 76, 8, 161,
79, 8, 158, 30, 160, 32, 33, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
119, 120, 121, 122, 123, 124, 125, 126, 127, 128,
129, 130, 8, 96, 133, 134, 135, 136, 137, 138,
139, 140, 141, 69, 9, 10, 11, 157, 147, 148,
149, 161, 151, 2, 3, 4, 5, 6, 7, 9,
9, 10, 11, 12, 13, 30, 8, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
8, 56, 9, 10, 11, 52, 53, 54, 8, 56,
118, 119, 120, 68, 8, 131, 132, 56, 9, 10,
11, 68, 161, 30, 8, 32, 33, 34, 35, 36,
37, 70, 71, 72, 73, 74, 75, 76, 31, 30,
79, 32, 33, 34, 35, 36, 8, 86, 87, 88,
89, 90, 91, 92, 93, 94, 95, 96, 97, 98,
99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
109, 110, 111, 112, 113, 114, 115, 116, 117, 118,
119, 120, 121, 122, 123, 124, 125, 126, 127, 128,
129, 130, 74, 75, 133, 134, 135, 136, 137, 138,
139, 140, 141, 84, 9, 10, 11, 1, 147, 148,
149, 2, 3, 4, 5, 6, 7, 14, 79, 50,
8, 12, 13, 8, 15, 30, 1, 32, 33, 34,
35, 36, 37, 38, 39, 40, 41, 42, 1, 43,
44, 45, 46, 47, 48, 49, 50, 51, 79, 8,
1, 82, 8, 79, 1, 30, 74, 75, 49, 50,
14, 105, 143, 107, 55, 158, 57, 58, 59, 60,
61, 62, 63, 64, 65, 66, 67, 8, 69, 70,
71, 72, 73, 16, 31, 115, 77, 78, 79, 115,
81, 152, 153, 154, 85, 86, 87, 88, 159, 90,
14, 92, 96, 94, 134, 135, 97, 98, 152, 105,
106, 102, 103, 104, 105, 106, 1, 108, 109, 14,
83, 31, 37, 114, 115, 1, 152, 153, 154, 16,
81, 122, 123, 124, 14, 105, 83, 107, 16, 69,
115, 14, 133, 134, 14, 136, 137, 138, 139, 140,
141, 142, 100, 101, 69, 31, 16, 148, 149, 134,
135, 152, 153, 154, 155, 16, 81, 69, 16, 160,
85, 162, 163, 164, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 16, 74,
75, 76, 105, 156, 107, 158, 1, 1, 83, 105,
106, 131, 132, 16, 89, 81, 91, 158, 93, 156,
95, 158, 105, 164, 107, 37, 58, 59, 133, 134,
105, 136, 137, 138, 139, 140, 141, 69, 158, 131,
132, 116, 117, 148, 149, 69, 1, 1, 110, 111,
125, 126, 127, 128, 146, 160, 156, 31, 163, 164,
126, 73, 31, 156, 31, 158, 158, 79, 160, 31,
31, 79, 31, 31, 86, 87, 88, 31, 90, 31,
92, 156, 94, 158, 159, 97, 81, 81, 31, 155,
102, 103, 104, 31, 160, 31, 108, 109, 164, 131,
132, 31, 114, 115, 31, 31, 73, 131, 132, 31,
122, 31, 79, 31, 146, 31, 1, 31, 31, 86,
87, 88, 146, 90, 79, 92, 158, 94, 31, 83,
97, 35, 126, 35, 158, 102, 103, 104, 37, 151,
37, 108, 109, 73, 35, 153, 31, 114, 115, 79,
158, 1, 69, 30, 35, 122, 86, 87, 88, 35,
90, 155, 92, 56, 94, 160, 160, 97, 68, 164,
164, 69, 102, 103, 104, 76, 73, 37, 108, 109,
88, 31, 79, 79, 114, 115, 0, 1, 82, 86,
87, 88, 122, 90, 81, 92, 84, 94, 83, 81,
97, 1, 156, 158, 158, 102, 103, 104, 91, 164,
1, 108, 109, 73, 131, 132, 84, 114, 115, 79,
49, 50, 51, 89, 93, 122, 86, 87, 88, 146,
90, 115, 92, 83, 94, 96, 1, 97, 115, 95,
31, 158, 102, 103, 104, 96, 73, 96, 108, 109,
134, 135, 79, 99, 114, 115, 99, 134, 135, 86,
87, 88, 122, 90, 112, 92, 113, 94, 69, 83,
97, 156, 126, 158, 115, 102, 103, 104, 130, 121,
143, 108, 109, 83, 129, 129, 81, 114, 115, 84,
126, 105, 83, 107, 126, 122, 151, 150, 112, 99,
100, 101, 116, 117, 1, 105, 156, 69, 158, 143,
150, 125, 126, 127, 128, 146, 116, 117, 83, 81,
69, 146, 146, 85, 146, 125, 126, 127, 128, 146,
131, 132, 152, 147, 99, 100, 101, 155, 157, 160,
105, 156, 156, 156, 158, 159, 156, 156, 156, 144,
145, 116, 117, 156, 156, 159, 156, 158, 158, 159,
125, 126, 127, 128, 156, 156, 156, 158, 156, 156,
156, 156, 134, 156, 136, 137, 138, 139, 140, 141,
156, 156, 131, 132, 156, 156, 148, 149, 157, 157,
157, 156, 158, 158, 159, 158, 157, 146, 160, 157,
159, 163, 164, 158, 158, 158, 158, 158, 105, 158,
107, 158, 158, 158, 158, 112, 159, 159, 115, 116,
117, 118, 119, 120, 121, 159, 159, 159, 159, 159,
159, 159, 159, 159, 159, 159, 159, 159, 159, 159,
159, 159, 159, 159, 159, 159, -1, 160, 160, 160,
160, 160, 160, 160, 160, 160, 160, 160, 160, 160,
160, 160, 159, 161, 160, 160, 160, 160, 160, 160,
160, 160, 160, 160, 160, 160, 160, 160, 160, 160,
160, 160, 160, 160, -1, 161, 161, 161, 161, 161,
161, 161, 161, 161, 161, 161, 161, 161, 161, 161,
161, 161, 161, 161, 161, 161, 161, 161, 161, 161,
161, 161, 161, 161, 161, 161, 161, 161, 161, 161,
161, 161, 161, 161, 161, -1, 162, 162, -1, 163,
-1, 164
);
protected $actionBase = array(
0, -2, 151, 555, 816, 830, 865, 489, 379, 622,
858, 676, 780, 780, 839, 780, 493, 745, 301, 301,
-57, 301, 301, 477, 477, 477, 618, 618, 618, 618,
-58, -58, 95, 700, 733, 770, 663, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 803, 803,
803, 803, 803, 803, 803, 803, 803, 803, 52, 530,
446, 570, 984, 990, 986, 991, 982, 981, 985, 987,
992, 911, 912, 727, 913, 914, 915, 916, 988, 872,
983, 989, 285, 285, 285, 285, 285, 285, 285, 285,
285, 285, 285, 285, 285, 285, 285, 285, 285, 285,
285, 285, 285, 300, 38, 168, 141, 141, 141, 141,
141, 141, 141, 141, 141, 141, 141, 141, 141, 141,
141, 141, 141, 141, 141, 141, 141, 141, 141, 141,
156, 156, 156, 203, 525, 525, 8, 598, 161, 868,
868, 868, 868, 868, 868, 868, 868, 868, 868, 349,
333, 435, 435, 435, 435, 435, 436, 436, 436, 436,
933, 564, 636, 635, 465, 470, 801, 801, 753, 753,
788, 746, 746, 746, 410, 410, 410, 74, 538, 396,
359, 414, 675, 675, 675, 675, 414, 414, 414, 414,
796, 996, 414, 414, 414, -103, 606, 713, 713, 881,
293, 293, 293, 713, 547, 762, 835, 547, 835, 15,
409, 789, -40, 96, -17, 789, 510, 829, 140, 19,
810, 444, 810, 742, 859, 886, 993, 232, 784, 909,
787, 910, 224, 661, 979, 979, 979, 979, 979, 979,
979, 979, 979, 979, 979, 997, 980, -24, 997, 997,
997, 568, -24, 358, 422, -24, 754, 980, 52, 805,
52, 52, 52, 52, 941, 52, 52, 52, 52, 52,
52, 946, 708, 704, 668, 347, 52, 530, 11, 11,
537, 66, 11, 11, 11, 11, 52, 52, 444, 737,
777, 534, 790, 68, 737, 737, 737, 187, 23, 201,
29, 527, 734, 734, 731, 748, 921, 921, 734, 743,
734, 748, 926, 734, 731, 731, 921, 731, 812, 208,
452, 332, 346, 731, 731, 455, 921, 223, 731, 731,
734, 734, 734, 731, 481, 734, 220, 211, 734, 734,
731, 731, 785, 786, 122, 921, 921, 921, 786, 340,
778, 778, 820, 821, 782, 712, 308, 274, 509, 192,
731, 712, 712, 734, 356, 782, 712, 782, 712, 775,
712, 712, 712, 782, 712, 743, 378, 712, 731, 484,
134, 712, 6, 927, 928, 656, 929, 924, 930, 952,
931, 934, 876, 939, 925, 935, 923, 922, 717, 507,
553, 806, 799, 920, 730, 730, 730, 918, 730, 730,
730, 730, 730, 730, 730, 730, 507, 811, 813, 776,
722, 942, 562, 580, 767, 871, 994, 995, 794, 798,
941, 974, 936, 815, 589, 960, 943, 826, 867, 944,
945, 961, 975, 976, 887, 732, 888, 896, 861, 947,
877, 730, 927, 934, 925, 935, 923, 922, 703, 694,
687, 692, 678, 672, 669, 671, 710, 917, 809, 862,
946, 919, 507, 863, 956, 864, 962, 963, 875, 779,
736, 869, 897, 948, 949, 950, 878, 977, 817, 957,
932, 964, 781, 898, 965, 966, 967, 968, 899, 879,
883, 822, 764, 954, 774, 900, 443, 739, 749, 953,
486, 940, 884, 901, 902, 969, 970, 971, 903, 937,
827, 958, 761, 959, 955, 828, 838, 526, 726, 728,
545, 560, 904, 905, 938, 714, 729, 840, 842, 978,
906, 567, 843, 592, 907, 973, 612, 627, 747, 885,
808, 783, 769, 951, 716, 844, 908, 845, 847, 854,
972, 855, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 449,
449, 449, 449, 449, 449, 301, 301, 301, 301, 449,
449, 449, 449, 449, 449, 449, 0, 0, 301, 0,
0, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 449, 449, 449, 449,
449, 449, 449, 449, 449, 449, 285, 285, 285, 285,
285, 285, 285, 285, 285, 285, 285, 285, 285, 285,
285, 285, 285, 285, 285, 285, 285, 285, 285, 285,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 285, 285, 285, 285, 285, 285, 285, 285, 285,
285, 285, 285, 285, 285, 285, 285, 285, 285, 285,
285, 285, 285, 285, 285, 285, 285, 285, 285, 285,
285, 285, 414, 414, 285, 0, 285, 414, 414, 414,
414, 414, 414, 414, 414, 414, 414, 285, 285, 285,
285, 285, 285, 285, 293, 293, 293, 293, 812, 414,
414, 414, 414, -37, 293, 293, 414, 414, -37, 414,
414, 414, 414, 414, 414, 0, 0, -24, 835, 0,
743, 743, 743, 743, 0, 0, 0, 0, 835, 835,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, -24, 835, 0, -24, 0, 743, 743, 414,
812, 812, 25, 414, 0, 0, 0, 0, -24, 743,
-24, 835, 11, 52, 25, 0, 492, 492, 492, 492,
0, 444, 812, 812, 812, 812, 812, 812, 812, 812,
812, 812, 812, 743, 812, 0, 743, 743, 743, 0,
0, 0, 0, 0, 743, 731, 0, 921, 0, 0,
0, 0, 734, 0, 0, 0, 0, 0, 0, 734,
926, 731, 731, 0, 0, 0, 0, 0, 0, 743,
0, 0, 0, 0, 0, 0, 0, 730, 779, 0,
779, 0, 730, 730, 730
);
protected $actionDefault = array(
3,32767, 99,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767, 97,
32767,32767,32767,32767,32767,32767, 554, 554, 554, 554,
235, 99,32767,32767,32767,32767, 430, 349, 349, 349,
32767,32767, 498, 498, 498, 498, 498, 498,32767,32767,
32767,32767,32767,32767, 430,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767, 97,32767,32767,32767,
35, 5, 6, 8, 9, 48, 15,32767,32767,32767,
32767,32767, 99,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767, 547,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767, 434, 413, 414, 416, 417, 348, 499, 553,
292, 550, 347, 142, 304, 294, 223, 295, 239, 240,
266, 344, 146, 378, 431, 380, 429, 433, 379, 354,
359, 360, 361, 362, 363, 364, 365, 366, 367, 368,
369, 370, 371, 352, 353, 432, 435, 436, 439, 440,
410, 409, 408, 376,32767,32767, 377, 351, 381,32767,
32767,32767,32767,32767,32767,32767,32767, 99,32767, 383,
382, 399, 400, 397, 398, 401, 402, 403, 404, 405,
32767,32767,32767,32767,32767, 327, 390, 391, 283, 283,
329,32767,32767,32767,32767,32767,32767, 492, 407,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767, 99,32767, 97,32767, 494, 373, 375, 462,
385, 386, 384, 355,32767, 469,32767, 99, 471,32767,
32767,32767, 108,32767,32767,32767, 493,32767, 500, 500,
32767, 455, 97,32767,32767,32767,32767, 261,32767,32767,
32767,32767, 561, 455, 107, 107, 107, 107, 107, 107,
107, 107, 107, 107, 107,32767, 107,32767,32767,32767,
97, 185,32767, 249, 251, 99, 515, 190,32767, 474,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767, 467, 190, 190,32767,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767, 455, 395,
135,32767, 135, 500, 387, 388, 389, 457, 500, 500,
500,32767,32767,32767, 190,32767, 472, 472, 97, 97,
97, 97, 467,32767, 190, 190,32767, 190, 108, 96,
96, 96, 96, 190, 190, 96, 100, 98, 190, 190,
32767,32767,32767, 190, 96,32767, 98, 98,32767,32767,
190, 190, 206, 204, 98,32767, 519, 520, 204, 98,
208, 208, 228, 228, 446, 285, 98, 96, 98, 98,
190, 285, 285,32767, 98, 446, 285, 446, 285, 192,
285, 285, 285, 446, 285,32767, 98, 285, 190, 96,
96, 285,32767,32767,32767, 457,32767,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
487,32767, 504, 517, 393, 394, 396, 502, 418, 419,
420, 421, 422, 423, 424, 426, 549,32767, 461,32767,
32767,32767,32767, 303, 559,32767, 559,32767,32767,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767, 560,32767, 500,32767,32767,32767,
32767, 392, 7, 74, 41, 42, 50, 56, 478, 479,
480, 481, 475, 476, 482, 477,32767, 483, 525,32767,
32767, 501, 552,32767,32767,32767,32767,32767,32767, 135,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
487,32767, 133,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767, 500,32767,32767,32767, 280, 282,32767,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767,32767, 500,32767,32767,32767, 268, 270,
32767,32767,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767,32767, 265,32767,32767, 343,32767,32767,32767,
32767, 323,32767,32767,32767,32767,32767,32767,32767,32767,
32767,32767, 148, 148, 3, 3, 306, 148, 148, 148,
306, 148, 306, 306, 148, 148, 148, 148, 148, 148,
180, 243, 246, 228, 228, 148, 315, 148
);
protected $goto = array(
190, 190, 650, 1020, 979, 399, 624, 798, 1019, 658,
393, 297, 298, 318, 546, 303, 398, 319, 400, 603,
362, 366, 531, 569, 573, 161, 161, 161, 161, 187,
187, 171, 173, 209, 191, 204, 187, 187, 187, 187,
187, 188, 188, 188, 188, 188, 188, 182, 183, 184,
185, 186, 206, 204, 207, 504, 505, 389, 506, 508,
509, 510, 511, 512, 513, 514, 515, 1046, 162, 163,
164, 189, 165, 166, 167, 160, 168, 169, 170, 172,
203, 205, 208, 230, 233, 236, 238, 249, 250, 251,
252, 253, 254, 255, 256, 257, 258, 259, 266, 267,
300, 301, 302, 394, 395, 396, 551, 210, 211, 212,
213, 214, 215, 216, 217, 218, 219, 220, 221, 222,
223, 224, 174, 225, 175, 192, 193, 194, 231, 182,
183, 184, 185, 186, 206, 1046, 195, 176, 177, 178,
196, 192, 179, 232, 197, 159, 198, 226, 180, 199,
227, 228, 181, 229, 200, 201, 202, 312, 312, 312,
312, 801, 577, 591, 594, 595, 596, 597, 615, 616,
617, 660, 799, 329, 530, 521, 590, 590, 568, 794,
794, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175, 1175,
1175, 858, 779, 859, 806, 775, 854, 849, 850, 863,
905, 807, 851, 804, 852, 853, 805, 295, 295, 295,
295, 832, 857, 607, 607, 364, 521, 773, 530, 969,
966, 967, 996, 997, 539, 540, 957, 964, 965, 371,
549, 588, 621, 779, 570, 779, 1244, 1244, 1193, 1193,
543, 584, 585, 1193, 1193, 1193, 1193, 1193, 1193, 1193,
1193, 1193, 1193, 326, 1244, 1144, 1144, 1144, 961, 1140,
1233, 469, 961, 961, 926, 961, 961, 961, 961, 961,
961, 1225, 1225, 1225, 1225, 1144, 470, 360, 471, 21,
1144, 1144, 1144, 1144, 477, 794, 1144, 1144, 1144, 332,
437, 437, 567, 1012, 618, 661, 632, 633, 542, 332,
332, 437, 623, 647, 647, 870, 653, 1010, 1094, 871,
447, 1218, 1219, 814, 332, 332, 1141, 332, 826, 1260,
5, 813, 6, 518, 518, 518, 789, 320, 1191, 1191,
523, 791, 332, 1191, 1191, 1191, 1191, 1191, 1191, 1191,
1191, 1191, 1191, 426, 405, 1142, 1201, 1202, 899, 899,
899, 899, 370, 536, 426, 893, 900, 897, 380, 657,
583, 507, 507, 308, 291, 1204, 507, 507, 507, 507,
507, 507, 507, 507, 507, 507, 1243, 1243, 516, 516,
516, 516, 1220, 1221, 819, 875, 1034, 572, 547, 582,
643, 522, 534, 587, 1243, 816, 948, 522, 945, 534,
685, 985, 363, 330, 331, 818, 828, 627, 924, 1246,
392, 1137, 579, 812, 418, 418, 418, 448, 523, 550,
442, 443, 989, 1029, 824, 1136, 910, 1251, 1252, 451,
600, 537, 1215, 1215, 1215, 682, 602, 604, 0, 622,
0, 0, 640, 644, 940, 648, 656, 936, 0, 0,
797, 0, 822, 1227, 1227, 1227, 1227, 929, 903, 903,
901, 903, 681, 0, 270, 519, 519, 0, 0, 520,
938, 933, 0, 827, 815, 984, 0, 0, 988, 0,
1211, 0, 0, 0, 1139, 0, 0, 908, 418, 418,
418, 418, 418, 418, 418, 418, 418, 418, 418, 0,
418, 0, 378, 379, 0, 0, 0, 630, 0, 631,
898, 382, 383, 384, 0, 641, 987, 0, 385, 1213,
1213, 987, 324, 1125, 884, 0, 0, 1126, 1129, 885,
1130, 0, 1027, 831, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 943, 943
);
protected $gotoCheck = array(
41, 41, 71, 128, 111, 64, 64, 25, 128, 8,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
57, 57, 57, 57, 57, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 22, 22, 22,
22, 14, 77, 77, 77, 77, 77, 77, 77, 77,
77, 77, 26, 88, 74, 74, 99, 99, 114, 21,
21, 99, 99, 99, 99, 99, 99, 99, 99, 99,
99, 63, 11, 63, 14, 6, 14, 14, 14, 14,
48, 14, 14, 14, 14, 14, 14, 23, 23, 23,
23, 44, 14, 107, 107, 74, 74, 5, 74, 107,
107, 107, 14, 14, 74, 74, 105, 105, 105, 74,
74, 54, 54, 11, 74, 11, 165, 165, 152, 152,
154, 74, 74, 152, 152, 152, 152, 152, 152, 152,
152, 152, 152, 161, 165, 71, 71, 71, 71, 19,
163, 74, 71, 71, 94, 71, 71, 71, 71, 71,
71, 8, 8, 8, 8, 71, 139, 60, 139, 74,
71, 71, 71, 71, 139, 21, 71, 71, 71, 13,
133, 133, 7, 7, 82, 7, 82, 82, 95, 13,
13, 133, 62, 7, 7, 71, 7, 7, 135, 71,
158, 158, 158, 34, 13, 13, 19, 13, 34, 13,
45, 34, 45, 18, 18, 18, 19, 28, 153, 153,
13, 17, 13, 153, 153, 153, 153, 153, 153, 153,
153, 153, 153, 18, 103, 19, 19, 19, 18, 18,
18, 18, 27, 8, 18, 18, 18, 84, 84, 84,
8, 155, 155, 151, 151, 13, 155, 155, 155, 155,
155, 155, 155, 155, 155, 155, 164, 164, 98, 98,
98, 98, 160, 160, 38, 16, 16, 98, 2, 2,
13, 8, 8, 16, 164, 36, 101, 8, 16, 8,
90, 113, 8, 88, 88, 16, 40, 16, 16, 164,
12, 144, 12, 16, 22, 22, 22, 141, 13, 8,
8, 8, 116, 131, 8, 16, 87, 8, 8, 80,
81, 47, 114, 114, 114, 47, 47, 47, -1, 47,
-1, -1, 47, 47, 47, 47, 47, 47, -1, -1,
24, -1, 8, 114, 114, 114, 114, 24, 24, 24,
24, 24, 24, -1, 23, 23, 23, -1, -1, 24,
24, 24, -1, 15, 15, 15, -1, -1, 15, -1,
114, -1, -1, -1, 13, -1, -1, 15, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, -1,
22, -1, 78, 78, -1, -1, -1, 78, -1, 78,
15, 78, 78, 78, -1, 78, 114, -1, 78, 114,
114, 114, 78, 76, 76, -1, -1, 76, 76, 76,
76, -1, 15, 15, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, 98, 98
);
protected $gotoBase = array(
0, 0, -276, 0, 0, 197, 186, 285, -11, 0,
0, -87, 90, 9, -164, 53, -51, 39, 62, -100,
0, -133, 154, 204, 446, 3, 168, 32, 48, 0,
0, 0, 0, 0, -34, 0, 73, 0, 77, 0,
-2, -1, 0, 0, 192, -365, 0, -232, 183, 0,
0, 0, 0, 0, 193, 0, 0, -23, 0, 0,
237, 0, 67, 178, -229, 0, 0, 0, 0, 0,
0, -6, 0, 0, -199, 0, 145, -173, 41, 0,
-19, -21, -376, 0, 70, 0, 0, 16, -280, 0,
23, 0, 0, 0, 233, 257, 0, 0, 352, -58,
0, 50, 0, 75, 0, -45, 0, -55, 0, 0,
0, 2, 0, 51, 171, 0, 13, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, -262, 0,
0, 12, 0, 260, 0, 45, 0, 0, 0, -189,
0, 10, 0, 0, 7, 0, 0, 0, 0, 0,
0, 58, 4, 94, 213, 127, 0, 0, 27, 0,
34, 225, 0, 231, 86, -54, 0, 0
);
protected $gotoDefault = array(
-32768, 482, 689, 4, 690, 763, 771, 566, 498, 659,
325, 592, 390, 1209, 856, 1033, 548, 790, 1153, 1161,
427, 793, 313, 327, 838, 839, 840, 367, 352, 358,
365, 613, 593, 464, 825, 421, 817, 456, 820, 420,
829, 158, 387, 480, 833, 3, 835, 525, 866, 353,
843, 354, 636, 845, 533, 847, 848, 361, 368, 369,
1038, 541, 589, 860, 237, 535, 861, 351, 862, 869,
356, 359, 645, 436, 475, 381, 1014, 576, 610, 432,
450, 599, 598, 586, 895, 457, 434, 909, 328, 917,
687, 1045, 605, 459, 925, 606, 932, 935, 499, 500,
449, 947, 268, 460, 974, 628, 629, 959, 608, 972,
444, 978, 422, 986, 1197, 425, 990, 260, 993, 269,
386, 401, 998, 999, 8, 1004, 651, 652, 10, 265,
479, 1028, 646, 419, 1044, 406, 1113, 1115, 527, 461,
1133, 1132, 639, 476, 1138, 1200, 416, 501, 445, 299,
502, 290, 316, 296, 517, 277, 317, 503, 446, 1206,
1214, 314, 29, 1234, 1245, 323, 545, 581
);
protected $ruleToNonTerminal = array(
0, 1, 3, 3, 2, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 6, 6, 6, 6, 6,
6, 6, 7, 7, 8, 9, 10, 10, 10, 11,
11, 12, 12, 13, 14, 14, 15, 15, 16, 16,
17, 17, 20, 20, 21, 22, 22, 23, 23, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
28, 28, 29, 29, 31, 33, 33, 27, 35, 35,
32, 37, 37, 34, 34, 36, 36, 38, 38, 30,
39, 39, 40, 42, 43, 43, 44, 45, 45, 47,
46, 46, 46, 46, 48, 48, 48, 48, 48, 48,
48, 48, 48, 48, 48, 48, 48, 48, 48, 48,
48, 48, 48, 48, 48, 48, 48, 48, 24, 24,
67, 67, 70, 70, 69, 68, 68, 61, 73, 73,
74, 74, 75, 75, 76, 76, 25, 25, 26, 26,
26, 79, 79, 79, 80, 80, 83, 83, 81, 81,
84, 85, 85, 55, 55, 63, 63, 66, 66, 66,
65, 86, 86, 87, 56, 56, 56, 56, 88, 88,
89, 89, 90, 90, 91, 92, 92, 93, 93, 94,
94, 53, 53, 49, 49, 96, 51, 51, 97, 50,
50, 52, 52, 62, 62, 62, 62, 77, 77, 100,
100, 102, 102, 102, 102, 101, 101, 101, 104, 104,
104, 105, 105, 107, 107, 107, 106, 106, 108, 108,
109, 109, 109, 103, 103, 78, 78, 78, 19, 19,
110, 110, 111, 111, 111, 111, 58, 112, 112, 113,
59, 115, 115, 116, 116, 117, 117, 82, 118, 118,
118, 118, 118, 123, 123, 124, 124, 125, 125, 125,
125, 125, 126, 127, 127, 122, 122, 119, 119, 121,
121, 129, 129, 128, 128, 128, 128, 128, 128, 120,
130, 130, 132, 131, 131, 60, 95, 133, 133, 54,
54, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 41, 41, 41, 41, 41, 41, 41,
41, 41, 41, 140, 134, 134, 139, 139, 142, 143,
143, 144, 145, 145, 145, 18, 18, 71, 71, 71,
71, 135, 135, 135, 135, 147, 147, 136, 136, 138,
138, 138, 141, 141, 152, 152, 152, 152, 152, 152,
152, 152, 152, 153, 153, 99, 155, 155, 155, 155,
137, 137, 137, 137, 137, 137, 137, 137, 57, 57,
150, 150, 150, 150, 156, 156, 146, 146, 146, 157,
157, 157, 157, 157, 157, 72, 72, 64, 64, 64,
64, 114, 114, 114, 114, 160, 159, 149, 149, 149,
149, 149, 149, 149, 148, 148, 148, 158, 158, 158,
158, 98, 154, 162, 162, 161, 161, 163, 163, 163,
163, 163, 163, 163, 163, 151, 151, 151, 151, 165,
166, 164, 164, 164, 164, 164, 164, 164, 164, 167,
167, 167, 167
);
protected $ruleToLength = array(
1, 1, 2, 0, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
1, 2, 1, 3, 4, 1, 2, 0, 1, 1,
1, 1, 1, 3, 5, 4, 3, 4, 2, 3,
1, 1, 7, 6, 2, 3, 1, 2, 3, 1,
2, 3, 1, 1, 3, 1, 3, 1, 2, 2,
3, 1, 3, 2, 3, 1, 3, 2, 0, 1,
1, 1, 1, 1, 3, 7, 10, 5, 7, 9,
5, 3, 3, 3, 3, 3, 3, 1, 2, 5,
7, 9, 6, 5, 6, 3, 2, 1, 1, 1,
0, 2, 1, 3, 8, 0, 4, 2, 1, 3,
0, 1, 0, 1, 3, 1, 8, 9, 8, 7,
6, 1, 2, 2, 0, 2, 0, 2, 0, 2,
2, 1, 3, 1, 4, 1, 4, 1, 1, 4,
2, 1, 3, 3, 3, 4, 4, 5, 0, 2,
4, 3, 1, 1, 7, 0, 2, 1, 3, 3,
4, 1, 4, 0, 2, 5, 0, 2, 6, 0,
2, 0, 3, 1, 2, 1, 1, 2, 0, 1,
3, 0, 1, 1, 1, 6, 8, 6, 1, 2,
1, 1, 1, 1, 1, 1, 3, 3, 3, 3,
1, 2, 1, 0, 1, 0, 2, 2, 2, 4,
1, 3, 1, 2, 2, 3, 2, 3, 1, 1,
2, 3, 1, 1, 3, 2, 0, 1, 5, 5,
10, 3, 1, 1, 3, 0, 2, 4, 5, 4,
4, 4, 3, 1, 1, 1, 1, 1, 1, 0,
1, 1, 2, 1, 1, 1, 1, 1, 1, 2,
1, 3, 1, 1, 3, 2, 2, 3, 1, 0,
1, 1, 3, 3, 3, 4, 1, 1, 2, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 2, 2, 2, 2, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 2, 2, 2, 2, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 5, 4,
3, 4, 4, 2, 2, 4, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 1, 3, 2,
1, 2, 4, 2, 2, 8, 9, 8, 9, 9,
10, 9, 10, 8, 3, 2, 0, 4, 2, 1,
3, 2, 2, 2, 4, 1, 1, 1, 1, 1,
1, 1, 1, 3, 1, 1, 1, 0, 3, 0,
1, 1, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 3, 3, 3, 4, 1, 1, 3,
1, 1, 1, 1, 1, 3, 2, 3, 0, 1,
1, 3, 1, 1, 1, 1, 1, 3, 1, 1,
4, 4, 1, 4, 4, 0, 1, 1, 1, 3,
3, 1, 4, 2, 2, 1, 3, 1, 4, 4,
3, 3, 3, 3, 1, 3, 1, 1, 3, 1,
1, 4, 1, 1, 1, 3, 1, 1, 2, 1,
3, 4, 3, 2, 0, 2, 2, 1, 2, 1,
1, 1, 4, 3, 3, 3, 3, 6, 3, 1,
1, 2, 1
);
protected function initReduceCallbacks() {
$this->reduceCallbacks = [
0 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
1 => function ($stackPos) {
$this->semValue = $this->handleNamespaces($this->semStack[$stackPos-(1-1)]);
},
2 => function ($stackPos) {
if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; };
},
3 => function ($stackPos) {
$this->semValue = array();
},
4 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
5 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
6 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
7 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
8 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
9 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
10 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
11 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
12 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
13 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
14 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
15 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
16 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
17 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
18 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
19 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
20 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
21 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
22 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
23 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
24 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
25 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
26 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
27 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
28 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
29 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
30 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
31 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
32 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
33 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
34 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
35 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
36 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
37 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
38 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
39 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
40 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
41 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
42 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
43 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
44 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
45 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
46 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
47 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
48 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
49 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
50 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
51 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
52 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
53 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
54 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
55 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
56 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
57 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
58 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
59 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
60 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
61 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
62 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
63 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
64 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
65 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
66 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
67 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
68 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
69 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
70 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
71 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
72 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
73 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
74 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
75 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
76 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
77 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
78 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
79 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
80 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
81 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
82 => function ($stackPos) {
$this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
83 => function ($stackPos) {
$this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
84 => function ($stackPos) {
$this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
85 => function ($stackPos) {
$this->semValue = new Node\Identifier($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
86 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
87 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
88 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
89 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
90 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
91 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
92 => function ($stackPos) {
$this->semValue = new Name(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
93 => function ($stackPos) {
$this->semValue = new Expr\Variable(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
94 => function ($stackPos) {
/* nothing */
},
95 => function ($stackPos) {
/* nothing */
},
96 => function ($stackPos) {
/* nothing */
},
97 => function ($stackPos) {
$this->emitError(new Error('A trailing comma is not allowed here', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes));
},
98 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
99 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
100 => function ($stackPos) {
$this->semValue = new Node\Attribute($this->semStack[$stackPos-(1-1)], [], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
101 => function ($stackPos) {
$this->semValue = new Node\Attribute($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
102 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
103 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
104 => function ($stackPos) {
$this->semValue = new Node\AttributeGroup($this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
105 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
106 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
107 => function ($stackPos) {
$this->semValue = [];
},
108 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
109 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
110 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
111 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
112 => function ($stackPos) {
$this->semValue = new Stmt\HaltCompiler($this->lexer->handleHaltCompiler(), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
113 => function ($stackPos) {
$this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(3-2)], null, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_SEMICOLON);
$this->checkNamespace($this->semValue);
},
114 => function ($stackPos) {
$this->semValue = new Stmt\Namespace_($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($this->semValue);
},
115 => function ($stackPos) {
$this->semValue = new Stmt\Namespace_(null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
$this->semValue->setAttribute('kind', Stmt\Namespace_::KIND_BRACED);
$this->checkNamespace($this->semValue);
},
116 => function ($stackPos) {
$this->semValue = new Stmt\Use_($this->semStack[$stackPos-(3-2)], Stmt\Use_::TYPE_NORMAL, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
117 => function ($stackPos) {
$this->semValue = new Stmt\Use_($this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
118 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
119 => function ($stackPos) {
$this->semValue = new Stmt\Const_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
120 => function ($stackPos) {
$this->semValue = Stmt\Use_::TYPE_FUNCTION;
},
121 => function ($stackPos) {
$this->semValue = Stmt\Use_::TYPE_CONSTANT;
},
122 => function ($stackPos) {
$this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->semStack[$stackPos-(7-2)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
},
123 => function ($stackPos) {
$this->semValue = new Stmt\GroupUse($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-5)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
124 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
125 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
126 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
127 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
128 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
129 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
130 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
131 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
132 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
133 => function ($stackPos) {
$this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1));
},
134 => function ($stackPos) {
$this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3));
},
135 => function ($stackPos) {
$this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(1-1)], null, Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(1-1));
},
136 => function ($stackPos) {
$this->semValue = new Stmt\UseUse($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], Stmt\Use_::TYPE_UNKNOWN, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->checkUseUse($this->semValue, $stackPos-(3-3));
},
137 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)]; $this->semValue->type = Stmt\Use_::TYPE_NORMAL;
},
138 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)]; $this->semValue->type = $this->semStack[$stackPos-(2-1)];
},
139 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
140 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
141 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
142 => function ($stackPos) {
$this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
143 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
144 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
145 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
146 => function ($stackPos) {
$this->semValue = new Node\Const_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
147 => function ($stackPos) {
if (is_array($this->semStack[$stackPos-(2-2)])) { $this->semValue = array_merge($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]); } else { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; };
},
148 => function ($stackPos) {
$this->semValue = array();
},
149 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
150 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
151 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
152 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
153 => function ($stackPos) {
throw new Error('__HALT_COMPILER() can only be used from the outermost scope', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
154 => function ($stackPos) {
if ($this->semStack[$stackPos-(3-2)]) {
$this->semValue = $this->semStack[$stackPos-(3-2)]; $attrs = $this->startAttributeStack[$stackPos-(3-1)]; $stmts = $this->semValue; if (!empty($attrs['comments'])) {$stmts[0]->setAttribute('comments', array_merge($attrs['comments'], $stmts[0]->getAttribute('comments', []))); };
} else {
$startAttributes = $this->startAttributeStack[$stackPos-(3-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; };
if (null === $this->semValue) { $this->semValue = array(); }
}
},
155 => function ($stackPos) {
$this->semValue = new Stmt\If_($this->semStack[$stackPos-(7-3)], ['stmts' => is_array($this->semStack[$stackPos-(7-5)]) ? $this->semStack[$stackPos-(7-5)] : array($this->semStack[$stackPos-(7-5)]), 'elseifs' => $this->semStack[$stackPos-(7-6)], 'else' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
},
156 => function ($stackPos) {
$this->semValue = new Stmt\If_($this->semStack[$stackPos-(10-3)], ['stmts' => $this->semStack[$stackPos-(10-6)], 'elseifs' => $this->semStack[$stackPos-(10-7)], 'else' => $this->semStack[$stackPos-(10-8)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes);
},
157 => function ($stackPos) {
$this->semValue = new Stmt\While_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
158 => function ($stackPos) {
$this->semValue = new Stmt\Do_($this->semStack[$stackPos-(7-5)], is_array($this->semStack[$stackPos-(7-2)]) ? $this->semStack[$stackPos-(7-2)] : array($this->semStack[$stackPos-(7-2)]), $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
},
159 => function ($stackPos) {
$this->semValue = new Stmt\For_(['init' => $this->semStack[$stackPos-(9-3)], 'cond' => $this->semStack[$stackPos-(9-5)], 'loop' => $this->semStack[$stackPos-(9-7)], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
160 => function ($stackPos) {
$this->semValue = new Stmt\Switch_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
161 => function ($stackPos) {
$this->semValue = new Stmt\Break_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
162 => function ($stackPos) {
$this->semValue = new Stmt\Continue_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
163 => function ($stackPos) {
$this->semValue = new Stmt\Return_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
164 => function ($stackPos) {
$this->semValue = new Stmt\Global_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
165 => function ($stackPos) {
$this->semValue = new Stmt\Static_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
166 => function ($stackPos) {
$this->semValue = new Stmt\Echo_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
167 => function ($stackPos) {
$this->semValue = new Stmt\InlineHTML($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
168 => function ($stackPos) {
$e = $this->semStack[$stackPos-(2-1)];
if ($e instanceof Expr\Throw_) {
// For backwards-compatibility reasons, convert throw in statement position into
// Stmt\Throw_ rather than Stmt\Expression(Expr\Throw_).
$this->semValue = new Stmt\Throw_($e->expr, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
} else {
$this->semValue = new Stmt\Expression($e, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
}
},
169 => function ($stackPos) {
$this->semValue = new Stmt\Unset_($this->semStack[$stackPos-(5-3)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
170 => function ($stackPos) {
$this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-5)][0], ['keyVar' => null, 'byRef' => $this->semStack[$stackPos-(7-5)][1], 'stmts' => $this->semStack[$stackPos-(7-7)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
},
171 => function ($stackPos) {
$this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(9-3)], $this->semStack[$stackPos-(9-7)][0], ['keyVar' => $this->semStack[$stackPos-(9-5)], 'byRef' => $this->semStack[$stackPos-(9-7)][1], 'stmts' => $this->semStack[$stackPos-(9-9)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
172 => function ($stackPos) {
$this->semValue = new Stmt\Foreach_($this->semStack[$stackPos-(6-3)], new Expr\Error($this->startAttributeStack[$stackPos-(6-4)] + $this->endAttributeStack[$stackPos-(6-4)]), ['stmts' => $this->semStack[$stackPos-(6-6)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
173 => function ($stackPos) {
$this->semValue = new Stmt\Declare_($this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
174 => function ($stackPos) {
$this->semValue = new Stmt\TryCatch($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-5)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes); $this->checkTryCatch($this->semValue);
},
175 => function ($stackPos) {
$this->semValue = new Stmt\Goto_($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
176 => function ($stackPos) {
$this->semValue = new Stmt\Label($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
177 => function ($stackPos) {
$this->semValue = array(); /* means: no statement */
},
178 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
179 => function ($stackPos) {
$startAttributes = $this->startAttributeStack[$stackPos-(1-1)]; if (isset($startAttributes['comments'])) { $this->semValue = new Stmt\Nop($startAttributes + $this->endAttributes); } else { $this->semValue = null; };
if ($this->semValue === null) $this->semValue = array(); /* means: no statement */
},
180 => function ($stackPos) {
$this->semValue = array();
},
181 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
182 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
183 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
184 => function ($stackPos) {
$this->semValue = new Stmt\Catch_($this->semStack[$stackPos-(8-3)], $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-7)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes);
},
185 => function ($stackPos) {
$this->semValue = null;
},
186 => function ($stackPos) {
$this->semValue = new Stmt\Finally_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
187 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
188 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
189 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
190 => function ($stackPos) {
$this->semValue = false;
},
191 => function ($stackPos) {
$this->semValue = true;
},
192 => function ($stackPos) {
$this->semValue = false;
},
193 => function ($stackPos) {
$this->semValue = true;
},
194 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
195 => function ($stackPos) {
$this->semValue = [];
},
196 => function ($stackPos) {
$this->semValue = new Stmt\Function_($this->semStack[$stackPos-(8-3)], ['byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-5)], 'returnType' => $this->semStack[$stackPos-(8-7)], 'stmts' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes);
},
197 => function ($stackPos) {
$this->semValue = new Stmt\Function_($this->semStack[$stackPos-(9-4)], ['byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-6)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
198 => function ($stackPos) {
$this->semValue = new Stmt\Class_($this->semStack[$stackPos-(8-3)], ['type' => $this->semStack[$stackPos-(8-2)], 'extends' => $this->semStack[$stackPos-(8-4)], 'implements' => $this->semStack[$stackPos-(8-5)], 'stmts' => $this->semStack[$stackPos-(8-7)], 'attrGroups' => $this->semStack[$stackPos-(8-1)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes);
$this->checkClass($this->semValue, $stackPos-(8-3));
},
199 => function ($stackPos) {
$this->semValue = new Stmt\Interface_($this->semStack[$stackPos-(7-3)], ['extends' => $this->semStack[$stackPos-(7-4)], 'stmts' => $this->semStack[$stackPos-(7-6)], 'attrGroups' => $this->semStack[$stackPos-(7-1)]], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
$this->checkInterface($this->semValue, $stackPos-(7-3));
},
200 => function ($stackPos) {
$this->semValue = new Stmt\Trait_($this->semStack[$stackPos-(6-3)], ['stmts' => $this->semStack[$stackPos-(6-5)], 'attrGroups' => $this->semStack[$stackPos-(6-1)]], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
201 => function ($stackPos) {
$this->semValue = 0;
},
202 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
},
203 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_FINAL;
},
204 => function ($stackPos) {
$this->semValue = null;
},
205 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)];
},
206 => function ($stackPos) {
$this->semValue = array();
},
207 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)];
},
208 => function ($stackPos) {
$this->semValue = array();
},
209 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)];
},
210 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
211 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
212 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
213 => function ($stackPos) {
$this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]);
},
214 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
215 => function ($stackPos) {
$this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]);
},
216 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
217 => function ($stackPos) {
$this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]);
},
218 => function ($stackPos) {
$this->semValue = null;
},
219 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
220 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
221 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
222 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
223 => function ($stackPos) {
$this->semValue = new Stmt\DeclareDeclare($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
224 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
225 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-3)];
},
226 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
227 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(5-3)];
},
228 => function ($stackPos) {
$this->semValue = array();
},
229 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
230 => function ($stackPos) {
$this->semValue = new Stmt\Case_($this->semStack[$stackPos-(4-2)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
231 => function ($stackPos) {
$this->semValue = new Stmt\Case_(null, $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
232 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
233 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
234 => function ($stackPos) {
$this->semValue = new Expr\Match_($this->semStack[$stackPos-(7-3)], $this->semStack[$stackPos-(7-6)], $this->startAttributeStack[$stackPos-(7-1)] + $this->endAttributes);
},
235 => function ($stackPos) {
$this->semValue = [];
},
236 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
237 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
238 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
239 => function ($stackPos) {
$this->semValue = new Node\MatchArm($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
240 => function ($stackPos) {
$this->semValue = new Node\MatchArm(null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
241 => function ($stackPos) {
$this->semValue = is_array($this->semStack[$stackPos-(1-1)]) ? $this->semStack[$stackPos-(1-1)] : array($this->semStack[$stackPos-(1-1)]);
},
242 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
243 => function ($stackPos) {
$this->semValue = array();
},
244 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
245 => function ($stackPos) {
$this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(5-3)], is_array($this->semStack[$stackPos-(5-5)]) ? $this->semStack[$stackPos-(5-5)] : array($this->semStack[$stackPos-(5-5)]), $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
246 => function ($stackPos) {
$this->semValue = array();
},
247 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
248 => function ($stackPos) {
$this->semValue = new Stmt\ElseIf_($this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-6)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
249 => function ($stackPos) {
$this->semValue = null;
},
250 => function ($stackPos) {
$this->semValue = new Stmt\Else_(is_array($this->semStack[$stackPos-(2-2)]) ? $this->semStack[$stackPos-(2-2)] : array($this->semStack[$stackPos-(2-2)]), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
251 => function ($stackPos) {
$this->semValue = null;
},
252 => function ($stackPos) {
$this->semValue = new Stmt\Else_($this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
253 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)], false);
},
254 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(2-2)], true);
},
255 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)], false);
},
256 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)], false);
},
257 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
258 => function ($stackPos) {
$this->semValue = array();
},
259 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
260 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
261 => function ($stackPos) {
$this->semValue = 0;
},
262 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PUBLIC;
},
263 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PROTECTED;
},
264 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PRIVATE;
},
265 => function ($stackPos) {
$this->semValue = new Node\Param($this->semStack[$stackPos-(6-6)], null, $this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes, $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-1)]);
$this->checkParam($this->semValue);
},
266 => function ($stackPos) {
$this->semValue = new Node\Param($this->semStack[$stackPos-(8-6)], $this->semStack[$stackPos-(8-8)], $this->semStack[$stackPos-(8-3)], $this->semStack[$stackPos-(8-4)], $this->semStack[$stackPos-(8-5)], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes, $this->semStack[$stackPos-(8-2)], $this->semStack[$stackPos-(8-1)]);
$this->checkParam($this->semValue);
},
267 => function ($stackPos) {
$this->semValue = new Node\Param(new Expr\Error($this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes), null, $this->semStack[$stackPos-(6-3)], $this->semStack[$stackPos-(6-4)], $this->semStack[$stackPos-(6-5)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes, $this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-1)]);
},
268 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
269 => function ($stackPos) {
$this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
270 => function ($stackPos) {
$this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
271 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
272 => function ($stackPos) {
$this->semValue = new Node\Name('static', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
273 => function ($stackPos) {
$this->semValue = $this->handleBuiltinTypes($this->semStack[$stackPos-(1-1)]);
},
274 => function ($stackPos) {
$this->semValue = new Node\Identifier('array', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
275 => function ($stackPos) {
$this->semValue = new Node\Identifier('callable', $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
276 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]);
},
277 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
278 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]);
},
279 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
280 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
281 => function ($stackPos) {
$this->semValue = new Node\NullableType($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
282 => function ($stackPos) {
$this->semValue = new Node\UnionType($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
283 => function ($stackPos) {
$this->semValue = null;
},
284 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
285 => function ($stackPos) {
$this->semValue = null;
},
286 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-2)];
},
287 => function ($stackPos) {
$this->semValue = null;
},
288 => function ($stackPos) {
$this->semValue = array();
},
289 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-2)];
},
290 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
291 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
292 => function ($stackPos) {
$this->semValue = new Node\Arg($this->semStack[$stackPos-(1-1)], false, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
293 => function ($stackPos) {
$this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], true, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
294 => function ($stackPos) {
$this->semValue = new Node\Arg($this->semStack[$stackPos-(2-2)], false, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
295 => function ($stackPos) {
$this->semValue = new Node\Arg($this->semStack[$stackPos-(3-3)], false, false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->semStack[$stackPos-(3-1)]);
},
296 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
297 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
298 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
299 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
300 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
301 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
302 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
303 => function ($stackPos) {
$this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
304 => function ($stackPos) {
$this->semValue = new Stmt\StaticVar($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
305 => function ($stackPos) {
if ($this->semStack[$stackPos-(2-2)] !== null) { $this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)]; }
},
306 => function ($stackPos) {
$this->semValue = array();
},
307 => function ($stackPos) {
$startAttributes = $this->lookaheadStartAttributes; if (isset($startAttributes['comments'])) { $nop = new Stmt\Nop($this->createCommentNopAttributes($startAttributes['comments'])); } else { $nop = null; };
if ($nop !== null) { $this->semStack[$stackPos-(1-1)][] = $nop; } $this->semValue = $this->semStack[$stackPos-(1-1)];
},
308 => function ($stackPos) {
$this->semValue = new Stmt\Property($this->semStack[$stackPos-(5-2)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes, $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-1)]);
$this->checkProperty($this->semValue, $stackPos-(5-2));
},
309 => function ($stackPos) {
$this->semValue = new Stmt\ClassConst($this->semStack[$stackPos-(5-4)], $this->semStack[$stackPos-(5-2)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes, $this->semStack[$stackPos-(5-1)]);
$this->checkClassConst($this->semValue, $stackPos-(5-2));
},
310 => function ($stackPos) {
$this->semValue = new Stmt\ClassMethod($this->semStack[$stackPos-(10-5)], ['type' => $this->semStack[$stackPos-(10-2)], 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-7)], 'returnType' => $this->semStack[$stackPos-(10-9)], 'stmts' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes);
$this->checkClassMethod($this->semValue, $stackPos-(10-2));
},
311 => function ($stackPos) {
$this->semValue = new Stmt\TraitUse($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
312 => function ($stackPos) {
$this->semValue = null; /* will be skipped */
},
313 => function ($stackPos) {
$this->semValue = array();
},
314 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
315 => function ($stackPos) {
$this->semValue = array();
},
316 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
317 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Precedence($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
318 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(5-1)][0], $this->semStack[$stackPos-(5-1)][1], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-4)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
319 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], $this->semStack[$stackPos-(4-3)], null, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
320 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
321 => function ($stackPos) {
$this->semValue = new Stmt\TraitUseAdaptation\Alias($this->semStack[$stackPos-(4-1)][0], $this->semStack[$stackPos-(4-1)][1], null, $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
322 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)]);
},
323 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
324 => function ($stackPos) {
$this->semValue = array(null, $this->semStack[$stackPos-(1-1)]);
},
325 => function ($stackPos) {
$this->semValue = null;
},
326 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
327 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
328 => function ($stackPos) {
$this->semValue = 0;
},
329 => function ($stackPos) {
$this->semValue = 0;
},
330 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
331 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
332 => function ($stackPos) {
$this->checkModifier($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $stackPos-(2-2)); $this->semValue = $this->semStack[$stackPos-(2-1)] | $this->semStack[$stackPos-(2-2)];
},
333 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PUBLIC;
},
334 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PROTECTED;
},
335 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_PRIVATE;
},
336 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_STATIC;
},
337 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_ABSTRACT;
},
338 => function ($stackPos) {
$this->semValue = Stmt\Class_::MODIFIER_FINAL;
},
339 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
340 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
341 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
342 => function ($stackPos) {
$this->semValue = new Node\VarLikeIdentifier(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
343 => function ($stackPos) {
$this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(1-1)], null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
344 => function ($stackPos) {
$this->semValue = new Stmt\PropertyProperty($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
345 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
346 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
347 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
348 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
349 => function ($stackPos) {
$this->semValue = array();
},
350 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
351 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
352 => function ($stackPos) {
$this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
353 => function ($stackPos) {
$this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
354 => function ($stackPos) {
$this->semValue = new Expr\Assign($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
355 => function ($stackPos) {
$this->semValue = new Expr\AssignRef($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
356 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
357 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
358 => function ($stackPos) {
$this->semValue = new Expr\Clone_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
359 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
360 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
361 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
362 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
363 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
364 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
365 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
366 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
367 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
368 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
369 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
370 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
371 => function ($stackPos) {
$this->semValue = new Expr\AssignOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
372 => function ($stackPos) {
$this->semValue = new Expr\PostInc($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
373 => function ($stackPos) {
$this->semValue = new Expr\PreInc($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
374 => function ($stackPos) {
$this->semValue = new Expr\PostDec($this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
375 => function ($stackPos) {
$this->semValue = new Expr\PreDec($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
376 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BooleanOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
377 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BooleanAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
378 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
379 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
380 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\LogicalXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
381 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseOr($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
382 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseAnd($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
383 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\BitwiseXor($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
384 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Concat($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
385 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Plus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
386 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Minus($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
387 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Mul($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
388 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Div($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
389 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Mod($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
390 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\ShiftLeft($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
391 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\ShiftRight($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
392 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Pow($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
393 => function ($stackPos) {
$this->semValue = new Expr\UnaryPlus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
394 => function ($stackPos) {
$this->semValue = new Expr\UnaryMinus($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
395 => function ($stackPos) {
$this->semValue = new Expr\BooleanNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
396 => function ($stackPos) {
$this->semValue = new Expr\BitwiseNot($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
397 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Identical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
398 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\NotIdentical($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
399 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Equal($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
400 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\NotEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
401 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Spaceship($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
402 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Smaller($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
403 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\SmallerOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
404 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Greater($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
405 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\GreaterOrEqual($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
406 => function ($stackPos) {
$this->semValue = new Expr\Instanceof_($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
407 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
408 => function ($stackPos) {
$this->semValue = new Expr\Ternary($this->semStack[$stackPos-(5-1)], $this->semStack[$stackPos-(5-3)], $this->semStack[$stackPos-(5-5)], $this->startAttributeStack[$stackPos-(5-1)] + $this->endAttributes);
},
409 => function ($stackPos) {
$this->semValue = new Expr\Ternary($this->semStack[$stackPos-(4-1)], null, $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
410 => function ($stackPos) {
$this->semValue = new Expr\BinaryOp\Coalesce($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
411 => function ($stackPos) {
$this->semValue = new Expr\Isset_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
412 => function ($stackPos) {
$this->semValue = new Expr\Empty_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
413 => function ($stackPos) {
$this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
414 => function ($stackPos) {
$this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_INCLUDE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
415 => function ($stackPos) {
$this->semValue = new Expr\Eval_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
416 => function ($stackPos) {
$this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
417 => function ($stackPos) {
$this->semValue = new Expr\Include_($this->semStack[$stackPos-(2-2)], Expr\Include_::TYPE_REQUIRE_ONCE, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
418 => function ($stackPos) {
$this->semValue = new Expr\Cast\Int_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
419 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes;
$attrs['kind'] = $this->getFloatCastKind($this->semStack[$stackPos-(2-1)]);
$this->semValue = new Expr\Cast\Double($this->semStack[$stackPos-(2-2)], $attrs);
},
420 => function ($stackPos) {
$this->semValue = new Expr\Cast\String_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
421 => function ($stackPos) {
$this->semValue = new Expr\Cast\Array_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
422 => function ($stackPos) {
$this->semValue = new Expr\Cast\Object_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
423 => function ($stackPos) {
$this->semValue = new Expr\Cast\Bool_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
424 => function ($stackPos) {
$this->semValue = new Expr\Cast\Unset_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
425 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes;
$attrs['kind'] = strtolower($this->semStack[$stackPos-(2-1)]) === 'exit' ? Expr\Exit_::KIND_EXIT : Expr\Exit_::KIND_DIE;
$this->semValue = new Expr\Exit_($this->semStack[$stackPos-(2-2)], $attrs);
},
426 => function ($stackPos) {
$this->semValue = new Expr\ErrorSuppress($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
427 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
428 => function ($stackPos) {
$this->semValue = new Expr\ShellExec($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
429 => function ($stackPos) {
$this->semValue = new Expr\Print_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
430 => function ($stackPos) {
$this->semValue = new Expr\Yield_(null, null, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
431 => function ($stackPos) {
$this->semValue = new Expr\Yield_($this->semStack[$stackPos-(2-2)], null, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
432 => function ($stackPos) {
$this->semValue = new Expr\Yield_($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-2)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
433 => function ($stackPos) {
$this->semValue = new Expr\YieldFrom($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
434 => function ($stackPos) {
$this->semValue = new Expr\Throw_($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
435 => function ($stackPos) {
$this->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-4)], 'returnType' => $this->semStack[$stackPos-(8-6)], 'expr' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes);
},
436 => function ($stackPos) {
$this->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'returnType' => $this->semStack[$stackPos-(9-7)], 'expr' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
437 => function ($stackPos) {
$this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(8-2)], 'params' => $this->semStack[$stackPos-(8-4)], 'uses' => $this->semStack[$stackPos-(8-6)], 'returnType' => $this->semStack[$stackPos-(8-7)], 'stmts' => $this->semStack[$stackPos-(8-8)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes);
},
438 => function ($stackPos) {
$this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'uses' => $this->semStack[$stackPos-(9-7)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => []], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
439 => function ($stackPos) {
$this->semValue = new Expr\ArrowFunction(['static' => false, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'returnType' => $this->semStack[$stackPos-(9-7)], 'expr' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
440 => function ($stackPos) {
$this->semValue = new Expr\ArrowFunction(['static' => true, 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-6)], 'returnType' => $this->semStack[$stackPos-(10-8)], 'expr' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes);
},
441 => function ($stackPos) {
$this->semValue = new Expr\Closure(['static' => false, 'byRef' => $this->semStack[$stackPos-(9-3)], 'params' => $this->semStack[$stackPos-(9-5)], 'uses' => $this->semStack[$stackPos-(9-7)], 'returnType' => $this->semStack[$stackPos-(9-8)], 'stmts' => $this->semStack[$stackPos-(9-9)], 'attrGroups' => $this->semStack[$stackPos-(9-1)]], $this->startAttributeStack[$stackPos-(9-1)] + $this->endAttributes);
},
442 => function ($stackPos) {
$this->semValue = new Expr\Closure(['static' => true, 'byRef' => $this->semStack[$stackPos-(10-4)], 'params' => $this->semStack[$stackPos-(10-6)], 'uses' => $this->semStack[$stackPos-(10-8)], 'returnType' => $this->semStack[$stackPos-(10-9)], 'stmts' => $this->semStack[$stackPos-(10-10)], 'attrGroups' => $this->semStack[$stackPos-(10-1)]], $this->startAttributeStack[$stackPos-(10-1)] + $this->endAttributes);
},
443 => function ($stackPos) {
$this->semValue = array(new Stmt\Class_(null, ['type' => 0, 'extends' => $this->semStack[$stackPos-(8-4)], 'implements' => $this->semStack[$stackPos-(8-5)], 'stmts' => $this->semStack[$stackPos-(8-7)], 'attrGroups' => $this->semStack[$stackPos-(8-1)]], $this->startAttributeStack[$stackPos-(8-1)] + $this->endAttributes), $this->semStack[$stackPos-(8-3)]);
$this->checkClass($this->semValue[0], -1);
},
444 => function ($stackPos) {
$this->semValue = new Expr\New_($this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
445 => function ($stackPos) {
list($class, $ctorArgs) = $this->semStack[$stackPos-(2-2)]; $this->semValue = new Expr\New_($class, $ctorArgs, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
446 => function ($stackPos) {
$this->semValue = array();
},
447 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(4-3)];
},
448 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(2-1)];
},
449 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
450 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
451 => function ($stackPos) {
$this->semValue = new Expr\ClosureUse($this->semStack[$stackPos-(2-2)], $this->semStack[$stackPos-(2-1)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
452 => function ($stackPos) {
$this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
453 => function ($stackPos) {
$this->semValue = new Expr\FuncCall($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
454 => function ($stackPos) {
$this->semValue = new Expr\StaticCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
455 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
456 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
457 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
458 => function ($stackPos) {
$this->semValue = new Name($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
459 => function ($stackPos) {
$this->semValue = new Name\FullyQualified(substr($this->semStack[$stackPos-(1-1)], 1), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
460 => function ($stackPos) {
$this->semValue = new Name\Relative(substr($this->semStack[$stackPos-(1-1)], 10), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
461 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
462 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
463 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
464 => function ($stackPos) {
$this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2;
},
465 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
466 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
467 => function ($stackPos) {
$this->semValue = null;
},
468 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
469 => function ($stackPos) {
$this->semValue = array();
},
470 => function ($stackPos) {
$this->semValue = array(new Scalar\EncapsedStringPart(Scalar\String_::parseEscapeSequences($this->semStack[$stackPos-(1-1)], '`'), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes));
},
471 => function ($stackPos) {
foreach ($this->semStack[$stackPos-(1-1)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '`', true); } }; $this->semValue = $this->semStack[$stackPos-(1-1)];
},
472 => function ($stackPos) {
$this->semValue = array();
},
473 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
474 => function ($stackPos) {
$this->semValue = new Expr\ConstFetch($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
475 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Line($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
476 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\File($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
477 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Dir($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
478 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Class_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
479 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Trait_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
480 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Method($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
481 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Function_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
482 => function ($stackPos) {
$this->semValue = new Scalar\MagicConst\Namespace_($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
483 => function ($stackPos) {
$this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
484 => function ($stackPos) {
$this->semValue = new Expr\ClassConstFetch($this->semStack[$stackPos-(3-1)], new Expr\Error($this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)]), $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes); $this->errorState = 2;
},
485 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_SHORT;
$this->semValue = new Expr\Array_($this->semStack[$stackPos-(3-2)], $attrs);
},
486 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes; $attrs['kind'] = Expr\Array_::KIND_LONG;
$this->semValue = new Expr\Array_($this->semStack[$stackPos-(4-3)], $attrs);
},
487 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
488 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes; $attrs['kind'] = ($this->semStack[$stackPos-(1-1)][0] === "'" || ($this->semStack[$stackPos-(1-1)][1] === "'" && ($this->semStack[$stackPos-(1-1)][0] === 'b' || $this->semStack[$stackPos-(1-1)][0] === 'B')) ? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED);
$this->semValue = new Scalar\String_(Scalar\String_::parse($this->semStack[$stackPos-(1-1)]), $attrs);
},
489 => function ($stackPos) {
$attrs = $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes; $attrs['kind'] = Scalar\String_::KIND_DOUBLE_QUOTED;
foreach ($this->semStack[$stackPos-(3-2)] as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) { $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, '"', true); } }; $this->semValue = new Scalar\Encapsed($this->semStack[$stackPos-(3-2)], $attrs);
},
490 => function ($stackPos) {
$this->semValue = $this->parseLNumber($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
491 => function ($stackPos) {
$this->semValue = new Scalar\DNumber(Scalar\DNumber::parse($this->semStack[$stackPos-(1-1)]), $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
492 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
493 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
494 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
495 => function ($stackPos) {
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
496 => function ($stackPos) {
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(2-1)], '', $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(2-2)] + $this->endAttributeStack[$stackPos-(2-2)], true);
},
497 => function ($stackPos) {
$this->semValue = $this->parseDocString($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-2)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes, $this->startAttributeStack[$stackPos-(3-3)] + $this->endAttributeStack[$stackPos-(3-3)], true);
},
498 => function ($stackPos) {
$this->semValue = null;
},
499 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
500 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
501 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
502 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
503 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
504 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
505 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
506 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
507 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
508 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
509 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
510 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
511 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
512 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
513 => function ($stackPos) {
$this->semValue = new Expr\MethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
514 => function ($stackPos) {
$this->semValue = new Expr\NullsafeMethodCall($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->semStack[$stackPos-(4-4)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
515 => function ($stackPos) {
$this->semValue = null;
},
516 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
517 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
518 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
519 => function ($stackPos) {
$this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
520 => function ($stackPos) {
$this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
521 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
522 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
523 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
524 => function ($stackPos) {
$this->semValue = new Expr\Variable(new Expr\Error($this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes), $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes); $this->errorState = 2;
},
525 => function ($stackPos) {
$var = $this->semStack[$stackPos-(1-1)]->name; $this->semValue = \is_string($var) ? new Node\VarLikeIdentifier($var, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes) : $var;
},
526 => function ($stackPos) {
$this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
527 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
528 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
529 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
530 => function ($stackPos) {
$this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
531 => function ($stackPos) {
$this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
532 => function ($stackPos) {
$this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
533 => function ($stackPos) {
$this->semValue = new Expr\StaticPropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
534 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
535 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
536 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
537 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
538 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
539 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
540 => function ($stackPos) {
$this->semValue = new Expr\Error($this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes); $this->errorState = 2;
},
541 => function ($stackPos) {
$this->semValue = new Expr\List_($this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
542 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)]; $end = count($this->semValue)-1; if ($this->semValue[$end] === null) array_pop($this->semValue);
},
543 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos];
},
544 => function ($stackPos) {
/* do nothing -- prevent default action of $$=$this->semStack[$1]. See $551. */
},
545 => function ($stackPos) {
$this->semStack[$stackPos-(3-1)][] = $this->semStack[$stackPos-(3-3)]; $this->semValue = $this->semStack[$stackPos-(3-1)];
},
546 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
547 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
548 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
549 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(1-1)], null, false, $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
550 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
551 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(4-4)], $this->semStack[$stackPos-(4-1)], true, $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
552 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(3-3)], $this->semStack[$stackPos-(3-1)], false, $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
553 => function ($stackPos) {
$this->semValue = new Expr\ArrayItem($this->semStack[$stackPos-(2-2)], null, false, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes, true, $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
554 => function ($stackPos) {
$this->semValue = null;
},
555 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
556 => function ($stackPos) {
$this->semStack[$stackPos-(2-1)][] = $this->semStack[$stackPos-(2-2)]; $this->semValue = $this->semStack[$stackPos-(2-1)];
},
557 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(1-1)]);
},
558 => function ($stackPos) {
$this->semValue = array($this->semStack[$stackPos-(2-1)], $this->semStack[$stackPos-(2-2)]);
},
559 => function ($stackPos) {
$this->semValue = new Scalar\EncapsedStringPart($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
560 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
561 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
562 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(4-1)], $this->semStack[$stackPos-(4-3)], $this->startAttributeStack[$stackPos-(4-1)] + $this->endAttributes);
},
563 => function ($stackPos) {
$this->semValue = new Expr\PropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
564 => function ($stackPos) {
$this->semValue = new Expr\NullsafePropertyFetch($this->semStack[$stackPos-(3-1)], $this->semStack[$stackPos-(3-3)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
565 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
566 => function ($stackPos) {
$this->semValue = new Expr\Variable($this->semStack[$stackPos-(3-2)], $this->startAttributeStack[$stackPos-(3-1)] + $this->endAttributes);
},
567 => function ($stackPos) {
$this->semValue = new Expr\ArrayDimFetch($this->semStack[$stackPos-(6-2)], $this->semStack[$stackPos-(6-4)], $this->startAttributeStack[$stackPos-(6-1)] + $this->endAttributes);
},
568 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(3-2)];
},
569 => function ($stackPos) {
$this->semValue = new Scalar\String_($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
570 => function ($stackPos) {
$this->semValue = $this->parseNumString($this->semStack[$stackPos-(1-1)], $this->startAttributeStack[$stackPos-(1-1)] + $this->endAttributes);
},
571 => function ($stackPos) {
$this->semValue = $this->parseNumString('-' . $this->semStack[$stackPos-(2-2)], $this->startAttributeStack[$stackPos-(2-1)] + $this->endAttributes);
},
572 => function ($stackPos) {
$this->semValue = $this->semStack[$stackPos-(1-1)];
},
];
}
}
<?php declare(strict_types=1);
namespace PhpParser\Parser;
use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Parser;
class Multiple implements Parser
{
/** @var Parser[] List of parsers to try, in order of preference */
private $parsers;
/**
* Create a parser which will try multiple parsers in an order of preference.
*
* Parsers will be invoked in the order they're provided to the constructor. If one of the
* parsers runs without throwing, it's output is returned. Otherwise the exception that the
* first parser generated is thrown.
*
* @param Parser[] $parsers
*/
public function __construct(array $parsers) {
$this->parsers = $parsers;
}
public function parse(string $code, ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing;
}
list($firstStmts, $firstError) = $this->tryParse($this->parsers[0], $errorHandler, $code);
if ($firstError === null) {
return $firstStmts;
}
for ($i = 1, $c = count($this->parsers); $i < $c; ++$i) {
list($stmts, $error) = $this->tryParse($this->parsers[$i], $errorHandler, $code);
if ($error === null) {
return $stmts;
}
}
throw $firstError;
}
private function tryParse(Parser $parser, ErrorHandler $errorHandler, $code) {
$stmts = null;
$error = null;
try {
$stmts = $parser->parse($code, $errorHandler);
} catch (Error $error) {}
return [$stmts, $error];
}
}
<?php declare(strict_types=1);
namespace PhpParser;
use PhpParser\Node\Arg;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\BinaryOp\Concat;
use PhpParser\Node\Identifier;
use PhpParser\Node\Name;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Use_;
class BuilderFactory
{
/**
* Creates a namespace builder.
*
* @param null|string|Node\Name $name Name of the namespace
*
* @return Builder\Namespace_ The created namespace builder
*/
public function namespace($name) : Builder\Namespace_ {
return new Builder\Namespace_($name);
}
/**
* Creates a class builder.
*
* @param string $name Name of the class
*
* @return Builder\Class_ The created class builder
*/
public function class(string $name) : Builder\Class_ {
return new Builder\Class_($name);
}
/**
* Creates an interface builder.
*
* @param string $name Name of the interface
*
* @return Builder\Interface_ The created interface builder
*/
public function interface(string $name) : Builder\Interface_ {
return new Builder\Interface_($name);
}
/**
* Creates a trait builder.
*
* @param string $name Name of the trait
*
* @return Builder\Trait_ The created trait builder
*/
public function trait(string $name) : Builder\Trait_ {
return new Builder\Trait_($name);
}
/**
* Creates a trait use builder.
*
* @param Node\Name|string ...$traits Trait names
*
* @return Builder\TraitUse The create trait use builder
*/
public function useTrait(...$traits) : Builder\TraitUse {
return new Builder\TraitUse(...$traits);
}
/**
* Creates a trait use adaptation builder.
*
* @param Node\Name|string|null $trait Trait name
* @param Node\Identifier|string $method Method name
*
* @return Builder\TraitUseAdaptation The create trait use adaptation builder
*/
public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation {
if ($method === null) {
$method = $trait;
$trait = null;
}
return new Builder\TraitUseAdaptation($trait, $method);
}
/**
* Creates a method builder.
*
* @param string $name Name of the method
*
* @return Builder\Method The created method builder
*/
public function method(string $name) : Builder\Method {
return new Builder\Method($name);
}
/**
* Creates a parameter builder.
*
* @param string $name Name of the parameter
*
* @return Builder\Param The created parameter builder
*/
public function param(string $name) : Builder\Param {
return new Builder\Param($name);
}
/**
* Creates a property builder.
*
* @param string $name Name of the property
*
* @return Builder\Property The created property builder
*/
public function property(string $name) : Builder\Property {
return new Builder\Property($name);
}
/**
* Creates a function builder.
*
* @param string $name Name of the function
*
* @return Builder\Function_ The created function builder
*/
public function function(string $name) : Builder\Function_ {
return new Builder\Function_($name);
}
/**
* Creates a namespace/class use builder.
*
* @param Node\Name|string $name Name of the entity (namespace or class) to alias
*
* @return Builder\Use_ The created use builder
*/
public function use($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_NORMAL);
}
/**
* Creates a function use builder.
*
* @param Node\Name|string $name Name of the function to alias
*
* @return Builder\Use_ The created use function builder
*/
public function useFunction($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_FUNCTION);
}
/**
* Creates a constant use builder.
*
* @param Node\Name|string $name Name of the const to alias
*
* @return Builder\Use_ The created use const builder
*/
public function useConst($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
}
/**
* Creates node a for a literal value.
*
* @param Expr|bool|null|int|float|string|array $value $value
*
* @return Expr
*/
public function val($value) : Expr {
return BuilderHelpers::normalizeValue($value);
}
/**
* Creates variable node.
*
* @param string|Expr $name Name
*
* @return Expr\Variable
*/
public function var($name) : Expr\Variable {
if (!\is_string($name) && !$name instanceof Expr) {
throw new \LogicException('Variable name must be string or Expr');
}
return new Expr\Variable($name);
}
/**
* Normalizes an argument list.
*
* Creates Arg nodes for all arguments and converts literal values to expressions.
*
* @param array $args List of arguments to normalize
*
* @return Arg[]
*/
public function args(array $args) : array {
$normalizedArgs = [];
foreach ($args as $arg) {
if ($arg instanceof Arg) {
$normalizedArgs[] = $arg;
} else {
$normalizedArgs[] = new Arg(BuilderHelpers::normalizeValue($arg));
}
}
return $normalizedArgs;
}
/**
* Creates a function call node.
*
* @param string|Name|Expr $name Function name
* @param array $args Function arguments
*
* @return Expr\FuncCall
*/
public function funcCall($name, array $args = []) : Expr\FuncCall {
return new Expr\FuncCall(
BuilderHelpers::normalizeNameOrExpr($name),
$this->args($args)
);
}
/**
* Creates a method call node.
*
* @param Expr $var Variable the method is called on
* @param string|Identifier|Expr $name Method name
* @param array $args Method arguments
*
* @return Expr\MethodCall
*/
public function methodCall(Expr $var, $name, array $args = []) : Expr\MethodCall {
return new Expr\MethodCall(
$var,
BuilderHelpers::normalizeIdentifierOrExpr($name),
$this->args($args)
);
}
/**
* Creates a static method call node.
*
* @param string|Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param array $args Method arguments
*
* @return Expr\StaticCall
*/
public function staticCall($class, $name, array $args = []) : Expr\StaticCall {
return new Expr\StaticCall(
BuilderHelpers::normalizeNameOrExpr($class),
BuilderHelpers::normalizeIdentifierOrExpr($name),
$this->args($args)
);
}
/**
* Creates an object creation node.
*
* @param string|Name|Expr $class Class name
* @param array $args Constructor arguments
*
* @return Expr\New_
*/
public function new($class, array $args = []) : Expr\New_ {
return new Expr\New_(
BuilderHelpers::normalizeNameOrExpr($class),
$this->args($args)
);
}
/**
* Creates a constant fetch node.
*
* @param string|Name $name Constant name
*
* @return Expr\ConstFetch
*/
public function constFetch($name) : Expr\ConstFetch {
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
}
/**
* Creates a property fetch node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Property name
*
* @return Expr\PropertyFetch
*/
public function propertyFetch(Expr $var, $name) : Expr\PropertyFetch {
return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name));
}
/**
* Creates a class constant fetch node.
*
* @param string|Name|Expr $class Class name
* @param string|Identifier $name Constant name
*
* @return Expr\ClassConstFetch
*/
public function classConstFetch($class, $name): Expr\ClassConstFetch {
return new Expr\ClassConstFetch(
BuilderHelpers::normalizeNameOrExpr($class),
BuilderHelpers::normalizeIdentifier($name)
);
}
/**
* Creates nested Concat nodes from a list of expressions.
*
* @param Expr|string ...$exprs Expressions or literal strings
*
* @return Concat
*/
public function concat(...$exprs) : Concat {
$numExprs = count($exprs);
if ($numExprs < 2) {
throw new \LogicException('Expected at least two expressions');
}
$lastConcat = $this->normalizeStringExpr($exprs[0]);
for ($i = 1; $i < $numExprs; $i++) {
$lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i]));
}
return $lastConcat;
}
/**
* @param string|Expr $expr
* @return Expr
*/
private function normalizeStringExpr($expr) : Expr {
if ($expr instanceof Expr) {
return $expr;
}
if (\is_string($expr)) {
return new String_($expr);
}
throw new \LogicException('Expected string or Expr');
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class MatchTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function getKeywordString(): string
{
return 'match';
}
public function getKeywordToken(): int
{
return \T_MATCH;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class NumericLiteralSeparatorEmulator extends TokenEmulator
{
const BIN = '(?:0b[01]+(?:_[01]+)*)';
const HEX = '(?:0x[0-9a-f]+(?:_[0-9a-f]+)*)';
const DEC = '(?:[0-9]+(?:_[0-9]+)*)';
const SIMPLE_FLOAT = '(?:' . self::DEC . '\.' . self::DEC . '?|\.' . self::DEC . ')';
const EXP = '(?:e[+-]?' . self::DEC . ')';
const FLOAT = '(?:' . self::SIMPLE_FLOAT . self::EXP . '?|' . self::DEC . self::EXP . ')';
const NUMBER = '~' . self::FLOAT . '|' . self::BIN . '|' . self::HEX . '|' . self::DEC . '~iA';
public function getPhpVersion(): string
{
return Emulative::PHP_7_4;
}
public function isEmulationNeeded(string $code) : bool
{
return preg_match('~[0-9]_[0-9]~', $code)
|| preg_match('~0x[0-9a-f]+_[0-9a-f]~i', $code);
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$codeOffset = 0;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
$token = $tokens[$i];
$tokenLen = \strlen(\is_array($token) ? $token[1] : $token);
if ($token[0] !== T_LNUMBER && $token[0] !== T_DNUMBER) {
$codeOffset += $tokenLen;
continue;
}
$res = preg_match(self::NUMBER, $code, $matches, 0, $codeOffset);
assert($res, "No number at number token position");
$match = $matches[0];
$matchLen = \strlen($match);
if ($matchLen === $tokenLen) {
// Original token already holds the full number.
$codeOffset += $tokenLen;
continue;
}
$tokenKind = $this->resolveIntegerOrFloatToken($match);
$newTokens = [[$tokenKind, $match, $token[2]]];
$numTokens = 1;
$len = $tokenLen;
while ($matchLen > $len) {
$nextToken = $tokens[$i + $numTokens];
$nextTokenText = \is_array($nextToken) ? $nextToken[1] : $nextToken;
$nextTokenLen = \strlen($nextTokenText);
$numTokens++;
if ($matchLen < $len + $nextTokenLen) {
// Split trailing characters into a partial token.
assert(is_array($nextToken), "Partial token should be an array token");
$partialText = substr($nextTokenText, $matchLen - $len);
$newTokens[] = [$nextToken[0], $partialText, $nextToken[2]];
break;
}
$len += $nextTokenLen;
}
array_splice($tokens, $i, $numTokens, $newTokens);
$c -= $numTokens - \count($newTokens);
$codeOffset += $matchLen;
}
return $tokens;
}
private function resolveIntegerOrFloatToken(string $str): int
{
$str = str_replace('_', '', $str);
if (stripos($str, '0b') === 0) {
$num = bindec($str);
} elseif (stripos($str, '0x') === 0) {
$num = hexdec($str);
} elseif (stripos($str, '0') === 0 && ctype_digit($str)) {
$num = octdec($str);
} else {
$num = +$str;
}
return is_float($num) ? T_DNUMBER : T_LNUMBER;
}
public function reverseEmulate(string $code, array $tokens): array
{
// Numeric separators were not legal code previously, don't bother.
return $tokens;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/** @internal */
abstract class TokenEmulator
{
abstract public function getPhpVersion(): string;
abstract public function isEmulationNeeded(string $code): bool;
/**
* @return array Modified Tokens
*/
abstract public function emulate(string $code, array $tokens): array;
/**
* @return array Modified Tokens
*/
abstract public function reverseEmulate(string $code, array $tokens): array;
public function preprocessCode(string $code, array &$patches): string {
return $code;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
/**
* Reverses emulation direction of the inner emulator.
*/
final class ReverseEmulator extends TokenEmulator
{
/** @var TokenEmulator Inner emulator */
private $emulator;
public function __construct(TokenEmulator $emulator) {
$this->emulator = $emulator;
}
public function getPhpVersion(): string {
return $this->emulator->getPhpVersion();
}
public function isEmulationNeeded(string $code): bool {
return $this->emulator->isEmulationNeeded($code);
}
public function emulate(string $code, array $tokens): array {
return $this->emulator->reverseEmulate($code, $tokens);
}
public function reverseEmulate(string $code, array $tokens): array {
return $this->emulator->emulate($code, $tokens);
}
public function preprocessCode(string $code, array &$patches): string {
return $code;
}
}<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class AttributeEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function isEmulationNeeded(string $code) : bool
{
return strpos($code, '#[') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way.
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') {
array_splice($tokens, $i, 2, [
[\T_ATTRIBUTE, '#[', $line]
]);
$c--;
continue;
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// TODO
return $tokens;
}
public function preprocessCode(string $code, array &$patches): string {
$pos = 0;
while (false !== $pos = strpos($code, '#[', $pos)) {
// Replace #[ with %[
$code[$pos] = '%';
$patches[] = [$pos, 'replace', '#'];
$pos += 2;
}
return $code;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class CoaleseEqualTokenEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_7_4;
}
public function isEmulationNeeded(string $code): bool
{
return strpos($code, '??=') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if (isset($tokens[$i + 1])) {
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
array_splice($tokens, $i, 2, [
[\T_COALESCE_EQUAL, '??=', $line]
]);
$c--;
continue;
}
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// ??= was not valid code previously, don't bother.
return $tokens;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class FnTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_7_4;
}
public function getKeywordString(): string
{
return 'fn';
}
public function getKeywordToken(): int
{
return \T_FN;
}
}<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class FlexibleDocStringEmulator extends TokenEmulator
{
const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
public function getPhpVersion(): string
{
return Emulative::PHP_7_3;
}
public function isEmulationNeeded(string $code) : bool
{
return strpos($code, '<<<') !== false;
}
public function emulate(string $code, array $tokens): array
{
// Handled by preprocessing + fixup.
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// Not supported.
return $tokens;
}
public function preprocessCode(string $code, array &$patches): string {
if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
// No heredoc/nowdoc found
return $code;
}
// Keep track of how much we need to adjust string offsets due to the modifications we
// already made
$posDelta = 0;
foreach ($matches as $match) {
$indentation = $match['indentation'][0];
$indentationStart = $match['indentation'][1];
$separator = $match['separator'][0];
$separatorStart = $match['separator'][1];
if ($indentation === '' && $separator !== '') {
// Ordinary heredoc/nowdoc
continue;
}
if ($indentation !== '') {
// Remove indentation
$indentationLen = strlen($indentation);
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
$patches[] = [$indentationStart + $posDelta, 'add', $indentation];
$posDelta -= $indentationLen;
}
if ($separator === '') {
// Insert newline as separator
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
$patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
$posDelta += 1;
}
}
return $code;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
abstract class KeywordEmulator extends TokenEmulator
{
abstract function getKeywordString(): string;
abstract function getKeywordToken(): int;
public function isEmulationNeeded(string $code): bool
{
return strpos(strtolower($code), $this->getKeywordString()) !== false;
}
public function emulate(string $code, array $tokens): array
{
$keywordString = $this->getKeywordString();
foreach ($tokens as $i => $token) {
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString) {
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $i);
if ($previousNonSpaceToken !== null && $previousNonSpaceToken[0] === \T_OBJECT_OPERATOR) {
continue;
}
$tokens[$i][0] = $this->getKeywordToken();
}
}
return $tokens;
}
/**
* @param mixed[] $tokens
* @return mixed[]|null
*/
private function getPreviousNonSpaceToken(array $tokens, int $start)
{
for ($i = $start - 1; $i >= 0; --$i) {
if ($tokens[$i][0] === T_WHITESPACE) {
continue;
}
return $tokens[$i];
}
return null;
}
public function reverseEmulate(string $code, array $tokens): array
{
$keywordToken = $this->getKeywordToken();
foreach ($tokens as $i => $token) {
if ($token[0] === $keywordToken) {
$tokens[$i][0] = \T_STRING;
}
}
return $tokens;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer\TokenEmulator;
use PhpParser\Lexer\Emulative;
final class NullsafeTokenEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function isEmulationNeeded(string $code): bool
{
return strpos($code, '?->') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
array_splice($tokens, $i, 2, [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
]);
$c--;
continue;
}
// Handle ?-> inside encapsed string.
if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
&& $tokens[$i - 1][0] === \T_VARIABLE
&& preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches)
) {
$replacement = [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line],
[\T_STRING, $matches[1], $line],
];
if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) {
$replacement[] = [
\T_ENCAPSED_AND_WHITESPACE,
\substr($tokens[$i][1], \strlen($matches[0])),
$line
];
}
array_splice($tokens, $i, 1, $replacement);
$c += \count($replacement) - 1;
continue;
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// ?-> was not valid code previously, don't bother.
return $tokens;
}
}
<?php declare(strict_types=1);
namespace PhpParser\Lexer;
use PhpParser\Error;
use PhpParser\ErrorHandler;
use PhpParser\Lexer;
use PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
use PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use PhpParser\Lexer\TokenEmulator\TokenEmulator;
use PhpParser\Parser\Tokens;
class Emulative extends Lexer
{
const PHP_7_3 = '7.3dev';
const PHP_7_4 = '7.4dev';
const PHP_8_0 = '8.0dev';
/** @var mixed[] Patches used to reverse changes introduced in the code */
private $patches = [];
/** @var TokenEmulator[] */
private $emulators = [];
/** @var string */
private $targetPhpVersion;
/**
* @param mixed[] $options Lexer options. In addition to the usual options,
* accepts a 'phpVersion' string that specifies the
* version to emulated. Defaults to newest supported.
*/
public function __construct(array $options = [])
{
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_0;
unset($options['phpVersion']);
parent::__construct($options);
$emulators = [
new FlexibleDocStringEmulator(),
new FnTokenEmulator(),
new MatchTokenEmulator(),
new CoaleseEqualTokenEmulator(),
new NumericLiteralSeparatorEmulator(),
new NullsafeTokenEmulator(),
new AttributeEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running
// and the PHP version we're targeting for emulation.
foreach ($emulators as $emulator) {
$emulatorPhpVersion = $emulator->getPhpVersion();
if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) {
$this->emulators[] = $emulator;
} else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
$this->emulators[] = new ReverseEmulator($emulator);
}
}
}
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
$emulators = array_filter($this->emulators, function($emulator) use($code) {
return $emulator->isEmulationNeeded($code);
});
if (empty($emulators)) {
// Nothing to emulate, yay
parent::startLexing($code, $errorHandler);
return;
}
$this->patches = [];
foreach ($emulators as $emulator) {
$code = $emulator->preprocessCode($code, $this->patches);
}
$collector = new ErrorHandler\Collecting();
parent::startLexing($code, $collector);
$this->sortPatches();
$this->fixupTokens();
$errors = $collector->getErrors();
if (!empty($errors)) {
$this->fixupErrors($errors);
foreach ($errors as $error) {
$errorHandler->handleError($error);
}
}
foreach ($emulators as $emulator) {
$this->tokens = $emulator->emulate($code, $this->tokens);
}
}
private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool {
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=');
}
private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool {
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<');
}
private function sortPatches()
{
// Patches may be contributed by different emulators.
// Make sure they are sorted by increasing patch position.
usort($this->patches, function($p1, $p2) {
return $p1[0] <=> $p2[0];
});
}
private function fixupTokens()
{
if (\count($this->patches) === 0) {
return;
}
// Load first patch
$patchIdx = 0;
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// We use a manual loop over the tokens, because we modify the array on the fly
$pos = 0;
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
$token = $this->tokens[$i];
if (\is_string($token)) {
if ($patchPos === $pos) {
// Only support replacement for string tokens.
assert($patchType === 'replace');
$this->tokens[$i] = $patchText;
// Fetch the next patch
$patchIdx++;
if ($patchIdx >= \count($this->patches)) {
// No more patches, we're done
return;
}
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
}
$pos += \strlen($token);
continue;
}
$len = \strlen($token[1]);
$posDelta = 0;
while ($patchPos >= $pos && $patchPos < $pos + $len) {
$patchTextLen = \strlen($patchText);
if ($patchType === 'remove') {
if ($patchPos === $pos && $patchTextLen === $len) {
// Remove token entirely
array_splice($this->tokens, $i, 1, []);
$i--;
$c--;
} else {
// Remove from token string
$this->tokens[$i][1] = substr_replace(
$token[1], '', $patchPos - $pos + $posDelta, $patchTextLen
);
$posDelta -= $patchTextLen;
}
} elseif ($patchType === 'add') {
// Insert into the token string
$this->tokens[$i][1] = substr_replace(
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
);
$posDelta += $patchTextLen;
} else if ($patchType === 'replace') {
// Replace inside the token string
$this->tokens[$i][1] = substr_replace(
$token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen
);
} else {
assert(false);
}
// Fetch the next patch
$patchIdx++;
if ($patchIdx >= \count($this->patches)) {
// No more patches, we're done
return;
}
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// Multiple patches may apply to the same token. Reload the current one to check
// If the new patch applies
$token = $this->tokens[$i];
}
$pos += $len;
}
// A patch did not apply
assert(false);
}
/**
* Fixup line and position information in errors.
*
* @param Error[] $errors
*/
private function fixupErrors(array $errors) {
foreach ($errors as $error) {
$attrs = $error->getAttributes();
$posDelta = 0;
$lineDelta = 0;
foreach ($this->patches as $patch) {
list($patchPos, $patchType, $patchText) = $patch;
if ($patchPos >= $attrs['startFilePos']) {
// No longer relevant
break;
}
if ($patchType === 'add') {
$posDelta += strlen($patchText);
$lineDelta += substr_count($patchText, "\n");
} else if ($patchType === 'remove') {
$posDelta -= strlen($patchText);
$lineDelta -= substr_count($patchText, "\n");
}
}
$attrs['startFilePos'] += $posDelta;
$attrs['endFilePos'] += $posDelta;
$attrs['startLine'] += $lineDelta;
$attrs['endLine'] += $lineDelta;
$error->setAttributes($attrs);
}
}
}
<?php declare(strict_types=1);
namespace PhpParser;
use PhpParser\Node\Name;
use PhpParser\Node\Name\FullyQualified;
use PhpParser\Node\Stmt;
class NameContext
{
/** @var null|Name Current namespace */
protected $namespace;
/** @var Name[][] Map of format [aliasType => [aliasName => originalName]] */
protected $aliases = [];
/** @var Name[][] Same as $aliases but preserving original case */
protected $origAliases = [];
/** @var ErrorHandler Error handler */
protected $errorHandler;
/**
* Create a name context.
*
* @param ErrorHandler $errorHandler Error handling used to report errors
*/
public function __construct(ErrorHandler $errorHandler) {
$this->errorHandler = $errorHandler;
}
/**
* Start a new namespace.
*
* This also resets the alias table.
*
* @param Name|null $namespace Null is the global namespace
*/
public function startNamespace(Name $namespace = null) {
$this->namespace = $namespace;
$this->origAliases = $this->aliases = [
Stmt\Use_::TYPE_NORMAL => [],
Stmt\Use_::TYPE_FUNCTION => [],
Stmt\Use_::TYPE_CONSTANT => [],
];
}
/**
* Add an alias / import.
*
* @param Name $name Original name
* @param string $aliasName Aliased name
* @param int $type One of Stmt\Use_::TYPE_*
* @param array $errorAttrs Attributes to use to report an error
*/
public function addAlias(Name $name, string $aliasName, int $type, array $errorAttrs = []) {
// Constant names are case sensitive, everything else case insensitive
if ($type === Stmt\Use_::TYPE_CONSTANT) {
$aliasLookupName = $aliasName;
} else {
$aliasLookupName = strtolower($aliasName);
}
if (isset($this->aliases[$type][$aliasLookupName])) {
$typeStringMap = [
Stmt\Use_::TYPE_NORMAL => '',
Stmt\Use_::TYPE_FUNCTION => 'function ',
Stmt\Use_::TYPE_CONSTANT => 'const ',
];
$this->errorHandler->handleError(new Error(
sprintf(
'Cannot use %s%s as %s because the name is already in use',
$typeStringMap[$type], $name, $aliasName
),
$errorAttrs
));
return;
}
$this->aliases[$type][$aliasLookupName] = $name;
$this->origAliases[$type][$aliasName] = $name;
}
/**
* Get current namespace.
*
* @return null|Name Namespace (or null if global namespace)
*/
public function getNamespace() {
return $this->namespace;
}
/**
* Get resolved name.
*
* @param Name $name Name to resolve
* @param int $type One of Stmt\Use_::TYPE_{FUNCTION|CONSTANT}
*
* @return null|Name Resolved name, or null if static resolution is not possible
*/
public function getResolvedName(Name $name, int $type) {
// don't resolve special class names
if ($type === Stmt\Use_::TYPE_NORMAL && $name->isSpecialClassName()) {
if (!$name->isUnqualified()) {
$this->errorHandler->handleError(new Error(
sprintf("'\\%s' is an invalid class name", $name->toString()),
$name->getAttributes()
));
}
return $name;
}
// fully qualified names are already resolved
if ($name->isFullyQualified()) {
return $name;
}
// Try to resolve aliases
if (null !== $resolvedName = $this->resolveAlias($name, $type)) {
return $resolvedName;
}
if ($type !== Stmt\Use_::TYPE_NORMAL && $name->isUnqualified()) {
if (null === $this->namespace) {
// outside of a namespace unaliased unqualified is same as fully qualified
return new FullyQualified($name, $name->getAttributes());
}
// Cannot resolve statically
return null;
}
// if no alias exists prepend current namespace
return FullyQualified::concat($this->namespace, $name, $name->getAttributes());
}
/**
* Get resolved class name.
*
* @param Name $name Class ame to resolve
*
* @return Name Resolved name
*/
public function getResolvedClassName(Name $name) : Name {
return $this->getResolvedName($name, Stmt\Use_::TYPE_NORMAL);
}
/**
* Get possible ways of writing a fully qualified name (e.g., by making use of aliases).
*
* @param string $name Fully-qualified name (without leading namespace separator)
* @param int $type One of Stmt\Use_::TYPE_*
*
* @return Name[] Possible representations of the name
*/
public function getPossibleNames(string $name, int $type) : array {
$lcName = strtolower($name);
if ($type === Stmt\Use_::TYPE_NORMAL) {
// self, parent and static must always be unqualified
if ($lcName === "self" || $lcName === "parent" || $lcName === "static") {
return [new Name($name)];
}
}
// Collect possible ways to write this name, starting with the fully-qualified name
$possibleNames = [new FullyQualified($name)];
if (null !== $nsRelativeName = $this->getNamespaceRelativeName($name, $lcName, $type)) {
// Make sure there is no alias that makes the normally namespace-relative name
// into something else
if (null === $this->resolveAlias($nsRelativeName, $type)) {
$possibleNames[] = $nsRelativeName;
}
}
// Check for relevant namespace use statements
foreach ($this->origAliases[Stmt\Use_::TYPE_NORMAL] as $alias => $orig) {
$lcOrig = $orig->toLowerString();
if (0 === strpos($lcName, $lcOrig . '\\')) {
$possibleNames[] = new Name($alias . substr($name, strlen($lcOrig)));
}
}
// Check for relevant type-specific use statements
foreach ($this->origAliases[$type] as $alias => $orig) {
if ($type === Stmt\Use_::TYPE_CONSTANT) {
// Constants are are complicated-sensitive
$normalizedOrig = $this->normalizeConstName($orig->toString());
if ($normalizedOrig === $this->normalizeConstName($name)) {
$possibleNames[] = new Name($alias);
}
} else {
// Everything else is case-insensitive
if ($orig->toLowerString() === $lcName) {
$possibleNames[] = new Name($alias);
}
}
}
return $possibleNames;
}
/**
* Get shortest representation of this fully-qualified name.
*
* @param string $name Fully-qualified name (without leading namespace separator)
* @param int $type One of Stmt\Use_::TYPE_*
*
* @return Name Shortest representation
*/
public function getShortName(string $name, int $type) : Name {
$possibleNames = $this->getPossibleNames($name, $type);
// Find shortest name
$shortestName = null;
$shortestLength = \INF;
foreach ($possibleNames as $possibleName) {
$length = strlen($possibleName->toCodeString());
if ($length < $shortestLength) {
$shortestName = $possibleName;
$shortestLength = $length;
}
}
return $shortestName;
}
private function resolveAlias(Name $name, $type) {
$firstPart = $name->getFirst();
if ($name->isQualified()) {
// resolve aliases for qualified names, always against class alias table
$checkName = strtolower($firstPart);
if (isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName])) {
$alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$checkName];
return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes());
}
} elseif ($name->isUnqualified()) {
// constant aliases are case-sensitive, function aliases case-insensitive
$checkName = $type === Stmt\Use_::TYPE_CONSTANT ? $firstPart : strtolower($firstPart);
if (isset($this->aliases[$type][$checkName])) {
// resolve unqualified aliases
return new FullyQualified($this->aliases[$type][$checkName], $name->getAttributes());
}
}
// No applicable aliases
return null;
}
private function getNamespaceRelativeName(string $name, string $lcName, int $type) {
if (null === $this->namespace) {
return new Name($name);
}
if ($type === Stmt\Use_::TYPE_CONSTANT) {
// The constants true/false/null always resolve to the global symbols, even inside a
// namespace, so they may be used without qualification
if ($lcName === "true" || $lcName === "false" || $lcName === "null") {
return new Name($name);
}
}
$namespacePrefix = strtolower($this->namespace . '\\');
if (0 === strpos($lcName, $namespacePrefix)) {
return new Name(substr($name, strlen($namespacePrefix)));
}
return null;
}
private function normalizeConstName(string $name) {
$nsSep = strrpos($name, '\\');
if (false === $nsSep) {
return $name;
}
// Constants have case-insensitive namespace and case-sensitive short-name
$ns = substr($name, 0, $nsSep);
$shortName = substr($name, $nsSep + 1);
return strtolower($ns) . '\\' . $shortName;
}
}
<?php declare(strict_types=1);
namespace PhpParser;
/*
* This parser is based on a skeleton written by Moriyoshi Koizumi, which in
* turn is based on work by Masato Bito.
*/
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\Cast\Double;
use PhpParser\Node\Name;
use PhpParser\Node\Param;
use PhpParser\Node\Scalar\Encapsed;
use PhpParser\Node\Scalar\LNumber;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\Class_;
use PhpParser\Node\Stmt\ClassConst;
use PhpParser\Node\Stmt\ClassMethod;
use PhpParser\Node\Stmt\Interface_;
use PhpParser\Node\Stmt\Namespace_;
use PhpParser\Node\Stmt\Property;
use PhpParser\Node\Stmt\TryCatch;
use PhpParser\Node\Stmt\UseUse;
use PhpParser\Node\VarLikeIdentifier;
abstract class ParserAbstract implements Parser
{
const SYMBOL_NONE = -1;
/*
* The following members will be filled with generated parsing data:
*/
/** @var int Size of $tokenToSymbol map */
protected $tokenToSymbolMapSize;
/** @var int Size of $action table */
protected $actionTableSize;
/** @var int Size of $goto table */
protected $gotoTableSize;
/** @var int Symbol number signifying an invalid token */
protected $invalidSymbol;
/** @var int Symbol number of error recovery token */
protected $errorSymbol;
/** @var int Action number signifying default action */
protected $defaultAction;
/** @var int Rule number signifying that an unexpected token was encountered */
protected $unexpectedTokenRule;
protected $YY2TBLSTATE;
/** @var int Number of non-leaf states */
protected $numNonLeafStates;
/** @var int[] Map of lexer tokens to internal symbols */
protected $tokenToSymbol;
/** @var string[] Map of symbols to their names */
protected $symbolToName;
/** @var array Names of the production rules (only necessary for debugging) */
protected $productions;
/** @var int[] Map of states to a displacement into the $action table. The corresponding action for this
* state/symbol pair is $action[$actionBase[$state] + $symbol]. If $actionBase[$state] is 0, the
action is defaulted, i.e. $actionDefault[$state] should be used instead. */
protected $actionBase;
/** @var int[] Table of actions. Indexed according to $actionBase comment. */
protected $action;
/** @var int[] Table indexed analogously to $action. If $actionCheck[$actionBase[$state] + $symbol] != $symbol
* then the action is defaulted, i.e. $actionDefault[$state] should be used instead. */
protected $actionCheck;
/** @var int[] Map of states to their default action */
protected $actionDefault;
/** @var callable[] Semantic action callbacks */
protected $reduceCallbacks;
/** @var int[] Map of non-terminals to a displacement into the $goto table. The corresponding goto state for this
* non-terminal/state pair is $goto[$gotoBase[$nonTerminal] + $state] (unless defaulted) */
protected $gotoBase;
/** @var int[] Table of states to goto after reduction. Indexed according to $gotoBase comment. */
protected $goto;
/** @var int[] Table indexed analogously to $goto. If $gotoCheck[$gotoBase[$nonTerminal] + $state] != $nonTerminal
* then the goto state is defaulted, i.e. $gotoDefault[$nonTerminal] should be used. */
protected $gotoCheck;
/** @var int[] Map of non-terminals to the default state to goto after their reduction */
protected $gotoDefault;
/** @var int[] Map of rules to the non-terminal on their left-hand side, i.e. the non-terminal to use for
* determining the state to goto after reduction. */
protected $ruleToNonTerminal;
/** @var int[] Map of rules to the length of their right-hand side, which is the number of elements that have to
* be popped from the stack(s) on reduction. */
protected $ruleToLength;
/*
* The following members are part of the parser state:
*/
/** @var Lexer Lexer that is used when parsing */
protected $lexer;
/** @var mixed Temporary value containing the result of last semantic action (reduction) */
protected $semValue;
/** @var array Semantic value stack (contains values of tokens and semantic action results) */
protected $semStack;
/** @var array[] Start attribute stack */
protected $startAttributeStack;
/** @var array[] End attribute stack */
protected $endAttributeStack;
/** @var array End attributes of last *shifted* token */
protected $endAttributes;
/** @var array Start attributes of last *read* token */
protected $lookaheadStartAttributes;
/** @var ErrorHandler Error handler */
protected $errorHandler;
/** @var int Error state, used to avoid error floods */
protected $errorState;
/**
* Initialize $reduceCallbacks map.
*/
abstract protected function initReduceCallbacks();
/**
* Creates a parser instance.
*
* Options: Currently none.
*
* @param Lexer $lexer A lexer
* @param array $options Options array.
*/
public function __construct(Lexer $lexer, array $options = []) {
$this->lexer = $lexer;
if (isset($options['throwOnError'])) {
throw new \LogicException(
'"throwOnError" is no longer supported, use "errorHandler" instead');
}
$this->initReduceCallbacks();
}
/**
* Parses PHP code into a node tree.
*
* If a non-throwing error handler is used, the parser will continue parsing after an error
* occurred and attempt to build a partial AST.
*
* @param string $code The source code to parse
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
* to ErrorHandler\Throwing.
*
* @return Node\Stmt[]|null Array of statements (or null non-throwing error handler is used and
* the parser was unable to recover from an error).
*/
public function parse(string $code, ErrorHandler $errorHandler = null) {
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
$this->lexer->startLexing($code, $this->errorHandler);
$result = $this->doParse();
// Clear out some of the interior state, so we don't hold onto unnecessary
// memory between uses of the parser
$this->startAttributeStack = [];
$this->endAttributeStack = [];
$this->semStack = [];
$this->semValue = null;
return $result;
}
protected function doParse() {
// We start off with no lookahead-token
$symbol = self::SYMBOL_NONE;
// The attributes for a node are taken from the first and last token of the node.
// From the first token only the startAttributes are taken and from the last only
// the endAttributes. Both are merged using the array union operator (+).
$startAttributes = [];
$endAttributes = [];
$this->endAttributes = $endAttributes;
// Keep stack of start and end attributes
$this->startAttributeStack = [];
$this->endAttributeStack = [$endAttributes];
// Start off in the initial state and keep a stack of previous states
$state = 0;
$stateStack = [$state];
// Semantic value stack (contains values of tokens and semantic action results)
$this->semStack = [];
// Current position in the stack(s)
$stackPos = 0;
$this->errorState = 0;
for (;;) {
//$this->traceNewState($state, $symbol);
if ($this->actionBase[$state] === 0) {
$rule = $this->actionDefault[$state];
} else {
if ($symbol === self::SYMBOL_NONE) {
// Fetch the next token id from the lexer and fetch additional info by-ref.
// The end attributes are fetched into a temporary variable and only set once the token is really
// shifted (not during read). Otherwise you would sometimes get off-by-one errors, when a rule is
// reduced after a token was read but not yet shifted.
$tokenId = $this->lexer->getNextToken($tokenValue, $startAttributes, $endAttributes);
// map the lexer token id to the internally used symbols
$symbol = $tokenId >= 0 && $tokenId < $this->tokenToSymbolMapSize
? $this->tokenToSymbol[$tokenId]
: $this->invalidSymbol;
if ($symbol === $this->invalidSymbol) {
throw new \RangeException(sprintf(
'The lexer returned an invalid token (id=%d, value=%s)',
$tokenId, $tokenValue
));
}
// Allow productions to access the start attributes of the lookahead token.
$this->lookaheadStartAttributes = $startAttributes;
//$this->traceRead($symbol);
}
$idx = $this->actionBase[$state] + $symbol;
if ((($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol)
|| ($state < $this->YY2TBLSTATE
&& ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol))
&& ($action = $this->action[$idx]) !== $this->defaultAction) {
/*
* >= numNonLeafStates: shift and reduce
* > 0: shift
* = 0: accept
* < 0: reduce
* = -YYUNEXPECTED: error
*/
if ($action > 0) {
/* shift */
//$this->traceShift($symbol);
++$stackPos;
$stateStack[$stackPos] = $state = $action;
$this->semStack[$stackPos] = $tokenValue;
$this->startAttributeStack[$stackPos] = $startAttributes;
$this->endAttributeStack[$stackPos] = $endAttributes;
$this->endAttributes = $endAttributes;
$symbol = self::SYMBOL_NONE;
if ($this->errorState) {
--$this->errorState;
}
if ($action < $this->numNonLeafStates) {
continue;
}
/* $yyn >= numNonLeafStates means shift-and-reduce */
$rule = $action - $this->numNonLeafStates;
} else {
$rule = -$action;
}
} else {
$rule = $this->actionDefault[$state];
}
}
for (;;) {
if ($rule === 0) {
/* accept */
//$this->traceAccept();
return $this->semValue;
} elseif ($rule !== $this->unexpectedTokenRule) {
/* reduce */
//$this->traceReduce($rule);
try {
$this->reduceCallbacks[$rule]($stackPos);
} catch (Error $e) {
if (-1 === $e->getStartLine() && isset($startAttributes['startLine'])) {
$e->setStartLine($startAttributes['startLine']);
}
$this->emitError($e);
// Can't recover from this type of error
return null;
}
/* Goto - shift nonterminal */
$lastEndAttributes = $this->endAttributeStack[$stackPos];
$ruleLength = $this->ruleToLength[$rule];
$stackPos -= $ruleLength;
$nonTerminal = $this->ruleToNonTerminal[$rule];
$idx = $this->gotoBase[$nonTerminal] + $stateStack[$stackPos];
if ($idx >= 0 && $idx < $this->gotoTableSize && $this->gotoCheck[$idx] === $nonTerminal) {
$state = $this->goto[$idx];
} else {
$state = $this->gotoDefault[$nonTerminal];
}
++$stackPos;
$stateStack[$stackPos] = $state;
$this->semStack[$stackPos] = $this->semValue;
$this->endAttributeStack[$stackPos] = $lastEndAttributes;
if ($ruleLength === 0) {
// Empty productions use the start attributes of the lookahead token.
$this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes;
}
} else {
/* error */
switch ($this->errorState) {
case 0:
$msg = $this->getErrorMessage($symbol, $state);
$this->emitError(new Error($msg, $startAttributes + $endAttributes));
// Break missing intentionally
case 1:
case 2:
$this->errorState = 3;
// Pop until error-expecting state uncovered
while (!(
(($idx = $this->actionBase[$state] + $this->errorSymbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol)
|| ($state < $this->YY2TBLSTATE
&& ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $this->errorSymbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] === $this->errorSymbol)
) || ($action = $this->action[$idx]) === $this->defaultAction) { // Not totally sure about this
if ($stackPos <= 0) {
// Could not recover from error
return null;
}
$state = $stateStack[--$stackPos];
//$this->tracePop($state);
}
//$this->traceShift($this->errorSymbol);
++$stackPos;
$stateStack[$stackPos] = $state = $action;
// We treat the error symbol as being empty, so we reset the end attributes
// to the end attributes of the last non-error symbol
$this->startAttributeStack[$stackPos] = $this->lookaheadStartAttributes;
$this->endAttributeStack[$stackPos] = $this->endAttributeStack[$stackPos - 1];
$this->endAttributes = $this->endAttributeStack[$stackPos - 1];
break;
case 3:
if ($symbol === 0) {
// Reached EOF without recovering from error
return null;
}
//$this->traceDiscard($symbol);
$symbol = self::SYMBOL_NONE;
break 2;
}
}
if ($state < $this->numNonLeafStates) {
break;
}
/* >= numNonLeafStates means shift-and-reduce */
$rule = $state - $this->numNonLeafStates;
}
}
throw new \RuntimeException('Reached end of parser loop');
}
protected function emitError(Error $error) {
$this->errorHandler->handleError($error);
}
/**
* Format error message including expected tokens.
*
* @param int $symbol Unexpected symbol
* @param int $state State at time of error
*
* @return string Formatted error message
*/
protected function getErrorMessage(int $symbol, int $state) : string {
$expectedString = '';
if ($expected = $this->getExpectedTokens($state)) {
$expectedString = ', expecting ' . implode(' or ', $expected);
}
return 'Syntax error, unexpected ' . $this->symbolToName[$symbol] . $expectedString;
}
/**
* Get limited number of expected tokens in given state.
*
* @param int $state State
*
* @return string[] Expected tokens. If too many, an empty array is returned.
*/
protected function getExpectedTokens(int $state) : array {
$expected = [];
$base = $this->actionBase[$state];
foreach ($this->symbolToName as $symbol => $name) {
$idx = $base + $symbol;
if ($idx >= 0 && $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
|| $state < $this->YY2TBLSTATE
&& ($idx = $this->actionBase[$state + $this->numNonLeafStates] + $symbol) >= 0
&& $idx < $this->actionTableSize && $this->actionCheck[$idx] === $symbol
) {
if ($this->action[$idx] !== $this->unexpectedTokenRule
&& $this->action[$idx] !== $this->defaultAction
&& $symbol !== $this->errorSymbol
) {
if (count($expected) === 4) {
/* Too many expected tokens */
return [];
}
$expected[] = $name;
}
}
}
return $expected;
}
/*
* Tracing functions used for debugging the parser.
*/
/*
protected function traceNewState($state, $symbol) {
echo '% State ' . $state
. ', Lookahead ' . ($symbol == self::SYMBOL_NONE ? '--none--' : $this->symbolToName[$symbol]) . "\n";
}
protected function traceRead($symbol) {
echo '% Reading ' . $this->symbolToName[$symbol] . "\n";
}
protected function traceShift($symbol) {
echo '% Shift ' . $this->symbolToName[$symbol] . "\n";
}
protected function traceAccept() {
echo "% Accepted.\n";
}
protected function traceReduce($n) {
echo '% Reduce by (' . $n . ') ' . $this->productions[$n] . "\n";
}
protected function tracePop($state) {
echo '% Recovering, uncovered state ' . $state . "\n";
}
protected function traceDiscard($symbol) {
echo '% Discard ' . $this->symbolToName[$symbol] . "\n";
}
*/
/*
* Helper functions invoked by semantic actions
*/
/**
* Moves statements of semicolon-style namespaces into $ns->stmts and checks various error conditions.
*
* @param Node\Stmt[] $stmts
* @return Node\Stmt[]
*/
protected function handleNamespaces(array $stmts) : array {
$hasErrored = false;
$style = $this->getNamespacingStyle($stmts);
if (null === $style) {
// not namespaced, nothing to do
return $stmts;
} elseif ('brace' === $style) {
// For braced namespaces we only have to check that there are no invalid statements between the namespaces
$afterFirstNamespace = false;
foreach ($stmts as $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
$afterFirstNamespace = true;
} elseif (!$stmt instanceof Node\Stmt\HaltCompiler
&& !$stmt instanceof Node\Stmt\Nop
&& $afterFirstNamespace && !$hasErrored) {
$this->emitError(new Error(
'No code may exist outside of namespace {}', $stmt->getAttributes()));
$hasErrored = true; // Avoid one error for every statement
}
}
return $stmts;
} else {
// For semicolon namespaces we have to move the statements after a namespace declaration into ->stmts
$resultStmts = [];
$targetStmts =& $resultStmts;
$lastNs = null;
foreach ($stmts as $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
if ($lastNs !== null) {
$this->fixupNamespaceAttributes($lastNs);
}
if ($stmt->stmts === null) {
$stmt->stmts = [];
$targetStmts =& $stmt->stmts;
$resultStmts[] = $stmt;
} else {
// This handles the invalid case of mixed style namespaces
$resultStmts[] = $stmt;
$targetStmts =& $resultStmts;
}
$lastNs = $stmt;
} elseif ($stmt instanceof Node\Stmt\HaltCompiler) {
// __halt_compiler() is not moved into the namespace
$resultStmts[] = $stmt;
} else {
$targetStmts[] = $stmt;
}
}
if ($lastNs !== null) {
$this->fixupNamespaceAttributes($lastNs);
}
return $resultStmts;
}
}
private function fixupNamespaceAttributes(Node\Stmt\Namespace_ $stmt) {
// We moved the statements into the namespace node, as such the end of the namespace node
// needs to be extended to the end of the statements.
if (empty($stmt->stmts)) {
return;
}
// We only move the builtin end attributes here. This is the best we can do with the
// knowledge we have.
$endAttributes = ['endLine', 'endFilePos', 'endTokenPos'];
$lastStmt = $stmt->stmts[count($stmt->stmts) - 1];
foreach ($endAttributes as $endAttribute) {
if ($lastStmt->hasAttribute($endAttribute)) {
$stmt->setAttribute($endAttribute, $lastStmt->getAttribute($endAttribute));
}
}
}
/**
* Determine namespacing style (semicolon or brace)
*
* @param Node[] $stmts Top-level statements.
*
* @return null|string One of "semicolon", "brace" or null (no namespaces)
*/
private function getNamespacingStyle(array $stmts) {
$style = null;
$hasNotAllowedStmts = false;
foreach ($stmts as $i => $stmt) {
if ($stmt instanceof Node\Stmt\Namespace_) {
$currentStyle = null === $stmt->stmts ? 'semicolon' : 'brace';
if (null === $style) {
$style = $currentStyle;
if ($hasNotAllowedStmts) {
$this->emitError(new Error(
'Namespace declaration statement has to be the very first statement in the script',
$stmt->getLine() // Avoid marking the entire namespace as an error
));
}
} elseif ($style !== $currentStyle) {
$this->emitError(new Error(
'Cannot mix bracketed namespace declarations with unbracketed namespace declarations',
$stmt->getLine() // Avoid marking the entire namespace as an error
));
// Treat like semicolon style for namespace normalization
return 'semicolon';
}
continue;
}
/* declare(), __halt_compiler() and nops can be used before a namespace declaration */
if ($stmt instanceof Node\Stmt\Declare_
|| $stmt instanceof Node\Stmt\HaltCompiler
|| $stmt instanceof Node\Stmt\Nop) {
continue;
}
/* There may be a hashbang line at the very start of the file */
if ($i === 0 && $stmt instanceof Node\Stmt\InlineHTML && preg_match('/\A#!.*\r?\n\z/', $stmt->value)) {
continue;
}
/* Everything else if forbidden before namespace declarations */
$hasNotAllowedStmts = true;
}
return $style;
}
/**
* Fix up parsing of static property calls in PHP 5.
*
* In PHP 5 A::$b[c][d] and A::$b[c][d]() have very different interpretation. The former is
* interpreted as (A::$b)[c][d], while the latter is the same as A::{$b[c][d]}(). We parse the
* latter as the former initially and this method fixes the AST into the correct form when we
* encounter the "()".
*
* @param Node\Expr\StaticPropertyFetch|Node\Expr\ArrayDimFetch $prop
* @param Node\Arg[] $args
* @param array $attributes
*
* @return Expr\StaticCall
*/
protected function fixupPhp5StaticPropCall($prop, array $args, array $attributes) : Expr\StaticCall {
if ($prop instanceof Node\Expr\StaticPropertyFetch) {
$name = $prop->name instanceof VarLikeIdentifier
? $prop->name->toString() : $prop->name;
$var = new Expr\Variable($name, $prop->name->getAttributes());
return new Expr\StaticCall($prop->class, $var, $args, $attributes);
} elseif ($prop instanceof Node\Expr\ArrayDimFetch) {
$tmp = $prop;
while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
$tmp = $tmp->var;
}
/** @var Expr\StaticPropertyFetch $staticProp */
$staticProp = $tmp->var;
// Set start attributes to attributes of innermost node
$tmp = $prop;
$this->fixupStartAttributes($tmp, $staticProp->name);
while ($tmp->var instanceof Node\Expr\ArrayDimFetch) {
$tmp = $tmp->var;
$this->fixupStartAttributes($tmp, $staticProp->name);
}
$name = $staticProp->name instanceof VarLikeIdentifier
? $staticProp->name->toString() : $staticProp->name;
$tmp->var = new Expr\Variable($name, $staticProp->name->getAttributes());
return new Expr\StaticCall($staticProp->class, $prop, $args, $attributes);
} else {
throw new \Exception;
}
}
protected function fixupStartAttributes(Node $to, Node $from) {
$startAttributes = ['startLine', 'startFilePos', 'startTokenPos'];
foreach ($startAttributes as $startAttribute) {
if ($from->hasAttribute($startAttribute)) {
$to->setAttribute($startAttribute, $from->getAttribute($startAttribute));
}
}
}
protected function handleBuiltinTypes(Name $name) {
$builtinTypes = [
'bool' => true,
'int' => true,
'float' => true,
'string' => true,
'iterable' => true,
'void' => true,
'object' => true,
'null' => true,
'false' => true,
'mixed' => true,
];
if (!$name->isUnqualified()) {
return $name;
}
$lowerName = $name->toLowerString();
if (!isset($builtinTypes[$lowerName])) {
return $name;
}
return new Node\Identifier($lowerName, $name->getAttributes());
}
/**
* Get combined start and end attributes at a stack location
*
* @param int $pos Stack location
*
* @return array Combined start and end attributes
*/
protected function getAttributesAt(int $pos) : array {
return $this->startAttributeStack[$pos] + $this->endAttributeStack[$pos];
}
protected function getFloatCastKind(string $cast): int
{
$cast = strtolower($cast);
if (strpos($cast, 'float') !== false) {
return Double::KIND_FLOAT;
}
if (strpos($cast, 'real') !== false) {
return Double::KIND_REAL;
}
return Double::KIND_DOUBLE;
}
protected function parseLNumber($str, $attributes, $allowInvalidOctal = false) {
try {
return LNumber::fromString($str, $attributes, $allowInvalidOctal);
} catch (Error $error) {
$this->emitError($error);
// Use dummy value
return new LNumber(0, $attributes);
}
}
/**
* Parse a T_NUM_STRING token into either an integer or string node.
*
* @param string $str Number string
* @param array $attributes Attributes
*
* @return LNumber|String_ Integer or string node.
*/
protected function parseNumString(string $str, array $attributes) {
if (!preg_match('/^(?:0|-?[1-9][0-9]*)$/', $str)) {
return new String_($str, $attributes);
}
$num = +$str;
if (!is_int($num)) {
return new String_($str, $attributes);
}
return new LNumber($num, $attributes);
}
protected function stripIndentation(
string $string, int $indentLen, string $indentChar,
bool $newlineAtStart, bool $newlineAtEnd, array $attributes
) {
if ($indentLen === 0) {
return $string;
}
$start = $newlineAtStart ? '(?:(?<=\n)|\A)' : '(?<=\n)';
$end = $newlineAtEnd ? '(?:(?=[\r\n])|\z)' : '(?=[\r\n])';
$regex = '/' . $start . '([ \t]*)(' . $end . ')?/';
return preg_replace_callback(
$regex,
function ($matches) use ($indentLen, $indentChar, $attributes) {
$prefix = substr($matches[1], 0, $indentLen);
if (false !== strpos($prefix, $indentChar === " " ? "\t" : " ")) {
$this->emitError(new Error(
'Invalid indentation - tabs and spaces cannot be mixed', $attributes
));
} elseif (strlen($prefix) < $indentLen && !isset($matches[2])) {
$this->emitError(new Error(
'Invalid body indentation level ' .
'(expecting an indentation level of at least ' . $indentLen . ')',
$attributes
));
}
return substr($matches[0], strlen($prefix));
},
$string
);
}
protected function parseDocString(
string $startToken, $contents, string $endToken,
array $attributes, array $endTokenAttributes, bool $parseUnicodeEscape
) {
$kind = strpos($startToken, "'") === false
? String_::KIND_HEREDOC : String_::KIND_NOWDOC;
$regex = '/\A[bB]?<<<[ \t]*[\'"]?([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)[\'"]?(?:\r\n|\n|\r)\z/';
$result = preg_match($regex, $startToken, $matches);
assert($result === 1);
$label = $matches[1];
$result = preg_match('/\A[ \t]*/', $endToken, $matches);
assert($result === 1);
$indentation = $matches[0];
$attributes['kind'] = $kind;
$attributes['docLabel'] = $label;
$attributes['docIndentation'] = $indentation;
$indentHasSpaces = false !== strpos($indentation, " ");
$indentHasTabs = false !== strpos($indentation, "\t");
if ($indentHasSpaces && $indentHasTabs) {
$this->emitError(new Error(
'Invalid indentation - tabs and spaces cannot be mixed',
$endTokenAttributes
));
// Proceed processing as if this doc string is not indented
$indentation = '';
}
$indentLen = \strlen($indentation);
$indentChar = $indentHasSpaces ? " " : "\t";
if (\is_string($contents)) {
if ($contents === '') {
return new String_('', $attributes);
}
$contents = $this->stripIndentation(
$contents, $indentLen, $indentChar, true, true, $attributes
);
$contents = preg_replace('~(\r\n|\n|\r)\z~', '', $contents);
if ($kind === String_::KIND_HEREDOC) {
$contents = String_::parseEscapeSequences($contents, null, $parseUnicodeEscape);
}
return new String_($contents, $attributes);
} else {
assert(count($contents) > 0);
if (!$contents[0] instanceof Node\Scalar\EncapsedStringPart) {
// If there is no leading encapsed string part, pretend there is an empty one
$this->stripIndentation(
'', $indentLen, $indentChar, true, false, $contents[0]->getAttributes()
);
}
$newContents = [];
foreach ($contents as $i => $part) {
if ($part instanceof Node\Scalar\EncapsedStringPart) {
$isLast = $i === \count($contents) - 1;
$part->value = $this->stripIndentation(
$part->value, $indentLen, $indentChar,
$i === 0, $isLast, $part->getAttributes()
);
$part->value = String_::parseEscapeSequences($part->value, null, $parseUnicodeEscape);
if ($isLast) {
$part->value = preg_replace('~(\r\n|\n|\r)\z~', '', $part->value);
}
if ('' === $part->value) {
continue;
}
}
$newContents[] = $part;
}
return new Encapsed($newContents, $attributes);
}
}
/**
* Create attributes for a zero-length common-capturing nop.
*
* @param Comment[] $comments
* @return array
*/
protected function createCommentNopAttributes(array $comments) {
$comment = $comments[count($comments) - 1];
$commentEndLine = $comment->getEndLine();
$commentEndFilePos = $comment->getEndFilePos();
$commentEndTokenPos = $comment->getEndTokenPos();
$attributes = ['comments' => $comments];
if (-1 !== $commentEndLine) {
$attributes['startLine'] = $commentEndLine;
$attributes['endLine'] = $commentEndLine;
}
if (-1 !== $commentEndFilePos) {
$attributes['startFilePos'] = $commentEndFilePos + 1;
$attributes['endFilePos'] = $commentEndFilePos;
}
if (-1 !== $commentEndTokenPos) {
$attributes['startTokenPos'] = $commentEndTokenPos + 1;
$attributes['endTokenPos'] = $commentEndTokenPos;
}
return $attributes;
}
protected function checkModifier($a, $b, $modifierPos) {
// Jumping through some hoops here because verifyModifier() is also used elsewhere
try {
Class_::verifyModifier($a, $b);
} catch (Error $error) {
$error->setAttributes($this->getAttributesAt($modifierPos));
$this->emitError($error);
}
}
protected function checkParam(Param $node) {
if ($node->variadic && null !== $node->default) {
$this->emitError(new Error(
'Variadic parameter cannot have a default value',
$node->default->getAttributes()
));
}
}
protected function checkTryCatch(TryCatch $node) {
if (empty($node->catches) && null === $node->finally) {
$this->emitError(new Error(
'Cannot use try without catch or finally', $node->getAttributes()
));
}
}
protected function checkNamespace(Namespace_ $node) {
if (null !== $node->stmts) {
foreach ($node->stmts as $stmt) {
if ($stmt instanceof Namespace_) {
$this->emitError(new Error(
'Namespace declarations cannot be nested', $stmt->getAttributes()
));
}
}
}
}
protected function checkClass(Class_ $node, $namePos) {
if (null !== $node->name && $node->name->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
$this->getAttributesAt($namePos)
));
}
if ($node->extends && $node->extends->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->extends),
$node->extends->getAttributes()
));
}
foreach ($node->implements as $interface) {
if ($interface->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
}
protected function checkInterface(Interface_ $node, $namePos) {
if (null !== $node->name && $node->name->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as class name as it is reserved', $node->name),
$this->getAttributesAt($namePos)
));
}
foreach ($node->extends as $interface) {
if ($interface->isSpecialClassName()) {
$this->emitError(new Error(
sprintf('Cannot use \'%s\' as interface name as it is reserved', $interface),
$interface->getAttributes()
));
}
}
}
protected function checkClassMethod(ClassMethod $node, $modifierPos) {
if ($node->flags & Class_::MODIFIER_STATIC) {
switch ($node->name->toLowerString()) {
case '__construct':
$this->emitError(new Error(
sprintf('Constructor %s() cannot be static', $node->name),
$this->getAttributesAt($modifierPos)));
break;
case '__destruct':
$this->emitError(new Error(
sprintf('Destructor %s() cannot be static', $node->name),
$this->getAttributesAt($modifierPos)));
break;
case '__clone':
$this->emitError(new Error(
sprintf('Clone method %s() cannot be static', $node->name),
$this->getAttributesAt($modifierPos)));
break;
}
}
}
protected function checkClassConst(ClassConst $node, $modifierPos) {
if ($node->flags & Class_::MODIFIER_STATIC) {
$this->emitError(new Error(
"Cannot use 'static' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Class_::MODIFIER_ABSTRACT) {
$this->emitError(new Error(
"Cannot use 'abstract' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Class_::MODIFIER_FINAL) {
$this->emitError(new Error(
"Cannot use 'final' as constant modifier",
$this->getAttributesAt($modifierPos)));
}
}
protected function checkProperty(Property $node, $modifierPos) {
if ($node->flags & Class_::MODIFIER_ABSTRACT) {
$this->emitError(new Error('Properties cannot be declared abstract',
$this->getAttributesAt($modifierPos)));
}
if ($node->flags & Class_::MODIFIER_FINAL) {
$this->emitError(new Error('Properties cannot be declared final',
$this->getAttributesAt($modifierPos)));
}
}
protected function checkUseUse(UseUse $node, $namePos) {
if ($node->alias && $node->alias->isSpecialClassName()) {
$this->emitError(new Error(
sprintf(
'Cannot use %s as %s because \'%2$s\' is a special class name',
$node->name, $node->alias
),
$this->getAttributesAt($namePos)
));
}
}
}
<?php declare(strict_types=1);
namespace PhpParser;
/**
* @codeCoverageIgnore
*/
class NodeVisitorAbstract implements NodeVisitor
{
public function beforeTraverse(array $nodes) {
return null;
}
public function enterNode(Node $node) {
return null;
}
public function leaveNode(Node $node) {
return null;
}
public function afterTraverse(array $nodes) {
return null;
}
}
BSD 3-Clause License
Copyright (c) 2011, Nikita Popov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
<?php return array (
'root' =>
array (
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'aliases' =>
array (
0 => '5.x-dev',
),
'reference' => 'aba1c342d817fb06725dfe8909ca601d5cb3b47e',
'name' => 'code-lts/doctum',
),
'versions' =>
array (
'code-lts/cli-tools' =>
array (
'pretty_version' => 'v1.4.0',
'version' => '1.4.0.0',
'aliases' =>
array (
),
'reference' => '3eec6592550b4e1ad99e04c236e94f1d0f8c9569',
),
'code-lts/doctum' =>
array (
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'aliases' =>
array (
0 => '5.x-dev',
),
'reference' => 'aba1c342d817fb06725dfe8909ca601d5cb3b47e',
),
'erusev/parsedown' =>
array (
'pretty_version' => '1.7.4',
'version' => '1.7.4.0',
'aliases' =>
array (
),
'reference' => 'cb17b6477dfff935958ba01325f2e8a2bfa6dab3',
),
'nikic/php-parser' =>
array (
'pretty_version' => 'v4.10.4',
'version' => '4.10.4.0',
'aliases' =>
array (
),
'reference' => 'c6d052fc58cb876152f89f532b95a8d7907e7f0e',
),
'ondram/ci-detector' =>
array (
'pretty_version' => '4.1.0',
'version' => '4.1.0.0',
'aliases' =>
array (
),
'reference' => '8a4b664e916df82ff26a44709942dfd593fa6f30',
),
'phpdocumentor/reflection-common' =>
array (
'pretty_version' => '2.2.0',
'version' => '2.2.0.0',
'aliases' =>
array (
),
'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b',
),
'phpdocumentor/reflection-docblock' =>
array (
'pretty_version' => '5.2.2',
'version' => '5.2.2.0',
'aliases' =>
array (
),
'reference' => '069a785b2141f5bcf49f3e353548dc1cce6df556',
),
'phpdocumentor/type-resolver' =>
array (
'pretty_version' => '1.4.0',
'version' => '1.4.0.0',
'aliases' =>
array (
),
'reference' => '6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0',
),
'phpmyadmin/twig-i18n-extension' =>
array (
'pretty_version' => 'v4.0.0',
'version' => '4.0.0.0',
'aliases' =>
array (
),
'reference' => '8d5898e03524487ec78db732516a5cecd33642b3',
),
'psr/container' =>
array (
'pretty_version' => '1.1.1',
'version' => '1.1.1.0',
'aliases' =>
array (
),
'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf',
),
'psr/log-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'symfony/console' =>
array (
'pretty_version' => 'v5.2.6',
'version' => '5.2.6.0',
'aliases' =>
array (
),
'reference' => '35f039df40a3b335ebf310f244cb242b3a83ac8d',
),
'symfony/deprecation-contracts' =>
array (
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
'aliases' =>
array (
),
'reference' => '5f38c8804a9e97d23e0c8d63341088cd8a22d627',
),
'symfony/filesystem' =>
array (
'pretty_version' => 'v5.2.6',
'version' => '5.2.6.0',
'aliases' =>
array (
),
'reference' => '8c86a82f51658188119e62cff0a050a12d09836f',
),
'symfony/finder' =>
array (
'pretty_version' => 'v5.2.4',
'version' => '5.2.4.0',
'aliases' =>
array (
),
'reference' => '0d639a0943822626290d169965804f79400e6a04',
),
'symfony/polyfill-ctype' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => 'c6c942b1ac76c82448322025e084cadc56048b4e',
),
'symfony/polyfill-intl-grapheme' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => '5601e09b69f26c1828b13b6bb87cb07cddba3170',
),
'symfony/polyfill-intl-normalizer' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => '43a0283138253ed1d48d352ab6d0bdb3f809f248',
),
'symfony/polyfill-mbstring' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => '5232de97ee3b75b0360528dae24e73db49566ab1',
),
'symfony/polyfill-php73' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => 'a678b42e92f86eca04b7fa4c0f6f19d097fb69e2',
),
'symfony/polyfill-php80' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => 'dc3063ba22c2a1fd2f45ed856374d79114998f91',
),
'symfony/process' =>
array (
'pretty_version' => 'v5.2.4',
'version' => '5.2.4.0',
'aliases' =>
array (
),
'reference' => '313a38f09c77fbcdc1d223e57d368cea76a2fd2f',
),
'symfony/service-contracts' =>
array (
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
'aliases' =>
array (
),
'reference' => 'f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb',
),
'symfony/string' =>
array (
'pretty_version' => 'v5.2.6',
'version' => '5.2.6.0',
'aliases' =>
array (
),
'reference' => 'ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572',
),
'symfony/yaml' =>
array (
'pretty_version' => 'v5.2.5',
'version' => '5.2.5.0',
'aliases' =>
array (
),
'reference' => '298a08ddda623485208506fcee08817807a251dd',
),
'twig/twig' =>
array (
'pretty_version' => 'v3.3.0',
'version' => '3.3.0.0',
'aliases' =>
array (
),
'reference' => '1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5',
),
'wdes/php-i18n-l10n' =>
array (
'pretty_version' => 'v4.0.0',
'version' => '4.0.0.0',
'aliases' =>
array (
),
'reference' => 'f64b9ca054a5204a48a88dc38aeb7c6dae2f4df5',
),
'webmozart/assert' =>
array (
'pretty_version' => '1.10.0',
'version' => '1.10.0.0',
'aliases' =>
array (
),
'reference' => '6964c76c7804814a842473e0c8fd15bab0f18e25',
),
),
);
<?php
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
class InstalledVersions
{
private static $installed = array (
'root' =>
array (
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'aliases' =>
array (
0 => '5.x-dev',
),
'reference' => 'aba1c342d817fb06725dfe8909ca601d5cb3b47e',
'name' => 'code-lts/doctum',
),
'versions' =>
array (
'code-lts/cli-tools' =>
array (
'pretty_version' => 'v1.4.0',
'version' => '1.4.0.0',
'aliases' =>
array (
),
'reference' => '3eec6592550b4e1ad99e04c236e94f1d0f8c9569',
),
'code-lts/doctum' =>
array (
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'aliases' =>
array (
0 => '5.x-dev',
),
'reference' => 'aba1c342d817fb06725dfe8909ca601d5cb3b47e',
),
'erusev/parsedown' =>
array (
'pretty_version' => '1.7.4',
'version' => '1.7.4.0',
'aliases' =>
array (
),
'reference' => 'cb17b6477dfff935958ba01325f2e8a2bfa6dab3',
),
'nikic/php-parser' =>
array (
'pretty_version' => 'v4.10.4',
'version' => '4.10.4.0',
'aliases' =>
array (
),
'reference' => 'c6d052fc58cb876152f89f532b95a8d7907e7f0e',
),
'ondram/ci-detector' =>
array (
'pretty_version' => '4.1.0',
'version' => '4.1.0.0',
'aliases' =>
array (
),
'reference' => '8a4b664e916df82ff26a44709942dfd593fa6f30',
),
'phpdocumentor/reflection-common' =>
array (
'pretty_version' => '2.2.0',
'version' => '2.2.0.0',
'aliases' =>
array (
),
'reference' => '1d01c49d4ed62f25aa84a747ad35d5a16924662b',
),
'phpdocumentor/reflection-docblock' =>
array (
'pretty_version' => '5.2.2',
'version' => '5.2.2.0',
'aliases' =>
array (
),
'reference' => '069a785b2141f5bcf49f3e353548dc1cce6df556',
),
'phpdocumentor/type-resolver' =>
array (
'pretty_version' => '1.4.0',
'version' => '1.4.0.0',
'aliases' =>
array (
),
'reference' => '6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0',
),
'phpmyadmin/twig-i18n-extension' =>
array (
'pretty_version' => 'v4.0.0',
'version' => '4.0.0.0',
'aliases' =>
array (
),
'reference' => '8d5898e03524487ec78db732516a5cecd33642b3',
),
'psr/container' =>
array (
'pretty_version' => '1.1.1',
'version' => '1.1.1.0',
'aliases' =>
array (
),
'reference' => '8622567409010282b7aeebe4bb841fe98b58dcaf',
),
'psr/log-implementation' =>
array (
'provided' =>
array (
0 => '1.0',
),
),
'symfony/console' =>
array (
'pretty_version' => 'v5.2.6',
'version' => '5.2.6.0',
'aliases' =>
array (
),
'reference' => '35f039df40a3b335ebf310f244cb242b3a83ac8d',
),
'symfony/deprecation-contracts' =>
array (
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
'aliases' =>
array (
),
'reference' => '5f38c8804a9e97d23e0c8d63341088cd8a22d627',
),
'symfony/filesystem' =>
array (
'pretty_version' => 'v5.2.6',
'version' => '5.2.6.0',
'aliases' =>
array (
),
'reference' => '8c86a82f51658188119e62cff0a050a12d09836f',
),
'symfony/finder' =>
array (
'pretty_version' => 'v5.2.4',
'version' => '5.2.4.0',
'aliases' =>
array (
),
'reference' => '0d639a0943822626290d169965804f79400e6a04',
),
'symfony/polyfill-ctype' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => 'c6c942b1ac76c82448322025e084cadc56048b4e',
),
'symfony/polyfill-intl-grapheme' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => '5601e09b69f26c1828b13b6bb87cb07cddba3170',
),
'symfony/polyfill-intl-normalizer' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => '43a0283138253ed1d48d352ab6d0bdb3f809f248',
),
'symfony/polyfill-mbstring' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => '5232de97ee3b75b0360528dae24e73db49566ab1',
),
'symfony/polyfill-php73' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => 'a678b42e92f86eca04b7fa4c0f6f19d097fb69e2',
),
'symfony/polyfill-php80' =>
array (
'pretty_version' => 'v1.22.1',
'version' => '1.22.1.0',
'aliases' =>
array (
),
'reference' => 'dc3063ba22c2a1fd2f45ed856374d79114998f91',
),
'symfony/process' =>
array (
'pretty_version' => 'v5.2.4',
'version' => '5.2.4.0',
'aliases' =>
array (
),
'reference' => '313a38f09c77fbcdc1d223e57d368cea76a2fd2f',
),
'symfony/service-contracts' =>
array (
'pretty_version' => 'v2.4.0',
'version' => '2.4.0.0',
'aliases' =>
array (
),
'reference' => 'f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb',
),
'symfony/string' =>
array (
'pretty_version' => 'v5.2.6',
'version' => '5.2.6.0',
'aliases' =>
array (
),
'reference' => 'ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572',
),
'symfony/yaml' =>
array (
'pretty_version' => 'v5.2.5',
'version' => '5.2.5.0',
'aliases' =>
array (
),
'reference' => '298a08ddda623485208506fcee08817807a251dd',
),
'twig/twig' =>
array (
'pretty_version' => 'v3.3.0',
'version' => '3.3.0.0',
'aliases' =>
array (
),
'reference' => '1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5',
),
'wdes/php-i18n-l10n' =>
array (
'pretty_version' => 'v4.0.0',
'version' => '4.0.0.0',
'aliases' =>
array (
),
'reference' => 'f64b9ca054a5204a48a88dc38aeb7c6dae2f4df5',
),
'webmozart/assert' =>
array (
'pretty_version' => '1.10.0',
'version' => '1.10.0.0',
'aliases' =>
array (
),
'reference' => '6964c76c7804814a842473e0c8fd15bab0f18e25',
),
),
);
private static $canGetVendors;
private static $installedByVendor = array();
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
public static function isInstalled($packageName)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return true;
}
}
return false;
}
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints($constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
public static function getRawData()
{
return self::$installed;
}
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
$installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
}
}
}
$installed[] = self::$installed;
return $installed;
}
}
<?php
// autoload_static.php @generated by Composer
namespace Composer\Autoload;
class ComposerStaticInitf2e3a964c11223b024edc03e013baa7e
{
public static $files = array (
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => __DIR__ . '/..' . '/symfony/string/Resources/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => __DIR__ . '/..' . '/symfony/polyfill-php73/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
);
public static $prefixLengthsPsr4 = array (
'p' =>
array (
'phpDocumentor\\Reflection\\' => 25,
),
'W' =>
array (
'Webmozart\\Assert\\' => 17,
'Wdes\\phpI18nL10n\\' => 17,
),
'T' =>
array (
'Twig\\' => 5,
),
'S' =>
array (
'Symfony\\Polyfill\\Php80\\' => 23,
'Symfony\\Polyfill\\Php73\\' => 23,
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
'Symfony\\Polyfill\\Intl\\Grapheme\\' => 31,
'Symfony\\Polyfill\\Ctype\\' => 23,
'Symfony\\Contracts\\Service\\' => 26,
'Symfony\\Component\\Yaml\\' => 23,
'Symfony\\Component\\String\\' => 25,
'Symfony\\Component\\Process\\' => 26,
'Symfony\\Component\\Finder\\' => 25,
'Symfony\\Component\\Filesystem\\' => 29,
'Symfony\\Component\\Console\\' => 26,
),
'P' =>
array (
'Psr\\Container\\' => 14,
'PhpParser\\' => 10,
'PhpMyAdmin\\Twig\\Extensions\\' => 27,
),
'O' =>
array (
'OndraM\\CiDetector\\' => 18,
),
'D' =>
array (
'Doctum\\' => 7,
),
'C' =>
array (
'CodeLts\\CliTools\\' => 17,
),
);
public static $prefixDirsPsr4 = array (
'phpDocumentor\\Reflection\\' =>
array (
0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
),
'Webmozart\\Assert\\' =>
array (
0 => __DIR__ . '/..' . '/webmozart/assert/src',
),
'Wdes\\phpI18nL10n\\' =>
array (
0 => __DIR__ . '/..' . '/wdes/php-i18n-l10n/src',
),
'Twig\\' =>
array (
0 => __DIR__ . '/..' . '/twig/twig/src',
),
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
),
'Symfony\\Polyfill\\Php73\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
),
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
),
'Symfony\\Polyfill\\Intl\\Grapheme\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-grapheme',
),
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Contracts\\Service\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/service-contracts',
),
'Symfony\\Component\\Yaml\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/yaml',
),
'Symfony\\Component\\String\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/string',
),
'Symfony\\Component\\Process\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/process',
),
'Symfony\\Component\\Finder\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/finder',
),
'Symfony\\Component\\Filesystem\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/filesystem',
),
'Symfony\\Component\\Console\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/console',
),
'Psr\\Container\\' =>
array (
0 => __DIR__ . '/..' . '/psr/container/src',
),
'PhpParser\\' =>
array (
0 => __DIR__ . '/..' . '/nikic/php-parser/lib/PhpParser',
),
'PhpMyAdmin\\Twig\\Extensions\\' =>
array (
0 => __DIR__ . '/..' . '/phpmyadmin/twig-i18n-extension/src',
),
'OndraM\\CiDetector\\' =>
array (
0 => __DIR__ . '/..' . '/ondram/ci-detector/src',
),
'Doctum\\' =>
array (
0 => __DIR__ . '/../..' . '/src',
),
'CodeLts\\CliTools\\' =>
array (
0 => __DIR__ . '/..' . '/code-lts/cli-tools/src',
),
);
public static $prefixesPsr0 = array (
'P' =>
array (
'Parsedown' =>
array (
0 => __DIR__ . '/..' . '/erusev/parsedown',
),
),
);
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
public static function getInitializer(ClassLoader $loader)
{
return \Closure::bind(function () use ($loader) {
$loader->prefixLengthsPsr4 = ComposerStaticInitf2e3a964c11223b024edc03e013baa7e::$prefixLengthsPsr4;
$loader->prefixDirsPsr4 = ComposerStaticInitf2e3a964c11223b024edc03e013baa7e::$prefixDirsPsr4;
$loader->prefixesPsr0 = ComposerStaticInitf2e3a964c11223b024edc03e013baa7e::$prefixesPsr0;
$loader->classMap = ComposerStaticInitf2e3a964c11223b024edc03e013baa7e::$classMap;
}, null, ClassLoader::class);
}
}
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Parsedown' => array($vendorDir . '/erusev/parsedown'),
);
{
"packages": [
{
"name": "code-lts/cli-tools",
"version": "v1.4.0",
"version_normalized": "1.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/code-lts/cli-tools.git",
"reference": "3eec6592550b4e1ad99e04c236e94f1d0f8c9569"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/code-lts/cli-tools/zipball/3eec6592550b4e1ad99e04c236e94f1d0f8c9569",
"reference": "3eec6592550b4e1ad99e04c236e94f1d0f8c9569",
"shasum": ""
},
"require": {
"ondram/ci-detector": "^4.0",
"php": "^7.1 || ^8.0",
"symfony/console": "~3.4|~4.3|~5.1|~5.2"
},
"require-dev": {
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7 || ^8 || ^9",
"wdes/coding-standard": "^3.0"
},
"time": "2021-03-24T01:21:40+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"CodeLts\\CliTools\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MPL-2.0"
],
"authors": [
{
"name": "William Desportes",
"email": "williamdes@wdes.fr",
"homepage": "https://william.wdes.fr"
},
{
"name": "Ondřej Mirtes",
"email": "ondrej@mirtes.cz",
"homepage": "https://ondrej.mirtes.cz"
}
],
"description": "CLI tools to manage output errors formatting for junit, checkstyle, teamcity, gitlab and github error formats",
"homepage": "https://github.com/code-lts/cli-tools",
"keywords": [
"checkstyle",
"cli",
"formatters",
"github",
"gitlab",
"junit",
"teamcity"
],
"support": {
"email": "williamdes@wdes.fr",
"issues": "https://github.com/code-lts/cli-tools/issues",
"source": "https://github.com/code-lts/cli-tools"
},
"install-path": "../code-lts/cli-tools"
},
{
"name": "erusev/parsedown",
"version": "1.7.4",
"version_normalized": "1.7.4.0",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35"
},
"time": "2019-12-30T22:54:17+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-0": {
"Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuil Rusev",
"email": "hello@erusev.com",
"homepage": "http://erusev.com"
}
],
"description": "Parser for Markdown.",
"homepage": "http://parsedown.org",
"keywords": [
"markdown",
"parser"
],
"support": {
"issues": "https://github.com/erusev/parsedown/issues",
"source": "https://github.com/erusev/parsedown/tree/1.7.x"
},
"install-path": "../erusev/parsedown"
},
{
"name": "nikic/php-parser",
"version": "v4.10.4",
"version_normalized": "4.10.4.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c6d052fc58cb876152f89f532b95a8d7907e7f0e",
"reference": "c6d052fc58cb876152f89f532b95a8d7907e7f0e",
"shasum": ""
},
"require": {
"ext-tokenizer": "*",
"php": ">=7.0"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
},
"time": "2020-12-20T10:01:03+00:00",
"bin": [
"bin/php-parse"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.9-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"PhpParser\\": "lib/PhpParser"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov"
}
],
"description": "A PHP parser written in PHP",
"keywords": [
"parser",
"php"
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.10.4"
},
"install-path": "../nikic/php-parser"
},
{
"name": "ondram/ci-detector",
"version": "4.1.0",
"version_normalized": "4.1.0.0",
"source": {
"type": "git",
"url": "https://github.com/OndraM/ci-detector.git",
"reference": "8a4b664e916df82ff26a44709942dfd593fa6f30"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/OndraM/ci-detector/zipball/8a4b664e916df82ff26a44709942dfd593fa6f30",
"reference": "8a4b664e916df82ff26a44709942dfd593fa6f30",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.2",
"lmc/coding-standard": "^1.3 || ^2.1",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/extension-installer": "^1.0.5",
"phpstan/phpstan": "^0.12.58",
"phpstan/phpstan-phpunit": "^0.12.16",
"phpunit/phpunit": "^7.1 || ^8.0 || ^9.0"
},
"time": "2021-04-14T09:16:52+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"OndraM\\CiDetector\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ondřej Machulda",
"email": "ondrej.machulda@gmail.com"
}
],
"description": "Detect continuous integration environment and provide unified access to properties of current build",
"keywords": [
"CircleCI",
"Codeship",
"Wercker",
"adapter",
"appveyor",
"aws",
"aws codebuild",
"azure",
"azure devops",
"azure pipelines",
"bamboo",
"bitbucket",
"buddy",
"ci-info",
"codebuild",
"continuous integration",
"continuousphp",
"devops",
"drone",
"github",
"gitlab",
"interface",
"jenkins",
"pipelines",
"sourcehut",
"teamcity",
"travis"
],
"support": {
"issues": "https://github.com/OndraM/ci-detector/issues",
"source": "https://github.com/OndraM/ci-detector/tree/4.1.0"
},
"install-path": "../ondram/ci-detector"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
"version_normalized": "2.2.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"time": "2020-06-27T09:03:43+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-2.x": "2.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jaap van Otterdijk",
"email": "opensource@ijaap.nl"
}
],
"description": "Common reflection classes used by phpdocumentor to reflect the code structure",
"homepage": "http://www.phpdoc.org",
"keywords": [
"FQSEN",
"phpDocumentor",
"phpdoc",
"reflection",
"static analysis"
],
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
},
"install-path": "../phpdocumentor/reflection-common"
},
{
"name": "phpdocumentor/reflection-docblock",
"version": "5.2.2",
"version_normalized": "5.2.2.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556",
"reference": "069a785b2141f5bcf49f3e353548dc1cce6df556",
"shasum": ""
},
"require": {
"ext-filter": "*",
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.2",
"phpdocumentor/type-resolver": "^1.3",
"webmozart/assert": "^1.9.1"
},
"require-dev": {
"mockery/mockery": "~1.3.2"
},
"time": "2020-09-03T19:13:55+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
},
{
"name": "Jaap van Otterdijk",
"email": "account@ijaap.nl"
}
],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"support": {
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master"
},
"install-path": "../phpdocumentor/reflection-docblock"
},
{
"name": "phpdocumentor/type-resolver",
"version": "1.4.0",
"version_normalized": "1.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpDocumentor/TypeResolver.git",
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
"reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"phpdocumentor/reflection-common": "^2.0"
},
"require-dev": {
"ext-tokenizer": "*"
},
"time": "2020-09-17T18:55:26+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-1.x": "1.x-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"phpDocumentor\\Reflection\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Mike van Riel",
"email": "me@mikevanriel.com"
}
],
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
"support": {
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0"
},
"install-path": "../phpdocumentor/type-resolver"
},
{
"name": "phpmyadmin/twig-i18n-extension",
"version": "v4.0.0",
"version_normalized": "4.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/phpmyadmin/twig-i18n-extension.git",
"reference": "8d5898e03524487ec78db732516a5cecd33642b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpmyadmin/twig-i18n-extension/zipball/8d5898e03524487ec78db732516a5cecd33642b3",
"reference": "8d5898e03524487ec78db732516a5cecd33642b3",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0",
"twig/twig": "^1.42.3|^2.0|^3.0"
},
"require-dev": {
"phpmyadmin/coding-standard": "^2.1.1",
"phpmyadmin/motranslator": "^5.2",
"phpstan/phpstan": "^0.12.66",
"phpunit/phpunit": "^7 || ^8 || ^9"
},
"time": "2021-02-25T00:20:17+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"PhpMyAdmin\\Twig\\Extensions\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "The phpMyAdmin Team",
"email": "developers@phpmyadmin.net",
"homepage": "https://www.phpmyadmin.net/team/"
}
],
"description": "Internationalization support for Twig via the gettext library",
"keywords": [
"gettext",
"i18n"
],
"support": {
"issues": "https://github.com/phpmyadmin/twig-i18n-extension/issues",
"source": "https://github.com/phpmyadmin/twig-i18n-extension"
},
"install-path": "../phpmyadmin/twig-i18n-extension"
},
{
"name": "psr/container",
"version": "1.1.1",
"version_normalized": "1.1.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
"reference": "8622567409010282b7aeebe4bb841fe98b58dcaf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf",
"reference": "8622567409010282b7aeebe4bb841fe98b58dcaf",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"time": "2021-03-05T17:36:06+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common Container Interface (PHP FIG PSR-11)",
"homepage": "https://github.com/php-fig/container",
"keywords": [
"PSR-11",
"container",
"container-interface",
"container-interop",
"psr"
],
"support": {
"issues": "https://github.com/php-fig/container/issues",
"source": "https://github.com/php-fig/container/tree/1.1.1"
},
"install-path": "../psr/container"
},
{
"name": "symfony/console",
"version": "v5.2.6",
"version_normalized": "5.2.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/35f039df40a3b335ebf310f244cb242b3a83ac8d",
"reference": "35f039df40a3b335ebf310f244cb242b3a83ac8d",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php73": "^1.8",
"symfony/polyfill-php80": "^1.15",
"symfony/service-contracts": "^1.1|^2",
"symfony/string": "^5.1"
},
"conflict": {
"symfony/dependency-injection": "<4.4",
"symfony/dotenv": "<5.1",
"symfony/event-dispatcher": "<4.4",
"symfony/lock": "<4.4",
"symfony/process": "<4.4"
},
"provide": {
"psr/log-implementation": "1.0"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "^4.4|^5.0",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/event-dispatcher": "^4.4|^5.0",
"symfony/lock": "^4.4|^5.0",
"symfony/process": "^4.4|^5.0",
"symfony/var-dumper": "^4.4|^5.0"
},
"suggest": {
"psr/log": "For using the console logger",
"symfony/event-dispatcher": "",
"symfony/lock": "",
"symfony/process": ""
},
"time": "2021-03-28T09:42:18+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Console\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Eases the creation of beautiful and testable command line interfaces",
"homepage": "https://symfony.com",
"keywords": [
"cli",
"command line",
"console",
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v5.2.6"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/console"
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.4.0",
"version_normalized": "2.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"time": "2021-03-23T23:28:01+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"installation-source": "dist",
"autoload": {
"files": [
"function.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/deprecation-contracts"
},
{
"name": "symfony/filesystem",
"version": "v5.2.6",
"version_normalized": "5.2.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "8c86a82f51658188119e62cff0a050a12d09836f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/8c86a82f51658188119e62cff0a050a12d09836f",
"reference": "8c86a82f51658188119e62cff0a050a12d09836f",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8"
},
"time": "2021-03-28T14:30:26+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Filesystem\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v5.2.6"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/filesystem"
},
{
"name": "symfony/finder",
"version": "v5.2.4",
"version_normalized": "5.2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "0d639a0943822626290d169965804f79400e6a04"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/0d639a0943822626290d169965804f79400e6a04",
"reference": "0d639a0943822626290d169965804f79400e6a04",
"shasum": ""
},
"require": {
"php": ">=7.2.5"
},
"time": "2021-02-15T18:55:04+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Finder\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v5.2.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/finder"
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.22.1",
"version_normalized": "1.22.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e",
"reference": "c6c942b1ac76c82448322025e084cadc56048b4e",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-ctype": "For best performance"
},
"time": "2021-01-07T16:49:33+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Ctype\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Gert de Pagter",
"email": "BackEndTea@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for ctype functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"ctype",
"polyfill",
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-ctype"
},
{
"name": "symfony/polyfill-intl-grapheme",
"version": "v1.22.1",
"version_normalized": "1.22.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-grapheme.git",
"reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5601e09b69f26c1828b13b6bb87cb07cddba3170",
"reference": "5601e09b69f26c1828b13b6bb87cb07cddba3170",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"time": "2021-01-22T09:19:47+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Grapheme\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's grapheme_* functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"grapheme",
"intl",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.22.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-intl-grapheme"
},
{
"name": "symfony/polyfill-intl-normalizer",
"version": "v1.22.1",
"version_normalized": "1.22.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
"reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/43a0283138253ed1d48d352ab6d0bdb3f809f248",
"reference": "43a0283138253ed1d48d352ab6d0bdb3f809f248",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-intl": "For best performance"
},
"time": "2021-01-22T09:19:47+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Normalizer\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's Normalizer class and related functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"intl",
"normalizer",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-intl-normalizer"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.22.1",
"version_normalized": "1.22.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "5232de97ee3b75b0360528dae24e73db49566ab1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1",
"reference": "5232de97ee3b75b0360528dae24e73db49566ab1",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"time": "2021-01-22T09:19:47+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-mbstring"
},
{
"name": "symfony/polyfill-php73",
"version": "v1.22.1",
"version_normalized": "1.22.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php73.git",
"reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a678b42e92f86eca04b7fa4c0f6f19d097fb69e2",
"reference": "a678b42e92f86eca04b7fa4c0f6f19d097fb69e2",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"time": "2021-01-07T16:49:33+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php73\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php73/tree/v1.22.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-php73"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.22.1",
"version_normalized": "1.22.1.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91",
"reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91",
"shasum": ""
},
"require": {
"php": ">=7.1"
},
"time": "2021-01-07T16:49:33+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/polyfill-php80"
},
{
"name": "symfony/process",
"version": "v5.2.4",
"version_normalized": "5.2.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/313a38f09c77fbcdc1d223e57d368cea76a2fd2f",
"reference": "313a38f09c77fbcdc1d223e57d368cea76a2fd2f",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-php80": "^1.15"
},
"time": "2021-01-27T10:15:41+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Process\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v5.2.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/process"
},
{
"name": "symfony/service-contracts",
"version": "v2.4.0",
"version_normalized": "2.4.0.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
"reference": "f040a30e04b57fbcc9c6cbcf4dbaa96bd318b9bb",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/container": "^1.1"
},
"suggest": {
"symfony/service-implementation": ""
},
"time": "2021-04-01T10:43:52+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Contracts\\Service\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to writing services",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/service-contracts/tree/v2.4.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/service-contracts"
},
{
"name": "symfony/string",
"version": "v5.2.6",
"version_normalized": "5.2.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572",
"reference": "ad0bd91bce2054103f5eaa18ebeba8d3bc2a0572",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "~1.8",
"symfony/polyfill-intl-grapheme": "~1.0",
"symfony/polyfill-intl-normalizer": "~1.0",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "~1.15"
},
"require-dev": {
"symfony/error-handler": "^4.4|^5.0",
"symfony/http-client": "^4.4|^5.0",
"symfony/translation-contracts": "^1.1|^2",
"symfony/var-exporter": "^4.4|^5.0"
},
"time": "2021-03-17T17:12:15+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\String\\": ""
},
"files": [
"Resources/functions.php"
],
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way",
"homepage": "https://symfony.com",
"keywords": [
"grapheme",
"i18n",
"string",
"unicode",
"utf-8",
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v5.2.6"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/string"
},
{
"name": "symfony/yaml",
"version": "v5.2.5",
"version_normalized": "5.2.5.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "298a08ddda623485208506fcee08817807a251dd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/298a08ddda623485208506fcee08817807a251dd",
"reference": "298a08ddda623485208506fcee08817807a251dd",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<4.4"
},
"require-dev": {
"symfony/console": "^4.4|^5.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
},
"time": "2021-03-06T07:59:01+00:00",
"bin": [
"Resources/bin/yaml-lint"
],
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v5.2.5"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"install-path": "../symfony/yaml"
},
{
"name": "twig/twig",
"version": "v3.3.0",
"version_normalized": "3.3.0.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5",
"reference": "1f3b7e2c06cc05d42936a8ad508ff1db7975cdc5",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"psr/container": "^1.0",
"symfony/phpunit-bridge": "^4.4.9|^5.0.9"
},
"time": "2021-02-08T09:54:36+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.3-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Twig Team",
"role": "Contributors"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.3.0"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"install-path": "../twig/twig"
},
{
"name": "wdes/php-i18n-l10n",
"version": "v4.0.0",
"version_normalized": "4.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/wdes/php-I18n-L10n.git",
"reference": "f64b9ca054a5204a48a88dc38aeb7c6dae2f4df5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wdes/php-I18n-L10n/zipball/f64b9ca054a5204a48a88dc38aeb7c6dae2f4df5",
"reference": "f64b9ca054a5204a48a88dc38aeb7c6dae2f4df5",
"shasum": ""
},
"require": {
"php": "^7.2.9 || ^8.0",
"phpmyadmin/twig-i18n-extension": "^4.0",
"twig/twig": "^3"
},
"require-dev": {
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^7 || ^8 || ^9",
"wdes/coding-standard": "^3"
},
"time": "2021-03-31T13:51:25+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"Wdes\\phpI18nL10n\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MPL-2.0"
],
"authors": [
{
"name": "William Desportes",
"email": "williamdes@wdes.fr"
}
],
"description": "PHP library/api to provide Internationalisation and Localisation",
"homepage": "https://github.com/wdes/php-I18n-l10n",
"keywords": [
"composer-package",
"gettext",
"i18n",
"l10n",
"library",
"mo",
"pot-generator",
"twig"
],
"support": {
"email": "williamdes@wdes.fr",
"issues": "https://github.com/wdes/php-I18n-l10n/issues",
"source": "https://github.com/wdes/php-I18n-l10n"
},
"install-path": "../wdes/php-i18n-l10n"
},
{
"name": "webmozart/assert",
"version": "1.10.0",
"version_normalized": "1.10.0.0",
"source": {
"type": "git",
"url": "https://github.com/webmozarts/assert.git",
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<4.6.1 || 4.6.2"
},
"require-dev": {
"phpunit/phpunit": "^8.5.13"
},
"time": "2021-03-09T10:59:23+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.10-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Assertions to validate method input/output with nice error messages.",
"keywords": [
"assert",
"check",
"validate"
],
"support": {
"issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/1.10.0"
},
"install-path": "../webmozart/assert"
}
],
"dev": false,
"dev-package-names": []
}
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 70220)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.20". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/type-resolver/src', $vendorDir . '/phpdocumentor/reflection-docblock/src'),
'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'),
'Wdes\\phpI18nL10n\\' => array($vendorDir . '/wdes/php-i18n-l10n/src'),
'Twig\\' => array($vendorDir . '/twig/twig/src'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Php73\\' => array($vendorDir . '/symfony/polyfill-php73'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
'Symfony\\Polyfill\\Intl\\Grapheme\\' => array($vendorDir . '/symfony/polyfill-intl-grapheme'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\String\\' => array($vendorDir . '/symfony/string'),
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),
'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'),
'Symfony\\Component\\Filesystem\\' => array($vendorDir . '/symfony/filesystem'),
'Symfony\\Component\\Console\\' => array($vendorDir . '/symfony/console'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'PhpMyAdmin\\Twig\\Extensions\\' => array($vendorDir . '/phpmyadmin/twig-i18n-extension/src'),
'OndraM\\CiDetector\\' => array($vendorDir . '/ondram/ci-detector/src'),
'Doctum\\' => array($baseDir . '/src'),
'CodeLts\\CliTools\\' => array($vendorDir . '/code-lts/cli-tools/src'),
);
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInitf2e3a964c11223b024edc03e013baa7e
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInitf2e3a964c11223b024edc03e013baa7e', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
spl_autoload_unregister(array('ComposerAutoloaderInitf2e3a964c11223b024edc03e013baa7e', 'loadClassLoader'));
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
if ($useStaticLoader) {
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInitf2e3a964c11223b024edc03e013baa7e::getInitializer($loader));
} else {
$map = require __DIR__ . '/autoload_namespaces.php';
foreach ($map as $namespace => $path) {
$loader->set($namespace, $path);
}
$map = require __DIR__ . '/autoload_psr4.php';
foreach ($map as $namespace => $path) {
$loader->setPsr4($namespace, $path);
}
$classMap = require __DIR__ . '/autoload_classmap.php';
if ($classMap) {
$loader->addClassMap($classMap);
}
}
$loader->register(true);
if ($useStaticLoader) {
$includeFiles = Composer\Autoload\ComposerStaticInitf2e3a964c11223b024edc03e013baa7e::$files;
} else {
$includeFiles = require __DIR__ . '/autoload_files.php';
}
foreach ($includeFiles as $fileIdentifier => $file) {
composerRequiref2e3a964c11223b024edc03e013baa7e($fileIdentifier, $file);
}
return $loader;
}
}
function composerRequiref2e3a964c11223b024edc03e013baa7e($fileIdentifier, $file)
{
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
require $file;
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
}
}
<?php
// autoload_classmap.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
);
Copyright (c) Nils Adermann, Jordi Boggiano
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.
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
'8825ede83f2f289127722d4e842cf7e8' => $vendorDir . '/symfony/polyfill-intl-grapheme/bootstrap.php',
'b6b991a57620e2fb6b2f66f03fe9ddc2' => $vendorDir . '/symfony/string/Resources/functions.php',
'0d59ee240a4cd96ddbb4ff164fccea4d' => $vendorDir . '/symfony/polyfill-php73/bootstrap.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
);
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
private $vendorDir;
// PSR-4
private $prefixLengthsPsr4 = array();
private $prefixDirsPsr4 = array();
private $fallbackDirsPsr4 = array();
// PSR-0
private $prefixesPsr0 = array();
private $fallbackDirsPsr0 = array();
private $useIncludePath = false;
private $classMap = array();
private $classMapAuthoritative = false;
private $missingClasses = array();
private $apcuPrefix;
private static $registeredLoaders = array();
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
}
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array $classMap Class to filename map
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*/
public function add($prefix, $paths, $prepend = false)
{
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
(array) $paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
(array) $paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = (array) $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
(array) $paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
(array) $paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
(array) $paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
(array) $paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
(array) $paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param array|string $paths The PSR-0 base directories
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param array|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return bool|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
includeFile($file);
return true;
}
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders indexed by their corresponding vendor directories.
*
* @return self[]
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*/
function includeFile($file)
{
include $file;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Php73;
/**
* @author Gabriel Caruso <carusogabriel34@gmail.com>
* @author Ion Bazan <ion.bazan@gmail.com>
*
* @internal
*/
final class Php73
{
public static $startAt = 1533462603;
/**
* @param bool $asNum
*
* @return array|float|int
*/
public static function hrtime($asNum = false)
{
$ns = microtime(false);
$s = substr($ns, 11) - self::$startAt;
$ns = 1E9 * (float) $ns;
if ($asNum) {
$ns += $s * 1E9;
return \PHP_INT_SIZE === 4 ? $ns : (int) $ns;
}
return [$s, (int) $ns];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php73 as p;
if (\PHP_VERSION_ID >= 70300) {
return;
}
if (!function_exists('is_countable')) {
function is_countable($value) { return is_array($value) || $value instanceof Countable || $value instanceof ResourceBundle || $value instanceof SimpleXmlElement; }
}
if (!function_exists('hrtime')) {
require_once __DIR__.'/Php73.php';
p\Php73::$startAt = (int) microtime(true);
function hrtime($as_number = false) { return p\Php73::hrtime($as_number); }
}
if (!function_exists('array_key_first')) {
function array_key_first(array $array) { foreach ($array as $key => $value) { return $key; } }
}
if (!function_exists('array_key_last')) {
function array_key_last(array $array) { return key(array_slice($array, -1, 1, true)); }
}
Copyright (c) 2018-2019 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
class JsonException extends Exception
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Logger;
use Psr\Log\AbstractLogger;
use Psr\Log\InvalidArgumentException;
use Psr\Log\LogLevel;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* PSR-3 compliant console logger.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @see https://www.php-fig.org/psr/psr-3/
*/
class ConsoleLogger extends AbstractLogger
{
public const INFO = 'info';
public const ERROR = 'error';
private $output;
private $verbosityLevelMap = [
LogLevel::EMERGENCY => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ALERT => OutputInterface::VERBOSITY_NORMAL,
LogLevel::CRITICAL => OutputInterface::VERBOSITY_NORMAL,
LogLevel::ERROR => OutputInterface::VERBOSITY_NORMAL,
LogLevel::WARNING => OutputInterface::VERBOSITY_NORMAL,
LogLevel::NOTICE => OutputInterface::VERBOSITY_VERBOSE,
LogLevel::INFO => OutputInterface::VERBOSITY_VERY_VERBOSE,
LogLevel::DEBUG => OutputInterface::VERBOSITY_DEBUG,
];
private $formatLevelMap = [
LogLevel::EMERGENCY => self::ERROR,
LogLevel::ALERT => self::ERROR,
LogLevel::CRITICAL => self::ERROR,
LogLevel::ERROR => self::ERROR,
LogLevel::WARNING => self::INFO,
LogLevel::NOTICE => self::INFO,
LogLevel::INFO => self::INFO,
LogLevel::DEBUG => self::INFO,
];
private $errored = false;
public function __construct(OutputInterface $output, array $verbosityLevelMap = [], array $formatLevelMap = [])
{
$this->output = $output;
$this->verbosityLevelMap = $verbosityLevelMap + $this->verbosityLevelMap;
$this->formatLevelMap = $formatLevelMap + $this->formatLevelMap;
}
/**
* {@inheritdoc}
*
* @return void
*/
public function log($level, $message, array $context = [])
{
if (!isset($this->verbosityLevelMap[$level])) {
throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level));
}
$output = $this->output;
// Write to the error output if necessary and available
if (self::ERROR === $this->formatLevelMap[$level]) {
if ($this->output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->errored = true;
}
// the if condition check isn't necessary -- it's the same one that $output will do internally anyway.
// We only do it for efficiency here as the message formatting is relatively expensive.
if ($output->getVerbosity() >= $this->verbosityLevelMap[$level]) {
$output->writeln(sprintf('<%1$s>[%2$s] %3$s</%1$s>', $this->formatLevelMap[$level], $level, $this->interpolate($message, $context)), $this->verbosityLevelMap[$level]);
}
}
/**
* Returns true when any messages have been logged at error levels.
*
* @return bool
*/
public function hasErrored()
{
return $this->errored;
}
/**
* Interpolates context values into the message placeholders.
*
* @author PHP Framework Interoperability Group
*/
private function interpolate(string $message, array $context): string
{
if (false === strpos($message, '{')) {
return $message;
}
$replacements = [];
foreach ($context as $key => $val) {
if (null === $val || is_scalar($val) || (\is_object($val) && method_exists($val, '__toString'))) {
$replacements["{{$key}}"] = $val;
} elseif ($val instanceof \DateTimeInterface) {
$replacements["{{$key}}"] = $val->format(\DateTime::RFC3339);
} elseif (\is_object($val)) {
$replacements["{{$key}}"] = '[object '.\get_class($val).']';
} else {
$replacements["{{$key}}"] = '['.\gettype($val).']';
}
}
return strtr($message, $replacements);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Represents a choice question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ChoiceQuestion extends Question
{
private $choices;
private $multiselect = false;
private $prompt = ' > ';
private $errorMessage = 'Value "%s" is invalid';
/**
* @param string $question The question to ask to the user
* @param array $choices The list of available choices
* @param mixed $default The default answer to return
*/
public function __construct(string $question, array $choices, $default = null)
{
if (!$choices) {
throw new \LogicException('Choice question must have at least 1 choice available.');
}
parent::__construct($question, $default);
$this->choices = $choices;
$this->setValidator($this->getDefaultValidator());
$this->setAutocompleterValues($choices);
}
/**
* Returns available choices.
*
* @return array
*/
public function getChoices()
{
return $this->choices;
}
/**
* Sets multiselect option.
*
* When multiselect is set to true, multiple choices can be answered.
*
* @return $this
*/
public function setMultiselect(bool $multiselect)
{
$this->multiselect = $multiselect;
$this->setValidator($this->getDefaultValidator());
return $this;
}
/**
* Returns whether the choices are multiselect.
*
* @return bool
*/
public function isMultiselect()
{
return $this->multiselect;
}
/**
* Gets the prompt for choices.
*
* @return string
*/
public function getPrompt()
{
return $this->prompt;
}
/**
* Sets the prompt for choices.
*
* @return $this
*/
public function setPrompt(string $prompt)
{
$this->prompt = $prompt;
return $this;
}
/**
* Sets the error message for invalid values.
*
* The error message has a string placeholder (%s) for the invalid value.
*
* @return $this
*/
public function setErrorMessage(string $errorMessage)
{
$this->errorMessage = $errorMessage;
$this->setValidator($this->getDefaultValidator());
return $this;
}
private function getDefaultValidator(): callable
{
$choices = $this->choices;
$errorMessage = $this->errorMessage;
$multiselect = $this->multiselect;
$isAssoc = $this->isAssoc($choices);
return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) {
if ($multiselect) {
// Check for a separated comma values
if (!preg_match('/^[^,]+(?:,[^,]+)*$/', $selected, $matches)) {
throw new InvalidArgumentException(sprintf($errorMessage, $selected));
}
$selectedChoices = explode(',', $selected);
} else {
$selectedChoices = [$selected];
}
if ($this->isTrimmable()) {
foreach ($selectedChoices as $k => $v) {
$selectedChoices[$k] = trim($v);
}
}
$multiselectChoices = [];
foreach ($selectedChoices as $value) {
$results = [];
foreach ($choices as $key => $choice) {
if ($choice === $value) {
$results[] = $key;
}
}
if (\count($results) > 1) {
throw new InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of "%s".', implode('" or "', $results)));
}
$result = array_search($value, $choices);
if (!$isAssoc) {
if (false !== $result) {
$result = $choices[$result];
} elseif (isset($choices[$value])) {
$result = $choices[$value];
}
} elseif (false === $result && isset($choices[$value])) {
$result = $value;
}
if (false === $result) {
throw new InvalidArgumentException(sprintf($errorMessage, $value));
}
// For associative choices, consistently return the key as string:
$multiselectChoices[] = $isAssoc ? (string) $result : $result;
}
if ($multiselect) {
return $multiselectChoices;
}
return current($multiselectChoices);
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
/**
* Represents a yes/no question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConfirmationQuestion extends Question
{
private $trueAnswerRegex;
/**
* @param string $question The question to ask to the user
* @param bool $default The default answer to return, true or false
* @param string $trueAnswerRegex A regex to match the "yes" answer
*/
public function __construct(string $question, bool $default = true, string $trueAnswerRegex = '/^y/i')
{
parent::__construct($question, $default);
$this->trueAnswerRegex = $trueAnswerRegex;
$this->setNormalizer($this->getDefaultNormalizer());
}
/**
* Returns the default answer normalizer.
*/
private function getDefaultNormalizer(): callable
{
$default = $this->getDefault();
$regex = $this->trueAnswerRegex;
return function ($answer) use ($default, $regex) {
if (\is_bool($answer)) {
return $answer;
}
$answerIsTrue = (bool) preg_match($regex, $answer);
if (false === $default) {
return $answer && $answerIsTrue;
}
return '' === $answer || $answerIsTrue;
};
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Question;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a Question.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Question
{
private $question;
private $attempts;
private $hidden = false;
private $hiddenFallback = true;
private $autocompleterCallback;
private $validator;
private $default;
private $normalizer;
private $trimmable = true;
private $multiline = false;
/**
* @param string $question The question to ask to the user
* @param mixed $default The default answer to return if the user enters nothing
*/
public function __construct(string $question, $default = null)
{
$this->question = $question;
$this->default = $default;
}
/**
* Returns the question.
*
* @return string
*/
public function getQuestion()
{
return $this->question;
}
/**
* Returns the default answer.
*
* @return mixed
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns whether the user response accepts newline characters.
*/
public function isMultiline(): bool
{
return $this->multiline;
}
/**
* Sets whether the user response should accept newline characters.
*
* @return $this
*/
public function setMultiline(bool $multiline): self
{
$this->multiline = $multiline;
return $this;
}
/**
* Returns whether the user response must be hidden.
*
* @return bool
*/
public function isHidden()
{
return $this->hidden;
}
/**
* Sets whether the user response must be hidden or not.
*
* @param bool $hidden
*
* @return $this
*
* @throws LogicException In case the autocompleter is also used
*/
public function setHidden($hidden)
{
if ($this->autocompleterCallback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->hidden = (bool) $hidden;
return $this;
}
/**
* In case the response can not be hidden, whether to fallback on non-hidden question or not.
*
* @return bool
*/
public function isHiddenFallback()
{
return $this->hiddenFallback;
}
/**
* Sets whether to fallback on non-hidden question if the response can not be hidden.
*
* @param bool $fallback
*
* @return $this
*/
public function setHiddenFallback($fallback)
{
$this->hiddenFallback = (bool) $fallback;
return $this;
}
/**
* Gets values for the autocompleter.
*
* @return iterable|null
*/
public function getAutocompleterValues()
{
$callback = $this->getAutocompleterCallback();
return $callback ? $callback('') : null;
}
/**
* Sets values for the autocompleter.
*
* @return $this
*
* @throws LogicException
*/
public function setAutocompleterValues(?iterable $values)
{
if (\is_array($values)) {
$values = $this->isAssoc($values) ? array_merge(array_keys($values), array_values($values)) : array_values($values);
$callback = static function () use ($values) {
return $values;
};
} elseif ($values instanceof \Traversable) {
$valueCache = null;
$callback = static function () use ($values, &$valueCache) {
return $valueCache ?? $valueCache = iterator_to_array($values, false);
};
} else {
$callback = null;
}
return $this->setAutocompleterCallback($callback);
}
/**
* Gets the callback function used for the autocompleter.
*/
public function getAutocompleterCallback(): ?callable
{
return $this->autocompleterCallback;
}
/**
* Sets the callback function used for the autocompleter.
*
* The callback is passed the user input as argument and should return an iterable of corresponding suggestions.
*
* @return $this
*/
public function setAutocompleterCallback(callable $callback = null): self
{
if ($this->hidden && null !== $callback) {
throw new LogicException('A hidden question cannot use the autocompleter.');
}
$this->autocompleterCallback = $callback;
return $this;
}
/**
* Sets a validator for the question.
*
* @return $this
*/
public function setValidator(callable $validator = null)
{
$this->validator = $validator;
return $this;
}
/**
* Gets the validator for the question.
*
* @return callable|null
*/
public function getValidator()
{
return $this->validator;
}
/**
* Sets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @return $this
*
* @throws InvalidArgumentException in case the number of attempts is invalid
*/
public function setMaxAttempts(?int $attempts)
{
if (null !== $attempts) {
$attempts = (int) $attempts;
if ($attempts < 1) {
throw new InvalidArgumentException('Maximum number of attempts must be a positive value.');
}
}
$this->attempts = $attempts;
return $this;
}
/**
* Gets the maximum number of attempts.
*
* Null means an unlimited number of attempts.
*
* @return int|null
*/
public function getMaxAttempts()
{
return $this->attempts;
}
/**
* Sets a normalizer for the response.
*
* The normalizer can be a callable (a string), a closure or a class implementing __invoke.
*
* @return $this
*/
public function setNormalizer(callable $normalizer)
{
$this->normalizer = $normalizer;
return $this;
}
/**
* Gets the normalizer for the response.
*
* The normalizer can ba a callable (a string), a closure or a class implementing __invoke.
*
* @return callable|null
*/
public function getNormalizer()
{
return $this->normalizer;
}
protected function isAssoc(array $array)
{
return (bool) \count(array_filter(array_keys($array), 'is_string'));
}
public function isTrimmable(): bool
{
return $this->trimmable;
}
/**
* @return $this
*/
public function setTrimmable(bool $trimmable): self
{
$this->trimmable = $trimmable;
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class SingleCommandApplication extends Command
{
private $version = 'UNKNOWN';
private $autoExit = true;
private $running = false;
public function setVersion(string $version): self
{
$this->version = $version;
return $this;
}
/**
* @final
*/
public function setAutoExit(bool $autoExit): self
{
$this->autoExit = $autoExit;
return $this;
}
public function run(InputInterface $input = null, OutputInterface $output = null): int
{
if ($this->running) {
return parent::run($input, $output);
}
// We use the command name as the application name
$application = new Application($this->getName() ?: 'UNKNOWN', $this->version);
$application->setAutoExit($this->autoExit);
// Fix the usage of the command displayed with "--help"
$this->setName($_SERVER['argv'][0]);
$application->add($this);
$application->setDefaultCommand($this->getName(), true);
$this->running = true;
try {
$ret = $application->run($input, $output);
} finally {
$this->running = false;
}
return $ret ?? 1;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
/**
* Contains all events dispatched by an Application.
*
* @author Francesco Levorato <git@flevour.net>
*/
final class ConsoleEvents
{
/**
* The COMMAND event allows you to attach listeners before any command is
* executed by the console. It also allows you to modify the command, input and output
* before they are handed to the command.
*
* @Event("Symfony\Component\Console\Event\ConsoleCommandEvent")
*/
public const COMMAND = 'console.command';
/**
* The SIGNAL event allows you to perform some actions
* after the command execution was interrupted.
*
* @Event("Symfony\Component\Console\Event\ConsoleSignalEvent")
*/
public const SIGNAL = 'console.signal';
/**
* The TERMINATE event allows you to attach listeners after a command is
* executed by the console.
*
* @Event("Symfony\Component\Console\Event\ConsoleTerminateEvent")
*/
public const TERMINATE = 'console.terminate';
/**
* The ERROR event occurs when an uncaught exception or error appears.
*
* This event allows you to deal with the exception/error or
* to modify the thrown exception.
*
* @Event("Symfony\Component\Console\Event\ConsoleErrorEvent")
*/
public const ERROR = 'console.error';
/**
* Event aliases.
*
* These aliases can be consumed by RegisterListenersPass.
*/
public const ALIASES = [
ConsoleCommandEvent::class => self::COMMAND,
ConsoleErrorEvent::class => self::ERROR,
ConsoleSignalEvent::class => self::SIGNAL,
ConsoleTerminateEvent::class => self::TERMINATE,
];
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to handle throwables thrown while running a command.
*
* @author Wouter de Jong <wouter@wouterj.nl>
*/
final class ConsoleErrorEvent extends ConsoleEvent
{
private $error;
private $exitCode;
public function __construct(InputInterface $input, OutputInterface $output, \Throwable $error, Command $command = null)
{
parent::__construct($command, $input, $output);
$this->error = $error;
}
public function getError(): \Throwable
{
return $this->error;
}
public function setError(\Throwable $error): void
{
$this->error = $error;
}
public function setExitCode(int $exitCode): void
{
$this->exitCode = $exitCode;
$r = new \ReflectionProperty($this->error, 'code');
$r->setAccessible(true);
$r->setValue($this->error, $this->exitCode);
}
public function getExitCode(): int
{
return null !== $this->exitCode ? $this->exitCode : (\is_int($this->error->getCode()) && 0 !== $this->error->getCode() ? $this->error->getCode() : 1);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author marie <marie@users.noreply.github.com>
*/
final class ConsoleSignalEvent extends ConsoleEvent
{
private $handlingSignal;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $handlingSignal)
{
parent::__construct($command, $input, $output);
$this->handlingSignal = $handlingSignal;
}
public function getHandlingSignal(): int
{
return $this->handlingSignal;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Contracts\EventDispatcher\Event;
/**
* Allows to inspect input and output of a command.
*
* @author Francesco Levorato <git@flevour.net>
*/
class ConsoleEvent extends Event
{
protected $command;
private $input;
private $output;
public function __construct(Command $command = null, InputInterface $input, OutputInterface $output)
{
$this->command = $command;
$this->input = $input;
$this->output = $output;
}
/**
* Gets the command that is executed.
*
* @return Command|null A Command instance
*/
public function getCommand()
{
return $this->command;
}
/**
* Gets the input instance.
*
* @return InputInterface An InputInterface instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance.
*
* @return OutputInterface An OutputInterface instance
*/
public function getOutput()
{
return $this->output;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
/**
* Allows to do things before the command is executed, like skipping the command or changing the input.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class ConsoleCommandEvent extends ConsoleEvent
{
/**
* The return code for skipped commands, this will also be passed into the terminate event.
*/
public const RETURN_CODE_DISABLED = 113;
/**
* Indicates if the command should be run or skipped.
*/
private $commandShouldRun = true;
/**
* Disables the command, so it won't be run.
*/
public function disableCommand(): bool
{
return $this->commandShouldRun = false;
}
public function enableCommand(): bool
{
return $this->commandShouldRun = true;
}
/**
* Returns true if the command is runnable, false otherwise.
*/
public function commandShouldRun(): bool
{
return $this->commandShouldRun;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Event;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Allows to manipulate the exit code of a command after its execution.
*
* @author Francesco Levorato <git@flevour.net>
*/
final class ConsoleTerminateEvent extends ConsoleEvent
{
private $exitCode;
public function __construct(Command $command, InputInterface $input, OutputInterface $output, int $exitCode)
{
parent::__construct($command, $input, $output);
$this->setExitCode($exitCode);
}
public function setExitCode(int $exitCode): void
{
$this->exitCode = $exitCode;
}
public function getExitCode(): int
{
return $this->exitCode;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class Color
{
private const COLORS = [
'black' => 0,
'red' => 1,
'green' => 2,
'yellow' => 3,
'blue' => 4,
'magenta' => 5,
'cyan' => 6,
'white' => 7,
'default' => 9,
];
private const AVAILABLE_OPTIONS = [
'bold' => ['set' => 1, 'unset' => 22],
'underscore' => ['set' => 4, 'unset' => 24],
'blink' => ['set' => 5, 'unset' => 25],
'reverse' => ['set' => 7, 'unset' => 27],
'conceal' => ['set' => 8, 'unset' => 28],
];
private $foreground;
private $background;
private $options = [];
public function __construct(string $foreground = '', string $background = '', array $options = [])
{
$this->foreground = $this->parseColor($foreground);
$this->background = $this->parseColor($background);
foreach ($options as $option) {
if (!isset(self::AVAILABLE_OPTIONS[$option])) {
throw new InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s).', $option, implode(', ', array_keys(self::AVAILABLE_OPTIONS))));
}
$this->options[$option] = self::AVAILABLE_OPTIONS[$option];
}
}
public function apply(string $text): string
{
return $this->set().$text.$this->unset();
}
public function set(): string
{
$setCodes = [];
if ('' !== $this->foreground) {
$setCodes[] = '3'.$this->foreground;
}
if ('' !== $this->background) {
$setCodes[] = '4'.$this->background;
}
foreach ($this->options as $option) {
$setCodes[] = $option['set'];
}
if (0 === \count($setCodes)) {
return '';
}
return sprintf("\033[%sm", implode(';', $setCodes));
}
public function unset(): string
{
$unsetCodes = [];
if ('' !== $this->foreground) {
$unsetCodes[] = 39;
}
if ('' !== $this->background) {
$unsetCodes[] = 49;
}
foreach ($this->options as $option) {
$unsetCodes[] = $option['unset'];
}
if (0 === \count($unsetCodes)) {
return '';
}
return sprintf("\033[%sm", implode(';', $unsetCodes));
}
private function parseColor(string $color): string
{
if ('' === $color) {
return '';
}
if ('#' === $color[0]) {
$color = substr($color, 1);
if (3 === \strlen($color)) {
$color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2];
}
if (6 !== \strlen($color)) {
throw new InvalidArgumentException(sprintf('Invalid "%s" color.', $color));
}
return $this->convertHexColorToAnsi(hexdec($color));
}
if (!isset(self::COLORS[$color])) {
throw new InvalidArgumentException(sprintf('Invalid "%s" color; expected one of (%s).', $color, implode(', ', array_keys(self::COLORS))));
}
return (string) self::COLORS[$color];
}
private function convertHexColorToAnsi(int $color): string
{
$r = ($color >> 16) & 255;
$g = ($color >> 8) & 255;
$b = $color & 255;
// see https://github.com/termstandard/colors/ for more information about true color support
if ('truecolor' !== getenv('COLORTERM')) {
return (string) $this->degradeHexColorToAnsi($r, $g, $b);
}
return sprintf('8;2;%d;%d;%d', $r, $g, $b);
}
private function degradeHexColorToAnsi(int $r, int $g, int $b): int
{
if (0 === round($this->getSaturation($r, $g, $b) / 50)) {
return 0;
}
return (round($b / 255) << 2) | (round($g / 255) << 1) | round($r / 255);
}
private function getSaturation(int $r, int $g, int $b): int
{
$r = $r / 255;
$g = $g / 255;
$b = $b / 255;
$v = max($r, $g, $b);
if (0 === $diff = $v - min($r, $g, $b)) {
return 0;
}
return (int) $diff * 100 / $v;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a command line argument.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputArgument
{
public const REQUIRED = 1;
public const OPTIONAL = 2;
public const IS_ARRAY = 4;
private $name;
private $mode;
private $default;
private $description;
/**
* @param string $name The argument name
* @param int|null $mode The argument mode: self::REQUIRED or self::OPTIONAL
* @param string $description A description text
* @param string|string[]|null $default The default value (for self::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*/
public function __construct(string $name, int $mode = null, string $description = '', $default = null)
{
if (null === $mode) {
$mode = self::OPTIONAL;
} elseif ($mode > 7 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->mode = $mode;
$this->description = $description;
$this->setDefault($default);
}
/**
* Returns the argument name.
*
* @return string The argument name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the argument is required.
*
* @return bool true if parameter mode is self::REQUIRED, false otherwise
*/
public function isRequired()
{
return self::REQUIRED === (self::REQUIRED & $this->mode);
}
/**
* Returns true if the argument can take multiple values.
*
* @return bool true if mode is self::IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::IS_ARRAY === (self::IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param string|string[]|null $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::REQUIRED === $this->mode && null !== $default) {
throw new LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = [];
} elseif (!\is_array($default)) {
throw new LogicException('A default value for an array argument must be an array.');
}
}
$this->default = $default;
}
/**
* Returns the default value.
*
* @return string|string[]|null The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\InvalidOptionException;
/**
* ArrayInput represents an input provided as an array.
*
* Usage:
*
* $input = new ArrayInput(['command' => 'foo:bar', 'foo' => 'bar', '--bar' => 'foobar']);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ArrayInput extends Input
{
private $parameters;
public function __construct(array $parameters, InputDefinition $definition = null)
{
$this->parameters = $parameters;
parent::__construct($definition);
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
{
foreach ($this->parameters as $param => $value) {
if ($param && \is_string($param) && '-' === $param[0]) {
continue;
}
return $value;
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, bool $onlyParams = false)
{
$values = (array) $values;
foreach ($this->parameters as $k => $v) {
if (!\is_int($k)) {
$v = $k;
}
if ($onlyParams && '--' === $v) {
return false;
}
if (\in_array($v, $values)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false)
{
$values = (array) $values;
foreach ($this->parameters as $k => $v) {
if ($onlyParams && ('--' === $k || (\is_int($k) && '--' === $v))) {
return $default;
}
if (\is_int($k)) {
if (\in_array($v, $values)) {
return true;
}
} elseif (\in_array($k, $values)) {
return $v;
}
}
return $default;
}
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
{
$params = [];
foreach ($this->parameters as $param => $val) {
if ($param && \is_string($param) && '-' === $param[0]) {
$glue = ('-' === $param[1]) ? '=' : ' ';
if (\is_array($val)) {
foreach ($val as $v) {
$params[] = $param.('' != $v ? $glue.$this->escapeToken($v) : '');
}
} else {
$params[] = $param.('' != $val ? $glue.$this->escapeToken($val) : '');
}
} else {
$params[] = \is_array($val) ? implode(' ', array_map([$this, 'escapeToken'], $val)) : $this->escapeToken($val);
}
}
return implode(' ', $params);
}
/**
* {@inheritdoc}
*/
protected function parse()
{
foreach ($this->parameters as $key => $value) {
if ('--' === $key) {
return;
}
if (0 === strpos($key, '--')) {
$this->addLongOption(substr($key, 2), $value);
} elseif (0 === strpos($key, '-')) {
$this->addShortOption(substr($key, 1), $value);
} else {
$this->addArgument($key, $value);
}
}
}
/**
* Adds a short option value.
*
* @throws InvalidOptionException When option given doesn't exist
*/
private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new InvalidOptionException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
/**
* Adds a long option value.
*
* @throws InvalidOptionException When option given doesn't exist
* @throws InvalidOptionException When a required value is missing
*/
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidOptionException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (null === $value) {
if ($option->isValueRequired()) {
throw new InvalidOptionException(sprintf('The "--%s" option requires a value.', $name));
}
if (!$option->isValueOptional()) {
$value = true;
}
}
$this->options[$name] = $value;
}
/**
* Adds an argument value.
*
* @param string|int $name The argument name
* @param mixed $value The value for the argument
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
private function addArgument($name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* ArgvInput represents an input coming from the CLI arguments.
*
* Usage:
*
* $input = new ArgvInput();
*
* By default, the `$_SERVER['argv']` array is used for the input values.
*
* This can be overridden by explicitly passing the input values in the constructor:
*
* $input = new ArgvInput($_SERVER['argv']);
*
* If you pass it yourself, don't forget that the first element of the array
* is the name of the running application.
*
* When passing an argument to the constructor, be sure that it respects
* the same rules as the argv one. It's almost always better to use the
* `StringInput` when you want to provide your own input.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html
* @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02
*/
class ArgvInput extends Input
{
private $tokens;
private $parsed;
public function __construct(array $argv = null, InputDefinition $definition = null)
{
$argv = $argv ?? $_SERVER['argv'] ?? [];
// strip the application name
array_shift($argv);
$this->tokens = $argv;
parent::__construct($definition);
}
protected function setTokens(array $tokens)
{
$this->tokens = $tokens;
}
/**
* {@inheritdoc}
*/
protected function parse()
{
$parseOptions = true;
$this->parsed = $this->tokens;
while (null !== $token = array_shift($this->parsed)) {
if ($parseOptions && '' == $token) {
$this->parseArgument($token);
} elseif ($parseOptions && '--' == $token) {
$parseOptions = false;
} elseif ($parseOptions && 0 === strpos($token, '--')) {
$this->parseLongOption($token);
} elseif ($parseOptions && '-' === $token[0] && '-' !== $token) {
$this->parseShortOption($token);
} else {
$this->parseArgument($token);
}
}
}
/**
* Parses a short option.
*/
private function parseShortOption(string $token)
{
$name = substr($token, 1);
if (\strlen($name) > 1) {
if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) {
// an option with a value (with no space)
$this->addShortOption($name[0], substr($name, 1));
} else {
$this->parseShortOptionSet($name);
}
} else {
$this->addShortOption($name, null);
}
}
/**
* Parses a short option set.
*
* @throws RuntimeException When option given doesn't exist
*/
private function parseShortOptionSet(string $name)
{
$len = \strlen($name);
for ($i = 0; $i < $len; ++$i) {
if (!$this->definition->hasShortcut($name[$i])) {
$encoding = mb_detect_encoding($name, null, true);
throw new RuntimeException(sprintf('The "-%s" option does not exist.', false === $encoding ? $name[$i] : mb_substr($name, $i, 1, $encoding)));
}
$option = $this->definition->getOptionForShortcut($name[$i]);
if ($option->acceptValue()) {
$this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1));
break;
} else {
$this->addLongOption($option->getName(), null);
}
}
}
/**
* Parses a long option.
*/
private function parseLongOption(string $token)
{
$name = substr($token, 2);
if (false !== $pos = strpos($name, '=')) {
if (0 === \strlen($value = substr($name, $pos + 1))) {
array_unshift($this->parsed, $value);
}
$this->addLongOption(substr($name, 0, $pos), $value);
} else {
$this->addLongOption($name, null);
}
}
/**
* Parses an argument.
*
* @throws RuntimeException When too many arguments are given
*/
private function parseArgument(string $token)
{
$c = \count($this->arguments);
// if input is expecting another argument, add it
if ($this->definition->hasArgument($c)) {
$arg = $this->definition->getArgument($c);
$this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token;
// if last argument isArray(), append token to last argument
} elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) {
$arg = $this->definition->getArgument($c - 1);
$this->arguments[$arg->getName()][] = $token;
// unexpected argument
} else {
$all = $this->definition->getArguments();
$symfonyCommandName = null;
if (($inputArgument = $all[$key = array_key_first($all)] ?? null) && 'command' === $inputArgument->getName()) {
$symfonyCommandName = $this->arguments['command'] ?? null;
unset($all[$key]);
}
if (\count($all)) {
if ($symfonyCommandName) {
$message = sprintf('Too many arguments to "%s" command, expected arguments "%s".', $symfonyCommandName, implode('" "', array_keys($all)));
} else {
$message = sprintf('Too many arguments, expected arguments "%s".', implode('" "', array_keys($all)));
}
} elseif ($symfonyCommandName) {
$message = sprintf('No arguments expected for "%s" command, got "%s".', $symfonyCommandName, $token);
} else {
$message = sprintf('No arguments expected, got "%s".', $token);
}
throw new RuntimeException($message);
}
}
/**
* Adds a short option value.
*
* @throws RuntimeException When option given doesn't exist
*/
private function addShortOption(string $shortcut, $value)
{
if (!$this->definition->hasShortcut($shortcut)) {
throw new RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut));
}
$this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value);
}
/**
* Adds a long option value.
*
* @throws RuntimeException When option given doesn't exist
*/
private function addLongOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new RuntimeException(sprintf('The "--%s" option does not exist.', $name));
}
$option = $this->definition->getOption($name);
if (null !== $value && !$option->acceptValue()) {
throw new RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
}
if (\in_array($value, ['', null], true) && $option->acceptValue() && \count($this->parsed)) {
// if option accepts an optional or mandatory argument
// let's see if there is one provided
$next = array_shift($this->parsed);
if ((isset($next[0]) && '-' !== $next[0]) || \in_array($next, ['', null], true)) {
$value = $next;
} else {
array_unshift($this->parsed, $next);
}
}
if (null === $value) {
if ($option->isValueRequired()) {
throw new RuntimeException(sprintf('The "--%s" option requires a value.', $name));
}
if (!$option->isArray() && !$option->isValueOptional()) {
$value = true;
}
}
if ($option->isArray()) {
$this->options[$name][] = $value;
} else {
$this->options[$name] = $value;
}
}
/**
* {@inheritdoc}
*/
public function getFirstArgument()
{
$isOption = false;
foreach ($this->tokens as $i => $token) {
if ($token && '-' === $token[0]) {
if (false !== strpos($token, '=') || !isset($this->tokens[$i + 1])) {
continue;
}
// If it's a long option, consider that everything after "--" is the option name.
// Otherwise, use the last char (if it's a short option set, only the last one can take a value with space separator)
$name = '-' === $token[1] ? substr($token, 2) : substr($token, -1);
if (!isset($this->options[$name]) && !$this->definition->hasShortcut($name)) {
// noop
} elseif ((isset($this->options[$name]) || isset($this->options[$name = $this->definition->shortcutToName($name)])) && $this->tokens[$i + 1] === $this->options[$name]) {
$isOption = true;
}
continue;
}
if ($isOption) {
$isOption = false;
continue;
}
return $token;
}
return null;
}
/**
* {@inheritdoc}
*/
public function hasParameterOption($values, bool $onlyParams = false)
{
$values = (array) $values;
foreach ($this->tokens as $token) {
if ($onlyParams && '--' === $token) {
return false;
}
foreach ($values as $value) {
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
return true;
}
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false)
{
$values = (array) $values;
$tokens = $this->tokens;
while (0 < \count($tokens)) {
$token = array_shift($tokens);
if ($onlyParams && '--' === $token) {
return $default;
}
foreach ($values as $value) {
if ($token === $value) {
return array_shift($tokens);
}
// Options with values:
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ('' !== $leading && 0 === strpos($token, $leading)) {
return substr($token, \strlen($leading));
}
}
}
return $default;
}
/**
* Returns a stringified representation of the args passed to the command.
*
* @return string
*/
public function __toString()
{
$tokens = array_map(function ($token) {
if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) {
return $match[1].$this->escapeToken($match[2]);
}
if ($token && '-' !== $token[0]) {
return $this->escapeToken($token);
}
return $token;
}, $this->tokens);
return implode(' ', $tokens);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* Input is the base class for all concrete Input classes.
*
* Three concrete classes are provided by default:
*
* * `ArgvInput`: The input comes from the CLI arguments (argv)
* * `StringInput`: The input is provided as a string
* * `ArrayInput`: The input is provided as an array
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Input implements InputInterface, StreamableInputInterface
{
protected $definition;
protected $stream;
protected $options = [];
protected $arguments = [];
protected $interactive = true;
public function __construct(InputDefinition $definition = null)
{
if (null === $definition) {
$this->definition = new InputDefinition();
} else {
$this->bind($definition);
$this->validate();
}
}
/**
* {@inheritdoc}
*/
public function bind(InputDefinition $definition)
{
$this->arguments = [];
$this->options = [];
$this->definition = $definition;
$this->parse();
}
/**
* Processes command line arguments.
*/
abstract protected function parse();
/**
* {@inheritdoc}
*/
public function validate()
{
$definition = $this->definition;
$givenArguments = $this->arguments;
$missingArguments = array_filter(array_keys($definition->getArguments()), function ($argument) use ($definition, $givenArguments) {
return !\array_key_exists($argument, $givenArguments) && $definition->getArgument($argument)->isRequired();
});
if (\count($missingArguments) > 0) {
throw new RuntimeException(sprintf('Not enough arguments (missing: "%s").', implode(', ', $missingArguments)));
}
}
/**
* {@inheritdoc}
*/
public function isInteractive()
{
return $this->interactive;
}
/**
* {@inheritdoc}
*/
public function setInteractive(bool $interactive)
{
$this->interactive = $interactive;
}
/**
* {@inheritdoc}
*/
public function getArguments()
{
return array_merge($this->definition->getArgumentDefaults(), $this->arguments);
}
/**
* {@inheritdoc}
*/
public function getArgument(string $name)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
return $this->arguments[$name] ?? $this->definition->getArgument($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setArgument(string $name, $value)
{
if (!$this->definition->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$this->arguments[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function hasArgument($name)
{
return $this->definition->hasArgument($name);
}
/**
* {@inheritdoc}
*/
public function getOptions()
{
return array_merge($this->definition->getOptionDefaults(), $this->options);
}
/**
* {@inheritdoc}
*/
public function getOption(string $name)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
return \array_key_exists($name, $this->options) ? $this->options[$name] : $this->definition->getOption($name)->getDefault();
}
/**
* {@inheritdoc}
*/
public function setOption(string $name, $value)
{
if (!$this->definition->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "%s" option does not exist.', $name));
}
$this->options[$name] = $value;
}
/**
* {@inheritdoc}
*/
public function hasOption(string $name)
{
return $this->definition->hasOption($name);
}
/**
* Escapes a token through escapeshellarg if it contains unsafe chars.
*
* @return string
*/
public function escapeToken(string $token)
{
return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token);
}
/**
* {@inheritdoc}
*/
public function setStream($stream)
{
$this->stream = $stream;
}
/**
* {@inheritdoc}
*/
public function getStream()
{
return $this->stream;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
/**
* InputAwareInterface should be implemented by classes that depends on the
* Console Input.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
interface InputAwareInterface
{
/**
* Sets the Console Input.
*/
public function setInput(InputInterface $input);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* StringInput represents an input provided as a string.
*
* Usage:
*
* $input = new StringInput('foo --bar="foobar"');
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StringInput extends ArgvInput
{
public const REGEX_STRING = '([^\s]+?)(?:\s|(?<!\\\\)"|(?<!\\\\)\'|$)';
public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\')';
/**
* @param string $input A string representing the parameters from the CLI
*/
public function __construct(string $input)
{
parent::__construct([]);
$this->setTokens($this->tokenize($input));
}
/**
* Tokenizes a string.
*
* @throws InvalidArgumentException When unable to parse input (should never happen)
*/
private function tokenize(string $input): array
{
$tokens = [];
$length = \strlen($input);
$cursor = 0;
while ($cursor < $length) {
if (preg_match('/\s+/A', $input, $match, 0, $cursor)) {
} elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, 0, $cursor)) {
$tokens[] = $match[1].$match[2].stripcslashes(str_replace(['"\'', '\'"', '\'\'', '""'], '', substr($match[3], 1, \strlen($match[3]) - 2)));
} elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, 0, $cursor)) {
$tokens[] = stripcslashes(substr($match[0], 1, \strlen($match[0]) - 2));
} elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, 0, $cursor)) {
$tokens[] = stripcslashes($match[1]);
} else {
// should never happen
throw new InvalidArgumentException(sprintf('Unable to parse input near "... %s ...".', substr($input, $cursor, 10)));
}
$cursor += \strlen($match[0]);
}
return $tokens;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Represents a command line option.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputOption
{
public const VALUE_NONE = 1;
public const VALUE_REQUIRED = 2;
public const VALUE_OPTIONAL = 4;
public const VALUE_IS_ARRAY = 8;
private $name;
private $shortcut;
private $mode;
private $default;
private $description;
/**
* @param string $name The option name
* @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the VALUE_* constants
* @param string $description A description text
* @param string|string[]|bool|null $default The default value (must be null for self::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*/
public function __construct(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
{
if (0 === strpos($name, '--')) {
$name = substr($name, 2);
}
if (empty($name)) {
throw new InvalidArgumentException('An option name cannot be empty.');
}
if (empty($shortcut)) {
$shortcut = null;
}
if (null !== $shortcut) {
if (\is_array($shortcut)) {
$shortcut = implode('|', $shortcut);
}
$shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-'));
$shortcuts = array_filter($shortcuts);
$shortcut = implode('|', $shortcuts);
if (empty($shortcut)) {
throw new InvalidArgumentException('An option shortcut cannot be empty.');
}
}
if (null === $mode) {
$mode = self::VALUE_NONE;
} elseif ($mode > 15 || $mode < 1) {
throw new InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode));
}
$this->name = $name;
$this->shortcut = $shortcut;
$this->mode = $mode;
$this->description = $description;
if ($this->isArray() && !$this->acceptValue()) {
throw new InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.');
}
$this->setDefault($default);
}
/**
* Returns the option shortcut.
*
* @return string|null The shortcut
*/
public function getShortcut()
{
return $this->shortcut;
}
/**
* Returns the option name.
*
* @return string The name
*/
public function getName()
{
return $this->name;
}
/**
* Returns true if the option accepts a value.
*
* @return bool true if value mode is not self::VALUE_NONE, false otherwise
*/
public function acceptValue()
{
return $this->isValueRequired() || $this->isValueOptional();
}
/**
* Returns true if the option requires a value.
*
* @return bool true if value mode is self::VALUE_REQUIRED, false otherwise
*/
public function isValueRequired()
{
return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode);
}
/**
* Returns true if the option takes an optional value.
*
* @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise
*/
public function isValueOptional()
{
return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode);
}
/**
* Returns true if the option can take multiple values.
*
* @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise
*/
public function isArray()
{
return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode);
}
/**
* Sets the default value.
*
* @param string|string[]|bool|null $default The default value
*
* @throws LogicException When incorrect default value is given
*/
public function setDefault($default = null)
{
if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) {
throw new LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.');
}
if ($this->isArray()) {
if (null === $default) {
$default = [];
} elseif (!\is_array($default)) {
throw new LogicException('A default value for an array option must be an array.');
}
}
$this->default = $this->acceptValue() ? $default : false;
}
/**
* Returns the default value.
*
* @return string|string[]|bool|null The default value
*/
public function getDefault()
{
return $this->default;
}
/**
* Returns the description text.
*
* @return string The description text
*/
public function getDescription()
{
return $this->description;
}
/**
* Checks whether the given option equals this one.
*
* @return bool
*/
public function equals(self $option)
{
return $option->getName() === $this->getName()
&& $option->getShortcut() === $this->getShortcut()
&& $option->getDefault() === $this->getDefault()
&& $option->isArray() === $this->isArray()
&& $option->isValueRequired() === $this->isValueRequired()
&& $option->isValueOptional() === $this->isValueOptional()
;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
/**
* StreamableInputInterface is the interface implemented by all input classes
* that have an input stream.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
interface StreamableInputInterface extends InputInterface
{
/**
* Sets the input stream to read from when interacting with the user.
*
* This is mainly useful for testing purpose.
*
* @param resource $stream The input stream
*/
public function setStream($stream);
/**
* Returns the input stream.
*
* @return resource|null
*/
public function getStream();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* A InputDefinition represents a set of valid command line arguments and options.
*
* Usage:
*
* $definition = new InputDefinition([
* new InputArgument('name', InputArgument::REQUIRED),
* new InputOption('foo', 'f', InputOption::VALUE_REQUIRED),
* ]);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InputDefinition
{
private $arguments;
private $requiredCount;
private $hasAnArrayArgument = false;
private $hasOptional;
private $options;
private $shortcuts;
/**
* @param array $definition An array of InputArgument and InputOption instance
*/
public function __construct(array $definition = [])
{
$this->setDefinition($definition);
}
/**
* Sets the definition of the input.
*/
public function setDefinition(array $definition)
{
$arguments = [];
$options = [];
foreach ($definition as $item) {
if ($item instanceof InputOption) {
$options[] = $item;
} else {
$arguments[] = $item;
}
}
$this->setArguments($arguments);
$this->setOptions($options);
}
/**
* Sets the InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function setArguments(array $arguments = [])
{
$this->arguments = [];
$this->requiredCount = 0;
$this->hasOptional = false;
$this->hasAnArrayArgument = false;
$this->addArguments($arguments);
}
/**
* Adds an array of InputArgument objects.
*
* @param InputArgument[] $arguments An array of InputArgument objects
*/
public function addArguments(?array $arguments = [])
{
if (null !== $arguments) {
foreach ($arguments as $argument) {
$this->addArgument($argument);
}
}
}
/**
* @throws LogicException When incorrect argument is given
*/
public function addArgument(InputArgument $argument)
{
if (isset($this->arguments[$argument->getName()])) {
throw new LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName()));
}
if ($this->hasAnArrayArgument) {
throw new LogicException('Cannot add an argument after an array argument.');
}
if ($argument->isRequired() && $this->hasOptional) {
throw new LogicException('Cannot add a required argument after an optional one.');
}
if ($argument->isArray()) {
$this->hasAnArrayArgument = true;
}
if ($argument->isRequired()) {
++$this->requiredCount;
} else {
$this->hasOptional = true;
}
$this->arguments[$argument->getName()] = $argument;
}
/**
* Returns an InputArgument by name or by position.
*
* @param string|int $name The InputArgument name or position
*
* @return InputArgument An InputArgument object
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument($name)
{
if (!$this->hasArgument($name)) {
throw new InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name));
}
$arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;
return $arguments[$name];
}
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name)
{
$arguments = \is_int($name) ? array_values($this->arguments) : $this->arguments;
return isset($arguments[$name]);
}
/**
* Gets the array of InputArgument objects.
*
* @return InputArgument[] An array of InputArgument objects
*/
public function getArguments()
{
return $this->arguments;
}
/**
* Returns the number of InputArguments.
*
* @return int The number of InputArguments
*/
public function getArgumentCount()
{
return $this->hasAnArrayArgument ? \PHP_INT_MAX : \count($this->arguments);
}
/**
* Returns the number of required InputArguments.
*
* @return int The number of required InputArguments
*/
public function getArgumentRequiredCount()
{
return $this->requiredCount;
}
/**
* Gets the default values.
*
* @return array An array of default values
*/
public function getArgumentDefaults()
{
$values = [];
foreach ($this->arguments as $argument) {
$values[$argument->getName()] = $argument->getDefault();
}
return $values;
}
/**
* Sets the InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function setOptions(array $options = [])
{
$this->options = [];
$this->shortcuts = [];
$this->addOptions($options);
}
/**
* Adds an array of InputOption objects.
*
* @param InputOption[] $options An array of InputOption objects
*/
public function addOptions(array $options = [])
{
foreach ($options as $option) {
$this->addOption($option);
}
}
/**
* @throws LogicException When option given already exist
*/
public function addOption(InputOption $option)
{
if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) {
throw new LogicException(sprintf('An option named "%s" already exists.', $option->getName()));
}
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) {
throw new LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut));
}
}
}
$this->options[$option->getName()] = $option;
if ($option->getShortcut()) {
foreach (explode('|', $option->getShortcut()) as $shortcut) {
$this->shortcuts[$shortcut] = $option->getName();
}
}
}
/**
* Returns an InputOption by name.
*
* @return InputOption A InputOption object
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption(string $name)
{
if (!$this->hasOption($name)) {
throw new InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name));
}
return $this->options[$name];
}
/**
* Returns true if an InputOption object exists by name.
*
* This method can't be used to check if the user included the option when
* executing the command (use getOption() instead).
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption(string $name)
{
return isset($this->options[$name]);
}
/**
* Gets the array of InputOption objects.
*
* @return InputOption[] An array of InputOption objects
*/
public function getOptions()
{
return $this->options;
}
/**
* Returns true if an InputOption object exists by shortcut.
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasShortcut(string $name)
{
return isset($this->shortcuts[$name]);
}
/**
* Gets an InputOption by shortcut.
*
* @return InputOption An InputOption object
*/
public function getOptionForShortcut(string $shortcut)
{
return $this->getOption($this->shortcutToName($shortcut));
}
/**
* Gets an array of default values.
*
* @return array An array of all default values
*/
public function getOptionDefaults()
{
$values = [];
foreach ($this->options as $option) {
$values[$option->getName()] = $option->getDefault();
}
return $values;
}
/**
* Returns the InputOption name given a shortcut.
*
* @throws InvalidArgumentException When option given does not exist
*
* @internal
*/
public function shortcutToName(string $shortcut): string
{
if (!isset($this->shortcuts[$shortcut])) {
throw new InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut));
}
return $this->shortcuts[$shortcut];
}
/**
* Gets the synopsis.
*
* @return string The synopsis
*/
public function getSynopsis(bool $short = false)
{
$elements = [];
if ($short && $this->getOptions()) {
$elements[] = '[options]';
} elseif (!$short) {
foreach ($this->getOptions() as $option) {
$value = '';
if ($option->acceptValue()) {
$value = sprintf(
' %s%s%s',
$option->isValueOptional() ? '[' : '',
strtoupper($option->getName()),
$option->isValueOptional() ? ']' : ''
);
}
$shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : '';
$elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value);
}
}
if (\count($elements) && $this->getArguments()) {
$elements[] = '[--]';
}
$tail = '';
foreach ($this->getArguments() as $argument) {
$element = '<'.$argument->getName().'>';
if ($argument->isArray()) {
$element .= '...';
}
if (!$argument->isRequired()) {
$element = '['.$element;
$tail .= ']';
}
$elements[] = $element;
}
return implode(' ', $elements).$tail;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Input;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
/**
* InputInterface is the interface implemented by all input classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface InputInterface
{
/**
* Returns the first argument from the raw parameters (not parsed).
*
* @return string|null The value of the first argument or null otherwise
*/
public function getFirstArgument();
/**
* Returns true if the raw parameters (not parsed) contain a value.
*
* This method is to be used to introspect the input parameters
* before they have been validated. It must be used carefully.
* Does not necessarily return the correct result for short options
* when multiple flags are combined in the same option.
*
* @param string|array $values The values to look for in the raw parameters (can be an array)
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return bool true if the value is contained in the raw parameters
*/
public function hasParameterOption($values, bool $onlyParams = false);
/**
* Returns the value of a raw option (not parsed).
*
* This method is to be used to introspect the input parameters
* before they have been validated. It must be used carefully.
* Does not necessarily return the correct result for short options
* when multiple flags are combined in the same option.
*
* @param string|array $values The value(s) to look for in the raw parameters (can be an array)
* @param mixed $default The default value to return if no result is found
* @param bool $onlyParams Only check real parameters, skip those following an end of options (--) signal
*
* @return mixed The option value
*/
public function getParameterOption($values, $default = false, bool $onlyParams = false);
/**
* Binds the current Input instance with the given arguments and options.
*
* @throws RuntimeException
*/
public function bind(InputDefinition $definition);
/**
* Validates the input.
*
* @throws RuntimeException When not enough arguments are given
*/
public function validate();
/**
* Returns all the given arguments merged with the default values.
*
* @return array
*/
public function getArguments();
/**
* Returns the argument value for a given argument name.
*
* @return string|string[]|null The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function getArgument(string $name);
/**
* Sets an argument value by name.
*
* @param string|string[]|null $value The argument value
*
* @throws InvalidArgumentException When argument given doesn't exist
*/
public function setArgument(string $name, $value);
/**
* Returns true if an InputArgument object exists by name or position.
*
* @param string|int $name The InputArgument name or position
*
* @return bool true if the InputArgument object exists, false otherwise
*/
public function hasArgument($name);
/**
* Returns all the given options merged with the default values.
*
* @return array
*/
public function getOptions();
/**
* Returns the option value for a given option name.
*
* @return string|string[]|bool|null The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function getOption(string $name);
/**
* Sets an option value by name.
*
* @param string|string[]|bool|null $value The option value
*
* @throws InvalidArgumentException When option given doesn't exist
*/
public function setOption(string $name, $value);
/**
* Returns true if an InputOption object exists by name.
*
* @return bool true if the InputOption object exists, false otherwise
*/
public function hasOption(string $name);
/**
* Is this input means interactive?
*
* @return bool
*/
public function isInteractive();
/**
* Sets the input interactivity.
*/
public function setInteractive(bool $interactive);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
*/
final class NullOutputFormatterStyle implements OutputFormatterStyleInterface
{
/**
* {@inheritdoc}
*/
public function apply(string $text): string
{
return $text;
}
/**
* {@inheritdoc}
*/
public function setBackground(string $color = null): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setForeground(string $color = null): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setOption(string $option): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function unsetOption(string $option): void
{
// do nothing
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* @author Tien Xuan Vo <tien.xuan.vo@gmail.com>
*/
final class NullOutputFormatter implements OutputFormatterInterface
{
private $style;
/**
* {@inheritdoc}
*/
public function format(?string $message): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getStyle(string $name): OutputFormatterStyleInterface
{
if ($this->style) {
return $this->style;
}
// to comply with the interface we must return a OutputFormatterStyleInterface
return $this->style = new NullOutputFormatterStyle();
}
/**
* {@inheritdoc}
*/
public function hasStyle(string $name): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDecorated(): bool
{
return false;
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated): void
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style): void
{
// do nothing
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter interface for console output that supports word wrapping.
*
* @author Roland Franssen <franssen.roland@gmail.com>
*/
interface WrappableOutputFormatterInterface extends OutputFormatterInterface
{
/**
* Formats a message according to the given styles, wrapping at `$width` (0 means no wrapping).
*/
public function formatAndWrap(?string $message, int $width);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter interface for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OutputFormatterInterface
{
/**
* Sets the decorated flag.
*/
public function setDecorated(bool $decorated);
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated();
/**
* Sets a new style.
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style);
/**
* Checks if output formatter has style with specified name.
*
* @return bool
*/
public function hasStyle(string $name);
/**
* Gets style options from style with specified name.
*
* @return OutputFormatterStyleInterface
*
* @throws \InvalidArgumentException When style isn't defined
*/
public function getStyle(string $name);
/**
* Formats a message according to the given styles.
*/
public function format(?string $message);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* Formatter class for console output.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
* @author Roland Franssen <franssen.roland@gmail.com>
*/
class OutputFormatter implements WrappableOutputFormatterInterface
{
private $decorated;
private $styles = [];
private $styleStack;
public function __clone()
{
$this->styleStack = clone $this->styleStack;
foreach ($this->styles as $key => $value) {
$this->styles[$key] = clone $value;
}
}
/**
* Escapes "<" special char in given text.
*
* @return string Escaped text
*/
public static function escape(string $text)
{
$text = preg_replace('/([^\\\\]?)</', '$1\\<', $text);
return self::escapeTrailingBackslash($text);
}
/**
* Escapes trailing "\" in given text.
*
* @internal
*/
public static function escapeTrailingBackslash(string $text): string
{
if ('\\' === substr($text, -1)) {
$len = \strlen($text);
$text = rtrim($text, '\\');
$text = str_replace("\0", '', $text);
$text .= str_repeat("\0", $len - \strlen($text));
}
return $text;
}
/**
* Initializes console output formatter.
*
* @param OutputFormatterStyleInterface[] $styles Array of "name => FormatterStyle" instances
*/
public function __construct(bool $decorated = false, array $styles = [])
{
$this->decorated = $decorated;
$this->setStyle('error', new OutputFormatterStyle('white', 'red'));
$this->setStyle('info', new OutputFormatterStyle('green'));
$this->setStyle('comment', new OutputFormatterStyle('yellow'));
$this->setStyle('question', new OutputFormatterStyle('black', 'cyan'));
foreach ($styles as $name => $style) {
$this->setStyle($name, $style);
}
$this->styleStack = new OutputFormatterStyleStack();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->decorated = $decorated;
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->decorated;
}
/**
* {@inheritdoc}
*/
public function setStyle(string $name, OutputFormatterStyleInterface $style)
{
$this->styles[strtolower($name)] = $style;
}
/**
* {@inheritdoc}
*/
public function hasStyle(string $name)
{
return isset($this->styles[strtolower($name)]);
}
/**
* {@inheritdoc}
*/
public function getStyle(string $name)
{
if (!$this->hasStyle($name)) {
throw new InvalidArgumentException(sprintf('Undefined style: "%s".', $name));
}
return $this->styles[strtolower($name)];
}
/**
* {@inheritdoc}
*/
public function format(?string $message)
{
return $this->formatAndWrap($message, 0);
}
/**
* {@inheritdoc}
*/
public function formatAndWrap(?string $message, int $width)
{
$offset = 0;
$output = '';
$tagRegex = '[a-z][^<>]*+';
$currentLineLength = 0;
preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#ix", $message, $matches, \PREG_OFFSET_CAPTURE);
foreach ($matches[0] as $i => $match) {
$pos = $match[1];
$text = $match[0];
if (0 != $pos && '\\' == $message[$pos - 1]) {
continue;
}
// add the text up to the next tag
$output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset), $output, $width, $currentLineLength);
$offset = $pos + \strlen($text);
// opening tag?
if ($open = '/' != $text[1]) {
$tag = $matches[1][$i][0];
} else {
$tag = $matches[3][$i][0] ?? '';
}
if (!$open && !$tag) {
// </>
$this->styleStack->pop();
} elseif (null === $style = $this->createStyleFromString($tag)) {
$output .= $this->applyCurrentStyle($text, $output, $width, $currentLineLength);
} elseif ($open) {
$this->styleStack->push($style);
} else {
$this->styleStack->pop($style);
}
}
$output .= $this->applyCurrentStyle(substr($message, $offset), $output, $width, $currentLineLength);
if (false !== strpos($output, "\0")) {
return strtr($output, ["\0" => '\\', '\\<' => '<']);
}
return str_replace('\\<', '<', $output);
}
/**
* @return OutputFormatterStyleStack
*/
public function getStyleStack()
{
return $this->styleStack;
}
/**
* Tries to create new style instance from string.
*/
private function createStyleFromString(string $string): ?OutputFormatterStyleInterface
{
if (isset($this->styles[$string])) {
return $this->styles[$string];
}
if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', $string, $matches, \PREG_SET_ORDER)) {
return null;
}
$style = new OutputFormatterStyle();
foreach ($matches as $match) {
array_shift($match);
$match[0] = strtolower($match[0]);
if ('fg' == $match[0]) {
$style->setForeground(strtolower($match[1]));
} elseif ('bg' == $match[0]) {
$style->setBackground(strtolower($match[1]));
} elseif ('href' === $match[0]) {
$style->setHref($match[1]);
} elseif ('options' === $match[0]) {
preg_match_all('([^,;]+)', strtolower($match[1]), $options);
$options = array_shift($options);
foreach ($options as $option) {
$style->setOption($option);
}
} else {
return null;
}
}
return $style;
}
/**
* Applies current style from stack to text, if must be applied.
*/
private function applyCurrentStyle(string $text, string $current, int $width, int &$currentLineLength): string
{
if ('' === $text) {
return '';
}
if (!$width) {
return $this->isDecorated() ? $this->styleStack->getCurrent()->apply($text) : $text;
}
if (!$currentLineLength && '' !== $current) {
$text = ltrim($text);
}
if ($currentLineLength) {
$prefix = substr($text, 0, $i = $width - $currentLineLength)."\n";
$text = substr($text, $i);
} else {
$prefix = '';
}
preg_match('~(\\n)$~', $text, $matches);
$text = $prefix.preg_replace('~([^\\n]{'.$width.'})\\ *~', "\$1\n", $text);
$text = rtrim($text, "\n").($matches[1] ?? '');
if (!$currentLineLength && '' !== $current && "\n" !== substr($current, -1)) {
$text = "\n".$text;
}
$lines = explode("\n", $text);
foreach ($lines as $line) {
$currentLineLength += \strlen($line);
if ($width <= $currentLineLength) {
$currentLineLength = 0;
}
}
if ($this->isDecorated()) {
foreach ($lines as $i => $line) {
$lines[$i] = $this->styleStack->getCurrent()->apply($line);
}
}
return implode("\n", $lines);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Color;
/**
* Formatter style class for defining styles.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
class OutputFormatterStyle implements OutputFormatterStyleInterface
{
private $color;
private $foreground;
private $background;
private $options;
private $href;
private $handlesHrefGracefully;
/**
* Initializes output formatter style.
*
* @param string|null $foreground The style foreground color name
* @param string|null $background The style background color name
*/
public function __construct(string $foreground = null, string $background = null, array $options = [])
{
$this->color = new Color($this->foreground = $foreground ?: '', $this->background = $background ?: '', $this->options = $options);
}
/**
* {@inheritdoc}
*/
public function setForeground(string $color = null)
{
$this->color = new Color($this->foreground = $color ?: '', $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function setBackground(string $color = null)
{
$this->color = new Color($this->foreground, $this->background = $color ?: '', $this->options);
}
public function setHref(string $url): void
{
$this->href = $url;
}
/**
* {@inheritdoc}
*/
public function setOption(string $option)
{
$this->options[] = $option;
$this->color = new Color($this->foreground, $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function unsetOption(string $option)
{
$pos = array_search($option, $this->options);
if (false !== $pos) {
unset($this->options[$pos]);
}
$this->color = new Color($this->foreground, $this->background, $this->options);
}
/**
* {@inheritdoc}
*/
public function setOptions(array $options)
{
$this->color = new Color($this->foreground, $this->background, $this->options = $options);
}
/**
* {@inheritdoc}
*/
public function apply(string $text)
{
if (null === $this->handlesHrefGracefully) {
$this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR')
&& (!getenv('KONSOLE_VERSION') || (int) getenv('KONSOLE_VERSION') > 201100);
}
if (null !== $this->href && $this->handlesHrefGracefully) {
$text = "\033]8;;$this->href\033\\$text\033]8;;\033\\";
}
return $this->color->apply($text);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
/**
* Formatter style interface for defining styles.
*
* @author Konstantin Kudryashov <ever.zet@gmail.com>
*/
interface OutputFormatterStyleInterface
{
/**
* Sets style foreground color.
*/
public function setForeground(string $color = null);
/**
* Sets style background color.
*/
public function setBackground(string $color = null);
/**
* Sets some specific style option.
*/
public function setOption(string $option);
/**
* Unsets some specific style option.
*/
public function unsetOption(string $option);
/**
* Sets multiple style options at once.
*/
public function setOptions(array $options);
/**
* Applies the style to a given text.
*
* @return string
*/
public function apply(string $text);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Formatter;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Contracts\Service\ResetInterface;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class OutputFormatterStyleStack implements ResetInterface
{
/**
* @var OutputFormatterStyleInterface[]
*/
private $styles;
private $emptyStyle;
public function __construct(OutputFormatterStyleInterface $emptyStyle = null)
{
$this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle();
$this->reset();
}
/**
* Resets stack (ie. empty internal arrays).
*/
public function reset()
{
$this->styles = [];
}
/**
* Pushes a style in the stack.
*/
public function push(OutputFormatterStyleInterface $style)
{
$this->styles[] = $style;
}
/**
* Pops a style from the stack.
*
* @return OutputFormatterStyleInterface
*
* @throws InvalidArgumentException When style tags incorrectly nested
*/
public function pop(OutputFormatterStyleInterface $style = null)
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
if (null === $style) {
return array_pop($this->styles);
}
foreach (array_reverse($this->styles, true) as $index => $stackedStyle) {
if ($style->apply('') === $stackedStyle->apply('')) {
$this->styles = \array_slice($this->styles, 0, $index);
return $stackedStyle;
}
}
throw new InvalidArgumentException('Incorrectly nested style tag found.');
}
/**
* Computes current style with stacks top codes.
*
* @return OutputFormatterStyle
*/
public function getCurrent()
{
if (empty($this->styles)) {
return $this->emptyStyle;
}
return $this->styles[\count($this->styles) - 1];
}
/**
* @return $this
*/
public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle)
{
$this->emptyStyle = $emptyStyle;
return $this;
}
/**
* @return OutputFormatterStyleInterface
*/
public function getEmptyStyle()
{
return $this->emptyStyle;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* A simple command loader using factories to instantiate commands lazily.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class FactoryCommandLoader implements CommandLoaderInterface
{
private $factories;
/**
* @param callable[] $factories Indexed by command names
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*/
public function has(string $name)
{
return isset($this->factories[$name]);
}
/**
* {@inheritdoc}
*/
public function get(string $name)
{
if (!isset($this->factories[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
$factory = $this->factories[$name];
return $factory();
}
/**
* {@inheritdoc}
*/
public function getNames()
{
return array_keys($this->factories);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Robin Chalas <robin.chalas@gmail.com>
*/
interface CommandLoaderInterface
{
/**
* Loads a command.
*
* @return Command
*
* @throws CommandNotFoundException
*/
public function get(string $name);
/**
* Checks if a command exists.
*
* @return bool
*/
public function has(string $name);
/**
* @return string[] All registered command names
*/
public function getNames();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\CommandLoader;
use Psr\Container\ContainerInterface;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* Loads commands from a PSR-11 container.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ContainerCommandLoader implements CommandLoaderInterface
{
private $container;
private $commandMap;
/**
* @param array $commandMap An array with command names as keys and service ids as values
*/
public function __construct(ContainerInterface $container, array $commandMap)
{
$this->container = $container;
$this->commandMap = $commandMap;
}
/**
* {@inheritdoc}
*/
public function get(string $name)
{
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
return $this->container->get($this->commandMap[$name]);
}
/**
* {@inheritdoc}
*/
public function has(string $name)
{
return isset($this->commandMap[$name]) && $this->container->has($this->commandMap[$name]);
}
/**
* {@inheritdoc}
*/
public function getNames()
{
return array_keys($this->commandMap);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArrayInput;
/**
* Eases the testing of console applications.
*
* When testing an application, don't forget to disable the auto exit flag:
*
* $application = new Application();
* $application->setAutoExit(false);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ApplicationTester
{
use TesterTrait;
private $application;
private $input;
private $statusCode;
public function __construct(Application $application)
{
$this->application = $application;
}
/**
* Executes the application.
*
* Available options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
*
* @return int The command exit code
*/
public function run(array $input, array $options = [])
{
$this->input = new ArrayInput($input);
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
if ($this->inputs) {
$this->input->setStream(self::createStream($this->inputs));
}
$this->initOutput($options);
return $this->statusCode = $this->application->run($this->input, $this->output);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
/**
* @author Amrouche Hamza <hamza.simperfit@gmail.com>
*/
trait TesterTrait
{
/** @var StreamOutput */
private $output;
private $inputs = [];
private $captureStreamsIndependently = false;
/**
* Gets the display returned by the last execution of the command or application.
*
* @throws \RuntimeException If it's called before the execute method
*
* @return string The display
*/
public function getDisplay(bool $normalize = false)
{
if (null === $this->output) {
throw new \RuntimeException('Output not initialized, did you execute the command before requesting the display?');
}
rewind($this->output->getStream());
$display = stream_get_contents($this->output->getStream());
if ($normalize) {
$display = str_replace(\PHP_EOL, "\n", $display);
}
return $display;
}
/**
* Gets the output written to STDERR by the application.
*
* @param bool $normalize Whether to normalize end of lines to \n or not
*
* @return string
*/
public function getErrorOutput(bool $normalize = false)
{
if (!$this->captureStreamsIndependently) {
throw new \LogicException('The error output is not available when the tester is run without "capture_stderr_separately" option set.');
}
rewind($this->output->getErrorOutput()->getStream());
$display = stream_get_contents($this->output->getErrorOutput()->getStream());
if ($normalize) {
$display = str_replace(\PHP_EOL, "\n", $display);
}
return $display;
}
/**
* Gets the input instance used by the last execution of the command or application.
*
* @return InputInterface The current input instance
*/
public function getInput()
{
return $this->input;
}
/**
* Gets the output instance used by the last execution of the command or application.
*
* @return OutputInterface The current output instance
*/
public function getOutput()
{
return $this->output;
}
/**
* Gets the status code returned by the last execution of the command or application.
*
* @throws \RuntimeException If it's called before the execute method
*
* @return int The status code
*/
public function getStatusCode()
{
if (null === $this->statusCode) {
throw new \RuntimeException('Status code not initialized, did you execute the command before requesting the status code?');
}
return $this->statusCode;
}
/**
* Sets the user inputs.
*
* @param array $inputs An array of strings representing each input
* passed to the command input stream
*
* @return $this
*/
public function setInputs(array $inputs)
{
$this->inputs = $inputs;
return $this;
}
/**
* Initializes the output property.
*
* Available options:
*
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
*/
private function initOutput(array $options)
{
$this->captureStreamsIndependently = \array_key_exists('capture_stderr_separately', $options) && $options['capture_stderr_separately'];
if (!$this->captureStreamsIndependently) {
$this->output = new StreamOutput(fopen('php://memory', 'w', false));
if (isset($options['decorated'])) {
$this->output->setDecorated($options['decorated']);
}
if (isset($options['verbosity'])) {
$this->output->setVerbosity($options['verbosity']);
}
} else {
$this->output = new ConsoleOutput(
$options['verbosity'] ?? ConsoleOutput::VERBOSITY_NORMAL,
$options['decorated'] ?? null
);
$errorOutput = new StreamOutput(fopen('php://memory', 'w', false));
$errorOutput->setFormatter($this->output->getFormatter());
$errorOutput->setVerbosity($this->output->getVerbosity());
$errorOutput->setDecorated($this->output->isDecorated());
$reflectedOutput = new \ReflectionObject($this->output);
$strErrProperty = $reflectedOutput->getProperty('stderr');
$strErrProperty->setAccessible(true);
$strErrProperty->setValue($this->output, $errorOutput);
$reflectedParent = $reflectedOutput->getParentClass();
$streamProperty = $reflectedParent->getProperty('stream');
$streamProperty->setAccessible(true);
$streamProperty->setValue($this->output, fopen('php://memory', 'w', false));
}
}
/**
* @return resource
*/
private static function createStream(array $inputs)
{
$stream = fopen('php://memory', 'r+', false);
foreach ($inputs as $input) {
fwrite($stream, $input.\PHP_EOL);
}
rewind($stream);
return $stream;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Tester;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\ArrayInput;
/**
* Eases the testing of console commands.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class CommandTester
{
use TesterTrait;
private $command;
private $input;
private $statusCode;
public function __construct(Command $command)
{
$this->command = $command;
}
/**
* Executes the command.
*
* Available execution options:
*
* * interactive: Sets the input interactive flag
* * decorated: Sets the output decorated flag
* * verbosity: Sets the output verbosity flag
* * capture_stderr_separately: Make output of stdOut and stdErr separately available
*
* @param array $input An array of command arguments and options
* @param array $options An array of execution options
*
* @return int The command exit code
*/
public function execute(array $input, array $options = [])
{
// set the command name automatically if the application requires
// this argument and no command name was passed
if (!isset($input['command'])
&& (null !== $application = $this->command->getApplication())
&& $application->getDefinition()->hasArgument('command')
) {
$input = array_merge(['command' => $this->command->getName()], $input);
}
$this->input = new ArrayInput($input);
// Use an in-memory input stream even if no inputs are set so that QuestionHelper::ask() does not rely on the blocking STDIN.
$this->input->setStream(self::createStream($this->inputs));
if (isset($options['interactive'])) {
$this->input->setInteractive($options['interactive']);
}
if (!isset($options['decorated'])) {
$options['decorated'] = false;
}
$this->initOutput($options);
return $this->statusCode = $this->command->run($this->input, $this->output);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\DependencyInjection;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\CommandLoader\ContainerCommandLoader;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\DependencyInjection\TypedReference;
/**
* Registers console commands.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class AddConsoleCommandPass implements CompilerPassInterface
{
private $commandLoaderServiceId;
private $commandTag;
private $noPreloadTag;
private $privateTagName;
public function __construct(string $commandLoaderServiceId = 'console.command_loader', string $commandTag = 'console.command', string $noPreloadTag = 'container.no_preload', string $privateTagName = 'container.private')
{
$this->commandLoaderServiceId = $commandLoaderServiceId;
$this->commandTag = $commandTag;
$this->noPreloadTag = $noPreloadTag;
$this->privateTagName = $privateTagName;
}
public function process(ContainerBuilder $container)
{
$commandServices = $container->findTaggedServiceIds($this->commandTag, true);
$lazyCommandMap = [];
$lazyCommandRefs = [];
$serviceIds = [];
foreach ($commandServices as $id => $tags) {
$definition = $container->getDefinition($id);
$definition->addTag($this->noPreloadTag);
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (isset($tags[0]['command'])) {
$commandName = $tags[0]['command'];
} else {
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
}
if (!$r->isSubclassOf(Command::class)) {
throw new InvalidArgumentException(sprintf('The service "%s" tagged "%s" must be a subclass of "%s".', $id, $this->commandTag, Command::class));
}
$commandName = $class::getDefaultName();
}
if (null === $commandName) {
if (!$definition->isPublic() || $definition->isPrivate() || $definition->hasTag($this->privateTagName)) {
$commandId = 'console.command.public_alias.'.$id;
$container->setAlias($commandId, $id)->setPublic(true);
$id = $commandId;
}
$serviceIds[] = $id;
continue;
}
unset($tags[0]);
$lazyCommandMap[$commandName] = $id;
$lazyCommandRefs[$id] = new TypedReference($id, $class);
$aliases = [];
foreach ($tags as $tag) {
if (isset($tag['command'])) {
$aliases[] = $tag['command'];
$lazyCommandMap[$tag['command']] = $id;
}
}
$definition->addMethodCall('setName', [$commandName]);
if ($aliases) {
$definition->addMethodCall('setAliases', [$aliases]);
}
}
$container
->register($this->commandLoaderServiceId, ContainerCommandLoader::class)
->setPublic(true)
->addTag($this->noPreloadTag)
->setArguments([ServiceLocatorTagPass::register($container, $lazyCommandRefs), $lazyCommandMap]);
$container->setParameter('console.command.ids', $serviceIds);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\Store\FlockStore;
use Symfony\Component\Lock\Store\SemaphoreStore;
/**
* Basic lock feature for commands.
*
* @author Geoffrey Brier <geoffrey.brier@gmail.com>
*/
trait LockableTrait
{
/** @var Lock */
private $lock;
/**
* Locks a command.
*/
private function lock(string $name = null, bool $blocking = false): bool
{
if (!class_exists(SemaphoreStore::class)) {
throw new LogicException('To enable the locking feature you must install the symfony/lock component.');
}
if (null !== $this->lock) {
throw new LogicException('A lock is already in place.');
}
if (SemaphoreStore::isSupported()) {
$store = new SemaphoreStore();
} else {
$store = new FlockStore();
}
$this->lock = (new LockFactory($store))->createLock($name ?: $this->getName());
if (!$this->lock->acquire($blocking)) {
$this->lock = null;
return false;
}
return true;
}
/**
* Releases the command lock if there is one.
*/
private function release()
{
if ($this->lock) {
$this->lock->release();
$this->lock = null;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Base class for all commands.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Command
{
// see https://tldp.org/LDP/abs/html/exitcodes.html
public const SUCCESS = 0;
public const FAILURE = 1;
/**
* @var string|null The default command name
*/
protected static $defaultName;
private $application;
private $name;
private $processTitle;
private $aliases = [];
private $definition;
private $hidden = false;
private $help = '';
private $description = '';
private $fullDefinition;
private $ignoreValidationErrors = false;
private $code;
private $synopsis = [];
private $usages = [];
private $helperSet;
/**
* @return string|null The default command name or null when no default name is set
*/
public static function getDefaultName()
{
$class = static::class;
$r = new \ReflectionProperty($class, 'defaultName');
return $class === $r->class ? static::$defaultName : null;
}
/**
* @param string|null $name The name of the command; passing null means it must be set in configure()
*
* @throws LogicException When the command name is empty
*/
public function __construct(string $name = null)
{
$this->definition = new InputDefinition();
if (null !== $name || null !== $name = static::getDefaultName()) {
$this->setName($name);
}
$this->configure();
}
/**
* Ignores validation errors.
*
* This is mainly useful for the help command.
*/
public function ignoreValidationErrors()
{
$this->ignoreValidationErrors = true;
}
public function setApplication(Application $application = null)
{
$this->application = $application;
if ($application) {
$this->setHelperSet($application->getHelperSet());
} else {
$this->helperSet = null;
}
$this->fullDefinition = null;
}
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Gets the helper set.
*
* @return HelperSet|null A HelperSet instance
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Gets the application instance for this command.
*
* @return Application|null An Application instance
*/
public function getApplication()
{
return $this->application;
}
/**
* Checks whether the command is enabled or not in the current environment.
*
* Override this to check for x or y and return false if the command can not
* run properly under the current conditions.
*
* @return bool
*/
public function isEnabled()
{
return true;
}
/**
* Configures the current command.
*/
protected function configure()
{
}
/**
* Executes the current command.
*
* This method is not abstract because you can use this class
* as a concrete class. In this case, instead of defining the
* execute() method, you set the code to execute by passing
* a Closure to the setCode() method.
*
* @return int 0 if everything went fine, or an exit code
*
* @throws LogicException When this abstract method is not implemented
*
* @see setCode()
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
throw new LogicException('You must override the execute() method in the concrete command class.');
}
/**
* Interacts with the user.
*
* This method is executed before the InputDefinition is validated.
* This means that this is the only place where the command can
* interactively ask for values of missing required arguments.
*/
protected function interact(InputInterface $input, OutputInterface $output)
{
}
/**
* Initializes the command after the input has been bound and before the input
* is validated.
*
* This is mainly useful when a lot of commands extends one main command
* where some things need to be initialized based on the input arguments and options.
*
* @see InputInterface::bind()
* @see InputInterface::validate()
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
}
/**
* Runs the command.
*
* The code to execute is either defined directly with the
* setCode() method or by overriding the execute() method
* in a sub-class.
*
* @return int The command exit code
*
* @throws \Exception When binding input fails. Bypass this by calling {@link ignoreValidationErrors()}.
*
* @see setCode()
* @see execute()
*/
public function run(InputInterface $input, OutputInterface $output)
{
// add the application arguments and options
$this->mergeApplicationDefinition();
// bind the input against the command specific arguments/options
try {
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
if (!$this->ignoreValidationErrors) {
throw $e;
}
}
$this->initialize($input, $output);
if (null !== $this->processTitle) {
if (\function_exists('cli_set_process_title')) {
if (!@cli_set_process_title($this->processTitle)) {
if ('Darwin' === \PHP_OS) {
$output->writeln('<comment>Running "cli_set_process_title" as an unprivileged user is not supported on MacOS.</comment>', OutputInterface::VERBOSITY_VERY_VERBOSE);
} else {
cli_set_process_title($this->processTitle);
}
}
} elseif (\function_exists('setproctitle')) {
setproctitle($this->processTitle);
} elseif (OutputInterface::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) {
$output->writeln('<comment>Install the proctitle PECL to be able to change the process title.</comment>');
}
}
if ($input->isInteractive()) {
$this->interact($input, $output);
}
// The command name argument is often omitted when a command is executed directly with its run() method.
// It would fail the validation if we didn't make sure the command argument is present,
// since it's required by the application.
if ($input->hasArgument('command') && null === $input->getArgument('command')) {
$input->setArgument('command', $this->getName());
}
$input->validate();
if ($this->code) {
$statusCode = ($this->code)($input, $output);
} else {
$statusCode = $this->execute($input, $output);
if (!\is_int($statusCode)) {
throw new \TypeError(sprintf('Return value of "%s::execute()" must be of the type int, "%s" returned.', static::class, get_debug_type($statusCode)));
}
}
return is_numeric($statusCode) ? (int) $statusCode : 0;
}
/**
* Sets the code to execute when running this command.
*
* If this method is used, it overrides the code defined
* in the execute() method.
*
* @param callable $code A callable(InputInterface $input, OutputInterface $output)
*
* @return $this
*
* @throws InvalidArgumentException
*
* @see execute()
*/
public function setCode(callable $code)
{
if ($code instanceof \Closure) {
$r = new \ReflectionFunction($code);
if (null === $r->getClosureThis()) {
set_error_handler(static function () {});
try {
if ($c = \Closure::bind($code, $this)) {
$code = $c;
}
} finally {
restore_error_handler();
}
}
}
$this->code = $code;
return $this;
}
/**
* Merges the application definition with the command definition.
*
* This method is not part of public API and should not be used directly.
*
* @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments
*/
public function mergeApplicationDefinition(bool $mergeArgs = true)
{
if (null === $this->application) {
return;
}
$this->fullDefinition = new InputDefinition();
$this->fullDefinition->setOptions($this->definition->getOptions());
$this->fullDefinition->addOptions($this->application->getDefinition()->getOptions());
if ($mergeArgs) {
$this->fullDefinition->setArguments($this->application->getDefinition()->getArguments());
$this->fullDefinition->addArguments($this->definition->getArguments());
} else {
$this->fullDefinition->setArguments($this->definition->getArguments());
}
}
/**
* Sets an array of argument and option instances.
*
* @param array|InputDefinition $definition An array of argument and option instances or a definition instance
*
* @return $this
*/
public function setDefinition($definition)
{
if ($definition instanceof InputDefinition) {
$this->definition = $definition;
} else {
$this->definition->setDefinition($definition);
}
$this->fullDefinition = null;
return $this;
}
/**
* Gets the InputDefinition attached to this Command.
*
* @return InputDefinition An InputDefinition instance
*/
public function getDefinition()
{
return $this->fullDefinition ?? $this->getNativeDefinition();
}
/**
* Gets the InputDefinition to be used to create representations of this Command.
*
* Can be overridden to provide the original command representation when it would otherwise
* be changed by merging with the application InputDefinition.
*
* This method is not part of public API and should not be used directly.
*
* @return InputDefinition An InputDefinition instance
*/
public function getNativeDefinition()
{
if (null === $this->definition) {
throw new LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', static::class));
}
return $this->definition;
}
/**
* Adds an argument.
*
* @param int|null $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL
* @param string|string[]|null $default The default value (for InputArgument::OPTIONAL mode only)
*
* @throws InvalidArgumentException When argument mode is not valid
*
* @return $this
*/
public function addArgument(string $name, int $mode = null, string $description = '', $default = null)
{
$this->definition->addArgument(new InputArgument($name, $mode, $description, $default));
if (null !== $this->fullDefinition) {
$this->fullDefinition->addArgument(new InputArgument($name, $mode, $description, $default));
}
return $this;
}
/**
* Adds an option.
*
* @param string|array|null $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts
* @param int|null $mode The option mode: One of the InputOption::VALUE_* constants
* @param string|string[]|bool|null $default The default value (must be null for InputOption::VALUE_NONE)
*
* @throws InvalidArgumentException If option mode is invalid or incompatible
*
* @return $this
*/
public function addOption(string $name, $shortcut = null, int $mode = null, string $description = '', $default = null)
{
$this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
if (null !== $this->fullDefinition) {
$this->fullDefinition->addOption(new InputOption($name, $shortcut, $mode, $description, $default));
}
return $this;
}
/**
* Sets the name of the command.
*
* This method can set both the namespace and the name if
* you separate them by a colon (:)
*
* $command->setName('foo:bar');
*
* @return $this
*
* @throws InvalidArgumentException When the name is invalid
*/
public function setName(string $name)
{
$this->validateName($name);
$this->name = $name;
return $this;
}
/**
* Sets the process title of the command.
*
* This feature should be used only when creating a long process command,
* like a daemon.
*
* @return $this
*/
public function setProcessTitle(string $title)
{
$this->processTitle = $title;
return $this;
}
/**
* Returns the command name.
*
* @return string|null
*/
public function getName()
{
return $this->name;
}
/**
* @param bool $hidden Whether or not the command should be hidden from the list of commands
* The default value will be true in Symfony 6.0
*
* @return Command The current instance
*
* @final since Symfony 5.1
*/
public function setHidden(bool $hidden /*= true*/)
{
$this->hidden = $hidden;
return $this;
}
/**
* @return bool whether the command should be publicly shown or not
*/
public function isHidden()
{
return $this->hidden;
}
/**
* Sets the description for the command.
*
* @return $this
*/
public function setDescription(string $description)
{
$this->description = $description;
return $this;
}
/**
* Returns the description for the command.
*
* @return string The description for the command
*/
public function getDescription()
{
return $this->description;
}
/**
* Sets the help for the command.
*
* @return $this
*/
public function setHelp(string $help)
{
$this->help = $help;
return $this;
}
/**
* Returns the help for the command.
*
* @return string The help for the command
*/
public function getHelp()
{
return $this->help;
}
/**
* Returns the processed help for the command replacing the %command.name% and
* %command.full_name% patterns with the real values dynamically.
*
* @return string The processed help for the command
*/
public function getProcessedHelp()
{
$name = $this->name;
$isSingleCommand = $this->application && $this->application->isSingleCommand();
$placeholders = [
'%command.name%',
'%command.full_name%',
];
$replacements = [
$name,
$isSingleCommand ? $_SERVER['PHP_SELF'] : $_SERVER['PHP_SELF'].' '.$name,
];
return str_replace($placeholders, $replacements, $this->getHelp() ?: $this->getDescription());
}
/**
* Sets the aliases for the command.
*
* @param string[] $aliases An array of aliases for the command
*
* @return $this
*
* @throws InvalidArgumentException When an alias is invalid
*/
public function setAliases(iterable $aliases)
{
foreach ($aliases as $alias) {
$this->validateName($alias);
}
$this->aliases = $aliases;
return $this;
}
/**
* Returns the aliases for the command.
*
* @return array An array of aliases for the command
*/
public function getAliases()
{
return $this->aliases;
}
/**
* Returns the synopsis for the command.
*
* @param bool $short Whether to show the short version of the synopsis (with options folded) or not
*
* @return string The synopsis
*/
public function getSynopsis(bool $short = false)
{
$key = $short ? 'short' : 'long';
if (!isset($this->synopsis[$key])) {
$this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short)));
}
return $this->synopsis[$key];
}
/**
* Add a command usage example, it'll be prefixed with the command name.
*
* @return $this
*/
public function addUsage(string $usage)
{
if (0 !== strpos($usage, $this->name)) {
$usage = sprintf('%s %s', $this->name, $usage);
}
$this->usages[] = $usage;
return $this;
}
/**
* Returns alternative usages of the command.
*
* @return array
*/
public function getUsages()
{
return $this->usages;
}
/**
* Gets a helper instance by name.
*
* @return mixed The helper value
*
* @throws LogicException if no HelperSet is defined
* @throws InvalidArgumentException if the helper is not defined
*/
public function getHelper(string $name)
{
if (null === $this->helperSet) {
throw new LogicException(sprintf('Cannot retrieve helper "%s" because there is no HelperSet defined. Did you forget to add your command to the application or to set the application on the command using the setApplication() method? You can also set the HelperSet directly using the setHelperSet() method.', $name));
}
return $this->helperSet->get($name);
}
/**
* Validates a command name.
*
* It must be non-empty and parts can optionally be separated by ":".
*
* @throws InvalidArgumentException When the name is invalid
*/
private function validateName(string $name)
{
if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) {
throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
/**
* Interface for command reacting to signal.
*
* @author Grégoire Pineau <lyrixx@lyrix.info>
*/
interface SignalableCommandInterface
{
/**
* Returns the list of signals to subscribe.
*/
public function getSubscribedSignals(): array;
/**
* The method will be called when the application is signaled.
*/
public function handleSignal(int $signal): void;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* ListCommand displays the list of all available commands for the application.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ListCommand extends Command
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setName('list')
->setDefinition([
new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
])
->setDescription('List commands')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command lists all commands:
<info>%command.full_name%</info>
You can also display the commands for a specific namespace:
<info>%command.full_name% test</info>
You can also output the information in other formats by using the <comment>--format</comment> option:
<info>%command.full_name% --format=xml</info>
It's also possible to get raw list of commands (useful for embedding command runner):
<info>%command.full_name% --raw</info>
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$helper = new DescriptorHelper();
$helper->describe($output, $this->getApplication(), [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
'namespace' => $input->getArgument('namespace'),
]);
return 0;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Command;
use Symfony\Component\Console\Helper\DescriptorHelper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* HelpCommand displays the help for a given command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelpCommand extends Command
{
private $command;
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->ignoreValidationErrors();
$this
->setName('help')
->setDefinition([
new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'),
new InputOption('format', null, InputOption::VALUE_REQUIRED, 'The output format (txt, xml, json, or md)', 'txt'),
new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'),
])
->setDescription('Display help for a command')
->setHelp(<<<'EOF'
The <info>%command.name%</info> command displays help for a given command:
<info>%command.full_name% list</info>
You can also output the help in other formats by using the <comment>--format</comment> option:
<info>%command.full_name% --format=xml list</info>
To display the list of available commands, please use the <info>list</info> command.
EOF
)
;
}
public function setCommand(Command $command)
{
$this->command = $command;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
if (null === $this->command) {
$this->command = $this->getApplication()->find($input->getArgument('command_name'));
}
$helper = new DescriptorHelper();
$helper->describe($output, $this->command, [
'format' => $input->getOption('format'),
'raw_text' => $input->getOption('raw'),
]);
$this->command = null;
return 0;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
/**
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class BufferedOutput extends Output
{
private $buffer = '';
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
{
$content = $this->buffer;
$this->buffer = '';
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite(string $message, bool $newline)
{
$this->buffer .= $message;
if ($newline) {
$this->buffer .= \PHP_EOL;
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* OutputInterface is the interface implemented by all Output classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface OutputInterface
{
public const VERBOSITY_QUIET = 16;
public const VERBOSITY_NORMAL = 32;
public const VERBOSITY_VERBOSE = 64;
public const VERBOSITY_VERY_VERBOSE = 128;
public const VERBOSITY_DEBUG = 256;
public const OUTPUT_NORMAL = 1;
public const OUTPUT_RAW = 2;
public const OUTPUT_PLAIN = 4;
/**
* Writes a message to the output.
*
* @param string|iterable $messages The message as an iterable of strings or a single string
* @param bool $newline Whether to add a newline
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function write($messages, bool $newline = false, int $options = 0);
/**
* Writes a message to the output and adds a newline at the end.
*
* @param string|iterable $messages The message as an iterable of strings or a single string
* @param int $options A bitmask of options (one of the OUTPUT or VERBOSITY constants), 0 is considered the same as self::OUTPUT_NORMAL | self::VERBOSITY_NORMAL
*/
public function writeln($messages, int $options = 0);
/**
* Sets the verbosity of the output.
*/
public function setVerbosity(int $level);
/**
* Gets the current verbosity of the output.
*
* @return int The current level of verbosity (one of the VERBOSITY constants)
*/
public function getVerbosity();
/**
* Returns whether verbosity is quiet (-q).
*
* @return bool true if verbosity is set to VERBOSITY_QUIET, false otherwise
*/
public function isQuiet();
/**
* Returns whether verbosity is verbose (-v).
*
* @return bool true if verbosity is set to VERBOSITY_VERBOSE, false otherwise
*/
public function isVerbose();
/**
* Returns whether verbosity is very verbose (-vv).
*
* @return bool true if verbosity is set to VERBOSITY_VERY_VERBOSE, false otherwise
*/
public function isVeryVerbose();
/**
* Returns whether verbosity is debug (-vvv).
*
* @return bool true if verbosity is set to VERBOSITY_DEBUG, false otherwise
*/
public function isDebug();
/**
* Sets the decorated flag.
*/
public function setDecorated(bool $decorated);
/**
* Gets the decorated flag.
*
* @return bool true if the output will decorate messages, false otherwise
*/
public function isDecorated();
public function setFormatter(OutputFormatterInterface $formatter);
/**
* Returns current output formatter instance.
*
* @return OutputFormatterInterface
*/
public function getFormatter();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* A BufferedOutput that keeps only the last N chars.
*
* @author Jérémy Derussé <jeremy@derusse.com>
*/
class TrimmedBufferOutput extends Output
{
private $maxLength;
private $buffer = '';
public function __construct(
int $maxLength,
?int $verbosity = self::VERBOSITY_NORMAL,
bool $decorated = false,
OutputFormatterInterface $formatter = null
) {
if ($maxLength <= 0) {
throw new InvalidArgumentException(sprintf('"%s()" expects a strictly positive maxLength. Got %d.', __METHOD__, $maxLength));
}
parent::__construct($verbosity, $decorated, $formatter);
$this->maxLength = $maxLength;
}
/**
* Empties buffer and returns its content.
*
* @return string
*/
public function fetch()
{
$content = $this->buffer;
$this->buffer = '';
return $content;
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
$this->buffer .= $message;
if ($newline) {
$this->buffer .= \PHP_EOL;
}
$this->buffer = substr($this->buffer, 0 - $this->maxLength);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* ConsoleOutput is the default class for all CLI output. It uses STDOUT and STDERR.
*
* This class is a convenient wrapper around `StreamOutput` for both STDOUT and STDERR.
*
* $output = new ConsoleOutput();
*
* This is equivalent to:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
* $stdErr = new StreamOutput(fopen('php://stderr', 'w'));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
private $stderr;
private $consoleSectionOutputs = [];
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
{
parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);
if (null === $formatter) {
// for BC reasons, stdErr has it own Formatter only when user don't inject a specific formatter.
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated);
return;
}
$actualDecorated = $this->isDecorated();
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());
if (null === $decorated) {
$this->setDecorated($actualDecorated && $this->stderr->isDecorated());
}
}
/**
* Creates a new output section.
*/
public function section(): ConsoleSectionOutput
{
return new ConsoleSectionOutput($this->getStream(), $this->consoleSectionOutputs, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter());
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
parent::setDecorated($decorated);
$this->stderr->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
parent::setFormatter($formatter);
$this->stderr->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
parent::setVerbosity($level);
$this->stderr->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getErrorOutput()
{
return $this->stderr;
}
/**
* {@inheritdoc}
*/
public function setErrorOutput(OutputInterface $error)
{
$this->stderr = $error;
}
/**
* Returns true if current environment supports writing console output to
* STDOUT.
*
* @return bool
*/
protected function hasStdoutSupport()
{
return false === $this->isRunningOS400();
}
/**
* Returns true if current environment supports writing console output to
* STDERR.
*
* @return bool
*/
protected function hasStderrSupport()
{
return false === $this->isRunningOS400();
}
/**
* Checks if current executing environment is IBM iSeries (OS400), which
* doesn't properly convert character-encodings between ASCII to EBCDIC.
*/
private function isRunningOS400(): bool
{
$checks = [
\function_exists('php_uname') ? php_uname('s') : '',
getenv('OSTYPE'),
\PHP_OS,
];
return false !== stripos(implode(';', $checks), 'OS400');
}
/**
* @return resource
*/
private function openOutputStream()
{
if (!$this->hasStdoutSupport()) {
return fopen('php://output', 'w');
}
return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w');
}
/**
* @return resource
*/
private function openErrorStream()
{
return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Terminal;
/**
* @author Pierre du Plessis <pdples@gmail.com>
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
*/
class ConsoleSectionOutput extends StreamOutput
{
private $content = [];
private $lines = 0;
private $sections;
private $terminal;
/**
* @param resource $stream
* @param ConsoleSectionOutput[] $sections
*/
public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
{
parent::__construct($stream, $verbosity, $decorated, $formatter);
array_unshift($sections, $this);
$this->sections = &$sections;
$this->terminal = new Terminal();
}
/**
* Clears previous output for this section.
*
* @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
*/
public function clear(int $lines = null)
{
if (empty($this->content) || !$this->isDecorated()) {
return;
}
if ($lines) {
array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
} else {
$lines = $this->lines;
$this->content = [];
}
$this->lines -= $lines;
parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
}
/**
* Overwrites the previous output with a new message.
*
* @param array|string $message
*/
public function overwrite($message)
{
$this->clear();
$this->writeln($message);
}
public function getContent(): string
{
return implode('', $this->content);
}
/**
* @internal
*/
public function addContent(string $input)
{
foreach (explode(\PHP_EOL, $input) as $lineContent) {
$this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
$this->content[] = $lineContent;
$this->content[] = \PHP_EOL;
}
}
/**
* {@inheritdoc}
*/
protected function doWrite($message, $newline)
{
if (!$this->isDecorated()) {
parent::doWrite($message, $newline);
return;
}
$erasedContent = $this->popStreamContentUntilCurrentSection();
$this->addContent($message);
parent::doWrite($message, true);
parent::doWrite($erasedContent, false);
}
/**
* At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
* current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
*/
private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
{
$numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
$erasedContent = [];
foreach ($this->sections as $section) {
if ($section === $this) {
break;
}
$numberOfLinesToClear += $section->lines;
$erasedContent[] = $section->getContent();
}
if ($numberOfLinesToClear > 0) {
// move cursor up n lines
parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false);
// erase to end of screen
parent::doWrite("\x1b[0J", false);
}
return implode('', array_reverse($erasedContent));
}
private function getDisplayLength(string $text): string
{
return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
/**
* ConsoleOutputInterface is the interface implemented by ConsoleOutput class.
* This adds information about stderr and section output stream.
*
* @author Dariusz Górecki <darek.krk@gmail.com>
*/
interface ConsoleOutputInterface extends OutputInterface
{
/**
* Gets the OutputInterface for errors.
*
* @return OutputInterface
*/
public function getErrorOutput();
public function setErrorOutput(OutputInterface $error);
public function section(): ConsoleSectionOutput;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\NullOutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* NullOutput suppresses all output.
*
* $output = new NullOutput();
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Tobias Schultze <http://tobion.de>
*/
class NullOutput implements OutputInterface
{
private $formatter;
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
if ($this->formatter) {
return $this->formatter;
}
// to comply with the interface we must return a OutputFormatterInterface
return $this->formatter = new NullOutputFormatter();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return false;
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return self::VERBOSITY_QUIET;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return true;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return false;
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return false;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $options = self::OUTPUT_NORMAL)
{
// do nothing
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{
// do nothing
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* StreamOutput writes the output to a given stream.
*
* Usage:
*
* $output = new StreamOutput(fopen('php://stdout', 'w'));
*
* As `StreamOutput` can use any stream, you can also use a file:
*
* $output = new StreamOutput(fopen('/path/to/output.log', 'a', false));
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class StreamOutput extends Output
{
private $stream;
/**
* @param resource $stream A stream resource
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*
* @throws InvalidArgumentException When first argument is not a real stream
*/
public function __construct($stream, int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = null, OutputFormatterInterface $formatter = null)
{
if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
throw new InvalidArgumentException('The StreamOutput class needs a stream as its first argument.');
}
$this->stream = $stream;
if (null === $decorated) {
$decorated = $this->hasColorSupport();
}
parent::__construct($verbosity, $decorated, $formatter);
}
/**
* Gets the stream attached to this StreamOutput instance.
*
* @return resource A stream resource
*/
public function getStream()
{
return $this->stream;
}
/**
* {@inheritdoc}
*/
protected function doWrite(string $message, bool $newline)
{
if ($newline) {
$message .= \PHP_EOL;
}
@fwrite($this->stream, $message);
fflush($this->stream);
}
/**
* Returns true if the stream supports colorization.
*
* Colorization is disabled if not supported by the stream:
*
* This is tricky on Windows, because Cygwin, Msys2 etc emulate pseudo
* terminals via named pipes, so we can only check the environment.
*
* Reference: Composer\XdebugHandler\Process::supportsColor
* https://github.com/composer/xdebug-handler
*
* @return bool true if the stream supports colorization, false otherwise
*/
protected function hasColorSupport()
{
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
return false;
}
if ('Hyper' === getenv('TERM_PROGRAM')) {
return true;
}
if (\DIRECTORY_SEPARATOR === '\\') {
return (\function_exists('sapi_windows_vt100_support')
&& @sapi_windows_vt100_support($this->stream))
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM');
}
return stream_isatty($this->stream);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Output;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
/**
* Base class for output classes.
*
* There are five levels of verbosity:
*
* * normal: no option passed (normal output)
* * verbose: -v (more output)
* * very verbose: -vv (highly extended output)
* * debug: -vvv (all debug output)
* * quiet: -q (no output)
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Output implements OutputInterface
{
private $verbosity;
private $formatter;
/**
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool $decorated Whether to decorate messages
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)
*/
public function __construct(?int $verbosity = self::VERBOSITY_NORMAL, bool $decorated = false, OutputFormatterInterface $formatter = null)
{
$this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity;
$this->formatter = $formatter ?: new OutputFormatter();
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->formatter = $formatter;
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->formatter;
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->formatter->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->formatter->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
$this->verbosity = $level;
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return self::VERBOSITY_QUIET === $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return self::VERBOSITY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return self::VERBOSITY_DEBUG <= $this->verbosity;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $options = self::OUTPUT_NORMAL)
{
$this->write($messages, true, $options);
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $options = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
$types = self::OUTPUT_NORMAL | self::OUTPUT_RAW | self::OUTPUT_PLAIN;
$type = $types & $options ?: self::OUTPUT_NORMAL;
$verbosities = self::VERBOSITY_QUIET | self::VERBOSITY_NORMAL | self::VERBOSITY_VERBOSE | self::VERBOSITY_VERY_VERBOSE | self::VERBOSITY_DEBUG;
$verbosity = $verbosities & $options ?: self::VERBOSITY_NORMAL;
if ($verbosity > $this->getVerbosity()) {
return;
}
foreach ($messages as $message) {
switch ($type) {
case OutputInterface::OUTPUT_NORMAL:
$message = $this->formatter->format($message);
break;
case OutputInterface::OUTPUT_RAW:
break;
case OutputInterface::OUTPUT_PLAIN:
$message = strip_tags($this->formatter->format($message));
break;
}
$this->doWrite($message, $newline);
}
}
/**
* Writes a message to the output.
*/
abstract protected function doWrite(string $message, bool $newline);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Command\HelpCommand;
use Symfony\Component\Console\Command\ListCommand;
use Symfony\Component\Console\Command\SignalableCommandInterface;
use Symfony\Component\Console\CommandLoader\CommandLoaderInterface;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleSignalEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\Console\Exception\CommandNotFoundException;
use Symfony\Component\Console\Exception\ExceptionInterface;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Exception\NamespaceNotFoundException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\DebugFormatterHelper;
use Symfony\Component\Console\Helper\FormatterHelper;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Helper\ProcessHelper;
use Symfony\Component\Console\Helper\QuestionHelper;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\SignalRegistry\SignalRegistry;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\ErrorHandler\ErrorHandler;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* An Application is the container for a collection of commands.
*
* It is the main entry point of a Console application.
*
* This class is optimized for a standard CLI environment.
*
* Usage:
*
* $app = new Application('myapp', '1.0 (stable)');
* $app->add(new SimpleCommand());
* $app->run();
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Application implements ResetInterface
{
private $commands = [];
private $wantHelps = false;
private $runningCommand;
private $name;
private $version;
private $commandLoader;
private $catchExceptions = true;
private $autoExit = true;
private $definition;
private $helperSet;
private $dispatcher;
private $terminal;
private $defaultCommand;
private $singleCommand = false;
private $initialized;
private $signalRegistry;
private $signalsToDispatchEvent = [];
public function __construct(string $name = 'UNKNOWN', string $version = 'UNKNOWN')
{
$this->name = $name;
$this->version = $version;
$this->terminal = new Terminal();
$this->defaultCommand = 'list';
if (\defined('SIGINT') && SignalRegistry::isSupported()) {
$this->signalRegistry = new SignalRegistry();
$this->signalsToDispatchEvent = [\SIGINT, \SIGTERM, \SIGUSR1, \SIGUSR2];
}
}
/**
* @final
*/
public function setDispatcher(EventDispatcherInterface $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function setCommandLoader(CommandLoaderInterface $commandLoader)
{
$this->commandLoader = $commandLoader;
}
public function getSignalRegistry(): SignalRegistry
{
if (!$this->signalRegistry) {
throw new RuntimeException('Signals are not supported. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
return $this->signalRegistry;
}
public function setSignalsToDispatchEvent(int ...$signalsToDispatchEvent)
{
$this->signalsToDispatchEvent = $signalsToDispatchEvent;
}
/**
* Runs the current application.
*
* @return int 0 if everything went fine, or an error code
*
* @throws \Exception When running fails. Bypass this when {@link setCatchExceptions()}.
*/
public function run(InputInterface $input = null, OutputInterface $output = null)
{
if (\function_exists('putenv')) {
@putenv('LINES='.$this->terminal->getHeight());
@putenv('COLUMNS='.$this->terminal->getWidth());
}
if (null === $input) {
$input = new ArgvInput();
}
if (null === $output) {
$output = new ConsoleOutput();
}
$renderException = function (\Throwable $e) use ($output) {
if ($output instanceof ConsoleOutputInterface) {
$this->renderThrowable($e, $output->getErrorOutput());
} else {
$this->renderThrowable($e, $output);
}
};
if ($phpHandler = set_exception_handler($renderException)) {
restore_exception_handler();
if (!\is_array($phpHandler) || !$phpHandler[0] instanceof ErrorHandler) {
$errorHandler = true;
} elseif ($errorHandler = $phpHandler[0]->setExceptionHandler($renderException)) {
$phpHandler[0]->setExceptionHandler($errorHandler);
}
}
$this->configureIO($input, $output);
try {
$exitCode = $this->doRun($input, $output);
} catch (\Exception $e) {
if (!$this->catchExceptions) {
throw $e;
}
$renderException($e);
$exitCode = $e->getCode();
if (is_numeric($exitCode)) {
$exitCode = (int) $exitCode;
if (0 === $exitCode) {
$exitCode = 1;
}
} else {
$exitCode = 1;
}
} finally {
// if the exception handler changed, keep it
// otherwise, unregister $renderException
if (!$phpHandler) {
if (set_exception_handler($renderException) === $renderException) {
restore_exception_handler();
}
restore_exception_handler();
} elseif (!$errorHandler) {
$finalHandler = $phpHandler[0]->setExceptionHandler(null);
if ($finalHandler !== $renderException) {
$phpHandler[0]->setExceptionHandler($finalHandler);
}
}
}
if ($this->autoExit) {
if ($exitCode > 255) {
$exitCode = 255;
}
exit($exitCode);
}
return $exitCode;
}
/**
* Runs the current application.
*
* @return int 0 if everything went fine, or an error code
*/
public function doRun(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(['--version', '-V'], true)) {
$output->writeln($this->getLongVersion());
return 0;
}
try {
// Makes ArgvInput::getFirstArgument() able to distinguish an option from an argument.
$input->bind($this->getDefinition());
} catch (ExceptionInterface $e) {
// Errors must be ignored, full binding/validation happens later when the command is known.
}
$name = $this->getCommandName($input);
if (true === $input->hasParameterOption(['--help', '-h'], true)) {
if (!$name) {
$name = 'help';
$input = new ArrayInput(['command_name' => $this->defaultCommand]);
} else {
$this->wantHelps = true;
}
}
if (!$name) {
$name = $this->defaultCommand;
$definition = $this->getDefinition();
$definition->setArguments(array_merge(
$definition->getArguments(),
[
'command' => new InputArgument('command', InputArgument::OPTIONAL, $definition->getArgument('command')->getDescription(), $name),
]
));
}
try {
$this->runningCommand = null;
// the command name MUST be the first element of the input
$command = $this->find($name);
} catch (\Throwable $e) {
if (!($e instanceof CommandNotFoundException && !$e instanceof NamespaceNotFoundException) || 1 !== \count($alternatives = $e->getAlternatives()) || !$input->isInteractive()) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
if (0 === $event->getExitCode()) {
return 0;
}
$e = $event->getError();
}
throw $e;
}
$alternative = $alternatives[0];
$style = new SymfonyStyle($input, $output);
$style->block(sprintf("\nCommand \"%s\" is not defined.\n", $name), null, 'error');
if (!$style->confirm(sprintf('Do you want to run "%s" instead? ', $alternative), false)) {
if (null !== $this->dispatcher) {
$event = new ConsoleErrorEvent($input, $output, $e);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
return $event->getExitCode();
}
return 1;
}
$command = $this->find($alternative);
}
$this->runningCommand = $command;
$exitCode = $this->doRunCommand($command, $input, $output);
$this->runningCommand = null;
return $exitCode;
}
/**
* {@inheritdoc}
*/
public function reset()
{
}
public function setHelperSet(HelperSet $helperSet)
{
$this->helperSet = $helperSet;
}
/**
* Get the helper set associated with the command.
*
* @return HelperSet The HelperSet instance associated with this command
*/
public function getHelperSet()
{
if (!$this->helperSet) {
$this->helperSet = $this->getDefaultHelperSet();
}
return $this->helperSet;
}
public function setDefinition(InputDefinition $definition)
{
$this->definition = $definition;
}
/**
* Gets the InputDefinition related to this Application.
*
* @return InputDefinition The InputDefinition instance
*/
public function getDefinition()
{
if (!$this->definition) {
$this->definition = $this->getDefaultInputDefinition();
}
if ($this->singleCommand) {
$inputDefinition = $this->definition;
$inputDefinition->setArguments();
return $inputDefinition;
}
return $this->definition;
}
/**
* Gets the help message.
*
* @return string A help message
*/
public function getHelp()
{
return $this->getLongVersion();
}
/**
* Gets whether to catch exceptions or not during commands execution.
*
* @return bool Whether to catch exceptions or not during commands execution
*/
public function areExceptionsCaught()
{
return $this->catchExceptions;
}
/**
* Sets whether to catch exceptions or not during commands execution.
*/
public function setCatchExceptions(bool $boolean)
{
$this->catchExceptions = $boolean;
}
/**
* Gets whether to automatically exit after a command execution or not.
*
* @return bool Whether to automatically exit after a command execution or not
*/
public function isAutoExitEnabled()
{
return $this->autoExit;
}
/**
* Sets whether to automatically exit after a command execution or not.
*/
public function setAutoExit(bool $boolean)
{
$this->autoExit = $boolean;
}
/**
* Gets the name of the application.
*
* @return string The application name
*/
public function getName()
{
return $this->name;
}
/**
* Sets the application name.
**/
public function setName(string $name)
{
$this->name = $name;
}
/**
* Gets the application version.
*
* @return string The application version
*/
public function getVersion()
{
return $this->version;
}
/**
* Sets the application version.
*/
public function setVersion(string $version)
{
$this->version = $version;
}
/**
* Returns the long version of the application.
*
* @return string The long application version
*/
public function getLongVersion()
{
if ('UNKNOWN' !== $this->getName()) {
if ('UNKNOWN' !== $this->getVersion()) {
return sprintf('%s <info>%s</info>', $this->getName(), $this->getVersion());
}
return $this->getName();
}
return 'Console Tool';
}
/**
* Registers a new command.
*
* @return Command The newly created command
*/
public function register(string $name)
{
return $this->add(new Command($name));
}
/**
* Adds an array of command objects.
*
* If a Command is not enabled it will not be added.
*
* @param Command[] $commands An array of commands
*/
public function addCommands(array $commands)
{
foreach ($commands as $command) {
$this->add($command);
}
}
/**
* Adds a command object.
*
* If a command with the same name already exists, it will be overridden.
* If the command is not enabled it will not be added.
*
* @return Command|null The registered command if enabled or null
*/
public function add(Command $command)
{
$this->init();
$command->setApplication($this);
if (!$command->isEnabled()) {
$command->setApplication(null);
return null;
}
// Will throw if the command is not correctly initialized.
$command->getDefinition();
if (!$command->getName()) {
throw new LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_debug_type($command)));
}
$this->commands[$command->getName()] = $command;
foreach ($command->getAliases() as $alias) {
$this->commands[$alias] = $command;
}
return $command;
}
/**
* Returns a registered command by name or alias.
*
* @return Command A Command object
*
* @throws CommandNotFoundException When given command name does not exist
*/
public function get(string $name)
{
$this->init();
if (!$this->has($name)) {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
// When the command has a different name than the one used at the command loader level
if (!isset($this->commands[$name])) {
throw new CommandNotFoundException(sprintf('The "%s" command cannot be found because it is registered under multiple names. Make sure you don\'t set a different name via constructor or "setName()".', $name));
}
$command = $this->commands[$name];
if ($this->wantHelps) {
$this->wantHelps = false;
$helpCommand = $this->get('help');
$helpCommand->setCommand($command);
return $helpCommand;
}
return $command;
}
/**
* Returns true if the command exists, false otherwise.
*
* @return bool true if the command exists, false otherwise
*/
public function has(string $name)
{
$this->init();
return isset($this->commands[$name]) || ($this->commandLoader && $this->commandLoader->has($name) && $this->add($this->commandLoader->get($name)));
}
/**
* Returns an array of all unique namespaces used by currently registered commands.
*
* It does not return the global namespace which always exists.
*
* @return string[] An array of namespaces
*/
public function getNamespaces()
{
$namespaces = [];
foreach ($this->all() as $command) {
if ($command->isHidden()) {
continue;
}
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName()));
foreach ($command->getAliases() as $alias) {
$namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias));
}
}
return array_values(array_unique(array_filter($namespaces)));
}
/**
* Finds a registered namespace by a name or an abbreviation.
*
* @return string A registered namespace
*
* @throws NamespaceNotFoundException When namespace is incorrect or ambiguous
*/
public function findNamespace(string $namespace)
{
$allNamespaces = $this->getNamespaces();
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace);
$namespaces = preg_grep('{^'.$expr.'}', $allNamespaces);
if (empty($namespaces)) {
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) {
if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new NamespaceNotFoundException($message, $alternatives);
}
$exact = \in_array($namespace, $namespaces, true);
if (\count($namespaces) > 1 && !$exact) {
throw new NamespaceNotFoundException(sprintf("The namespace \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $namespace, $this->getAbbreviationSuggestions(array_values($namespaces))), array_values($namespaces));
}
return $exact ? $namespace : reset($namespaces);
}
/**
* Finds a command by name or alias.
*
* Contrary to get, this command tries to find the best
* match if you give it an abbreviation of a name or alias.
*
* @return Command A Command instance
*
* @throws CommandNotFoundException When command name is incorrect or ambiguous
*/
public function find(string $name)
{
$this->init();
$aliases = [];
foreach ($this->commands as $command) {
foreach ($command->getAliases() as $alias) {
if (!$this->has($alias)) {
$this->commands[$alias] = $command;
}
}
}
if ($this->has($name)) {
return $this->get($name);
}
$allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
if (empty($commands)) {
$commands = preg_grep('{^'.$expr.'}i', $allCommands);
}
// if no commands matched or we just matched namespaces
if (empty($commands) || \count(preg_grep('{^'.$expr.'$}i', $commands)) < 1) {
if (false !== $pos = strrpos($name, ':')) {
// check if a namespace exists and contains commands
$this->findNamespace(substr($name, 0, $pos));
}
$message = sprintf('Command "%s" is not defined.', $name);
if ($alternatives = $this->findAlternatives($name, $allCommands)) {
// remove hidden commands
$alternatives = array_filter($alternatives, function ($name) {
return !$this->get($name)->isHidden();
});
if (1 == \count($alternatives)) {
$message .= "\n\nDid you mean this?\n ";
} else {
$message .= "\n\nDid you mean one of these?\n ";
}
$message .= implode("\n ", $alternatives);
}
throw new CommandNotFoundException($message, array_values($alternatives));
}
// filter out aliases for commands which are already on the list
if (\count($commands) > 1) {
$commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use (&$commandList, $commands, &$aliases) {
if (!$commandList[$nameOrAlias] instanceof Command) {
$commandList[$nameOrAlias] = $this->commandLoader->get($nameOrAlias);
}
$commandName = $commandList[$nameOrAlias]->getName();
$aliases[$nameOrAlias] = $commandName;
return $commandName === $nameOrAlias || !\in_array($commandName, $commands);
}));
}
if (\count($commands) > 1) {
$usableWidth = $this->terminal->getWidth() - 10;
$abbrevs = array_values($commands);
$maxLen = 0;
foreach ($abbrevs as $abbrev) {
$maxLen = max(Helper::strlen($abbrev), $maxLen);
}
$abbrevs = array_map(function ($cmd) use ($commandList, $usableWidth, $maxLen, &$commands) {
if ($commandList[$cmd]->isHidden()) {
unset($commands[array_search($cmd, $commands)]);
return false;
}
$abbrev = str_pad($cmd, $maxLen, ' ').' '.$commandList[$cmd]->getDescription();
return Helper::strlen($abbrev) > $usableWidth ? Helper::substr($abbrev, 0, $usableWidth - 3).'...' : $abbrev;
}, array_values($commands));
if (\count($commands) > 1) {
$suggestions = $this->getAbbreviationSuggestions(array_filter($abbrevs));
throw new CommandNotFoundException(sprintf("Command \"%s\" is ambiguous.\nDid you mean one of these?\n%s.", $name, $suggestions), array_values($commands));
}
}
$command = $this->get(reset($commands));
if ($command->isHidden()) {
throw new CommandNotFoundException(sprintf('The command "%s" does not exist.', $name));
}
return $command;
}
/**
* Gets the commands (registered in the given namespace if provided).
*
* The array keys are the full names and the values the command instances.
*
* @return Command[] An array of Command instances
*/
public function all(string $namespace = null)
{
$this->init();
if (null === $namespace) {
if (!$this->commandLoader) {
return $this->commands;
}
$commands = $this->commands;
foreach ($this->commandLoader->getNames() as $name) {
if (!isset($commands[$name]) && $this->has($name)) {
$commands[$name] = $this->get($name);
}
}
return $commands;
}
$commands = [];
foreach ($this->commands as $name => $command) {
if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) {
$commands[$name] = $command;
}
}
if ($this->commandLoader) {
foreach ($this->commandLoader->getNames() as $name) {
if (!isset($commands[$name]) && $namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1) && $this->has($name)) {
$commands[$name] = $this->get($name);
}
}
}
return $commands;
}
/**
* Returns an array of possible abbreviations given a set of names.
*
* @return string[][] An array of abbreviations
*/
public static function getAbbreviations(array $names)
{
$abbrevs = [];
foreach ($names as $name) {
for ($len = \strlen($name); $len > 0; --$len) {
$abbrev = substr($name, 0, $len);
$abbrevs[$abbrev][] = $name;
}
}
return $abbrevs;
}
public function renderThrowable(\Throwable $e, OutputInterface $output): void
{
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
$this->doRenderThrowable($e, $output);
if (null !== $this->runningCommand) {
$output->writeln(sprintf('<info>%s</info>', sprintf($this->runningCommand->getSynopsis(), $this->getName())), OutputInterface::VERBOSITY_QUIET);
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
}
protected function doRenderThrowable(\Throwable $e, OutputInterface $output): void
{
do {
$message = trim($e->getMessage());
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$class = get_debug_type($e);
$title = sprintf(' [%s%s] ', $class, 0 !== ($code = $e->getCode()) ? ' ('.$code.')' : '');
$len = Helper::strlen($title);
} else {
$len = 0;
}
if (false !== strpos($message, "@anonymous\0")) {
$message = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $message);
}
$width = $this->terminal->getWidth() ? $this->terminal->getWidth() - 1 : \PHP_INT_MAX;
$lines = [];
foreach ('' !== $message ? preg_split('/\r?\n/', $message) : [] as $line) {
foreach ($this->splitStringByWidth($line, $width - 4) as $line) {
// pre-format lines to get the right string length
$lineLength = Helper::strlen($line) + 4;
$lines[] = [$line, $lineLength];
$len = max($lineLength, $len);
}
}
$messages = [];
if (!$e instanceof ExceptionInterface || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<comment>%s</comment>', OutputFormatter::escape(sprintf('In %s line %s:', basename($e->getFile()) ?: 'n/a', $e->getLine() ?: 'n/a')));
}
$messages[] = $emptyLine = sprintf('<error>%s</error>', str_repeat(' ', $len));
if ('' === $message || OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$messages[] = sprintf('<error>%s%s</error>', $title, str_repeat(' ', max(0, $len - Helper::strlen($title))));
}
foreach ($lines as $line) {
$messages[] = sprintf('<error> %s %s</error>', OutputFormatter::escape($line[0]), str_repeat(' ', $len - $line[1]));
}
$messages[] = $emptyLine;
$messages[] = '';
$output->writeln($messages, OutputInterface::VERBOSITY_QUIET);
if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) {
$output->writeln('<comment>Exception trace:</comment>', OutputInterface::VERBOSITY_QUIET);
// exception related properties
$trace = $e->getTrace();
array_unshift($trace, [
'function' => '',
'file' => $e->getFile() ?: 'n/a',
'line' => $e->getLine() ?: 'n/a',
'args' => [],
]);
for ($i = 0, $count = \count($trace); $i < $count; ++$i) {
$class = $trace[$i]['class'] ?? '';
$type = $trace[$i]['type'] ?? '';
$function = $trace[$i]['function'] ?? '';
$file = $trace[$i]['file'] ?? 'n/a';
$line = $trace[$i]['line'] ?? 'n/a';
$output->writeln(sprintf(' %s%s at <info>%s:%s</info>', $class, $function ? $type.$function.'()' : '', $file, $line), OutputInterface::VERBOSITY_QUIET);
}
$output->writeln('', OutputInterface::VERBOSITY_QUIET);
}
} while ($e = $e->getPrevious());
}
/**
* Configures the input and output instances based on the user arguments and options.
*/
protected function configureIO(InputInterface $input, OutputInterface $output)
{
if (true === $input->hasParameterOption(['--ansi'], true)) {
$output->setDecorated(true);
} elseif (true === $input->hasParameterOption(['--no-ansi'], true)) {
$output->setDecorated(false);
}
if (true === $input->hasParameterOption(['--no-interaction', '-n'], true)) {
$input->setInteractive(false);
}
switch ($shellVerbosity = (int) getenv('SHELL_VERBOSITY')) {
case -1: $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); break;
case 1: $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); break;
case 2: $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); break;
case 3: $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); break;
default: $shellVerbosity = 0; break;
}
if (true === $input->hasParameterOption(['--quiet', '-q'], true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_QUIET);
$shellVerbosity = -1;
} else {
if ($input->hasParameterOption('-vvv', true) || $input->hasParameterOption('--verbose=3', true) || 3 === $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_DEBUG);
$shellVerbosity = 3;
} elseif ($input->hasParameterOption('-vv', true) || $input->hasParameterOption('--verbose=2', true) || 2 === $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
$shellVerbosity = 2;
} elseif ($input->hasParameterOption('-v', true) || $input->hasParameterOption('--verbose=1', true) || $input->hasParameterOption('--verbose', true) || $input->getParameterOption('--verbose', false, true)) {
$output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
$shellVerbosity = 1;
}
}
if (-1 === $shellVerbosity) {
$input->setInteractive(false);
}
if (\function_exists('putenv')) {
@putenv('SHELL_VERBOSITY='.$shellVerbosity);
}
$_ENV['SHELL_VERBOSITY'] = $shellVerbosity;
$_SERVER['SHELL_VERBOSITY'] = $shellVerbosity;
}
/**
* Runs the current command.
*
* If an event dispatcher has been attached to the application,
* events are also dispatched during the life-cycle of the command.
*
* @return int 0 if everything went fine, or an error code
*/
protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output)
{
foreach ($command->getHelperSet() as $helper) {
if ($helper instanceof InputAwareInterface) {
$helper->setInput($input);
}
}
if ($command instanceof SignalableCommandInterface) {
if (!$this->signalRegistry) {
throw new RuntimeException('Unable to subscribe to signal events. Make sure that the `pcntl` extension is installed and that "pcntl_*" functions are not disabled by your php.ini\'s "disable_functions" directive.');
}
if ($this->dispatcher) {
foreach ($this->signalsToDispatchEvent as $signal) {
$event = new ConsoleSignalEvent($command, $input, $output, $signal);
$this->signalRegistry->register($signal, function ($signal, $hasNext) use ($event) {
$this->dispatcher->dispatch($event, ConsoleEvents::SIGNAL);
// No more handlers, we try to simulate PHP default behavior
if (!$hasNext) {
if (!\in_array($signal, [\SIGUSR1, \SIGUSR2], true)) {
exit(0);
}
}
});
}
}
foreach ($command->getSubscribedSignals() as $signal) {
$this->signalRegistry->register($signal, [$command, 'handleSignal']);
}
}
if (null === $this->dispatcher) {
return $command->run($input, $output);
}
// bind before the console.command event, so the listeners have access to input options/arguments
try {
$command->mergeApplicationDefinition();
$input->bind($command->getDefinition());
} catch (ExceptionInterface $e) {
// ignore invalid options/arguments for now, to allow the event listeners to customize the InputDefinition
}
$event = new ConsoleCommandEvent($command, $input, $output);
$e = null;
try {
$this->dispatcher->dispatch($event, ConsoleEvents::COMMAND);
if ($event->commandShouldRun()) {
$exitCode = $command->run($input, $output);
} else {
$exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED;
}
} catch (\Throwable $e) {
$event = new ConsoleErrorEvent($input, $output, $e, $command);
$this->dispatcher->dispatch($event, ConsoleEvents::ERROR);
$e = $event->getError();
if (0 === $exitCode = $event->getExitCode()) {
$e = null;
}
}
$event = new ConsoleTerminateEvent($command, $input, $output, $exitCode);
$this->dispatcher->dispatch($event, ConsoleEvents::TERMINATE);
if (null !== $e) {
throw $e;
}
return $event->getExitCode();
}
/**
* Gets the name of the command based on input.
*
* @return string|null
*/
protected function getCommandName(InputInterface $input)
{
return $this->singleCommand ? $this->defaultCommand : $input->getFirstArgument();
}
/**
* Gets the default input definition.
*
* @return InputDefinition An InputDefinition instance
*/
protected function getDefaultInputDefinition()
{
return new InputDefinition([
new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'),
new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display help for the given command. When no command is given display help for the <info>'.$this->defaultCommand.'</info> command'),
new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'),
new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'),
new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version'),
new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'),
new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'),
new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'),
]);
}
/**
* Gets the default commands that should always be available.
*
* @return Command[] An array of default Command instances
*/
protected function getDefaultCommands()
{
return [new HelpCommand(), new ListCommand()];
}
/**
* Gets the default helper set with the helpers that should always be available.
*
* @return HelperSet A HelperSet instance
*/
protected function getDefaultHelperSet()
{
return new HelperSet([
new FormatterHelper(),
new DebugFormatterHelper(),
new ProcessHelper(),
new QuestionHelper(),
]);
}
/**
* Returns abbreviated suggestions in string format.
*/
private function getAbbreviationSuggestions(array $abbrevs): string
{
return ' '.implode("\n ", $abbrevs);
}
/**
* Returns the namespace part of the command name.
*
* This method is not part of public API and should not be used directly.
*
* @return string The namespace of the command
*/
public function extractNamespace(string $name, int $limit = null)
{
$parts = explode(':', $name, -1);
return implode(':', null === $limit ? $parts : \array_slice($parts, 0, $limit));
}
/**
* Finds alternative of $name among $collection,
* if nothing is found in $collection, try in $abbrevs.
*
* @return string[] A sorted array of similar string
*/
private function findAlternatives(string $name, iterable $collection): array
{
$threshold = 1e3;
$alternatives = [];
$collectionParts = [];
foreach ($collection as $item) {
$collectionParts[$item] = explode(':', $item);
}
foreach (explode(':', $name) as $i => $subname) {
foreach ($collectionParts as $collectionName => $parts) {
$exists = isset($alternatives[$collectionName]);
if (!isset($parts[$i]) && $exists) {
$alternatives[$collectionName] += $threshold;
continue;
} elseif (!isset($parts[$i])) {
continue;
}
$lev = levenshtein($subname, $parts[$i]);
if ($lev <= \strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) {
$alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev;
} elseif ($exists) {
$alternatives[$collectionName] += $threshold;
}
}
}
foreach ($collection as $item) {
$lev = levenshtein($name, $item);
if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev;
}
}
$alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2 * $threshold; });
ksort($alternatives, \SORT_NATURAL | \SORT_FLAG_CASE);
return array_keys($alternatives);
}
/**
* Sets the default Command name.
*
* @return self
*/
public function setDefaultCommand(string $commandName, bool $isSingleCommand = false)
{
$this->defaultCommand = $commandName;
if ($isSingleCommand) {
// Ensure the command exist
$this->find($commandName);
$this->singleCommand = true;
}
return $this;
}
/**
* @internal
*/
public function isSingleCommand(): bool
{
return $this->singleCommand;
}
private function splitStringByWidth(string $string, int $width): array
{
// str_split is not suitable for multi-byte characters, we should use preg_split to get char array properly.
// additionally, array_slice() is not enough as some character has doubled width.
// we need a function to split string not by character count but by string width
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return str_split($string, $width);
}
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = [];
$line = '';
$offset = 0;
while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
$offset += \strlen($m[0]);
foreach (preg_split('//u', $m[0]) as $char) {
// test if $char could be appended to current line
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
}
$lines[] = \count($lines) ? str_pad($line, $width) : $line;
mb_convert_variables($encoding, 'utf8', $lines);
return $lines;
}
/**
* Returns all namespaces of the command name.
*
* @return string[] The namespaces of the command
*/
private function extractAllNamespaces(string $name): array
{
// -1 as third argument is needed to skip the command short name when exploding
$parts = explode(':', $name, -1);
$namespaces = [];
foreach ($parts as $part) {
if (\count($namespaces)) {
$namespaces[] = end($namespaces).':'.$part;
} else {
$namespaces[] = $part;
}
}
return $namespaces;
}
private function init()
{
if ($this->initialized) {
return;
}
$this->initialized = true;
foreach ($this->getDefaultCommands() as $command) {
$this->add($command);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Helper\SymfonyQuestionHelper;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Helper\TableCell;
use Symfony\Component\Console\Helper\TableSeparator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\TrimmedBufferOutput;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
/**
* Output decorator helpers for the Symfony Style Guide.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyStyle extends OutputStyle
{
public const MAX_LINE_LENGTH = 120;
private $input;
private $questionHelper;
private $progressBar;
private $lineLength;
private $bufferedOutput;
public function __construct(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->bufferedOutput = new TrimmedBufferOutput(\DIRECTORY_SEPARATOR === '\\' ? 4 : 2, $output->getVerbosity(), false, clone $output->getFormatter());
// Windows cmd wraps lines as soon as the terminal width is reached, whether there are following chars or not.
$width = (new Terminal())->getWidth() ?: self::MAX_LINE_LENGTH;
$this->lineLength = min($width - (int) (\DIRECTORY_SEPARATOR === '\\'), self::MAX_LINE_LENGTH);
parent::__construct($output);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
*/
public function block($messages, ?string $type = null, ?string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = true)
{
$messages = \is_array($messages) ? array_values($messages) : [$messages];
$this->autoPrependBlock();
$this->writeln($this->createBlock($messages, $type, $style, $prefix, $padding, $escape));
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function title(string $message)
{
$this->autoPrependBlock();
$this->writeln([
sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
sprintf('<comment>%s</>', str_repeat('=', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
]);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function section(string $message)
{
$this->autoPrependBlock();
$this->writeln([
sprintf('<comment>%s</>', OutputFormatter::escapeTrailingBackslash($message)),
sprintf('<comment>%s</>', str_repeat('-', Helper::strlenWithoutDecoration($this->getFormatter(), $message))),
]);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function listing(array $elements)
{
$this->autoPrependText();
$elements = array_map(function ($element) {
return sprintf(' * %s', $element);
}, $elements);
$this->writeln($elements);
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function text($message)
{
$this->autoPrependText();
$messages = \is_array($message) ? array_values($message) : [$message];
foreach ($messages as $message) {
$this->writeln(sprintf(' %s', $message));
}
}
/**
* Formats a command comment.
*
* @param string|array $message
*/
public function comment($message)
{
$this->block($message, null, null, '<fg=default;bg=default> // </>', false, false);
}
/**
* {@inheritdoc}
*/
public function success($message)
{
$this->block($message, 'OK', 'fg=black;bg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
public function error($message)
{
$this->block($message, 'ERROR', 'fg=white;bg=red', ' ', true);
}
/**
* {@inheritdoc}
*/
public function warning($message)
{
$this->block($message, 'WARNING', 'fg=black;bg=yellow', ' ', true);
}
/**
* {@inheritdoc}
*/
public function note($message)
{
$this->block($message, 'NOTE', 'fg=yellow', ' ! ');
}
/**
* Formats an info message.
*
* @param string|array $message
*/
public function info($message)
{
$this->block($message, 'INFO', 'fg=green', ' ', true);
}
/**
* {@inheritdoc}
*/
public function caution($message)
{
$this->block($message, 'CAUTION', 'fg=white;bg=red', ' ! ', true);
}
/**
* {@inheritdoc}
*/
public function table(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* Formats a horizontal table.
*/
public function horizontalTable(array $headers, array $rows)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$table->setHeaders($headers);
$table->setRows($rows);
$table->setStyle($style);
$table->setHorizontal(true);
$table->render();
$this->newLine();
}
/**
* Formats a list of key/value horizontally.
*
* Each row can be one of:
* * 'A title'
* * ['key' => 'value']
* * new TableSeparator()
*
* @param string|array|TableSeparator ...$list
*/
public function definitionList(...$list)
{
$style = clone Table::getStyleDefinition('symfony-style-guide');
$style->setCellHeaderFormat('<info>%s</info>');
$table = new Table($this);
$headers = [];
$row = [];
foreach ($list as $value) {
if ($value instanceof TableSeparator) {
$headers[] = $value;
$row[] = $value;
continue;
}
if (\is_string($value)) {
$headers[] = new TableCell($value, ['colspan' => 2]);
$row[] = null;
continue;
}
if (!\is_array($value)) {
throw new InvalidArgumentException('Value should be an array, string, or an instance of TableSeparator.');
}
$headers[] = key($value);
$row[] = current($value);
}
$table->setHeaders($headers);
$table->setRows([$row]);
$table->setHorizontal();
$table->setStyle($style);
$table->render();
$this->newLine();
}
/**
* {@inheritdoc}
*/
public function ask(string $question, ?string $default = null, $validator = null)
{
$question = new Question($question, $default);
$question->setValidator($validator);
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function askHidden(string $question, $validator = null)
{
$question = new Question($question);
$question->setHidden(true);
$question->setValidator($validator);
return $this->askQuestion($question);
}
/**
* {@inheritdoc}
*/
public function confirm($question, $default = true)
{
return $this->askQuestion(new ConfirmationQuestion($question, $default));
}
/**
* {@inheritdoc}
*/
public function choice(string $question, array $choices, $default = null)
{
if (null !== $default) {
$values = array_flip($choices);
$default = $values[$default] ?? $default;
}
return $this->askQuestion(new ChoiceQuestion($question, $choices, $default));
}
/**
* {@inheritdoc}
*/
public function progressStart(int $max = 0)
{
$this->progressBar = $this->createProgressBar($max);
$this->progressBar->start();
}
/**
* {@inheritdoc}
*/
public function progressAdvance(int $step = 1)
{
$this->getProgressBar()->advance($step);
}
/**
* {@inheritdoc}
*/
public function progressFinish()
{
$this->getProgressBar()->finish();
$this->newLine(2);
$this->progressBar = null;
}
/**
* {@inheritdoc}
*/
public function createProgressBar(int $max = 0)
{
$progressBar = parent::createProgressBar($max);
if ('\\' !== \DIRECTORY_SEPARATOR || 'Hyper' === getenv('TERM_PROGRAM')) {
$progressBar->setEmptyBarCharacter('â–‘'); // light shade character \u2591
$progressBar->setProgressCharacter('');
$progressBar->setBarCharacter('â–“'); // dark shade character \u2593
}
return $progressBar;
}
/**
* @return mixed
*/
public function askQuestion(Question $question)
{
if ($this->input->isInteractive()) {
$this->autoPrependBlock();
}
if (!$this->questionHelper) {
$this->questionHelper = new SymfonyQuestionHelper();
}
$answer = $this->questionHelper->ask($this->input, $this, $question);
if ($this->input->isInteractive()) {
$this->newLine();
$this->bufferedOutput->write("\n");
}
return $answer;
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
parent::writeln($message, $type);
$this->writeBuffer($message, true, $type);
}
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
{
if (!is_iterable($messages)) {
$messages = [$messages];
}
foreach ($messages as $message) {
parent::write($message, $newline, $type);
$this->writeBuffer($message, $newline, $type);
}
}
/**
* {@inheritdoc}
*/
public function newLine(int $count = 1)
{
parent::newLine($count);
$this->bufferedOutput->write(str_repeat("\n", $count));
}
/**
* Returns a new instance which makes use of stderr if available.
*
* @return self
*/
public function getErrorStyle()
{
return new self($this->input, $this->getErrorOutput());
}
private function getProgressBar(): ProgressBar
{
if (!$this->progressBar) {
throw new RuntimeException('The ProgressBar is not started.');
}
return $this->progressBar;
}
private function autoPrependBlock(): void
{
$chars = substr(str_replace(\PHP_EOL, "\n", $this->bufferedOutput->fetch()), -2);
if (!isset($chars[0])) {
$this->newLine(); //empty history, so we should start with a new line.
return;
}
//Prepend new line for each non LF chars (This means no blank line was output before)
$this->newLine(2 - substr_count($chars, "\n"));
}
private function autoPrependText(): void
{
$fetched = $this->bufferedOutput->fetch();
//Prepend new line if last char isn't EOL:
if ("\n" !== substr($fetched, -1)) {
$this->newLine();
}
}
private function writeBuffer(string $message, bool $newLine, int $type): void
{
// We need to know if the last chars are PHP_EOL
$this->bufferedOutput->write($message, $newLine, $type);
}
private function createBlock(iterable $messages, string $type = null, string $style = null, string $prefix = ' ', bool $padding = false, bool $escape = false): array
{
$indentLength = 0;
$prefixLength = Helper::strlenWithoutDecoration($this->getFormatter(), $prefix);
$lines = [];
if (null !== $type) {
$type = sprintf('[%s] ', $type);
$indentLength = \strlen($type);
$lineIndentation = str_repeat(' ', $indentLength);
}
// wrap and add newlines for each element
foreach ($messages as $key => $message) {
if ($escape) {
$message = OutputFormatter::escape($message);
}
$decorationLength = Helper::strlen($message) - Helper::strlenWithoutDecoration($this->getFormatter(), $message);
$messageLineLength = min($this->lineLength - $prefixLength - $indentLength + $decorationLength, $this->lineLength);
$messageLines = explode(\PHP_EOL, wordwrap($message, $messageLineLength, \PHP_EOL, true));
foreach ($messageLines as $messageLine) {
$lines[] = $messageLine;
}
if (\count($messages) > 1 && $key < \count($messages) - 1) {
$lines[] = '';
}
}
$firstLineIndex = 0;
if ($padding && $this->isDecorated()) {
$firstLineIndex = 1;
array_unshift($lines, '');
$lines[] = '';
}
foreach ($lines as $i => &$line) {
if (null !== $type) {
$line = $firstLineIndex === $i ? $type.$line : $lineIndentation.$line;
}
$line = $prefix.$line;
$line .= str_repeat(' ', max($this->lineLength - Helper::strlenWithoutDecoration($this->getFormatter(), $line), 0));
if ($style) {
$line = sprintf('<%s>%s</>', $style, $line);
}
}
return $lines;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
/**
* Output style helpers.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
interface StyleInterface
{
/**
* Formats a command title.
*/
public function title(string $message);
/**
* Formats a section title.
*/
public function section(string $message);
/**
* Formats a list.
*/
public function listing(array $elements);
/**
* Formats informational text.
*
* @param string|array $message
*/
public function text($message);
/**
* Formats a success result bar.
*
* @param string|array $message
*/
public function success($message);
/**
* Formats an error result bar.
*
* @param string|array $message
*/
public function error($message);
/**
* Formats an warning result bar.
*
* @param string|array $message
*/
public function warning($message);
/**
* Formats a note admonition.
*
* @param string|array $message
*/
public function note($message);
/**
* Formats a caution admonition.
*
* @param string|array $message
*/
public function caution($message);
/**
* Formats a table.
*/
public function table(array $headers, array $rows);
/**
* Asks a question.
*
* @return mixed
*/
public function ask(string $question, ?string $default = null, callable $validator = null);
/**
* Asks a question with the user input hidden.
*
* @return mixed
*/
public function askHidden(string $question, callable $validator = null);
/**
* Asks for confirmation.
*
* @return bool
*/
public function confirm(string $question, bool $default = true);
/**
* Asks a choice question.
*
* @param string|int|null $default
*
* @return mixed
*/
public function choice(string $question, array $choices, $default = null);
/**
* Add newline(s).
*/
public function newLine(int $count = 1);
/**
* Starts the progress output.
*/
public function progressStart(int $max = 0);
/**
* Advances the progress output X steps.
*/
public function progressAdvance(int $step = 1);
/**
* Finishes the progress output.
*/
public function progressFinish();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Style;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Decorates output to add console style guide helpers.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
abstract class OutputStyle implements OutputInterface, StyleInterface
{
private $output;
public function __construct(OutputInterface $output)
{
$this->output = $output;
}
/**
* {@inheritdoc}
*/
public function newLine(int $count = 1)
{
$this->output->write(str_repeat(\PHP_EOL, $count));
}
/**
* @return ProgressBar
*/
public function createProgressBar(int $max = 0)
{
return new ProgressBar($this->output, $max);
}
/**
* {@inheritdoc}
*/
public function write($messages, bool $newline = false, int $type = self::OUTPUT_NORMAL)
{
$this->output->write($messages, $newline, $type);
}
/**
* {@inheritdoc}
*/
public function writeln($messages, int $type = self::OUTPUT_NORMAL)
{
$this->output->writeln($messages, $type);
}
/**
* {@inheritdoc}
*/
public function setVerbosity(int $level)
{
$this->output->setVerbosity($level);
}
/**
* {@inheritdoc}
*/
public function getVerbosity()
{
return $this->output->getVerbosity();
}
/**
* {@inheritdoc}
*/
public function setDecorated(bool $decorated)
{
$this->output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
public function isDecorated()
{
return $this->output->isDecorated();
}
/**
* {@inheritdoc}
*/
public function setFormatter(OutputFormatterInterface $formatter)
{
$this->output->setFormatter($formatter);
}
/**
* {@inheritdoc}
*/
public function getFormatter()
{
return $this->output->getFormatter();
}
/**
* {@inheritdoc}
*/
public function isQuiet()
{
return $this->output->isQuiet();
}
/**
* {@inheritdoc}
*/
public function isVerbose()
{
return $this->output->isVerbose();
}
/**
* {@inheritdoc}
*/
public function isVeryVerbose()
{
return $this->output->isVeryVerbose();
}
/**
* {@inheritdoc}
*/
public function isDebug()
{
return $this->output->isDebug();
}
protected function getErrorOutput()
{
if (!$this->output instanceof ConsoleOutputInterface) {
return $this->output;
}
return $this->output->getErrorOutput();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\SignalRegistry;
final class SignalRegistry
{
private $signalHandlers = [];
public function __construct()
{
if (\function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
}
}
public function register(int $signal, callable $signalHandler): void
{
if (!isset($this->signalHandlers[$signal])) {
$previousCallback = pcntl_signal_get_handler($signal);
if (\is_callable($previousCallback)) {
$this->signalHandlers[$signal][] = $previousCallback;
}
}
$this->signalHandlers[$signal][] = $signalHandler;
pcntl_signal($signal, [$this, 'handle']);
}
public static function isSupported(): bool
{
if (!\function_exists('pcntl_signal')) {
return false;
}
if (\in_array('pcntl_signal', explode(',', ini_get('disable_functions')))) {
return false;
}
return true;
}
/**
* @internal
*/
public function handle(int $signal): void
{
$count = \count($this->signalHandlers[$signal]);
foreach ($this->signalHandlers[$signal] as $i => $signalHandler) {
$hasNext = $i !== $count - 1;
$signalHandler($signal, $hasNext);
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Yewhen Khoptynskyi <khoptynskyi@gmail.com>
*/
class TableCellStyle
{
public const DEFAULT_ALIGN = 'left';
private $options = [
'fg' => 'default',
'bg' => 'default',
'options' => null,
'align' => self::DEFAULT_ALIGN,
'cellFormat' => null,
];
private $tagOptions = [
'fg',
'bg',
'options',
];
private $alignMap = [
'left' => \STR_PAD_RIGHT,
'center' => \STR_PAD_BOTH,
'right' => \STR_PAD_LEFT,
];
public function __construct(array $options = [])
{
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
throw new InvalidArgumentException(sprintf('The TableCellStyle does not support the following options: \'%s\'.', implode('\', \'', $diff)));
}
if (isset($options['align']) && !\array_key_exists($options['align'], $this->alignMap)) {
throw new InvalidArgumentException(sprintf('Wrong align value. Value must be following: \'%s\'.', implode('\', \'', array_keys($this->alignMap))));
}
$this->options = array_merge($this->options, $options);
}
public function getOptions(): array
{
return $this->options;
}
/**
* Gets options we need for tag for example fg, bg.
*
* @return string[]
*/
public function getTagOptions()
{
return array_filter(
$this->getOptions(),
function ($key) {
return \in_array($key, $this->tagOptions) && isset($this->options[$key]);
},
\ARRAY_FILTER_USE_KEY
);
}
public function getPadByAlign()
{
return $this->alignMap[$this->getOptions()['align']];
}
public function getCellFormat(): ?string
{
return $this->getOptions()['cellFormat'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\WrappableOutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Provides helpers to display a table.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Саша Стаменковић <umpirsky@gmail.com>
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
* @author Max Grigorian <maxakawizard@gmail.com>
* @author Dany Maillard <danymaillard93b@gmail.com>
*/
class Table
{
private const SEPARATOR_TOP = 0;
private const SEPARATOR_TOP_BOTTOM = 1;
private const SEPARATOR_MID = 2;
private const SEPARATOR_BOTTOM = 3;
private const BORDER_OUTSIDE = 0;
private const BORDER_INSIDE = 1;
private $headerTitle;
private $footerTitle;
/**
* Table headers.
*/
private $headers = [];
/**
* Table rows.
*/
private $rows = [];
private $horizontal = false;
/**
* Column widths cache.
*/
private $effectiveColumnWidths = [];
/**
* Number of columns cache.
*
* @var int
*/
private $numberOfColumns;
/**
* @var OutputInterface
*/
private $output;
/**
* @var TableStyle
*/
private $style;
/**
* @var array
*/
private $columnStyles = [];
/**
* User set column widths.
*
* @var array
*/
private $columnWidths = [];
private $columnMaxWidths = [];
private static $styles;
private $rendered = false;
public function __construct(OutputInterface $output)
{
$this->output = $output;
if (!self::$styles) {
self::$styles = self::initStyles();
}
$this->setStyle('default');
}
/**
* Sets a style definition.
*/
public static function setStyleDefinition(string $name, TableStyle $style)
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
self::$styles[$name] = $style;
}
/**
* Gets a style definition by name.
*
* @return TableStyle
*/
public static function getStyleDefinition(string $name)
{
if (!self::$styles) {
self::$styles = self::initStyles();
}
if (isset(self::$styles[$name])) {
return self::$styles[$name];
}
throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
}
/**
* Sets table style.
*
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return $this
*/
public function setStyle($name)
{
$this->style = $this->resolveStyle($name);
return $this;
}
/**
* Gets the current table style.
*
* @return TableStyle
*/
public function getStyle()
{
return $this->style;
}
/**
* Sets table column style.
*
* @param TableStyle|string $name The style name or a TableStyle instance
*
* @return $this
*/
public function setColumnStyle(int $columnIndex, $name)
{
$this->columnStyles[$columnIndex] = $this->resolveStyle($name);
return $this;
}
/**
* Gets the current style for a column.
*
* If style was not set, it returns the global table style.
*
* @return TableStyle
*/
public function getColumnStyle(int $columnIndex)
{
return $this->columnStyles[$columnIndex] ?? $this->getStyle();
}
/**
* Sets the minimum width of a column.
*
* @return $this
*/
public function setColumnWidth(int $columnIndex, int $width)
{
$this->columnWidths[$columnIndex] = $width;
return $this;
}
/**
* Sets the minimum width of all columns.
*
* @return $this
*/
public function setColumnWidths(array $widths)
{
$this->columnWidths = [];
foreach ($widths as $index => $width) {
$this->setColumnWidth($index, $width);
}
return $this;
}
/**
* Sets the maximum width of a column.
*
* Any cell within this column which contents exceeds the specified width will be wrapped into multiple lines, while
* formatted strings are preserved.
*
* @return $this
*/
public function setColumnMaxWidth(int $columnIndex, int $width): self
{
if (!$this->output->getFormatter() instanceof WrappableOutputFormatterInterface) {
throw new \LogicException(sprintf('Setting a maximum column width is only supported when using a "%s" formatter, got "%s".', WrappableOutputFormatterInterface::class, get_debug_type($this->output->getFormatter())));
}
$this->columnMaxWidths[$columnIndex] = $width;
return $this;
}
public function setHeaders(array $headers)
{
$headers = array_values($headers);
if (!empty($headers) && !\is_array($headers[0])) {
$headers = [$headers];
}
$this->headers = $headers;
return $this;
}
public function setRows(array $rows)
{
$this->rows = [];
return $this->addRows($rows);
}
public function addRows(array $rows)
{
foreach ($rows as $row) {
$this->addRow($row);
}
return $this;
}
public function addRow($row)
{
if ($row instanceof TableSeparator) {
$this->rows[] = $row;
return $this;
}
if (!\is_array($row)) {
throw new InvalidArgumentException('A row must be an array or a TableSeparator instance.');
}
$this->rows[] = array_values($row);
return $this;
}
/**
* Adds a row to the table, and re-renders the table.
*/
public function appendRow($row): self
{
if (!$this->output instanceof ConsoleSectionOutput) {
throw new RuntimeException(sprintf('Output should be an instance of "%s" when calling "%s".', ConsoleSectionOutput::class, __METHOD__));
}
if ($this->rendered) {
$this->output->clear($this->calculateRowCount());
}
$this->addRow($row);
$this->render();
return $this;
}
public function setRow($column, array $row)
{
$this->rows[$column] = $row;
return $this;
}
public function setHeaderTitle(?string $title): self
{
$this->headerTitle = $title;
return $this;
}
public function setFooterTitle(?string $title): self
{
$this->footerTitle = $title;
return $this;
}
public function setHorizontal(bool $horizontal = true): self
{
$this->horizontal = $horizontal;
return $this;
}
/**
* Renders table to output.
*
* Example:
*
* +---------------+-----------------------+------------------+
* | ISBN | Title | Author |
* +---------------+-----------------------+------------------+
* | 99921-58-10-7 | Divine Comedy | Dante Alighieri |
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
* | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien |
* +---------------+-----------------------+------------------+
*/
public function render()
{
$divider = new TableSeparator();
if ($this->horizontal) {
$rows = [];
foreach ($this->headers[0] ?? [] as $i => $header) {
$rows[$i] = [$header];
foreach ($this->rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
if (isset($row[$i])) {
$rows[$i][] = $row[$i];
} elseif ($rows[$i][0] instanceof TableCell && $rows[$i][0]->getColspan() >= 2) {
// Noop, there is a "title"
} else {
$rows[$i][] = null;
}
}
}
} else {
$rows = array_merge($this->headers, [$divider], $this->rows);
}
$this->calculateNumberOfColumns($rows);
$rows = $this->buildTableRows($rows);
$this->calculateColumnsWidth($rows);
$isHeader = !$this->horizontal;
$isFirstRow = $this->horizontal;
foreach ($rows as $row) {
if ($divider === $row) {
$isHeader = false;
$isFirstRow = true;
continue;
}
if ($row instanceof TableSeparator) {
$this->renderRowSeparator();
continue;
}
if (!$row) {
continue;
}
if ($isHeader || $isFirstRow) {
if ($isFirstRow) {
$this->renderRowSeparator(self::SEPARATOR_TOP_BOTTOM);
$isFirstRow = false;
} else {
$this->renderRowSeparator(self::SEPARATOR_TOP, $this->headerTitle, $this->style->getHeaderTitleFormat());
}
}
if ($this->horizontal) {
$this->renderRow($row, $this->style->getCellRowFormat(), $this->style->getCellHeaderFormat());
} else {
$this->renderRow($row, $isHeader ? $this->style->getCellHeaderFormat() : $this->style->getCellRowFormat());
}
}
$this->renderRowSeparator(self::SEPARATOR_BOTTOM, $this->footerTitle, $this->style->getFooterTitleFormat());
$this->cleanup();
$this->rendered = true;
}
/**
* Renders horizontal header separator.
*
* Example:
*
* +-----+-----------+-------+
*/
private function renderRowSeparator(int $type = self::SEPARATOR_MID, string $title = null, string $titleFormat = null)
{
if (0 === $count = $this->numberOfColumns) {
return;
}
$borders = $this->style->getBorderChars();
if (!$borders[0] && !$borders[2] && !$this->style->getCrossingChar()) {
return;
}
$crossings = $this->style->getCrossingChars();
if (self::SEPARATOR_MID === $type) {
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[2], $crossings[8], $crossings[0], $crossings[4]];
} elseif (self::SEPARATOR_TOP === $type) {
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[1], $crossings[2], $crossings[3]];
} elseif (self::SEPARATOR_TOP_BOTTOM === $type) {
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[9], $crossings[10], $crossings[11]];
} else {
[$horizontal, $leftChar, $midChar, $rightChar] = [$borders[0], $crossings[7], $crossings[6], $crossings[5]];
}
$markup = $leftChar;
for ($column = 0; $column < $count; ++$column) {
$markup .= str_repeat($horizontal, $this->effectiveColumnWidths[$column]);
$markup .= $column === $count - 1 ? $rightChar : $midChar;
}
if (null !== $title) {
$titleLength = Helper::strlenWithoutDecoration($formatter = $this->output->getFormatter(), $formattedTitle = sprintf($titleFormat, $title));
$markupLength = Helper::strlen($markup);
if ($titleLength > $limit = $markupLength - 4) {
$titleLength = $limit;
$formatLength = Helper::strlenWithoutDecoration($formatter, sprintf($titleFormat, ''));
$formattedTitle = sprintf($titleFormat, Helper::substr($title, 0, $limit - $formatLength - 3).'...');
}
$titleStart = ($markupLength - $titleLength) / 2;
if (false === mb_detect_encoding($markup, null, true)) {
$markup = substr_replace($markup, $formattedTitle, $titleStart, $titleLength);
} else {
$markup = mb_substr($markup, 0, $titleStart).$formattedTitle.mb_substr($markup, $titleStart + $titleLength);
}
}
$this->output->writeln(sprintf($this->style->getBorderFormat(), $markup));
}
/**
* Renders vertical column separator.
*/
private function renderColumnSeparator(int $type = self::BORDER_OUTSIDE): string
{
$borders = $this->style->getBorderChars();
return sprintf($this->style->getBorderFormat(), self::BORDER_OUTSIDE === $type ? $borders[1] : $borders[3]);
}
/**
* Renders table row.
*
* Example:
*
* | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens |
*/
private function renderRow(array $row, string $cellFormat, string $firstCellFormat = null)
{
$rowContent = $this->renderColumnSeparator(self::BORDER_OUTSIDE);
$columns = $this->getRowColumns($row);
$last = \count($columns) - 1;
foreach ($columns as $i => $column) {
if ($firstCellFormat && 0 === $i) {
$rowContent .= $this->renderCell($row, $column, $firstCellFormat);
} else {
$rowContent .= $this->renderCell($row, $column, $cellFormat);
}
$rowContent .= $this->renderColumnSeparator($last === $i ? self::BORDER_OUTSIDE : self::BORDER_INSIDE);
}
$this->output->writeln($rowContent);
}
/**
* Renders table cell with padding.
*/
private function renderCell(array $row, int $column, string $cellFormat): string
{
$cell = $row[$column] ?? '';
$width = $this->effectiveColumnWidths[$column];
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// add the width of the following columns(numbers of colspan).
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $nextColumn) {
$width += $this->getColumnSeparatorWidth() + $this->effectiveColumnWidths[$nextColumn];
}
}
// str_pad won't work properly with multi-byte strings, we need to fix the padding
if (false !== $encoding = mb_detect_encoding($cell, null, true)) {
$width += \strlen($cell) - mb_strwidth($cell, $encoding);
}
$style = $this->getColumnStyle($column);
if ($cell instanceof TableSeparator) {
return sprintf($style->getBorderFormat(), str_repeat($style->getBorderChars()[2], $width));
}
$width += Helper::strlen($cell) - Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
$content = sprintf($style->getCellRowContentFormat(), $cell);
$padType = $style->getPadType();
if ($cell instanceof TableCell && $cell->getStyle() instanceof TableCellStyle) {
$isNotStyledByTag = !preg_match('/^<(\w+|(\w+=[\w,]+;?)*)>.+<\/(\w+|(\w+=\w+;?)*)?>$/', $cell);
if ($isNotStyledByTag) {
$cellFormat = $cell->getStyle()->getCellFormat();
if (!\is_string($cellFormat)) {
$tag = http_build_query($cell->getStyle()->getTagOptions(), null, ';');
$cellFormat = '<'.$tag.'>%s</>';
}
if (strstr($content, '</>')) {
$content = str_replace('</>', '', $content);
$width -= 3;
}
if (strstr($content, '<fg=default;bg=default>')) {
$content = str_replace('<fg=default;bg=default>', '', $content);
$width -= \strlen('<fg=default;bg=default>');
}
}
$padType = $cell->getStyle()->getPadByAlign();
}
return sprintf($cellFormat, str_pad($content, $width, $style->getPaddingChar(), $padType));
}
/**
* Calculate number of columns for this table.
*/
private function calculateNumberOfColumns(array $rows)
{
$columns = [0];
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
$columns[] = $this->getNumberOfColumns($row);
}
$this->numberOfColumns = max($columns);
}
private function buildTableRows(array $rows): TableRows
{
/** @var WrappableOutputFormatterInterface $formatter */
$formatter = $this->output->getFormatter();
$unmergedRows = [];
for ($rowKey = 0; $rowKey < \count($rows); ++$rowKey) {
$rows = $this->fillNextRows($rows, $rowKey);
// Remove any new line breaks and replace it with a new line
foreach ($rows[$rowKey] as $column => $cell) {
$colspan = $cell instanceof TableCell ? $cell->getColspan() : 1;
if (isset($this->columnMaxWidths[$column]) && Helper::strlenWithoutDecoration($formatter, $cell) > $this->columnMaxWidths[$column]) {
$cell = $formatter->formatAndWrap($cell, $this->columnMaxWidths[$column] * $colspan);
}
if (!strstr($cell, "\n")) {
continue;
}
$escaped = implode("\n", array_map([OutputFormatter::class, 'escapeTrailingBackslash'], explode("\n", $cell)));
$cell = $cell instanceof TableCell ? new TableCell($escaped, ['colspan' => $cell->getColspan()]) : $escaped;
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
foreach ($lines as $lineKey => $line) {
if ($colspan > 1) {
$line = new TableCell($line, ['colspan' => $colspan]);
}
if (0 === $lineKey) {
$rows[$rowKey][$column] = $line;
} else {
if (!\array_key_exists($rowKey, $unmergedRows) || !\array_key_exists($lineKey, $unmergedRows[$rowKey])) {
$unmergedRows[$rowKey][$lineKey] = $this->copyRow($rows, $rowKey);
}
$unmergedRows[$rowKey][$lineKey][$column] = $line;
}
}
}
}
return new TableRows(function () use ($rows, $unmergedRows): \Traversable {
foreach ($rows as $rowKey => $row) {
yield $this->fillCells($row);
if (isset($unmergedRows[$rowKey])) {
foreach ($unmergedRows[$rowKey] as $unmergedRow) {
yield $this->fillCells($unmergedRow);
}
}
}
});
}
private function calculateRowCount(): int
{
$numberOfRows = \count(iterator_to_array($this->buildTableRows(array_merge($this->headers, [new TableSeparator()], $this->rows))));
if ($this->headers) {
++$numberOfRows; // Add row for header separator
}
if (\count($this->rows) > 0) {
++$numberOfRows; // Add row for footer separator
}
return $numberOfRows;
}
/**
* fill rows that contains rowspan > 1.
*
* @throws InvalidArgumentException
*/
private function fillNextRows(array $rows, int $line): array
{
$unmergedRows = [];
foreach ($rows[$line] as $column => $cell) {
if (null !== $cell && !$cell instanceof TableCell && !is_scalar($cell) && !(\is_object($cell) && method_exists($cell, '__toString'))) {
throw new InvalidArgumentException(sprintf('A cell must be a TableCell, a scalar or an object implementing "__toString()", "%s" given.', get_debug_type($cell)));
}
if ($cell instanceof TableCell && $cell->getRowspan() > 1) {
$nbLines = $cell->getRowspan() - 1;
$lines = [$cell];
if (strstr($cell, "\n")) {
$lines = explode("\n", str_replace("\n", "<fg=default;bg=default>\n</>", $cell));
$nbLines = \count($lines) > $nbLines ? substr_count($cell, "\n") : $nbLines;
$rows[$line][$column] = new TableCell($lines[0], ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
unset($lines[0]);
}
// create a two dimensional array (rowspan x colspan)
$unmergedRows = array_replace_recursive(array_fill($line + 1, $nbLines, []), $unmergedRows);
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
$value = $lines[$unmergedRowKey - $line] ?? '';
$unmergedRows[$unmergedRowKey][$column] = new TableCell($value, ['colspan' => $cell->getColspan(), 'style' => $cell->getStyle()]);
if ($nbLines === $unmergedRowKey - $line) {
break;
}
}
}
}
foreach ($unmergedRows as $unmergedRowKey => $unmergedRow) {
// we need to know if $unmergedRow will be merged or inserted into $rows
if (isset($rows[$unmergedRowKey]) && \is_array($rows[$unmergedRowKey]) && ($this->getNumberOfColumns($rows[$unmergedRowKey]) + $this->getNumberOfColumns($unmergedRows[$unmergedRowKey]) <= $this->numberOfColumns)) {
foreach ($unmergedRow as $cellKey => $cell) {
// insert cell into row at cellKey position
array_splice($rows[$unmergedRowKey], $cellKey, 0, [$cell]);
}
} else {
$row = $this->copyRow($rows, $unmergedRowKey - 1);
foreach ($unmergedRow as $column => $cell) {
if (!empty($cell)) {
$row[$column] = $unmergedRow[$column];
}
}
array_splice($rows, $unmergedRowKey, 0, [$row]);
}
}
return $rows;
}
/**
* fill cells for a row that contains colspan > 1.
*/
private function fillCells($row)
{
$newRow = [];
foreach ($row as $column => $cell) {
$newRow[] = $cell;
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
foreach (range($column + 1, $column + $cell->getColspan() - 1) as $position) {
// insert empty value at column position
$newRow[] = '';
}
}
}
return $newRow ?: $row;
}
private function copyRow(array $rows, int $line): array
{
$row = $rows[$line];
foreach ($row as $cellKey => $cellValue) {
$row[$cellKey] = '';
if ($cellValue instanceof TableCell) {
$row[$cellKey] = new TableCell('', ['colspan' => $cellValue->getColspan()]);
}
}
return $row;
}
/**
* Gets number of columns by row.
*/
private function getNumberOfColumns(array $row): int
{
$columns = \count($row);
foreach ($row as $column) {
$columns += $column instanceof TableCell ? ($column->getColspan() - 1) : 0;
}
return $columns;
}
/**
* Gets list of columns for the given row.
*/
private function getRowColumns(array $row): array
{
$columns = range(0, $this->numberOfColumns - 1);
foreach ($row as $cellKey => $cell) {
if ($cell instanceof TableCell && $cell->getColspan() > 1) {
// exclude grouped columns.
$columns = array_diff($columns, range($cellKey + 1, $cellKey + $cell->getColspan() - 1));
}
}
return $columns;
}
/**
* Calculates columns widths.
*/
private function calculateColumnsWidth(iterable $rows)
{
for ($column = 0; $column < $this->numberOfColumns; ++$column) {
$lengths = [];
foreach ($rows as $row) {
if ($row instanceof TableSeparator) {
continue;
}
foreach ($row as $i => $cell) {
if ($cell instanceof TableCell) {
$textContent = Helper::removeDecoration($this->output->getFormatter(), $cell);
$textLength = Helper::strlen($textContent);
if ($textLength > 0) {
$contentColumns = str_split($textContent, ceil($textLength / $cell->getColspan()));
foreach ($contentColumns as $position => $content) {
$row[$i + $position] = $content;
}
}
}
}
$lengths[] = $this->getCellWidth($row, $column);
}
$this->effectiveColumnWidths[$column] = max($lengths) + Helper::strlen($this->style->getCellRowContentFormat()) - 2;
}
}
private function getColumnSeparatorWidth(): int
{
return Helper::strlen(sprintf($this->style->getBorderFormat(), $this->style->getBorderChars()[3]));
}
private function getCellWidth(array $row, int $column): int
{
$cellWidth = 0;
if (isset($row[$column])) {
$cell = $row[$column];
$cellWidth = Helper::strlenWithoutDecoration($this->output->getFormatter(), $cell);
}
$columnWidth = $this->columnWidths[$column] ?? 0;
$cellWidth = max($cellWidth, $columnWidth);
return isset($this->columnMaxWidths[$column]) ? min($this->columnMaxWidths[$column], $cellWidth) : $cellWidth;
}
/**
* Called after rendering to cleanup cache data.
*/
private function cleanup()
{
$this->effectiveColumnWidths = [];
$this->numberOfColumns = null;
}
private static function initStyles(): array
{
$borderless = new TableStyle();
$borderless
->setHorizontalBorderChars('=')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar(' ')
;
$compact = new TableStyle();
$compact
->setHorizontalBorderChars('')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar('')
->setCellRowContentFormat('%s')
;
$styleGuide = new TableStyle();
$styleGuide
->setHorizontalBorderChars('-')
->setVerticalBorderChars(' ')
->setDefaultCrossingChar(' ')
->setCellHeaderFormat('%s')
;
$box = (new TableStyle())
->setHorizontalBorderChars('─')
->setVerticalBorderChars('│')
->setCrossingChars('┼', '┌', '┬', 'â”<C3A2>', '┤', '┘', 'â”´', 'â””', '├')
;
$boxDouble = (new TableStyle())
->setHorizontalBorderChars('â•<C3A2>', '─')
->setVerticalBorderChars('║', '│')
->setCrossingChars('┼', 'â•”', '╤', 'â•—', 'â•¢', 'â•<C3A2>', '╧', 'â•š', 'â•Ÿ', 'â• ', '╪', 'â•£')
;
return [
'default' => new TableStyle(),
'borderless' => $borderless,
'compact' => $compact,
'symfony-style-guide' => $styleGuide,
'box' => $box,
'box-double' => $boxDouble,
];
}
private function resolveStyle($name): TableStyle
{
if ($name instanceof TableStyle) {
return $name;
}
if (isset(self::$styles[$name])) {
return self::$styles[$name];
}
throw new InvalidArgumentException(sprintf('Style "%s" is not defined.', $name));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* HelperSet represents a set of helpers to be used with a command.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class HelperSet implements \IteratorAggregate
{
/**
* @var Helper[]
*/
private $helpers = [];
private $command;
/**
* @param Helper[] $helpers An array of helper
*/
public function __construct(array $helpers = [])
{
foreach ($helpers as $alias => $helper) {
$this->set($helper, \is_int($alias) ? null : $alias);
}
}
public function set(HelperInterface $helper, string $alias = null)
{
$this->helpers[$helper->getName()] = $helper;
if (null !== $alias) {
$this->helpers[$alias] = $helper;
}
$helper->setHelperSet($this);
}
/**
* Returns true if the helper if defined.
*
* @return bool true if the helper is defined, false otherwise
*/
public function has(string $name)
{
return isset($this->helpers[$name]);
}
/**
* Gets a helper value.
*
* @return HelperInterface The helper instance
*
* @throws InvalidArgumentException if the helper is not defined
*/
public function get(string $name)
{
if (!$this->has($name)) {
throw new InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name));
}
return $this->helpers[$name];
}
public function setCommand(Command $command = null)
{
$this->command = $command;
}
/**
* Gets the command associated with this helper set.
*
* @return Command A Command instance
*/
public function getCommand()
{
return $this->command;
}
/**
* @return Helper[]
*/
public function getIterator()
{
return new \ArrayIterator($this->helpers);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Marks a row as being a separator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TableSeparator extends TableCell
{
public function __construct(array $options = [])
{
parent::__construct('', $options);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Input\InputAwareInterface;
use Symfony\Component\Console\Input\InputInterface;
/**
* An implementation of InputAwareInterface for Helpers.
*
* @author Wouter J <waldio.webdesign@gmail.com>
*/
abstract class InputAwareHelper extends Helper implements InputAwareInterface
{
protected $input;
/**
* {@inheritdoc}
*/
public function setInput(InputInterface $input)
{
$this->input = $input;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
/**
* @author Roland Franssen <franssen.roland@gmail.com>
*/
final class Dumper
{
private $output;
private $dumper;
private $cloner;
private $handler;
public function __construct(OutputInterface $output, CliDumper $dumper = null, ClonerInterface $cloner = null)
{
$this->output = $output;
$this->dumper = $dumper;
$this->cloner = $cloner;
if (class_exists(CliDumper::class)) {
$this->handler = function ($var): string {
$dumper = $this->dumper ?? $this->dumper = new CliDumper(null, null, CliDumper::DUMP_LIGHT_ARRAY | CliDumper::DUMP_COMMA_SEPARATOR);
$dumper->setColors($this->output->isDecorated());
return rtrim($dumper->dump(($this->cloner ?? $this->cloner = new VarCloner())->cloneVar($var)->withRefHandles(false), true));
};
} else {
$this->handler = function ($var): string {
switch (true) {
case null === $var:
return 'null';
case true === $var:
return 'true';
case false === $var:
return 'false';
case \is_string($var):
return '"'.$var.'"';
default:
return rtrim(print_r($var, true));
}
};
}
}
public function __invoke($var): string
{
return ($this->handler)($var);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Process;
/**
* The ProcessHelper class provides helpers to run external processes.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class ProcessHelper extends Helper
{
/**
* Runs an external process.
*
* @param array|Process $cmd An instance of Process or an array of the command and arguments
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The process that ran
*/
public function run(OutputInterface $output, $cmd, string $error = null, callable $callback = null, int $verbosity = OutputInterface::VERBOSITY_VERY_VERBOSE): Process
{
if (!class_exists(Process::class)) {
throw new \LogicException('The ProcessHelper cannot be run as the Process component is not installed. Try running "compose require symfony/process".');
}
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$formatter = $this->getHelperSet()->get('debug_formatter');
if ($cmd instanceof Process) {
$cmd = [$cmd];
}
if (!\is_array($cmd)) {
throw new \TypeError(sprintf('The "command" argument of "%s()" must be an array or a "%s" instance, "%s" given.', __METHOD__, Process::class, get_debug_type($cmd)));
}
if (\is_string($cmd[0] ?? null)) {
$process = new Process($cmd);
$cmd = [];
} elseif (($cmd[0] ?? null) instanceof Process) {
$process = $cmd[0];
unset($cmd[0]);
} else {
throw new \InvalidArgumentException(sprintf('Invalid command provided to "%s()": the command should be an array whose first element is either the path to the binary to run or a "Process" object.', __METHOD__));
}
if ($verbosity <= $output->getVerbosity()) {
$output->write($formatter->start(spl_object_hash($process), $this->escapeString($process->getCommandLine())));
}
if ($output->isDebug()) {
$callback = $this->wrapCallback($output, $process, $callback);
}
$process->run($callback, $cmd);
if ($verbosity <= $output->getVerbosity()) {
$message = $process->isSuccessful() ? 'Command ran successfully' : sprintf('%s Command did not run successfully', $process->getExitCode());
$output->write($formatter->stop(spl_object_hash($process), $message, $process->isSuccessful()));
}
if (!$process->isSuccessful() && null !== $error) {
$output->writeln(sprintf('<error>%s</error>', $this->escapeString($error)));
}
return $process;
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @param string|Process $cmd An instance of Process or a command to run
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return Process The process that ran
*
* @throws ProcessFailedException
*
* @see run()
*/
public function mustRun(OutputInterface $output, $cmd, string $error = null, callable $callback = null): Process
{
$process = $this->run($output, $cmd, $error, $callback);
if (!$process->isSuccessful()) {
throw new ProcessFailedException($process);
}
return $process;
}
/**
* Wraps a Process callback to add debugging output.
*/
public function wrapCallback(OutputInterface $output, Process $process, callable $callback = null): callable
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$formatter = $this->getHelperSet()->get('debug_formatter');
return function ($type, $buffer) use ($output, $process, $callback, $formatter) {
$output->write($formatter->progress(spl_object_hash($process), $this->escapeString($buffer), Process::ERR === $type));
if (null !== $callback) {
$callback($type, $buffer);
}
};
}
private function escapeString(string $str): string
{
return str_replace('<', '\\<', $str);
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'process';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Descriptor\DescriptorInterface;
use Symfony\Component\Console\Descriptor\JsonDescriptor;
use Symfony\Component\Console\Descriptor\MarkdownDescriptor;
use Symfony\Component\Console\Descriptor\TextDescriptor;
use Symfony\Component\Console\Descriptor\XmlDescriptor;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* This class adds helper method to describe objects in various formats.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class DescriptorHelper extends Helper
{
/**
* @var DescriptorInterface[]
*/
private $descriptors = [];
public function __construct()
{
$this
->register('txt', new TextDescriptor())
->register('xml', new XmlDescriptor())
->register('json', new JsonDescriptor())
->register('md', new MarkdownDescriptor())
;
}
/**
* Describes an object if supported.
*
* Available options are:
* * format: string, the output format name
* * raw_text: boolean, sets output type as raw
*
* @throws InvalidArgumentException when the given format is not supported
*/
public function describe(OutputInterface $output, ?object $object, array $options = [])
{
$options = array_merge([
'raw_text' => false,
'format' => 'txt',
], $options);
if (!isset($this->descriptors[$options['format']])) {
throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format']));
}
$descriptor = $this->descriptors[$options['format']];
$descriptor->describe($output, $object, $options);
}
/**
* Registers a descriptor.
*
* @return $this
*/
public function register(string $format, DescriptorInterface $descriptor)
{
$this->descriptors[$format] = $descriptor;
return $this;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'descriptor';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Terminal;
/**
* The ProgressBar provides helpers to display progress output.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Chris Jones <leeked@gmail.com>
*/
final class ProgressBar
{
private $barWidth = 28;
private $barChar;
private $emptyBarChar = '-';
private $progressChar = '>';
private $format;
private $internalFormat;
private $redrawFreq = 1;
private $writeCount;
private $lastWriteTime;
private $minSecondsBetweenRedraws = 0;
private $maxSecondsBetweenRedraws = 1;
private $output;
private $step = 0;
private $max;
private $startTime;
private $stepWidth;
private $percent = 0.0;
private $formatLineCount;
private $messages = [];
private $overwrite = true;
private $terminal;
private $previousMessage;
private $cursor;
private static $formatters;
private static $formats;
/**
* @param int $max Maximum steps (0 if unknown)
*/
public function __construct(OutputInterface $output, int $max = 0, float $minSecondsBetweenRedraws = 1 / 25)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->output = $output;
$this->setMaxSteps($max);
$this->terminal = new Terminal();
if (0 < $minSecondsBetweenRedraws) {
$this->redrawFreq = null;
$this->minSecondsBetweenRedraws = $minSecondsBetweenRedraws;
}
if (!$this->output->isDecorated()) {
// disable overwrite when output does not support ANSI codes.
$this->overwrite = false;
// set a reasonable redraw frequency so output isn't flooded
$this->redrawFreq = null;
}
$this->startTime = time();
$this->cursor = new Cursor($output);
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*
* @param string $name The placeholder name (including the delimiter char like %)
* @param callable $callable A PHP callable
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable): void
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name.
*
* @param string $name The placeholder name (including the delimiter char like %)
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition(string $name): ?callable
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
return self::$formatters[$name] ?? null;
}
/**
* Sets a format for a given name.
*
* This method also allow you to override an existing format.
*
* @param string $name The format name
* @param string $format A format string
*/
public static function setFormatDefinition(string $name, string $format): void
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
self::$formats[$name] = $format;
}
/**
* Gets the format for a given name.
*
* @param string $name The format name
*
* @return string|null A format string
*/
public static function getFormatDefinition(string $name): ?string
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return self::$formats[$name] ?? null;
}
/**
* Associates a text with a named placeholder.
*
* The text is displayed when the progress bar is rendered but only
* when the corresponding placeholder is part of the custom format line
* (by wrapping the name with %).
*
* @param string $message The text to associate with the placeholder
* @param string $name The name of the placeholder
*/
public function setMessage(string $message, string $name = 'message')
{
$this->messages[$name] = $message;
}
public function getMessage(string $name = 'message')
{
return $this->messages[$name];
}
public function getStartTime(): int
{
return $this->startTime;
}
public function getMaxSteps(): int
{
return $this->max;
}
public function getProgress(): int
{
return $this->step;
}
private function getStepWidth(): int
{
return $this->stepWidth;
}
public function getProgressPercent(): float
{
return $this->percent;
}
public function getBarOffset(): float
{
return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? min(5, $this->barWidth / 15) * $this->writeCount : $this->step) % $this->barWidth);
}
public function getEstimated(): float
{
if (!$this->step) {
return 0;
}
return round((time() - $this->startTime) / $this->step * $this->max);
}
public function getRemaining(): float
{
if (!$this->step) {
return 0;
}
return round((time() - $this->startTime) / $this->step * ($this->max - $this->step));
}
public function setBarWidth(int $size)
{
$this->barWidth = max(1, $size);
}
public function getBarWidth(): int
{
return $this->barWidth;
}
public function setBarCharacter(string $char)
{
$this->barChar = $char;
}
public function getBarCharacter(): string
{
if (null === $this->barChar) {
return $this->max ? '=' : $this->emptyBarChar;
}
return $this->barChar;
}
public function setEmptyBarCharacter(string $char)
{
$this->emptyBarChar = $char;
}
public function getEmptyBarCharacter(): string
{
return $this->emptyBarChar;
}
public function setProgressCharacter(string $char)
{
$this->progressChar = $char;
}
public function getProgressCharacter(): string
{
return $this->progressChar;
}
public function setFormat(string $format)
{
$this->format = null;
$this->internalFormat = $format;
}
/**
* Sets the redraw frequency.
*
* @param int|float $freq The frequency in steps
*/
public function setRedrawFrequency(?int $freq)
{
$this->redrawFreq = null !== $freq ? max(1, $freq) : null;
}
public function minSecondsBetweenRedraws(float $seconds): void
{
$this->minSecondsBetweenRedraws = $seconds;
}
public function maxSecondsBetweenRedraws(float $seconds): void
{
$this->maxSecondsBetweenRedraws = $seconds;
}
/**
* Returns an iterator that will automatically update the progress bar when iterated.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), if null it will be inferred from $iterable
*/
public function iterate(iterable $iterable, int $max = null): iterable
{
$this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0));
foreach ($iterable as $key => $value) {
yield $key => $value;
$this->advance();
}
$this->finish();
}
/**
* Starts the progress output.
*
* @param int|null $max Number of steps to complete the bar (0 if indeterminate), null to leave unchanged
*/
public function start(int $max = null)
{
$this->startTime = time();
$this->step = 0;
$this->percent = 0.0;
if (null !== $max) {
$this->setMaxSteps($max);
}
$this->display();
}
/**
* Advances the progress output X steps.
*
* @param int $step Number of steps to advance
*/
public function advance(int $step = 1)
{
$this->setProgress($this->step + $step);
}
/**
* Sets whether to overwrite the progressbar, false for new line.
*/
public function setOverwrite(bool $overwrite)
{
$this->overwrite = $overwrite;
}
public function setProgress(int $step)
{
if ($this->max && $step > $this->max) {
$this->max = $step;
} elseif ($step < 0) {
$step = 0;
}
$redrawFreq = $this->redrawFreq ?? (($this->max ?: 10) / 10);
$prevPeriod = (int) ($this->step / $redrawFreq);
$currPeriod = (int) ($step / $redrawFreq);
$this->step = $step;
$this->percent = $this->max ? (float) $this->step / $this->max : 0;
$timeInterval = microtime(true) - $this->lastWriteTime;
// Draw regardless of other limits
if ($this->max === $step) {
$this->display();
return;
}
// Throttling
if ($timeInterval < $this->minSecondsBetweenRedraws) {
return;
}
// Draw each step period, but not too late
if ($prevPeriod !== $currPeriod || $timeInterval >= $this->maxSecondsBetweenRedraws) {
$this->display();
}
}
public function setMaxSteps(int $max)
{
$this->format = null;
$this->max = max(0, $max);
$this->stepWidth = $this->max ? Helper::strlen((string) $this->max) : 4;
}
/**
* Finishes the progress output.
*/
public function finish(): void
{
if (!$this->max) {
$this->max = $this->step;
}
if ($this->step === $this->max && !$this->overwrite) {
// prevent double 100% output
return;
}
$this->setProgress($this->max);
}
/**
* Outputs the current progress string.
*/
public function display(): void
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite($this->buildLine());
}
/**
* Removes the progress bar from the current line.
*
* This is useful if you wish to write some output
* while a progress bar is running.
* Call display() to show the progress bar again.
*/
public function clear(): void
{
if (!$this->overwrite) {
return;
}
if (null === $this->format) {
$this->setRealFormat($this->internalFormat ?: $this->determineBestFormat());
}
$this->overwrite('');
}
private function setRealFormat(string $format)
{
// try to use the _nomax variant if available
if (!$this->max && null !== self::getFormatDefinition($format.'_nomax')) {
$this->format = self::getFormatDefinition($format.'_nomax');
} elseif (null !== self::getFormatDefinition($format)) {
$this->format = self::getFormatDefinition($format);
} else {
$this->format = $format;
}
$this->formatLineCount = substr_count($this->format, "\n");
}
/**
* Overwrites a previous message to the output.
*/
private function overwrite(string $message): void
{
if ($this->previousMessage === $message) {
return;
}
$originalMessage = $message;
if ($this->overwrite) {
if (null !== $this->previousMessage) {
if ($this->output instanceof ConsoleSectionOutput) {
$messageLines = explode("\n", $message);
$lineCount = \count($messageLines);
foreach ($messageLines as $messageLine) {
$messageLineLength = Helper::strlenWithoutDecoration($this->output->getFormatter(), $messageLine);
if ($messageLineLength > $this->terminal->getWidth()) {
$lineCount += floor($messageLineLength / $this->terminal->getWidth());
}
}
$this->output->clear($lineCount);
} else {
if ($this->formatLineCount > 0) {
$this->cursor->moveUp($this->formatLineCount);
}
$this->cursor->moveToColumn(1);
$this->cursor->clearLine();
}
}
} elseif ($this->step > 0) {
$message = \PHP_EOL.$message;
}
$this->previousMessage = $originalMessage;
$this->lastWriteTime = microtime(true);
$this->output->write($message);
++$this->writeCount;
}
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->max ? 'verbose' : 'verbose_nomax';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
return $this->max ? 'very_verbose' : 'very_verbose_nomax';
case OutputInterface::VERBOSITY_DEBUG:
return $this->max ? 'debug' : 'debug_nomax';
default:
return $this->max ? 'normal' : 'normal_nomax';
}
}
private static function initPlaceholderFormatters(): array
{
return [
'bar' => function (self $bar, OutputInterface $output) {
$completeBars = $bar->getBarOffset();
$display = str_repeat($bar->getBarCharacter(), $completeBars);
if ($completeBars < $bar->getBarWidth()) {
$emptyBars = $bar->getBarWidth() - $completeBars - Helper::strlenWithoutDecoration($output->getFormatter(), $bar->getProgressCharacter());
$display .= $bar->getProgressCharacter().str_repeat($bar->getEmptyBarCharacter(), $emptyBars);
}
return $display;
},
'elapsed' => function (self $bar) {
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.');
}
return Helper::formatTime($bar->getRemaining());
},
'estimated' => function (self $bar) {
if (!$bar->getMaxSteps()) {
throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
}
return Helper::formatTime($bar->getEstimated());
},
'memory' => function (self $bar) {
return Helper::formatMemory(memory_get_usage(true));
},
'current' => function (self $bar) {
return str_pad($bar->getProgress(), $bar->getStepWidth(), ' ', \STR_PAD_LEFT);
},
'max' => function (self $bar) {
return $bar->getMaxSteps();
},
'percent' => function (self $bar) {
return floor($bar->getProgressPercent() * 100);
},
];
}
private static function initFormats(): array
{
return [
'normal' => ' %current%/%max% [%bar%] %percent:3s%%',
'normal_nomax' => ' %current% [%bar%]',
'verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%',
'verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'very_verbose' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%',
'very_verbose_nomax' => ' %current% [%bar%] %elapsed:6s%',
'debug' => ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%',
'debug_nomax' => ' %current% [%bar%] %elapsed:6s% %memory:6s%',
];
}
private function buildLine(): string
{
$regex = "{%([a-z\-_]+)(?:\:([^%]+))?%}i";
$callback = function ($matches) {
if ($formatter = $this::getPlaceholderFormatterDefinition($matches[1])) {
$text = $formatter($this, $this->output);
} elseif (isset($this->messages[$matches[1]])) {
$text = $this->messages[$matches[1]];
} else {
return $matches[0];
}
if (isset($matches[2])) {
$text = sprintf('%'.$matches[2], $text);
}
return $text;
};
$line = preg_replace_callback($regex, $callback, $this->format);
// gets string length for each sub line with multiline format
$linesLength = array_map(function ($subLine) {
return Helper::strlenWithoutDecoration($this->output->getFormatter(), rtrim($subLine, "\r"));
}, explode("\n", $line));
$linesWidth = max($linesLength);
$terminalWidth = $this->terminal->getWidth();
if ($linesWidth <= $terminalWidth) {
return $line;
}
$this->setBarWidth($this->barWidth - $linesWidth + $terminalWidth);
return preg_replace_callback($regex, $callback, $this->format);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
/**
* Defines the styles for a Table.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Саша Стаменковић <umpirsky@gmail.com>
* @author Dany Maillard <danymaillard93b@gmail.com>
*/
class TableStyle
{
private $paddingChar = ' ';
private $horizontalOutsideBorderChar = '-';
private $horizontalInsideBorderChar = '-';
private $verticalOutsideBorderChar = '|';
private $verticalInsideBorderChar = '|';
private $crossingChar = '+';
private $crossingTopRightChar = '+';
private $crossingTopMidChar = '+';
private $crossingTopLeftChar = '+';
private $crossingMidRightChar = '+';
private $crossingBottomRightChar = '+';
private $crossingBottomMidChar = '+';
private $crossingBottomLeftChar = '+';
private $crossingMidLeftChar = '+';
private $crossingTopLeftBottomChar = '+';
private $crossingTopMidBottomChar = '+';
private $crossingTopRightBottomChar = '+';
private $headerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $footerTitleFormat = '<fg=black;bg=white;options=bold> %s </>';
private $cellHeaderFormat = '<info>%s</info>';
private $cellRowFormat = '%s';
private $cellRowContentFormat = ' %s ';
private $borderFormat = '%s';
private $padType = \STR_PAD_RIGHT;
/**
* Sets padding character, used for cell padding.
*
* @return $this
*/
public function setPaddingChar(string $paddingChar)
{
if (!$paddingChar) {
throw new LogicException('The padding char must not be empty.');
}
$this->paddingChar = $paddingChar;
return $this;
}
/**
* Gets padding character, used for cell padding.
*
* @return string
*/
public function getPaddingChar()
{
return $this->paddingChar;
}
/**
* Sets horizontal border characters.
*
* <code>
* â•”â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╤â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╤â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•—
* 1 ISBN 2 Title │ Author ║
* â• â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╪â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╪â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•£
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* â•šâ•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╧â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╧â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>
* </code>
*/
public function setHorizontalBorderChars(string $outside, string $inside = null): self
{
$this->horizontalOutsideBorderChar = $outside;
$this->horizontalInsideBorderChar = $inside ?? $outside;
return $this;
}
/**
* Sets vertical border characters.
*
* <code>
* â•”â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╤â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╤â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•—
* ║ ISBN │ Title │ Author ║
* â• â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>1â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╪â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╪â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•£
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* ╟───────2───────┼──────────────────────────┼──────────────────╢
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* â•šâ•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╧â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>╧â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>
* </code>
*/
public function setVerticalBorderChars(string $outside, string $inside = null): self
{
$this->verticalOutsideBorderChar = $outside;
$this->verticalInsideBorderChar = $inside ?? $outside;
return $this;
}
/**
* Gets border characters.
*
* @internal
*/
public function getBorderChars(): array
{
return [
$this->horizontalOutsideBorderChar,
$this->verticalOutsideBorderChar,
$this->horizontalInsideBorderChar,
$this->verticalInsideBorderChar,
];
}
/**
* Sets crossing characters.
*
* Example:
* <code>
* 1â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>2â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>2â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>3
* ║ ISBN │ Title │ Author ║
* 8'â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>0'â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>0'â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>4'
* ║ 99921-58-10-7 │ Divine Comedy │ Dante Alighieri ║
* ║ 9971-5-0210-0 │ A Tale of Two Cities │ Charles Dickens ║
* 8───────────────0──────────────────────────0──────────────────4
* ║ 960-425-059-0 │ The Lord of the Rings │ J. R. R. Tolkien ║
* ║ 80-902734-1-6 │ And Then There Were None │ Agatha Christie ║
* 7â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>6â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>6â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>â•<C3A2>5
* </code>
*
* @param string $cross Crossing char (see #0 of example)
* @param string $topLeft Top left char (see #1 of example)
* @param string $topMid Top mid char (see #2 of example)
* @param string $topRight Top right char (see #3 of example)
* @param string $midRight Mid right char (see #4 of example)
* @param string $bottomRight Bottom right char (see #5 of example)
* @param string $bottomMid Bottom mid char (see #6 of example)
* @param string $bottomLeft Bottom left char (see #7 of example)
* @param string $midLeft Mid left char (see #8 of example)
* @param string|null $topLeftBottom Top left bottom char (see #8' of example), equals to $midLeft if null
* @param string|null $topMidBottom Top mid bottom char (see #0' of example), equals to $cross if null
* @param string|null $topRightBottom Top right bottom char (see #4' of example), equals to $midRight if null
*/
public function setCrossingChars(string $cross, string $topLeft, string $topMid, string $topRight, string $midRight, string $bottomRight, string $bottomMid, string $bottomLeft, string $midLeft, string $topLeftBottom = null, string $topMidBottom = null, string $topRightBottom = null): self
{
$this->crossingChar = $cross;
$this->crossingTopLeftChar = $topLeft;
$this->crossingTopMidChar = $topMid;
$this->crossingTopRightChar = $topRight;
$this->crossingMidRightChar = $midRight;
$this->crossingBottomRightChar = $bottomRight;
$this->crossingBottomMidChar = $bottomMid;
$this->crossingBottomLeftChar = $bottomLeft;
$this->crossingMidLeftChar = $midLeft;
$this->crossingTopLeftBottomChar = $topLeftBottom ?? $midLeft;
$this->crossingTopMidBottomChar = $topMidBottom ?? $cross;
$this->crossingTopRightBottomChar = $topRightBottom ?? $midRight;
return $this;
}
/**
* Sets default crossing character used for each cross.
*
* @see {@link setCrossingChars()} for setting each crossing individually.
*/
public function setDefaultCrossingChar(string $char): self
{
return $this->setCrossingChars($char, $char, $char, $char, $char, $char, $char, $char, $char);
}
/**
* Gets crossing character.
*
* @return string
*/
public function getCrossingChar()
{
return $this->crossingChar;
}
/**
* Gets crossing characters.
*
* @internal
*/
public function getCrossingChars(): array
{
return [
$this->crossingChar,
$this->crossingTopLeftChar,
$this->crossingTopMidChar,
$this->crossingTopRightChar,
$this->crossingMidRightChar,
$this->crossingBottomRightChar,
$this->crossingBottomMidChar,
$this->crossingBottomLeftChar,
$this->crossingMidLeftChar,
$this->crossingTopLeftBottomChar,
$this->crossingTopMidBottomChar,
$this->crossingTopRightBottomChar,
];
}
/**
* Sets header cell format.
*
* @return $this
*/
public function setCellHeaderFormat(string $cellHeaderFormat)
{
$this->cellHeaderFormat = $cellHeaderFormat;
return $this;
}
/**
* Gets header cell format.
*
* @return string
*/
public function getCellHeaderFormat()
{
return $this->cellHeaderFormat;
}
/**
* Sets row cell format.
*
* @return $this
*/
public function setCellRowFormat(string $cellRowFormat)
{
$this->cellRowFormat = $cellRowFormat;
return $this;
}
/**
* Gets row cell format.
*
* @return string
*/
public function getCellRowFormat()
{
return $this->cellRowFormat;
}
/**
* Sets row cell content format.
*
* @return $this
*/
public function setCellRowContentFormat(string $cellRowContentFormat)
{
$this->cellRowContentFormat = $cellRowContentFormat;
return $this;
}
/**
* Gets row cell content format.
*
* @return string
*/
public function getCellRowContentFormat()
{
return $this->cellRowContentFormat;
}
/**
* Sets table border format.
*
* @return $this
*/
public function setBorderFormat(string $borderFormat)
{
$this->borderFormat = $borderFormat;
return $this;
}
/**
* Gets table border format.
*
* @return string
*/
public function getBorderFormat()
{
return $this->borderFormat;
}
/**
* Sets cell padding type.
*
* @return $this
*/
public function setPadType(int $padType)
{
if (!\in_array($padType, [\STR_PAD_LEFT, \STR_PAD_RIGHT, \STR_PAD_BOTH], true)) {
throw new InvalidArgumentException('Invalid padding type. Expected one of (STR_PAD_LEFT, STR_PAD_RIGHT, STR_PAD_BOTH).');
}
$this->padType = $padType;
return $this;
}
/**
* Gets cell padding type.
*
* @return int
*/
public function getPadType()
{
return $this->padType;
}
public function getHeaderTitleFormat(): string
{
return $this->headerTitleFormat;
}
public function setHeaderTitleFormat(string $format): self
{
$this->headerTitleFormat = $format;
return $this;
}
public function getFooterTitleFormat(): string
{
return $this->footerTitleFormat;
}
public function setFooterTitleFormat(string $format): self
{
$this->footerTitleFormat = $format;
return $this;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* @internal
*/
class TableRows implements \IteratorAggregate
{
private $generator;
public function __construct(callable $generator)
{
$this->generator = $generator;
}
public function getIterator(): \Traversable
{
$g = $this->generator;
return $g();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatter;
/**
* The Formatter class provides helpers to format messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FormatterHelper extends Helper
{
/**
* Formats a message within a section.
*
* @return string The format section
*/
public function formatSection(string $section, string $message, string $style = 'info')
{
return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message);
}
/**
* Formats a message as a block of text.
*
* @param string|array $messages The message to write in the block
*
* @return string The formatter message
*/
public function formatBlock($messages, string $style, bool $large = false)
{
if (!\is_array($messages)) {
$messages = [$messages];
}
$len = 0;
$lines = [];
foreach ($messages as $message) {
$message = OutputFormatter::escape($message);
$lines[] = sprintf($large ? ' %s ' : ' %s ', $message);
$len = max(self::strlen($message) + ($large ? 4 : 2), $len);
}
$messages = $large ? [str_repeat(' ', $len)] : [];
for ($i = 0; isset($lines[$i]); ++$i) {
$messages[] = $lines[$i].str_repeat(' ', $len - self::strlen($lines[$i]));
}
if ($large) {
$messages[] = str_repeat(' ', $len);
}
for ($i = 0; isset($messages[$i]); ++$i) {
$messages[$i] = sprintf('<%s>%s</%s>', $style, $messages[$i], $style);
}
return implode("\n", $messages);
}
/**
* Truncates a message to the given length.
*
* @return string
*/
public function truncate(string $message, int $length, string $suffix = '...')
{
$computedLength = $length - self::strlen($suffix);
if ($computedLength > self::strlen($message)) {
return $message;
}
return self::substr($message, 0, $length).$suffix;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'formatter';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* HelperInterface is the interface all helpers must implement.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface HelperInterface
{
/**
* Sets the helper set associated with this helper.
*/
public function setHelperSet(HelperSet $helperSet = null);
/**
* Gets the helper set associated with this helper.
*
* @return HelperSet A HelperSet instance
*/
public function getHelperSet();
/**
* Returns the canonical name of this helper.
*
* @return string The canonical name
*/
public function getName();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Cursor;
use Symfony\Component\Console\Exception\MissingInputException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StreamableInputInterface;
use Symfony\Component\Console\Output\ConsoleOutputInterface;
use Symfony\Component\Console\Output\ConsoleSectionOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Terminal;
use function Symfony\Component\String\s;
/**
* The QuestionHelper class provides helpers to interact with the user.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class QuestionHelper extends Helper
{
private $inputStream;
private static $shell;
private static $stty = true;
private static $stdinIsInteractive;
/**
* Asks a question to the user.
*
* @return mixed The user answer
*
* @throws RuntimeException If there is no data to read in the input stream
*/
public function ask(InputInterface $input, OutputInterface $output, Question $question)
{
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
if (!$input->isInteractive()) {
return $this->getDefaultAnswer($question);
}
if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
$this->inputStream = $stream;
}
try {
if (!$question->getValidator()) {
return $this->doAsk($output, $question);
}
$interviewer = function () use ($output, $question) {
return $this->doAsk($output, $question);
};
return $this->validateAttempts($interviewer, $output, $question);
} catch (MissingInputException $exception) {
$input->setInteractive(false);
if (null === $fallbackOutput = $this->getDefaultAnswer($question)) {
throw $exception;
}
return $fallbackOutput;
}
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'question';
}
/**
* Prevents usage of stty.
*/
public static function disableStty()
{
self::$stty = false;
}
/**
* Asks the question to the user.
*
* @return bool|mixed|string|null
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function doAsk(OutputInterface $output, Question $question)
{
$this->writePrompt($output, $question);
$inputStream = $this->inputStream ?: \STDIN;
$autocomplete = $question->getAutocompleterCallback();
if (\function_exists('sapi_windows_cp_set')) {
// Codepage used by cmd.exe on Windows to allow special characters (éàüñ).
@sapi_windows_cp_set(1252);
}
if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
try {
$hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
$ret = $question->isTrimmable() ? trim($hiddenResponse) : $hiddenResponse;
} catch (RuntimeException $e) {
if (!$question->isHiddenFallback()) {
throw $e;
}
}
}
if (false === $ret) {
$ret = $this->readInput($inputStream, $question);
if (false === $ret) {
throw new MissingInputException('Aborted.');
}
if ($question->isTrimmable()) {
$ret = trim($ret);
}
}
} else {
$autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
$ret = $question->isTrimmable() ? trim($autocomplete) : $autocomplete;
}
if ($output instanceof ConsoleSectionOutput) {
$output->addContent($ret);
}
$ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
if ($normalizer = $question->getNormalizer()) {
return $normalizer($ret);
}
return $ret;
}
/**
* @return mixed
*/
private function getDefaultAnswer(Question $question)
{
$default = $question->getDefault();
if (null === $default) {
return $default;
}
if ($validator = $question->getValidator()) {
return \call_user_func($question->getValidator(), $default);
} elseif ($question instanceof ChoiceQuestion) {
$choices = $question->getChoices();
if (!$question->isMultiselect()) {
return $choices[$default] ?? $default;
}
$default = explode(',', $default);
foreach ($default as $k => $v) {
$v = $question->isTrimmable() ? trim($v) : $v;
$default[$k] = $choices[$v] ?? $v;
}
}
return $default;
}
/**
* Outputs the question prompt.
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$message = $question->getQuestion();
if ($question instanceof ChoiceQuestion) {
$output->writeln(array_merge([
$question->getQuestion(),
], $this->formatChoiceQuestionChoices($question, 'info')));
$message = $question->getPrompt();
}
$output->write($message);
}
/**
* @return string[]
*/
protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag)
{
$messages = [];
$maxWidth = max(array_map('self::strlen', array_keys($choices = $question->getChoices())));
foreach ($choices as $key => $value) {
$padding = str_repeat(' ', $maxWidth - self::strlen($key));
$messages[] = sprintf(" [<$tag>%s$padding</$tag>] %s", $key, $value);
}
return $messages;
}
/**
* Outputs an error message.
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
$message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
} else {
$message = '<error>'.$error->getMessage().'</error>';
}
$output->writeln($message);
}
/**
* Autocompletes a question.
*
* @param resource $inputStream
*/
private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete): string
{
$cursor = new Cursor($output, $inputStream);
$fullChoice = '';
$ret = '';
$i = 0;
$ofs = -1;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
$sttyMode = shell_exec('stty -g');
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
shell_exec('stty -icanon -echo');
// Add highlighted text style
$output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
// Read a keypress
while (!feof($inputStream)) {
$c = fread($inputStream, 1);
// as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
if (false === $c || ('' === $ret && '' === $c && null === $question->getDefault())) {
shell_exec(sprintf('stty %s', $sttyMode));
throw new MissingInputException('Aborted.');
} elseif ("\177" === $c) { // Backspace Character
if (0 === $numMatches && 0 !== $i) {
--$i;
$cursor->moveLeft(s($fullChoice)->slice(-1)->width(false));
$fullChoice = self::substr($fullChoice, 0, $i);
}
if (0 === $i) {
$ofs = -1;
$matches = $autocomplete($ret);
$numMatches = \count($matches);
} else {
$numMatches = 0;
}
// Pop the last character off the end of our string
$ret = self::substr($ret, 0, $i);
} elseif ("\033" === $c) {
// Did we read an escape sequence?
$c .= fread($inputStream, 2);
// A = Up Arrow. B = Down Arrow
if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
if ('A' === $c[2] && -1 === $ofs) {
$ofs = 0;
}
if (0 === $numMatches) {
continue;
}
$ofs += ('A' === $c[2]) ? -1 : 1;
$ofs = ($numMatches + $ofs) % $numMatches;
}
} elseif (\ord($c) < 32) {
if ("\t" === $c || "\n" === $c) {
if ($numMatches > 0 && -1 !== $ofs) {
$ret = (string) $matches[$ofs];
// Echo out remaining chars for current match
$remainingCharacters = substr($ret, \strlen(trim($this->mostRecentlyEnteredValue($fullChoice))));
$output->write($remainingCharacters);
$fullChoice .= $remainingCharacters;
$i = (false === $encoding = mb_detect_encoding($fullChoice, null, true)) ? \strlen($fullChoice) : mb_strlen($fullChoice, $encoding);
$matches = array_filter(
$autocomplete($ret),
function ($match) use ($ret) {
return '' === $ret || 0 === strpos($match, $ret);
}
);
$numMatches = \count($matches);
$ofs = -1;
}
if ("\n" === $c) {
$output->write($c);
break;
}
$numMatches = 0;
}
continue;
} else {
if ("\x80" <= $c) {
$c .= fread($inputStream, ["\xC0" => 1, "\xD0" => 1, "\xE0" => 2, "\xF0" => 3][$c & "\xF0"]);
}
$output->write($c);
$ret .= $c;
$fullChoice .= $c;
++$i;
$tempRet = $ret;
if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
$tempRet = $this->mostRecentlyEnteredValue($fullChoice);
}
$numMatches = 0;
$ofs = 0;
foreach ($autocomplete($ret) as $value) {
// If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
if (0 === strpos($value, $tempRet)) {
$matches[$numMatches++] = $value;
}
}
}
$cursor->clearLineAfter();
if ($numMatches > 0 && -1 !== $ofs) {
$cursor->savePosition();
// Write highlighted text, complete the partially entered response
$charactersEntered = \strlen(trim($this->mostRecentlyEnteredValue($fullChoice)));
$output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $charactersEntered)).'</hl>');
$cursor->restorePosition();
}
}
// Reset stty so it behaves normally again
shell_exec(sprintf('stty %s', $sttyMode));
return $fullChoice;
}
private function mostRecentlyEnteredValue(string $entered): string
{
// Determine the most recent value that the user entered
if (false === strpos($entered, ',')) {
return $entered;
}
$choices = explode(',', $entered);
if (\strlen($lastChoice = trim($choices[\count($choices) - 1])) > 0) {
return $lastChoice;
}
return $entered;
}
/**
* Gets a hidden response from user.
*
* @param resource $inputStream The handler resource
* @param bool $trimmable Is the answer trimmable
*
* @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
*/
private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = true): string
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
// handle code running from a phar
if ('phar:' === substr(__FILE__, 0, 5)) {
$tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
copy($exe, $tmpExe);
$exe = $tmpExe;
}
$sExec = shell_exec('"'.$exe.'"');
$value = $trimmable ? rtrim($sExec) : $sExec;
$output->writeln('');
if (isset($tmpExe)) {
unlink($tmpExe);
}
return $value;
}
if (self::$stty && Terminal::hasSttyAvailable()) {
$sttyMode = shell_exec('stty -g');
shell_exec('stty -echo');
} elseif ($this->isInteractiveInput($inputStream)) {
throw new RuntimeException('Unable to hide the response.');
}
$value = fgets($inputStream, 4096);
if (self::$stty && Terminal::hasSttyAvailable()) {
shell_exec(sprintf('stty %s', $sttyMode));
}
if (false === $value) {
throw new MissingInputException('Aborted.');
}
if ($trimmable) {
$value = trim($value);
}
$output->writeln('');
return $value;
}
/**
* Validates an attempt.
*
* @param callable $interviewer A callable that will ask for a question and return the result
*
* @return mixed The validated response
*
* @throws \Exception In case the max number of attempts has been reached and no valid response has been given
*/
private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
{
$error = null;
$attempts = $question->getMaxAttempts();
while (null === $attempts || $attempts--) {
if (null !== $error) {
$this->writeError($output, $error);
}
try {
return $question->getValidator()($interviewer());
} catch (RuntimeException $e) {
throw $e;
} catch (\Exception $error) {
}
}
throw $error;
}
private function isInteractiveInput($inputStream): bool
{
if ('php://stdin' !== (stream_get_meta_data($inputStream)['uri'] ?? null)) {
return false;
}
if (null !== self::$stdinIsInteractive) {
return self::$stdinIsInteractive;
}
if (\function_exists('stream_isatty')) {
return self::$stdinIsInteractive = stream_isatty(fopen('php://stdin', 'r'));
}
if (\function_exists('posix_isatty')) {
return self::$stdinIsInteractive = posix_isatty(fopen('php://stdin', 'r'));
}
if (!\function_exists('exec')) {
return self::$stdinIsInteractive = true;
}
exec('stty 2> /dev/null', $output, $status);
return self::$stdinIsInteractive = 1 !== $status;
}
/**
* Reads one or more lines of input and returns what is read.
*
* @param resource $inputStream The handler resource
* @param Question $question The question being asked
*
* @return string|bool The input received, false in case input could not be read
*/
private function readInput($inputStream, Question $question)
{
if (!$question->isMultiline()) {
return fgets($inputStream, 4096);
}
$multiLineStreamReader = $this->cloneInputStream($inputStream);
if (null === $multiLineStreamReader) {
return false;
}
$ret = '';
while (false !== ($char = fgetc($multiLineStreamReader))) {
if (\PHP_EOL === "{$ret}{$char}") {
break;
}
$ret .= $char;
}
return $ret;
}
/**
* Clones an input stream in order to act on one instance of the same
* stream without affecting the other instance.
*
* @param resource $inputStream The handler resource
*
* @return resource|null The cloned resource, null in case it could not be cloned
*/
private function cloneInputStream($inputStream)
{
$streamMetaData = stream_get_meta_data($inputStream);
$seekable = $streamMetaData['seekable'] ?? false;
$mode = $streamMetaData['mode'] ?? 'rb';
$uri = $streamMetaData['uri'] ?? null;
if (null === $uri) {
return null;
}
$cloneStream = fopen($uri, $mode);
// For seekable and writable streams, add all the same data to the
// cloned stream and then seek to the same offset.
if (true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
$offset = ftell($inputStream);
rewind($inputStream);
stream_copy_to_stream($inputStream, $cloneStream);
fseek($inputStream, $offset);
fseek($cloneStream, $offset);
}
return $cloneStream;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\String\UnicodeString;
/**
* Helper is the base class for all helper classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class Helper implements HelperInterface
{
protected $helperSet = null;
/**
* {@inheritdoc}
*/
public function setHelperSet(HelperSet $helperSet = null)
{
$this->helperSet = $helperSet;
}
/**
* {@inheritdoc}
*/
public function getHelperSet()
{
return $this->helperSet;
}
/**
* Returns the length of a string, using mb_strwidth if it is available.
*
* @return int The length of the string
*/
public static function strlen(?string $string)
{
$string ?? $string = '';
if (preg_match('//u', $string)) {
return (new UnicodeString($string))->width(false);
}
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return \strlen($string);
}
return mb_strwidth($string, $encoding);
}
/**
* Returns the subset of a string, using mb_substr if it is available.
*
* @return string The string subset
*/
public static function substr(?string $string, int $from, int $length = null)
{
$string ?? $string = '';
if (false === $encoding = mb_detect_encoding($string, null, true)) {
return substr($string, $from, $length);
}
return mb_substr($string, $from, $length, $encoding);
}
public static function formatTime($secs)
{
static $timeFormats = [
[0, '< 1 sec'],
[1, '1 sec'],
[2, 'secs', 1],
[60, '1 min'],
[120, 'mins', 60],
[3600, '1 hr'],
[7200, 'hrs', 3600],
[86400, '1 day'],
[172800, 'days', 86400],
];
foreach ($timeFormats as $index => $format) {
if ($secs >= $format[0]) {
if ((isset($timeFormats[$index + 1]) && $secs < $timeFormats[$index + 1][0])
|| $index == \count($timeFormats) - 1
) {
if (2 == \count($format)) {
return $format[1];
}
return floor($secs / $format[2]).' '.$format[1];
}
}
}
}
public static function formatMemory(int $memory)
{
if ($memory >= 1024 * 1024 * 1024) {
return sprintf('%.1f GiB', $memory / 1024 / 1024 / 1024);
}
if ($memory >= 1024 * 1024) {
return sprintf('%.1f MiB', $memory / 1024 / 1024);
}
if ($memory >= 1024) {
return sprintf('%d KiB', $memory / 1024);
}
return sprintf('%d B', $memory);
}
public static function strlenWithoutDecoration(OutputFormatterInterface $formatter, ?string $string)
{
$string = self::removeDecoration($formatter, $string);
if (preg_match('//u', $string)) {
return (new UnicodeString($string))->width(true);
}
return self::strlen($string);
}
public static function removeDecoration(OutputFormatterInterface $formatter, ?string $string)
{
$isDecorated = $formatter->isDecorated();
$formatter->setDecorated(false);
// remove <...> formatting
$string = $formatter->format($string ?? '');
// remove already formatted characters
$string = preg_replace("/\033\[[^m]*m/", '', $string);
$formatter->setDecorated($isDecorated);
return $string;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
/**
* Helps outputting debug information when running an external program from a command.
*
* An external program can be a Process, an HTTP request, or anything else.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DebugFormatterHelper extends Helper
{
private $colors = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'default'];
private $started = [];
private $count = -1;
/**
* Starts a debug formatting session.
*
* @return string
*/
public function start(string $id, string $message, string $prefix = 'RUN')
{
$this->started[$id] = ['border' => ++$this->count % \count($this->colors)];
return sprintf("%s<bg=blue;fg=white> %s </> <fg=blue>%s</>\n", $this->getBorder($id), $prefix, $message);
}
/**
* Adds progress to a formatting session.
*
* @return string
*/
public function progress(string $id, string $buffer, bool $error = false, string $prefix = 'OUT', string $errorPrefix = 'ERR')
{
$message = '';
if ($error) {
if (isset($this->started[$id]['out'])) {
$message .= "\n";
unset($this->started[$id]['out']);
}
if (!isset($this->started[$id]['err'])) {
$message .= sprintf('%s<bg=red;fg=white> %s </> ', $this->getBorder($id), $errorPrefix);
$this->started[$id]['err'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=red;fg=white> %s </> ", $this->getBorder($id), $errorPrefix), $buffer);
} else {
if (isset($this->started[$id]['err'])) {
$message .= "\n";
unset($this->started[$id]['err']);
}
if (!isset($this->started[$id]['out'])) {
$message .= sprintf('%s<bg=green;fg=white> %s </> ', $this->getBorder($id), $prefix);
$this->started[$id]['out'] = true;
}
$message .= str_replace("\n", sprintf("\n%s<bg=green;fg=white> %s </> ", $this->getBorder($id), $prefix), $buffer);
}
return $message;
}
/**
* Stops a formatting session.
*
* @return string
*/
public function stop(string $id, string $message, bool $successful, string $prefix = 'RES')
{
$trailingEOL = isset($this->started[$id]['out']) || isset($this->started[$id]['err']) ? "\n" : '';
if ($successful) {
return sprintf("%s%s<bg=green;fg=white> %s </> <fg=green>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
}
$message = sprintf("%s%s<bg=red;fg=white> %s </> <fg=red>%s</>\n", $trailingEOL, $this->getBorder($id), $prefix, $message);
unset($this->started[$id]['out'], $this->started[$id]['err']);
return $message;
}
private function getBorder(string $id): string
{
return sprintf('<bg=%s> </>', $this->colors[$this->started[$id]['border']]);
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'debug_formatter';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
use Symfony\Component\Console\Question\ConfirmationQuestion;
use Symfony\Component\Console\Question\Question;
use Symfony\Component\Console\Style\SymfonyStyle;
/**
* Symfony Style Guide compliant question helper.
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
class SymfonyQuestionHelper extends QuestionHelper
{
/**
* {@inheritdoc}
*/
protected function writePrompt(OutputInterface $output, Question $question)
{
$text = OutputFormatter::escapeTrailingBackslash($question->getQuestion());
$default = $question->getDefault();
if ($question->isMultiline()) {
$text .= sprintf(' (press %s to continue)', $this->getEofShortcut());
}
switch (true) {
case null === $default:
$text = sprintf(' <info>%s</info>:', $text);
break;
case $question instanceof ConfirmationQuestion:
$text = sprintf(' <info>%s (yes/no)</info> [<comment>%s</comment>]:', $text, $default ? 'yes' : 'no');
break;
case $question instanceof ChoiceQuestion && $question->isMultiselect():
$choices = $question->getChoices();
$default = explode(',', $default);
foreach ($default as $key => $value) {
$default[$key] = $choices[trim($value)];
}
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape(implode(', ', $default)));
break;
case $question instanceof ChoiceQuestion:
$choices = $question->getChoices();
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($choices[$default] ?? $default));
break;
default:
$text = sprintf(' <info>%s</info> [<comment>%s</comment>]:', $text, OutputFormatter::escape($default));
}
$output->writeln($text);
$prompt = ' > ';
if ($question instanceof ChoiceQuestion) {
$output->writeln($this->formatChoiceQuestionChoices($question, 'comment'));
$prompt = $question->getPrompt();
}
$output->write($prompt);
}
/**
* {@inheritdoc}
*/
protected function writeError(OutputInterface $output, \Exception $error)
{
if ($output instanceof SymfonyStyle) {
$output->newLine();
$output->error($error->getMessage());
return;
}
parent::writeError($output, $error);
}
private function getEofShortcut(): string
{
if (false !== strpos(\PHP_OS, 'WIN')) {
return '<comment>Ctrl+Z</comment> then <comment>Enter</comment>';
}
return '<comment>Ctrl+D</comment>';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\LogicException;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
class ProgressIndicator
{
private $output;
private $startTime;
private $format;
private $message;
private $indicatorValues;
private $indicatorCurrent;
private $indicatorChangeInterval;
private $indicatorUpdateTime;
private $started = false;
private static $formatters;
private static $formats;
/**
* @param int $indicatorChangeInterval Change interval in milliseconds
* @param array|null $indicatorValues Animated indicator characters
*/
public function __construct(OutputInterface $output, string $format = null, int $indicatorChangeInterval = 100, array $indicatorValues = null)
{
$this->output = $output;
if (null === $format) {
$format = $this->determineBestFormat();
}
if (null === $indicatorValues) {
$indicatorValues = ['-', '\\', '|', '/'];
}
$indicatorValues = array_values($indicatorValues);
if (2 > \count($indicatorValues)) {
throw new InvalidArgumentException('Must have at least 2 indicator value characters.');
}
$this->format = self::getFormatDefinition($format);
$this->indicatorChangeInterval = $indicatorChangeInterval;
$this->indicatorValues = $indicatorValues;
$this->startTime = time();
}
/**
* Sets the current indicator message.
*/
public function setMessage(?string $message)
{
$this->message = $message;
$this->display();
}
/**
* Starts the indicator output.
*/
public function start(string $message)
{
if ($this->started) {
throw new LogicException('Progress indicator already started.');
}
$this->message = $message;
$this->started = true;
$this->startTime = time();
$this->indicatorUpdateTime = $this->getCurrentTimeInMilliseconds() + $this->indicatorChangeInterval;
$this->indicatorCurrent = 0;
$this->display();
}
/**
* Advances the indicator.
*/
public function advance()
{
if (!$this->started) {
throw new LogicException('Progress indicator has not yet been started.');
}
if (!$this->output->isDecorated()) {
return;
}
$currentTime = $this->getCurrentTimeInMilliseconds();
if ($currentTime < $this->indicatorUpdateTime) {
return;
}
$this->indicatorUpdateTime = $currentTime + $this->indicatorChangeInterval;
++$this->indicatorCurrent;
$this->display();
}
/**
* Finish the indicator with message.
*
* @param $message
*/
public function finish(string $message)
{
if (!$this->started) {
throw new LogicException('Progress indicator has not yet been started.');
}
$this->message = $message;
$this->display();
$this->output->writeln('');
$this->started = false;
}
/**
* Gets the format for a given name.
*
* @return string|null A format string
*/
public static function getFormatDefinition(string $name)
{
if (!self::$formats) {
self::$formats = self::initFormats();
}
return self::$formats[$name] ?? null;
}
/**
* Sets a placeholder formatter for a given name.
*
* This method also allow you to override an existing placeholder.
*/
public static function setPlaceholderFormatterDefinition(string $name, callable $callable)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
self::$formatters[$name] = $callable;
}
/**
* Gets the placeholder formatter for a given name (including the delimiter char like %).
*
* @return callable|null A PHP callable
*/
public static function getPlaceholderFormatterDefinition(string $name)
{
if (!self::$formatters) {
self::$formatters = self::initPlaceholderFormatters();
}
return self::$formatters[$name] ?? null;
}
private function display()
{
if (OutputInterface::VERBOSITY_QUIET === $this->output->getVerbosity()) {
return;
}
$this->overwrite(preg_replace_callback("{%([a-z\-_]+)(?:\:([^%]+))?%}i", function ($matches) {
if ($formatter = self::getPlaceholderFormatterDefinition($matches[1])) {
return $formatter($this);
}
return $matches[0];
}, $this->format));
}
private function determineBestFormat(): string
{
switch ($this->output->getVerbosity()) {
// OutputInterface::VERBOSITY_QUIET: display is disabled anyway
case OutputInterface::VERBOSITY_VERBOSE:
return $this->output->isDecorated() ? 'verbose' : 'verbose_no_ansi';
case OutputInterface::VERBOSITY_VERY_VERBOSE:
case OutputInterface::VERBOSITY_DEBUG:
return $this->output->isDecorated() ? 'very_verbose' : 'very_verbose_no_ansi';
default:
return $this->output->isDecorated() ? 'normal' : 'normal_no_ansi';
}
}
/**
* Overwrites a previous message to the output.
*/
private function overwrite(string $message)
{
if ($this->output->isDecorated()) {
$this->output->write("\x0D\x1B[2K");
$this->output->write($message);
} else {
$this->output->writeln($message);
}
}
private function getCurrentTimeInMilliseconds(): float
{
return round(microtime(true) * 1000);
}
private static function initPlaceholderFormatters(): array
{
return [
'indicator' => function (self $indicator) {
return $indicator->indicatorValues[$indicator->indicatorCurrent % \count($indicator->indicatorValues)];
},
'message' => function (self $indicator) {
return $indicator->message;
},
'elapsed' => function (self $indicator) {
return Helper::formatTime(time() - $indicator->startTime);
},
'memory' => function () {
return Helper::formatMemory(memory_get_usage(true));
},
];
}
private static function initFormats(): array
{
return [
'normal' => ' %indicator% %message%',
'normal_no_ansi' => ' %message%',
'verbose' => ' %indicator% %message% (%elapsed:6s%)',
'verbose_no_ansi' => ' %message% (%elapsed:6s%)',
'very_verbose' => ' %indicator% %message% (%elapsed:6s%, %memory:6s%)',
'very_verbose_no_ansi' => ' %message% (%elapsed:6s%, %memory:6s%)',
];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Helper;
use Symfony\Component\Console\Exception\InvalidArgumentException;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class TableCell
{
private $value;
private $options = [
'rowspan' => 1,
'colspan' => 1,
'style' => null,
];
public function __construct(string $value = '', array $options = [])
{
$this->value = $value;
// check option names
if ($diff = array_diff(array_keys($options), array_keys($this->options))) {
throw new InvalidArgumentException(sprintf('The TableCell does not support the following options: \'%s\'.', implode('\', \'', $diff)));
}
if (isset($options['style']) && !$options['style'] instanceof TableCellStyle) {
throw new InvalidArgumentException('The style option must be an instance of "TableCellStyle".');
}
$this->options = array_merge($this->options, $options);
}
/**
* Returns the cell value.
*
* @return string
*/
public function __toString()
{
return $this->value;
}
/**
* Gets number of colspan.
*
* @return int
*/
public function getColspan()
{
return (int) $this->options['colspan'];
}
/**
* Gets number of rowspan.
*
* @return int
*/
public function getRowspan()
{
return (int) $this->options['rowspan'];
}
public function getStyle(): ?TableCellStyle
{
return $this->options['style'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\EventListener;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleErrorEvent;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Event\ConsoleTerminateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* @author James Halsall <james.t.halsall@googlemail.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ErrorListener implements EventSubscriberInterface
{
private $logger;
public function __construct(LoggerInterface $logger = null)
{
$this->logger = $logger;
}
public function onConsoleError(ConsoleErrorEvent $event)
{
if (null === $this->logger) {
return;
}
$error = $event->getError();
if (!$inputString = $this->getInputString($event)) {
$this->logger->critical('An error occurred while using the console. Message: "{message}"', ['exception' => $error, 'message' => $error->getMessage()]);
return;
}
$this->logger->critical('Error thrown while running command "{command}". Message: "{message}"', ['exception' => $error, 'command' => $inputString, 'message' => $error->getMessage()]);
}
public function onConsoleTerminate(ConsoleTerminateEvent $event)
{
if (null === $this->logger) {
return;
}
$exitCode = $event->getExitCode();
if (0 === $exitCode) {
return;
}
if (!$inputString = $this->getInputString($event)) {
$this->logger->debug('The console exited with code "{code}"', ['code' => $exitCode]);
return;
}
$this->logger->debug('Command "{command}" exited with code "{code}"', ['command' => $inputString, 'code' => $exitCode]);
}
public static function getSubscribedEvents()
{
return [
ConsoleEvents::ERROR => ['onConsoleError', -128],
ConsoleEvents::TERMINATE => ['onConsoleTerminate', -128],
];
}
private static function getInputString(ConsoleEvent $event): ?string
{
$commandName = $event->getCommand() ? $event->getCommand()->getName() : null;
$input = $event->getInput();
if (method_exists($input, '__toString')) {
if ($commandName) {
return str_replace(["'$commandName'", "\"$commandName\""], $commandName, (string) $input);
}
return (string) $input;
}
return $commandName;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
abstract class Descriptor implements DescriptorInterface
{
/**
* @var OutputInterface
*/
protected $output;
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = [])
{
$this->output = $output;
switch (true) {
case $object instanceof InputArgument:
$this->describeInputArgument($object, $options);
break;
case $object instanceof InputOption:
$this->describeInputOption($object, $options);
break;
case $object instanceof InputDefinition:
$this->describeInputDefinition($object, $options);
break;
case $object instanceof Command:
$this->describeCommand($object, $options);
break;
case $object instanceof Application:
$this->describeApplication($object, $options);
break;
default:
throw new InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_debug_type($object)));
}
}
/**
* Writes content to output.
*/
protected function write(string $content, bool $decorated = false)
{
$this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW);
}
/**
* Describes an InputArgument instance.
*
* @return string|mixed
*/
abstract protected function describeInputArgument(InputArgument $argument, array $options = []);
/**
* Describes an InputOption instance.
*
* @return string|mixed
*/
abstract protected function describeInputOption(InputOption $option, array $options = []);
/**
* Describes an InputDefinition instance.
*
* @return string|mixed
*/
abstract protected function describeInputDefinition(InputDefinition $definition, array $options = []);
/**
* Describes a Command instance.
*
* @return string|mixed
*/
abstract protected function describeCommand(Command $command, array $options = []);
/**
* Describes an Application instance.
*
* @return string|mixed
*/
abstract protected function describeApplication(Application $application, array $options = []);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* JSON descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class JsonDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeData($this->getInputArgumentData($argument), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeData($this->getInputOptionData($option), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeData($this->getInputDefinitionData($definition), $options);
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$this->writeData($this->getCommandData($command), $options);
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace, true);
$commands = [];
foreach ($description->getCommands() as $command) {
$commands[] = $this->getCommandData($command);
}
$data = [];
if ('UNKNOWN' !== $application->getName()) {
$data['application']['name'] = $application->getName();
if ('UNKNOWN' !== $application->getVersion()) {
$data['application']['version'] = $application->getVersion();
}
}
$data['commands'] = $commands;
if ($describedNamespace) {
$data['namespace'] = $describedNamespace;
} else {
$data['namespaces'] = array_values($description->getNamespaces());
}
$this->writeData($data, $options);
}
/**
* Writes data as json.
*/
private function writeData(array $data, array $options)
{
$flags = $options['json_encoding'] ?? 0;
$this->write(json_encode($data, $flags));
}
private function getInputArgumentData(InputArgument $argument): array
{
return [
'name' => $argument->getName(),
'is_required' => $argument->isRequired(),
'is_array' => $argument->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $argument->getDescription()),
'default' => \INF === $argument->getDefault() ? 'INF' : $argument->getDefault(),
];
}
private function getInputOptionData(InputOption $option): array
{
return [
'name' => '--'.$option->getName(),
'shortcut' => $option->getShortcut() ? '-'.str_replace('|', '|-', $option->getShortcut()) : '',
'accept_value' => $option->acceptValue(),
'is_value_required' => $option->isValueRequired(),
'is_multiple' => $option->isArray(),
'description' => preg_replace('/\s*[\r\n]\s*/', ' ', $option->getDescription()),
'default' => \INF === $option->getDefault() ? 'INF' : $option->getDefault(),
];
}
private function getInputDefinitionData(InputDefinition $definition): array
{
$inputArguments = [];
foreach ($definition->getArguments() as $name => $argument) {
$inputArguments[$name] = $this->getInputArgumentData($argument);
}
$inputOptions = [];
foreach ($definition->getOptions() as $name => $option) {
$inputOptions[$name] = $this->getInputOptionData($option);
}
return ['arguments' => $inputArguments, 'options' => $inputOptions];
}
private function getCommandData(Command $command): array
{
$command->mergeApplicationDefinition(false);
return [
'name' => $command->getName(),
'usage' => array_merge([$command->getSynopsis()], $command->getUsages(), $command->getAliases()),
'description' => $command->getDescription(),
'help' => $command->getProcessedHelp(),
'definition' => $this->getInputDefinitionData($command->getDefinition()),
'hidden' => $command->isHidden(),
];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\CommandNotFoundException;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*
* @internal
*/
class ApplicationDescription
{
public const GLOBAL_NAMESPACE = '_global';
private $application;
private $namespace;
private $showHidden;
/**
* @var array
*/
private $namespaces;
/**
* @var Command[]
*/
private $commands;
/**
* @var Command[]
*/
private $aliases;
public function __construct(Application $application, string $namespace = null, bool $showHidden = false)
{
$this->application = $application;
$this->namespace = $namespace;
$this->showHidden = $showHidden;
}
public function getNamespaces(): array
{
if (null === $this->namespaces) {
$this->inspectApplication();
}
return $this->namespaces;
}
/**
* @return Command[]
*/
public function getCommands(): array
{
if (null === $this->commands) {
$this->inspectApplication();
}
return $this->commands;
}
/**
* @throws CommandNotFoundException
*/
public function getCommand(string $name): Command
{
if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) {
throw new CommandNotFoundException(sprintf('Command "%s" does not exist.', $name));
}
return $this->commands[$name] ?? $this->aliases[$name];
}
private function inspectApplication()
{
$this->commands = [];
$this->namespaces = [];
$all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null);
foreach ($this->sortCommands($all) as $namespace => $commands) {
$names = [];
/** @var Command $command */
foreach ($commands as $name => $command) {
if (!$command->getName() || (!$this->showHidden && $command->isHidden())) {
continue;
}
if ($command->getName() === $name) {
$this->commands[$name] = $command;
} else {
$this->aliases[$name] = $command;
}
$names[] = $name;
}
$this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names];
}
}
private function sortCommands(array $commands): array
{
$namespacedCommands = [];
$globalCommands = [];
$sortedCommands = [];
foreach ($commands as $name => $command) {
$key = $this->application->extractNamespace($name, 1);
if (\in_array($key, ['', self::GLOBAL_NAMESPACE], true)) {
$globalCommands[$name] = $command;
} else {
$namespacedCommands[$key][$name] = $command;
}
}
if ($globalCommands) {
ksort($globalCommands);
$sortedCommands[self::GLOBAL_NAMESPACE] = $globalCommands;
}
if ($namespacedCommands) {
ksort($namespacedCommands);
foreach ($namespacedCommands as $key => $commandsSet) {
ksort($commandsSet);
$sortedCommands[$key] = $commandsSet;
}
}
return $sortedCommands;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Descriptor interface.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
interface DescriptorInterface
{
/**
* Describes an object if supported.
*
* @param object $object
*/
public function describe(OutputInterface $output, $object, array $options = []);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* Text descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class TextDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
if (null !== $argument->getDefault() && (!\is_array($argument->getDefault()) || \count($argument->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($argument->getDefault()));
} else {
$default = '';
}
$totalWidth = $options['total_width'] ?? Helper::strlen($argument->getName());
$spacingWidth = $totalWidth - \strlen($argument->getName());
$this->writeText(sprintf(' <info>%s</info> %s%s%s',
$argument->getName(),
str_repeat(' ', $spacingWidth),
// + 4 = 2 spaces before <info>, 2 spaces after </info>
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $argument->getDescription()),
$default
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
if ($option->acceptValue() && null !== $option->getDefault() && (!\is_array($option->getDefault()) || \count($option->getDefault()))) {
$default = sprintf('<comment> [default: %s]</comment>', $this->formatDefaultValue($option->getDefault()));
} else {
$default = '';
}
$value = '';
if ($option->acceptValue()) {
$value = '='.strtoupper($option->getName());
if ($option->isValueOptional()) {
$value = '['.$value.']';
}
}
$totalWidth = $options['total_width'] ?? $this->calculateTotalWidthForOptions([$option]);
$synopsis = sprintf('%s%s',
$option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ',
sprintf('--%s%s', $option->getName(), $value)
);
$spacingWidth = $totalWidth - Helper::strlen($synopsis);
$this->writeText(sprintf(' <info>%s</info> %s%s%s%s',
$synopsis,
str_repeat(' ', $spacingWidth),
// + 4 = 2 spaces before <info>, 2 spaces after </info>
preg_replace('/\s*[\r\n]\s*/', "\n".str_repeat(' ', $totalWidth + 4), $option->getDescription()),
$default,
$option->isArray() ? '<comment> (multiple values allowed)</comment>' : ''
), $options);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions());
foreach ($definition->getArguments() as $argument) {
$totalWidth = max($totalWidth, Helper::strlen($argument->getName()));
}
if ($definition->getArguments()) {
$this->writeText('<comment>Arguments:</comment>', $options);
$this->writeText("\n");
foreach ($definition->getArguments() as $argument) {
$this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth]));
$this->writeText("\n");
}
}
if ($definition->getArguments() && $definition->getOptions()) {
$this->writeText("\n");
}
if ($definition->getOptions()) {
$laterOptions = [];
$this->writeText('<comment>Options:</comment>', $options);
foreach ($definition->getOptions() as $option) {
if (\strlen($option->getShortcut()) > 1) {
$laterOptions[] = $option;
continue;
}
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
foreach ($laterOptions as $option) {
$this->writeText("\n");
$this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth]));
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->mergeApplicationDefinition(false);
if ($description = $command->getDescription()) {
$this->writeText('<comment>Description:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.$description);
$this->writeText("\n\n");
}
$this->writeText('<comment>Usage:</comment>', $options);
foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) {
$this->writeText("\n");
$this->writeText(' '.OutputFormatter::escape($usage), $options);
}
$this->writeText("\n");
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->writeText("\n");
$this->describeInputDefinition($definition, $options);
$this->writeText("\n");
}
$help = $command->getProcessedHelp();
if ($help && $help !== $description) {
$this->writeText("\n");
$this->writeText('<comment>Help:</comment>', $options);
$this->writeText("\n");
$this->writeText(' '.str_replace("\n", "\n ", $help), $options);
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace);
if (isset($options['raw_text']) && $options['raw_text']) {
$width = $this->getColumnWidth($description->getCommands());
foreach ($description->getCommands() as $command) {
$this->writeText(sprintf("%-{$width}s %s", $command->getName(), $command->getDescription()), $options);
$this->writeText("\n");
}
} else {
if ('' != $help = $application->getHelp()) {
$this->writeText("$help\n\n", $options);
}
$this->writeText("<comment>Usage:</comment>\n", $options);
$this->writeText(" command [options] [arguments]\n\n", $options);
$this->describeInputDefinition(new InputDefinition($application->getDefinition()->getOptions()), $options);
$this->writeText("\n");
$this->writeText("\n");
$commands = $description->getCommands();
$namespaces = $description->getNamespaces();
if ($describedNamespace && $namespaces) {
// make sure all alias commands are included when describing a specific namespace
$describedNamespaceInfo = reset($namespaces);
foreach ($describedNamespaceInfo['commands'] as $name) {
$commands[$name] = $description->getCommand($name);
}
}
// calculate max. width based on available commands per namespace
$width = $this->getColumnWidth(array_merge(...array_values(array_map(function ($namespace) use ($commands) {
return array_intersect($namespace['commands'], array_keys($commands));
}, array_values($namespaces)))));
if ($describedNamespace) {
$this->writeText(sprintf('<comment>Available commands for the "%s" namespace:</comment>', $describedNamespace), $options);
} else {
$this->writeText('<comment>Available commands:</comment>', $options);
}
foreach ($namespaces as $namespace) {
$namespace['commands'] = array_filter($namespace['commands'], function ($name) use ($commands) {
return isset($commands[$name]);
});
if (!$namespace['commands']) {
continue;
}
if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->writeText("\n");
$this->writeText(' <comment>'.$namespace['id'].'</comment>', $options);
}
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$spacingWidth = $width - Helper::strlen($name);
$command = $commands[$name];
$commandAliases = $name === $command->getName() ? $this->getCommandAliasesText($command) : '';
$this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $commandAliases.$command->getDescription()), $options);
}
}
$this->writeText("\n");
}
}
/**
* {@inheritdoc}
*/
private function writeText(string $content, array $options = [])
{
$this->write(
isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content,
isset($options['raw_output']) ? !$options['raw_output'] : true
);
}
/**
* Formats command aliases to show them in the command description.
*/
private function getCommandAliasesText(Command $command): string
{
$text = '';
$aliases = $command->getAliases();
if ($aliases) {
$text = '['.implode('|', $aliases).'] ';
}
return $text;
}
/**
* Formats input option/argument default value.
*
* @param mixed $default
*/
private function formatDefaultValue($default): string
{
if (\INF === $default) {
return 'INF';
}
if (\is_string($default)) {
$default = OutputFormatter::escape($default);
} elseif (\is_array($default)) {
foreach ($default as $key => $value) {
if (\is_string($value)) {
$default[$key] = OutputFormatter::escape($value);
}
}
}
return str_replace('\\\\', '\\', json_encode($default, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
}
/**
* @param (Command|string)[] $commands
*/
private function getColumnWidth(array $commands): int
{
$widths = [];
foreach ($commands as $command) {
if ($command instanceof Command) {
$widths[] = Helper::strlen($command->getName());
foreach ($command->getAliases() as $alias) {
$widths[] = Helper::strlen($alias);
}
} else {
$widths[] = Helper::strlen($command);
}
}
return $widths ? max($widths) + 2 : 0;
}
/**
* @param InputOption[] $options
*/
private function calculateTotalWidthForOptions(array $options): int
{
$totalWidth = 0;
foreach ($options as $option) {
// "-" + shortcut + ", --" + name
$nameLength = 1 + max(Helper::strlen($option->getShortcut()), 1) + 4 + Helper::strlen($option->getName());
if ($option->acceptValue()) {
$valueLength = 1 + Helper::strlen($option->getName()); // = + value
$valueLength += $option->isValueOptional() ? 2 : 0; // [ + ]
$nameLength += $valueLength;
}
$totalWidth = max($totalWidth, $nameLength);
}
return $totalWidth;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Markdown descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class MarkdownDescriptor extends Descriptor
{
/**
* {@inheritdoc}
*/
public function describe(OutputInterface $output, $object, array $options = [])
{
$decorated = $output->isDecorated();
$output->setDecorated(false);
parent::describe($output, $object, $options);
$output->setDecorated($decorated);
}
/**
* {@inheritdoc}
*/
protected function write(string $content, bool $decorated = true)
{
parent::write($content, $decorated);
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->write(
'#### `'.($argument->getName() ?: '<none>')."`\n\n"
.($argument->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $argument->getDescription())."\n\n" : '')
.'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n"
.'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`'
);
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$name = '--'.$option->getName();
if ($option->getShortcut()) {
$name .= '|-'.str_replace('|', '|-', $option->getShortcut()).'';
}
$this->write(
'#### `'.$name.'`'."\n\n"
.($option->getDescription() ? preg_replace('/\s*[\r\n]\s*/', "\n", $option->getDescription())."\n\n" : '')
.'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n"
.'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n"
.'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n"
.'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`'
);
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
if ($showArguments = \count($definition->getArguments()) > 0) {
$this->write('### Arguments');
foreach ($definition->getArguments() as $argument) {
$this->write("\n\n");
if (null !== $describeInputArgument = $this->describeInputArgument($argument)) {
$this->write($describeInputArgument);
}
}
}
if (\count($definition->getOptions()) > 0) {
if ($showArguments) {
$this->write("\n\n");
}
$this->write('### Options');
foreach ($definition->getOptions() as $option) {
$this->write("\n\n");
if (null !== $describeInputOption = $this->describeInputOption($option)) {
$this->write($describeInputOption);
}
}
}
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$command->mergeApplicationDefinition(false);
$this->write(
'`'.$command->getName()."`\n"
.str_repeat('-', Helper::strlen($command->getName()) + 2)."\n\n"
.($command->getDescription() ? $command->getDescription()."\n\n" : '')
.'### Usage'."\n\n"
.array_reduce(array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()), function ($carry, $usage) {
return $carry.'* `'.$usage.'`'."\n";
})
);
if ($help = $command->getProcessedHelp()) {
$this->write("\n");
$this->write($help);
}
$definition = $command->getDefinition();
if ($definition->getOptions() || $definition->getArguments()) {
$this->write("\n\n");
$this->describeInputDefinition($definition);
}
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$describedNamespace = $options['namespace'] ?? null;
$description = new ApplicationDescription($application, $describedNamespace);
$title = $this->getApplicationTitle($application);
$this->write($title."\n".str_repeat('=', Helper::strlen($title)));
foreach ($description->getNamespaces() as $namespace) {
if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) {
$this->write("\n\n");
$this->write('**'.$namespace['id'].':**');
}
$this->write("\n\n");
$this->write(implode("\n", array_map(function ($commandName) use ($description) {
return sprintf('* [`%s`](#%s)', $commandName, str_replace(':', '', $description->getCommand($commandName)->getName()));
}, $namespace['commands'])));
}
foreach ($description->getCommands() as $command) {
$this->write("\n\n");
if (null !== $describeCommand = $this->describeCommand($command)) {
$this->write($describeCommand);
}
}
}
private function getApplicationTitle(Application $application): string
{
if ('UNKNOWN' !== $application->getName()) {
if ('UNKNOWN' !== $application->getVersion()) {
return sprintf('%s %s', $application->getName(), $application->getVersion());
}
return $application->getName();
}
return 'Console Tool';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Descriptor;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
/**
* XML descriptor.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*
* @internal
*/
class XmlDescriptor extends Descriptor
{
public function getInputDefinitionDocument(InputDefinition $definition): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($definitionXML = $dom->createElement('definition'));
$definitionXML->appendChild($argumentsXML = $dom->createElement('arguments'));
foreach ($definition->getArguments() as $argument) {
$this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument));
}
$definitionXML->appendChild($optionsXML = $dom->createElement('options'));
foreach ($definition->getOptions() as $option) {
$this->appendDocument($optionsXML, $this->getInputOptionDocument($option));
}
return $dom;
}
public function getCommandDocument(Command $command): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($commandXML = $dom->createElement('command'));
$command->mergeApplicationDefinition(false);
$commandXML->setAttribute('id', $command->getName());
$commandXML->setAttribute('name', $command->getName());
$commandXML->setAttribute('hidden', $command->isHidden() ? 1 : 0);
$commandXML->appendChild($usagesXML = $dom->createElement('usages'));
foreach (array_merge([$command->getSynopsis()], $command->getAliases(), $command->getUsages()) as $usage) {
$usagesXML->appendChild($dom->createElement('usage', $usage));
}
$commandXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription())));
$commandXML->appendChild($helpXML = $dom->createElement('help'));
$helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp())));
$definitionXML = $this->getInputDefinitionDocument($command->getDefinition());
$this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0));
return $dom;
}
public function getApplicationDocument(Application $application, string $namespace = null): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($rootXml = $dom->createElement('symfony'));
if ('UNKNOWN' !== $application->getName()) {
$rootXml->setAttribute('name', $application->getName());
if ('UNKNOWN' !== $application->getVersion()) {
$rootXml->setAttribute('version', $application->getVersion());
}
}
$rootXml->appendChild($commandsXML = $dom->createElement('commands'));
$description = new ApplicationDescription($application, $namespace, true);
if ($namespace) {
$commandsXML->setAttribute('namespace', $namespace);
}
foreach ($description->getCommands() as $command) {
$this->appendDocument($commandsXML, $this->getCommandDocument($command));
}
if (!$namespace) {
$rootXml->appendChild($namespacesXML = $dom->createElement('namespaces'));
foreach ($description->getNamespaces() as $namespaceDescription) {
$namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace'));
$namespaceArrayXML->setAttribute('id', $namespaceDescription['id']);
foreach ($namespaceDescription['commands'] as $name) {
$namespaceArrayXML->appendChild($commandXML = $dom->createElement('command'));
$commandXML->appendChild($dom->createTextNode($name));
}
}
}
return $dom;
}
/**
* {@inheritdoc}
*/
protected function describeInputArgument(InputArgument $argument, array $options = [])
{
$this->writeDocument($this->getInputArgumentDocument($argument));
}
/**
* {@inheritdoc}
*/
protected function describeInputOption(InputOption $option, array $options = [])
{
$this->writeDocument($this->getInputOptionDocument($option));
}
/**
* {@inheritdoc}
*/
protected function describeInputDefinition(InputDefinition $definition, array $options = [])
{
$this->writeDocument($this->getInputDefinitionDocument($definition));
}
/**
* {@inheritdoc}
*/
protected function describeCommand(Command $command, array $options = [])
{
$this->writeDocument($this->getCommandDocument($command));
}
/**
* {@inheritdoc}
*/
protected function describeApplication(Application $application, array $options = [])
{
$this->writeDocument($this->getApplicationDocument($application, $options['namespace'] ?? null));
}
/**
* Appends document children to parent node.
*/
private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent)
{
foreach ($importedParent->childNodes as $childNode) {
$parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true));
}
}
/**
* Writes DOM document.
*/
private function writeDocument(\DOMDocument $dom)
{
$dom->formatOutput = true;
$this->write($dom->saveXML());
}
private function getInputArgumentDocument(InputArgument $argument): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($objectXML = $dom->createElement('argument'));
$objectXML->setAttribute('name', $argument->getName());
$objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0);
$objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($argument->getDescription()));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
$defaults = \is_array($argument->getDefault()) ? $argument->getDefault() : (\is_bool($argument->getDefault()) ? [var_export($argument->getDefault(), true)] : ($argument->getDefault() ? [$argument->getDefault()] : []));
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
return $dom;
}
private function getInputOptionDocument(InputOption $option): \DOMDocument
{
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->appendChild($objectXML = $dom->createElement('option'));
$objectXML->setAttribute('name', '--'.$option->getName());
$pos = strpos($option->getShortcut(), '|');
if (false !== $pos) {
$objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos));
$objectXML->setAttribute('shortcuts', '-'.str_replace('|', '|-', $option->getShortcut()));
} else {
$objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : '');
}
$objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0);
$objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0);
$objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0);
$objectXML->appendChild($descriptionXML = $dom->createElement('description'));
$descriptionXML->appendChild($dom->createTextNode($option->getDescription()));
if ($option->acceptValue()) {
$defaults = \is_array($option->getDefault()) ? $option->getDefault() : (\is_bool($option->getDefault()) ? [var_export($option->getDefault(), true)] : ($option->getDefault() ? [$option->getDefault()] : []));
$objectXML->appendChild($defaultsXML = $dom->createElement('defaults'));
if (!empty($defaults)) {
foreach ($defaults as $default) {
$defaultsXML->appendChild($defaultXML = $dom->createElement('default'));
$defaultXML->appendChild($dom->createTextNode($default));
}
}
}
return $dom;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect namespace typed in the console.
*
* @author Pierre du Plessis <pdples@gmail.com>
*/
class NamespaceNotFoundException extends CommandNotFoundException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* ExceptionInterface.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents failure to read input from stdin.
*
* @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
*/
class MissingInputException extends RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect command name typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class CommandNotFoundException extends \InvalidArgumentException implements ExceptionInterface
{
private $alternatives;
/**
* @param string $message Exception message to throw
* @param string[] $alternatives List of similar defined names
* @param int $code Exception code
* @param \Throwable|null $previous Previous exception used for the exception chaining
*/
public function __construct(string $message, array $alternatives = [], int $code = 0, \Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->alternatives = $alternatives;
}
/**
* @return string[] A list of similar defined names
*/
public function getAlternatives()
{
return $this->alternatives;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* Represents an incorrect option name typed in the console.
*
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class InvalidOptionException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console\Exception;
/**
* @author Jérôme Tamarelle <jerome@tamarelle.net>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Pierre du Plessis <pdples@gmail.com>
*/
final class Cursor
{
private $output;
private $input;
public function __construct(OutputInterface $output, $input = null)
{
$this->output = $output;
$this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
}
public function moveUp(int $lines = 1): self
{
$this->output->write(sprintf("\x1b[%dA", $lines));
return $this;
}
public function moveDown(int $lines = 1): self
{
$this->output->write(sprintf("\x1b[%dB", $lines));
return $this;
}
public function moveRight(int $columns = 1): self
{
$this->output->write(sprintf("\x1b[%dC", $columns));
return $this;
}
public function moveLeft(int $columns = 1): self
{
$this->output->write(sprintf("\x1b[%dD", $columns));
return $this;
}
public function moveToColumn(int $column): self
{
$this->output->write(sprintf("\x1b[%dG", $column));
return $this;
}
public function moveToPosition(int $column, int $row): self
{
$this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column));
return $this;
}
public function savePosition(): self
{
$this->output->write("\x1b7");
return $this;
}
public function restorePosition(): self
{
$this->output->write("\x1b8");
return $this;
}
public function hide(): self
{
$this->output->write("\x1b[?25l");
return $this;
}
public function show(): self
{
$this->output->write("\x1b[?25h\x1b[?0c");
return $this;
}
/**
* Clears all the output from the current line.
*/
public function clearLine(): self
{
$this->output->write("\x1b[2K");
return $this;
}
/**
* Clears all the output from the current line after the current position.
*/
public function clearLineAfter(): self
{
$this->output->write("\x1b[K");
return $this;
}
/**
* Clears all the output from the cursors' current position to the end of the screen.
*/
public function clearOutput(): self
{
$this->output->write("\x1b[0J");
return $this;
}
/**
* Clears the entire screen.
*/
public function clearScreen(): self
{
$this->output->write("\x1b[2J");
return $this;
}
/**
* Returns the current cursor position as x,y coordinates.
*/
public function getCurrentPosition(): array
{
static $isTtySupported;
if (null === $isTtySupported && \function_exists('proc_open')) {
$isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
}
if (!$isTtySupported) {
return [1, 1];
}
$sttyMode = shell_exec('stty -g');
shell_exec('stty -icanon -echo');
@fwrite($this->input, "\033[6n");
$code = trim(fread($this->input, 1024));
shell_exec(sprintf('stty %s', $sttyMode));
sscanf($code, "\033[%d;%dR", $row, $col);
return [$col, $row];
}
}
Copyright (c) 2004-2021 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Console;
class Terminal
{
private static $width;
private static $height;
private static $stty;
/**
* Gets the terminal width.
*
* @return int
*/
public function getWidth()
{
$width = getenv('COLUMNS');
if (false !== $width) {
return (int) trim($width);
}
if (null === self::$width) {
self::initDimensions();
}
return self::$width ?: 80;
}
/**
* Gets the terminal height.
*
* @return int
*/
public function getHeight()
{
$height = getenv('LINES');
if (false !== $height) {
return (int) trim($height);
}
if (null === self::$height) {
self::initDimensions();
}
return self::$height ?: 50;
}
/**
* @internal
*
* @return bool
*/
public static function hasSttyAvailable()
{
if (null !== self::$stty) {
return self::$stty;
}
// skip check if exec function is disabled
if (!\function_exists('exec')) {
return false;
}
exec('stty 2>&1', $output, $exitcode);
return self::$stty = 0 === $exitcode;
}
private static function initDimensions()
{
if ('\\' === \DIRECTORY_SEPARATOR) {
if (preg_match('/^(\d+)x(\d+)(?: \((\d+)x(\d+)\))?$/', trim(getenv('ANSICON')), $matches)) {
// extract [w, H] from "wxh (WxH)"
// or [w, h] from "wxh"
self::$width = (int) $matches[1];
self::$height = isset($matches[4]) ? (int) $matches[4] : (int) $matches[2];
} elseif (!self::hasVt100Support() && self::hasSttyAvailable()) {
// only use stty on Windows if the terminal does not support vt100 (e.g. Windows 7 + git-bash)
// testing for stty in a Windows 10 vt100-enabled console will implicitly disable vt100 support on STDOUT
self::initDimensionsUsingStty();
} elseif (null !== $dimensions = self::getConsoleMode()) {
// extract [w, h] from "wxh"
self::$width = (int) $dimensions[0];
self::$height = (int) $dimensions[1];
}
} else {
self::initDimensionsUsingStty();
}
}
/**
* Returns whether STDOUT has vt100 support (some Windows 10+ configurations).
*/
private static function hasVt100Support(): bool
{
return \function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(fopen('php://stdout', 'w'));
}
/**
* Initializes dimensions using the output of an stty columns line.
*/
private static function initDimensionsUsingStty()
{
if ($sttyString = self::getSttyColumns()) {
if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) {
// extract [w, h] from "rows h; columns w;"
self::$width = (int) $matches[2];
self::$height = (int) $matches[1];
} elseif (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) {
// extract [w, h] from "; h rows; w columns"
self::$width = (int) $matches[2];
self::$height = (int) $matches[1];
}
}
}
/**
* Runs and parses mode CON if it's available, suppressing any error output.
*
* @return int[]|null An array composed of the width and the height or null if it could not be parsed
*/
private static function getConsoleMode(): ?array
{
$info = self::readFromProcess('mode CON');
if (null === $info || !preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) {
return null;
}
return [(int) $matches[2], (int) $matches[1]];
}
/**
* Runs and parses stty -a if it's available, suppressing any error output.
*/
private static function getSttyColumns(): ?string
{
return self::readFromProcess('stty -a | grep columns');
}
private static function readFromProcess(string $command): ?string
{
if (!\function_exists('proc_open')) {
return null;
}
$descriptorspec = [
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$process = proc_open($command, $descriptorspec, $pipes, null, null, ['suppress_errors' => true]);
if (!\is_resource($process)) {
return null;
}
$info = stream_get_contents($pipes[1]);
fclose($pipes[1]);
fclose($pipes[2]);
proc_close($process);
return $info;
}
}
MZ<EFBFBD>ÿÿ¸@躴 Í!¸LÍ!This program cannot be run in DOS mode.
$Æ,Í;§Bž;§Bž;§Bž2ßמ:§Bž2ßÁž-§Bž2ßÆž9§Bž2ßÑž?§Bža9ž8§Bž;§Cž§Bž2ßÈž:§Bž2ßÖž:§Bž2ßÓž:§BžRich;§BžPEL¬MoOà 
8 @`@<40><00>"P@ Pp!8!@ Ø.text 
 `.rdataÎ
@@.data<00>0@À.rsrc @@@.relocÌP"@Bj$¸æ@èxjöÿ @ƒeÐð<E280B9>EÐPVÿ @EЃàûPVÿ @<00>MÔÿX @ƒeü<00>EÔPÿ5H @ÿL @YYÿ5\ @<00>EÔPÿ5` @ÿD @YYÈÿP @ƒMüÿ<C3BC>MÔÿT @3ÀèHÃ; 0@uóÃé¬h€@裡l3@Ç$40@ÿ5h3@£40@h$0@h(0@h 0@ÿ  @ƒÄ£00@…À}jè¹YÃjh"@è3Û‰]üd¡p‰]ä¿€3@SVWÿ0 @;Ãt;Æu3öF‰uäëÿ4 @ëÚ3öF¡|3@;Æu
jè\Yë;¡|3@…Àu,‰5|3@hð @hä @è§YY…ÀtÇEüþÿÿÿ¸ÿé݉5<0@¡|3@;Æuhà @hØ @èlYYÇ|3@9]äuSWÿ8 @9Œ3@thŒ3@èƒY…Àt
SjSÿŒ3@¡$0@ ¼ @ÿ5$0@ÿ5(0@ÿ5 0@èþÿÿƒÄ £80@9,0@u7PÿÀ @ ‰MàPQèŽYYÃEà£80@3Û9,0@uPÿh @9<0@uÿœ @ÇEüþÿÿÿ¡80@èûøMZf9@t3ÀëM¡<@<00>@<00>8PE·H<18>ù t<1B>ù uÕƒ¸vÌ3É9ˆøëƒxtv¼3É9ˆè•ÁÁj£,0@ÿp @jÿÿl @YY£„3@£ˆ3@ÿÌ @ t3@ÿˆ @ p3@¡¨ @£x3@èV謃=0@u @ÿ¬ @Yègƒ=0@ÿu jÿÿ° @Y3ÀÃè{éŸýÿÿÿUì<E280B9>ì(£H1@‰ D1@@1@<1@‰581@‰=41@`1@fŒ T1@01@,1@fŒ%(1@fŒ-$1@œ<>X1@E£L1@E£P1@<00>E£\1@‹…àüÿÿǘ0@¡P1@£L0@Ç@0@ ÀÇD0@¡0@‰…Øüÿÿ¡0@‰…Üüÿÿÿ @£<>0@jè?Yjÿ @h!@ÿ$ @ƒ=<3D>0@ujèYh Àÿ( @Pÿ, @ÉÃÿUìE<00>8csmàu*ƒxu$@= t=!t="t=@™uèÐ3À]ÂhH@ÿ @3ÀÃÿ%¤ @jh("@èbÿ5ˆ3@5Œ @ÿÖY‰Eäƒøÿu ÿuÿÄ @Yëgjè’Yƒeüÿ5ˆ3@ÿÖ‰Eäÿ5„3@ÿÖYY‰Eà<45>EàP<C3A0>EäPÿu5l @ÿÖYPèU‰EÜÿuäÿÖ£ˆ3@ÿuàÿփģ„3@ÇEüþÿÿÿè EÜèÃjèÿUìÿuèNÿÿÿ÷ØÀ÷ØYH]ÃÿV¸ü!@¾ü!@Wø;Æs…ÀtÿЃÇ;þrñ_^ÃÿV¸"@¾"@Wø;Æs…ÀtÿЃÇ;þrñ_^Ãÿ%È @ÌÌÌÌÿUìM¸MZf9t3À]ÃA<Á<>8PEuï3Ò¹ f9H”‹Â]ÃÌÌÌÌÌÌÌÌÌÌÌÿUìEH<È·ASV·q3ÒW<C392>D…öv} H ;ùr XÙ;ûr
BƒÀ(;Örè3À_^[]ÃÌÌÌÌÌÌÌÌÌÌÌÌÿUìjþhH"@he@PƒìSVW¡0@1Eø3ÅP<C385>Eðd£‰eèÇEüh@è*ÿÿÿƒÄ…ÀtUE-@Ph@èPÿÿÿƒÄ…Àt;@$Áè÷ЃàÇEüþÿÿÿMðd‰ Y_^[‹å]Ã3Ò=À”ÂÂÃeèÇEüþÿÿÿ3ÀMðd‰ Y_^[‹å]ÃÌÿ%¸ @ÿ%´ @ÌÌhe@dÿ5D$‰l$<10>l$+àSVW¡0@1Eü3ÅP‰eèÿuøEüÇEüþÿÿÿ‰Eø<45>Eðd£ÃMðd‰ Y__^[‹å]QÃÿUìÿuÿuÿu ÿuh‡@h0@èçƒÄÿVhh3öVèÙƒÄ …Àt VVVVVèƒÄ^Ã3ÀÃÿUìƒì¡0@ƒeøƒeüSW¿Næ@»»ÿÿ;Çt …Ãt ÷У0@ë`V<>EøPÿ< @uü3uøÿ @3ðÿ @3ðÿ @<33>EðPÿ @Eô3Eð3ð;÷u¾Oæ@»ë …óu‹ÆÁà ð‰50@÷Ö‰50@^_[ÉÃÿ%t @ÿ%x @ÿ%| @ÿ%€ @ÿ%„ @ÿ%<25> @ÿ%” @ÿ%˜ @ÿ%Ð @Pdÿ5<00>D$ +d$ SVW‰(‹è¡0@3ÅP‰EðÿuüÇEüÿÿÿÿ<C3BF>Eôd£ÃMôd‰ Y__^[‹å]QÃMð3Íè¯÷ÿÿéÝÿÿÿ<C3BF>MÔÿ%T @T$<08>B JÌ3Èè<C388>÷ÿÿJü3Èè†÷ÿÿ¸l"@ésÿÿÿ¸#Ê#Ü#ˆ)r)b)H)4))ú(æ(Ò(´(¬((ž)ú#à$%Ê%&d&®&¤$('Ä'Ö'è'þ'(((6(¦'H(Z(t(†('''''l'^'R'F'>'>(0'¶'¸)@W@Š@¬MoOl€!@0@˜0@bad allocationH0@ð!@RSDSÑŒ³´<>J¨!öÌëLZc:\users\seld\documents\visual studio 2010\Projects\hiddeninp\Release\hiddeninp.pdbeæþÿÿÿÐÿÿÿþÿÿÿ@@þÿÿÿÌÿÿÿþÿÿÿ:@þÿÿÿØÿÿÿþÿÿÿË@ß@ÿÿÿÿÝ@"d"@à"ì# $#ô&D H#(h ¸#Ê#Ü#ˆ)r)b)H)4))ú(æ(Ò(´(¬((ž)ú#à$%Ê%&d&®&¤$('Ä'Ö'è'þ'(((6(¦'H(Z(t(†('''''l'^'R'F'>'>(0'¶'¸)GetConsoleMode·SetConsoleMode;GetStdHandleKERNEL32.dll??$?6DU?$char_traits@D@std@@V?$allocator@D@1@@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@0@AAV10@ABV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@AJ?cin@std@@3V?$basic_istream@DU?$char_traits@D@std@@@1@AÂ??$getline@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@YAAAV?$basic_istream@DU?$char_traits@D@std@@@0@AAV10@AAV?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@0@@Z??6?$basic_ostream@DU?$char_traits@D@std@@@std@@QAEAAV01@P6AAAV01@AAV01@@Z@Z_??1?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ{??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@XZ³?endl@std@@YAAAV?$basic_ostream@DU?$char_traits@D@std@@@1@AAV21@@ZMSVCP90.dll_amsg_exitŸ__getmainargs,_cexit|_exitf_XcptFilterÌexit __initenv_initterm_initterm_e<_configthreadlocaleã__setusermatherr _adjust_fdivË__p__commodeÏ__p__fmodej_encode_pointerà__set_app_typeK_crt_debugger_hookC?terminate@@YAXXZMSVCR90.dllæ_unlock__dllonexitv_lock_onexit`_decode_pointers_except_handler4_common _invoke_watson?_controlfp_s½InterlockedExchange!SleepºInterlockedCompareExchange-TerminateProcess©GetCurrentProcess>UnhandledExceptionFilterSetUnhandledExceptionFilterÑIsDebuggerPresentTQueryPerformanceCounterfGetTickCount­GetCurrentThreadIdªGetCurrentProcessIdOGetSystemTimeAsFileTimes__CxxFrameHandler3Næ@»±¿Dÿÿÿÿÿÿÿÿþÿÿÿ$!@ 8Ph  <00> @(äÈCVä(4VS_VERSION_INFO½ïþStringFileInfob040904b0ÊQFileDescriptionReads from stdin without leaking info to the terminal and outputs back to stdout6 FileVersion1, 0, 0, 08 InternalNamehiddeninputPLegalCopyrightJordi Boggiano - 2012HOriginalFilenamehiddeninput.exe: ProductNameHidden Input: ProductVersion1, 0, 0, 0DVarFileInfo$Translation °<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.21022.8" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b"></assemblyIdentity>
</dependentAssembly>
</dependency>
</assembly>PAPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDINGPADDINGXXPADDING@00!0/080F0L0T0^0d0n0{0‰0—0¡0¨0®0³0¸0½0Â0È0Ð0ä0ÿ01#1-1@1J1O1T1v1{1„1‰11§1­1´1È1Í1Ó1Û1á1ç1ô1ú12"2*23292A2M2_2j2p2¹2¿2Ç2Î2Ó2Ù2ß2ç2í2ô2û2 333%303N3T3Z3`3f3l3s3z3<7A>3ˆ3<CB86>33<E28093>3¥3­3µ3Á3Ê3Ï3Õ3ß3è3ó3ÿ34444%4;4B444š4¡4¬4²4Æ4Û4æ4þ45!5^5c5„5‰5¨5H6M6_6}66—677 7*7w7|7Á7ä7ñ7ý78 88=8E8P8V8\8b8h8n8t8z8€8œ8â89 $Ü0è0ì01 1t1x12 2@2\2`2h2t20 0<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Glob matches globbing patterns against text.
*
* if match_glob("foo.*", "foo.bar") echo "matched\n";
*
* // prints foo.bar and foo.baz
* $regex = glob_to_regex("foo.*");
* for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t)
* {
* if (/$regex/) echo "matched: $car\n";
* }
*
* Glob implements glob(3) style matching that can be used to match
* against text, rather than fetching names from a filesystem.
*
* Based on the Perl Text::Glob module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*/
class Glob
{
/**
* Returns a regexp which is the equivalent of the glob pattern.
*
* @return string
*/
public static function toRegex(string $glob, bool $strictLeadingDot = true, bool $strictWildcardSlash = true, string $delimiter = '#')
{
$firstByte = true;
$escaping = false;
$inCurlies = 0;
$regex = '';
$sizeGlob = \strlen($glob);
for ($i = 0; $i < $sizeGlob; ++$i) {
$car = $glob[$i];
if ($firstByte && $strictLeadingDot && '.' !== $car) {
$regex .= '(?=[^\.])';
}
$firstByte = '/' === $car;
if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) {
$car = '[^/]++/';
if (!isset($glob[$i + 3])) {
$car .= '?';
}
if ($strictLeadingDot) {
$car = '(?=[^\.])'.$car;
}
$car = '/(?:'.$car.')*';
$i += 2 + isset($glob[$i + 3]);
if ('/' === $delimiter) {
$car = str_replace('/', '\\/', $car);
}
}
if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) {
$regex .= "\\$car";
} elseif ('*' === $car) {
$regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*');
} elseif ('?' === $car) {
$regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.');
} elseif ('{' === $car) {
$regex .= $escaping ? '\\{' : '(';
if (!$escaping) {
++$inCurlies;
}
} elseif ('}' === $car && $inCurlies) {
$regex .= $escaping ? '}' : ')';
if (!$escaping) {
--$inCurlies;
}
} elseif (',' === $car && $inCurlies) {
$regex .= $escaping ? ',' : '|';
} elseif ('\\' === $car) {
if ($escaping) {
$regex .= '\\\\';
$escaping = false;
} else {
$escaping = true;
}
continue;
} else {
$regex .= $car;
}
$escaping = false;
}
return $delimiter.'^'.$regex.'$'.$delimiter;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\NumberComparator;
/**
* SizeRangeFilterIterator filters out files that are not in the given size range.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SizeRangeFilterIterator extends \FilterIterator
{
private $comparators = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param NumberComparator[] $comparators An array of NumberComparator instances
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (!$fileinfo->isFile()) {
return true;
}
$filesize = $fileinfo->getSize();
foreach ($this->comparators as $compare) {
if (!$compare->test($filesize)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* ExcludeDirectoryFilterIterator filters out directories.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator
{
private $iterator;
private $isRecursive;
private $excludedDirs = [];
private $excludedPattern;
/**
* @param \Iterator $iterator The Iterator to filter
* @param string[] $directories An array of directories to exclude
*/
public function __construct(\Iterator $iterator, array $directories)
{
$this->iterator = $iterator;
$this->isRecursive = $iterator instanceof \RecursiveIterator;
$patterns = [];
foreach ($directories as $directory) {
$directory = rtrim($directory, '/');
if (!$this->isRecursive || false !== strpos($directory, '/')) {
$patterns[] = preg_quote($directory, '#');
} else {
$this->excludedDirs[$directory] = true;
}
}
if ($patterns) {
$this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#';
}
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool True if the value should be kept, false otherwise
*/
public function accept()
{
if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) {
return false;
}
if ($this->excludedPattern) {
$path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath();
$path = str_replace('\\', '/', $path);
return !preg_match($this->excludedPattern, $path);
}
return true;
}
/**
* @return bool
*/
public function hasChildren()
{
return $this->isRecursive && $this->iterator->hasChildren();
}
public function getChildren()
{
$children = new self($this->iterator->getChildren(), []);
$children->excludedDirs = $this->excludedDirs;
$children->excludedPattern = $this->excludedPattern;
return $children;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* PathFilterIterator filters files by path patterns (e.g. some/special/dir).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author WÅodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class PathFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$filename = $this->current()->getRelativePathname();
if ('\\' === \DIRECTORY_SEPARATOR) {
$filename = str_replace('\\', '/', $filename);
}
return $this->isAccepted($filename);
}
/**
* Converts strings to regexp.
*
* PCRE patterns are left unchanged.
*
* Default conversion:
* 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/'
*
* Use only / as directory separator (on Windows also).
*
* @param string $str Pattern: regexp or dirname
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class MultiplePcreFilterIterator extends \FilterIterator
{
protected $matchRegexps = [];
protected $noMatchRegexps = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param string[] $matchPatterns An array of patterns that need to match
* @param string[] $noMatchPatterns An array of patterns that need to not match
*/
public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns)
{
foreach ($matchPatterns as $pattern) {
$this->matchRegexps[] = $this->toRegex($pattern);
}
foreach ($noMatchPatterns as $pattern) {
$this->noMatchRegexps[] = $this->toRegex($pattern);
}
parent::__construct($iterator);
}
/**
* Checks whether the string is accepted by the regex filters.
*
* If there is no regexps defined in the class, this method will accept the string.
* Such case can be handled by child classes before calling the method if they want to
* apply a different behavior.
*
* @return bool
*/
protected function isAccepted(string $string)
{
// should at least not match one rule to exclude
foreach ($this->noMatchRegexps as $regex) {
if (preg_match($regex, $string)) {
return false;
}
}
// should at least match one rule
if ($this->matchRegexps) {
foreach ($this->matchRegexps as $regex) {
if (preg_match($regex, $string)) {
return true;
}
}
return false;
}
// If there is no match rules, the file is accepted
return true;
}
/**
* Checks whether the string is a regex.
*
* @return bool
*/
protected function isRegex(string $str)
{
if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) {
$start = substr($m[1], 0, 1);
$end = substr($m[1], -1);
if ($start === $end) {
return !preg_match('/[*?[:alnum:] \\\\]/', $start);
}
foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) {
if ($start === $delimiters[0] && $end === $delimiters[1]) {
return true;
}
}
}
return false;
}
/**
* Converts string into regexp.
*
* @return string
*/
abstract protected function toRegex(string $str);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Finder\SplFileInfo;
/**
* Extends the \RecursiveDirectoryIterator to support relative paths.
*
* @author Victor Berchet <victor@suumit.com>
*/
class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator
{
/**
* @var bool
*/
private $ignoreUnreadableDirs;
/**
* @var bool
*/
private $rewindable;
// these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations
private $rootPath;
private $subPath;
private $directorySeparator = '/';
/**
* @throws \RuntimeException
*/
public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false)
{
if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) {
throw new \RuntimeException('This iterator only support returning current as fileinfo.');
}
parent::__construct($path, $flags);
$this->ignoreUnreadableDirs = $ignoreUnreadableDirs;
$this->rootPath = $path;
if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) {
$this->directorySeparator = \DIRECTORY_SEPARATOR;
}
}
/**
* Return an instance of SplFileInfo with support for relative paths.
*
* @return SplFileInfo File information
*/
public function current()
{
// the logic here avoids redoing the same work in all iterations
if (null === $subPathname = $this->subPath) {
$subPathname = $this->subPath = (string) $this->getSubPath();
}
if ('' !== $subPathname) {
$subPathname .= $this->directorySeparator;
}
$subPathname .= $this->getFilename();
if ('/' !== $basePath = $this->rootPath) {
$basePath .= $this->directorySeparator;
}
return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname);
}
/**
* @return \RecursiveIterator
*
* @throws AccessDeniedException
*/
public function getChildren()
{
try {
$children = parent::getChildren();
if ($children instanceof self) {
// parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore
$children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs;
// performance optimization to avoid redoing the same work in all children
$children->rewindable = &$this->rewindable;
$children->rootPath = $this->rootPath;
}
return $children;
} catch (\UnexpectedValueException $e) {
if ($this->ignoreUnreadableDirs) {
// If directory is unreadable and finder is set to ignore it, a fake empty content is returned.
return new \RecursiveArrayIterator([]);
} else {
throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e);
}
}
}
/**
* Do nothing for non rewindable stream.
*/
public function rewind()
{
if (false === $this->isRewindable()) {
return;
}
parent::rewind();
}
/**
* Checks if the stream is rewindable.
*
* @return bool true when the stream is rewindable, false otherwise
*/
public function isRewindable()
{
if (null !== $this->rewindable) {
return $this->rewindable;
}
if (false !== $stream = @opendir($this->getPath())) {
$infos = stream_get_meta_data($stream);
closedir($stream);
if ($infos['seekable']) {
return $this->rewindable = true;
}
}
return $this->rewindable = false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FileTypeFilterIterator only keeps files, directories, or both.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileTypeFilterIterator extends \FilterIterator
{
public const ONLY_FILES = 1;
public const ONLY_DIRECTORIES = 2;
private $mode;
/**
* @param \Iterator $iterator The Iterator to filter
* @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES)
*/
public function __construct(\Iterator $iterator, int $mode)
{
$this->mode = $mode;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) {
return false;
} elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) {
return false;
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Comparator\DateComparator;
/**
* DateRangeFilterIterator filters out files that are not in the given date range (last modified dates).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateRangeFilterIterator extends \FilterIterator
{
private $comparators = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param DateComparator[] $comparators An array of DateComparator instances
*/
public function __construct(\Iterator $iterator, array $comparators)
{
$this->comparators = $comparators;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
if (!file_exists($fileinfo->getPathname())) {
return false;
}
$filedate = $fileinfo->getMTime();
foreach ($this->comparators as $compare) {
if (!$compare->test($filedate)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* FilecontentFilterIterator filters files by their contents using patterns (regexps or strings).
*
* @author Fabien Potencier <fabien@symfony.com>
* @author WÅodzimierz Gajda <gajdaw@gajdaw.pl>
*/
class FilecontentFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
if (!$this->matchRegexps && !$this->noMatchRegexps) {
return true;
}
$fileinfo = $this->current();
if ($fileinfo->isDir() || !$fileinfo->isReadable()) {
return false;
}
$content = $fileinfo->getContents();
if (!$content) {
return false;
}
return $this->isAccepted($content);
}
/**
* Converts string to regexp if necessary.
*
* @param string $str Pattern: string or regexp
*
* @return string regexp corresponding to a given string or regexp
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* @author Jérémy Derussé <jeremy@derusse.com>
*
* @internal
*/
class LazyIterator implements \IteratorAggregate
{
private $iteratorFactory;
public function __construct(callable $iteratorFactory)
{
$this->iteratorFactory = $iteratorFactory;
}
public function getIterator(): \Traversable
{
yield from ($this->iteratorFactory)();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* SortableIterator applies a sort on a given Iterator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SortableIterator implements \IteratorAggregate
{
public const SORT_BY_NONE = 0;
public const SORT_BY_NAME = 1;
public const SORT_BY_TYPE = 2;
public const SORT_BY_ACCESSED_TIME = 3;
public const SORT_BY_CHANGED_TIME = 4;
public const SORT_BY_MODIFIED_TIME = 5;
public const SORT_BY_NAME_NATURAL = 6;
private $iterator;
private $sort;
/**
* @param \Traversable $iterator The Iterator to filter
* @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback)
*
* @throws \InvalidArgumentException
*/
public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false)
{
$this->iterator = $iterator;
$order = $reverseOrder ? -1 : 1;
if (self::SORT_BY_NAME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_NAME_NATURAL === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_TYPE === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
if ($a->isDir() && $b->isFile()) {
return -$order;
} elseif ($a->isFile() && $b->isDir()) {
return $order;
}
return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname());
};
} elseif (self::SORT_BY_ACCESSED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getATime() - $b->getATime());
};
} elseif (self::SORT_BY_CHANGED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getCTime() - $b->getCTime());
};
} elseif (self::SORT_BY_MODIFIED_TIME === $sort) {
$this->sort = static function (\SplFileInfo $a, \SplFileInfo $b) use ($order) {
return $order * ($a->getMTime() - $b->getMTime());
};
} elseif (self::SORT_BY_NONE === $sort) {
$this->sort = $order;
} elseif (\is_callable($sort)) {
$this->sort = $reverseOrder ? static function (\SplFileInfo $a, \SplFileInfo $b) use ($sort) { return -$sort($a, $b); } : $sort;
} else {
throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.');
}
}
/**
* @return \Traversable
*/
public function getIterator()
{
if (1 === $this->sort) {
return $this->iterator;
}
$array = iterator_to_array($this->iterator, true);
if (-1 === $this->sort) {
$array = array_reverse($array);
} else {
uasort($array, $this->sort);
}
return new \ArrayIterator($array);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* CustomFilterIterator filters files by applying anonymous functions.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CustomFilterIterator extends \FilterIterator
{
private $filters = [];
/**
* @param \Iterator $iterator The Iterator to filter
* @param callable[] $filters An array of PHP callbacks
*
* @throws \InvalidArgumentException
*/
public function __construct(\Iterator $iterator, array $filters)
{
foreach ($filters as $filter) {
if (!\is_callable($filter)) {
throw new \InvalidArgumentException('Invalid PHP callback.');
}
}
$this->filters = $filters;
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
$fileinfo = $this->current();
foreach ($this->filters as $filter) {
if (false === $filter($fileinfo)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
use Symfony\Component\Finder\Glob;
/**
* FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FilenameFilterIterator extends MultiplePcreFilterIterator
{
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
return $this->isAccepted($this->current()->getFilename());
}
/**
* Converts glob to regexp.
*
* PCRE patterns are left unchanged.
* Glob strings are transformed with Glob::toRegex().
*
* @param string $str Pattern: glob or regexp
*
* @return string regexp corresponding to a given glob or regexp
*/
protected function toRegex(string $str)
{
return $this->isRegex($str) ? $str : Glob::toRegex($str);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Iterator;
/**
* DepthRangeFilterIterator limits the directory depth.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DepthRangeFilterIterator extends \FilterIterator
{
private $minDepth = 0;
/**
* @param \RecursiveIteratorIterator $iterator The Iterator to filter
* @param int $minDepth The min depth
* @param int $maxDepth The max depth
*/
public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX)
{
$this->minDepth = $minDepth;
$iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth);
parent::__construct($iterator);
}
/**
* Filters the iterator values.
*
* @return bool true if the value should be kept, false otherwise
*/
public function accept()
{
return $this->getInnerIterator()->getDepth() >= $this->minDepth;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
use Symfony\Component\Finder\Comparator\DateComparator;
use Symfony\Component\Finder\Comparator\NumberComparator;
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
use Symfony\Component\Finder\Iterator\CustomFilterIterator;
use Symfony\Component\Finder\Iterator\DateRangeFilterIterator;
use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator;
use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator;
use Symfony\Component\Finder\Iterator\FilecontentFilterIterator;
use Symfony\Component\Finder\Iterator\FilenameFilterIterator;
use Symfony\Component\Finder\Iterator\LazyIterator;
use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator;
use Symfony\Component\Finder\Iterator\SortableIterator;
/**
* Finder allows to build rules to find files and directories.
*
* It is a thin wrapper around several specialized iterator classes.
*
* All rules may be invoked several times.
*
* All methods return the current Finder object to allow chaining:
*
* $finder = Finder::create()->files()->name('*.php')->in(__DIR__);
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Finder implements \IteratorAggregate, \Countable
{
public const IGNORE_VCS_FILES = 1;
public const IGNORE_DOT_FILES = 2;
public const IGNORE_VCS_IGNORED_FILES = 4;
private $mode = 0;
private $names = [];
private $notNames = [];
private $exclude = [];
private $filters = [];
private $depths = [];
private $sizes = [];
private $followLinks = false;
private $reverseSorting = false;
private $sort = false;
private $ignore = 0;
private $dirs = [];
private $dates = [];
private $iterators = [];
private $contains = [];
private $notContains = [];
private $paths = [];
private $notPaths = [];
private $ignoreUnreadableDirs = false;
private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg'];
public function __construct()
{
$this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES;
}
/**
* Creates a new Finder.
*
* @return static
*/
public static function create()
{
return new static();
}
/**
* Restricts the matching to directories only.
*
* @return $this
*/
public function directories()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES;
return $this;
}
/**
* Restricts the matching to files only.
*
* @return $this
*/
public function files()
{
$this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES;
return $this;
}
/**
* Adds tests for the directory depth.
*
* Usage:
*
* $finder->depth('> 1') // the Finder will start matching at level 1.
* $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point.
* $finder->depth(['>= 1', '< 3'])
*
* @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels
*
* @return $this
*
* @see DepthRangeFilterIterator
* @see NumberComparator
*/
public function depth($levels)
{
foreach ((array) $levels as $level) {
$this->depths[] = new Comparator\NumberComparator($level);
}
return $this;
}
/**
* Adds tests for file dates (last modified).
*
* The date must be something that strtotime() is able to parse:
*
* $finder->date('since yesterday');
* $finder->date('until 2 days ago');
* $finder->date('> now - 2 hours');
* $finder->date('>= 2005-10-15');
* $finder->date(['>= 2005-10-15', '<= 2006-05-27']);
*
* @param string|string[] $dates A date range string or an array of date ranges
*
* @return $this
*
* @see strtotime
* @see DateRangeFilterIterator
* @see DateComparator
*/
public function date($dates)
{
foreach ((array) $dates as $date) {
$this->dates[] = new Comparator\DateComparator($date);
}
return $this;
}
/**
* Adds rules that files must match.
*
* You can use patterns (delimited with / sign), globs or simple strings.
*
* $finder->name('*.php')
* $finder->name('/\.php$/') // same as above
* $finder->name('test.php')
* $finder->name(['test.py', 'test.php'])
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function name($patterns)
{
$this->names = array_merge($this->names, (array) $patterns);
return $this;
}
/**
* Adds rules that files must not match.
*
* @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notName($patterns)
{
$this->notNames = array_merge($this->notNames, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must match.
*
* Strings or PCRE patterns can be used:
*
* $finder->contains('Lorem ipsum')
* $finder->contains('/Lorem ipsum/i')
* $finder->contains(['dolor', '/ipsum/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function contains($patterns)
{
$this->contains = array_merge($this->contains, (array) $patterns);
return $this;
}
/**
* Adds tests that file contents must not match.
*
* Strings or PCRE patterns can be used:
*
* $finder->notContains('Lorem ipsum')
* $finder->notContains('/Lorem ipsum/i')
* $finder->notContains(['lorem', '/dolor/i'])
*
* @param string|string[] $patterns A pattern (string or regexp) or an array of patterns
*
* @return $this
*
* @see FilecontentFilterIterator
*/
public function notContains($patterns)
{
$this->notContains = array_merge($this->notContains, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->path('some/special/dir')
* $finder->path('/some\/special\/dir/') // same as above
* $finder->path(['some dir', 'another/dir'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function path($patterns)
{
$this->paths = array_merge($this->paths, (array) $patterns);
return $this;
}
/**
* Adds rules that filenames must not match.
*
* You can use patterns (delimited with / sign) or simple strings.
*
* $finder->notPath('some/special/dir')
* $finder->notPath('/some\/special\/dir/') // same as above
* $finder->notPath(['some/file.txt', 'another/file.log'])
*
* Use only / as dirname separator.
*
* @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns
*
* @return $this
*
* @see FilenameFilterIterator
*/
public function notPath($patterns)
{
$this->notPaths = array_merge($this->notPaths, (array) $patterns);
return $this;
}
/**
* Adds tests for file sizes.
*
* $finder->size('> 10K');
* $finder->size('<= 1Ki');
* $finder->size(4);
* $finder->size(['> 10K', '< 20K'])
*
* @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges
*
* @return $this
*
* @see SizeRangeFilterIterator
* @see NumberComparator
*/
public function size($sizes)
{
foreach ((array) $sizes as $size) {
$this->sizes[] = new Comparator\NumberComparator($size);
}
return $this;
}
/**
* Excludes directories.
*
* Directories passed as argument must be relative to the ones defined with the `in()` method. For example:
*
* $finder->in(__DIR__)->exclude('ruby');
*
* @param string|array $dirs A directory path or an array of directories
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function exclude($dirs)
{
$this->exclude = array_merge($this->exclude, (array) $dirs);
return $this;
}
/**
* Excludes "hidden" directories and files (starting with a dot).
*
* This option is enabled by default.
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreDotFiles(bool $ignoreDotFiles)
{
if ($ignoreDotFiles) {
$this->ignore |= static::IGNORE_DOT_FILES;
} else {
$this->ignore &= ~static::IGNORE_DOT_FILES;
}
return $this;
}
/**
* Forces the finder to ignore version control directories.
*
* This option is enabled by default.
*
* @return $this
*
* @see ExcludeDirectoryFilterIterator
*/
public function ignoreVCS(bool $ignoreVCS)
{
if ($ignoreVCS) {
$this->ignore |= static::IGNORE_VCS_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_FILES;
}
return $this;
}
/**
* Forces Finder to obey .gitignore and ignore files based on rules listed there.
*
* This option is disabled by default.
*
* @return $this
*/
public function ignoreVCSIgnored(bool $ignoreVCSIgnored)
{
if ($ignoreVCSIgnored) {
$this->ignore |= static::IGNORE_VCS_IGNORED_FILES;
} else {
$this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES;
}
return $this;
}
/**
* Adds VCS patterns.
*
* @see ignoreVCS()
*
* @param string|string[] $pattern VCS patterns to ignore
*/
public static function addVCSPattern($pattern)
{
foreach ((array) $pattern as $p) {
self::$vcsPatterns[] = $p;
}
self::$vcsPatterns = array_unique(self::$vcsPatterns);
}
/**
* Sorts files and directories by an anonymous function.
*
* The anonymous function receives two \SplFileInfo instances to compare.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sort(\Closure $closure)
{
$this->sort = $closure;
return $this;
}
/**
* Sorts files and directories by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByName(bool $useNaturalSort = false)
{
$this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME;
return $this;
}
/**
* Sorts files and directories by type (directories before files), then by name.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByType()
{
$this->sort = Iterator\SortableIterator::SORT_BY_TYPE;
return $this;
}
/**
* Sorts files and directories by the last accessed time.
*
* This is the time that the file was last accessed, read or written to.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByAccessedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME;
return $this;
}
/**
* Reverses the sorting.
*
* @return $this
*/
public function reverseSorting()
{
$this->reverseSorting = true;
return $this;
}
/**
* Sorts files and directories by the last inode changed time.
*
* This is the time that the inode information was last modified (permissions, owner, group or other metadata).
*
* On Windows, since inode is not available, changed time is actually the file creation time.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByChangedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME;
return $this;
}
/**
* Sorts files and directories by the last modified time.
*
* This is the last time the actual contents of the file were last modified.
*
* This can be slow as all the matching files and directories must be retrieved for comparison.
*
* @return $this
*
* @see SortableIterator
*/
public function sortByModifiedTime()
{
$this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME;
return $this;
}
/**
* Filters the iterator with an anonymous function.
*
* The anonymous function receives a \SplFileInfo and must return false
* to remove files.
*
* @return $this
*
* @see CustomFilterIterator
*/
public function filter(\Closure $closure)
{
$this->filters[] = $closure;
return $this;
}
/**
* Forces the following of symlinks.
*
* @return $this
*/
public function followLinks()
{
$this->followLinks = true;
return $this;
}
/**
* Tells finder to ignore unreadable directories.
*
* By default, scanning unreadable directories content throws an AccessDeniedException.
*
* @return $this
*/
public function ignoreUnreadableDirs(bool $ignore = true)
{
$this->ignoreUnreadableDirs = $ignore;
return $this;
}
/**
* Searches files and directories which match defined rules.
*
* @param string|string[] $dirs A directory path or an array of directories
*
* @return $this
*
* @throws DirectoryNotFoundException if one of the directories does not exist
*/
public function in($dirs)
{
$resolvedDirs = [];
foreach ((array) $dirs as $dir) {
if (is_dir($dir)) {
$resolvedDirs[] = $this->normalizeDir($dir);
} elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) {
sort($glob);
$resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob));
} else {
throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir));
}
}
$this->dirs = array_merge($this->dirs, $resolvedDirs);
return $this;
}
/**
* Returns an Iterator for the current Finder configuration.
*
* This method implements the IteratorAggregate interface.
*
* @return \Iterator|SplFileInfo[] An iterator
*
* @throws \LogicException if the in() method has not been called
*/
public function getIterator()
{
if (0 === \count($this->dirs) && 0 === \count($this->iterators)) {
throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.');
}
if (1 === \count($this->dirs) && 0 === \count($this->iterators)) {
$iterator = $this->searchInDirectory($this->dirs[0]);
if ($this->sort || $this->reverseSorting) {
$iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
}
return $iterator;
}
$iterator = new \AppendIterator();
foreach ($this->dirs as $dir) {
$iterator->append(new \IteratorIterator(new LazyIterator(function () use ($dir) {
return $this->searchInDirectory($dir);
})));
}
foreach ($this->iterators as $it) {
$iterator->append($it);
}
if ($this->sort || $this->reverseSorting) {
$iterator = (new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting))->getIterator();
}
return $iterator;
}
/**
* Appends an existing set of files/directories to the finder.
*
* The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array.
*
* @return $this
*
* @throws \InvalidArgumentException when the given argument is not iterable
*/
public function append(iterable $iterator)
{
if ($iterator instanceof \IteratorAggregate) {
$this->iterators[] = $iterator->getIterator();
} elseif ($iterator instanceof \Iterator) {
$this->iterators[] = $iterator;
} elseif ($iterator instanceof \Traversable || \is_array($iterator)) {
$it = new \ArrayIterator();
foreach ($iterator as $file) {
$file = $file instanceof \SplFileInfo ? $file : new \SplFileInfo($file);
$it[$file->getPathname()] = $file;
}
$this->iterators[] = $it;
} else {
throw new \InvalidArgumentException('Finder::append() method wrong argument type.');
}
return $this;
}
/**
* Check if any results were found.
*
* @return bool
*/
public function hasResults()
{
foreach ($this->getIterator() as $_) {
return true;
}
return false;
}
/**
* Counts all the results collected by the iterators.
*
* @return int
*/
public function count()
{
return iterator_count($this->getIterator());
}
private function searchInDirectory(string $dir): \Iterator
{
$exclude = $this->exclude;
$notPaths = $this->notPaths;
if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) {
$exclude = array_merge($exclude, self::$vcsPatterns);
}
if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) {
$notPaths[] = '#(^|/)\..+(/|$)#';
}
if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) {
$gitignoreFilePath = sprintf('%s/.gitignore', $dir);
if (!is_readable($gitignoreFilePath)) {
throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath));
}
$notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]);
}
$minDepth = 0;
$maxDepth = \PHP_INT_MAX;
foreach ($this->depths as $comparator) {
switch ($comparator->getOperator()) {
case '>':
$minDepth = $comparator->getTarget() + 1;
break;
case '>=':
$minDepth = $comparator->getTarget();
break;
case '<':
$maxDepth = $comparator->getTarget() - 1;
break;
case '<=':
$maxDepth = $comparator->getTarget();
break;
default:
$minDepth = $maxDepth = $comparator->getTarget();
}
}
$flags = \RecursiveDirectoryIterator::SKIP_DOTS;
if ($this->followLinks) {
$flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS;
}
$iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs);
if ($exclude) {
$iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude);
}
$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST);
if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) {
$iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth);
}
if ($this->mode) {
$iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode);
}
if ($this->names || $this->notNames) {
$iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames);
}
if ($this->contains || $this->notContains) {
$iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains);
}
if ($this->sizes) {
$iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes);
}
if ($this->dates) {
$iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates);
}
if ($this->filters) {
$iterator = new Iterator\CustomFilterIterator($iterator, $this->filters);
}
if ($this->paths || $notPaths) {
$iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths);
}
return $iterator;
}
/**
* Normalizes given directory names by removing trailing slashes.
*
* Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper
*/
private function normalizeDir(string $dir): string
{
if ('/' === $dir) {
return $dir;
}
$dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR);
if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) {
$dir .= '/';
}
return $dir;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* NumberComparator compiles a simple comparison to an anonymous
* subroutine, which you can call with a value to be tested again.
*
* Now this would be very pointless, if NumberCompare didn't understand
* magnitudes.
*
* The target value may use magnitudes of kilobytes (k, ki),
* megabytes (m, mi), or gigabytes (g, gi). Those suffixed
* with an i use the appropriate 2**n version in accordance with the
* IEC standard: http://physics.nist.gov/cuu/Units/binary.html
*
* Based on the Perl Number::Compare module.
*
* @author Fabien Potencier <fabien@symfony.com> PHP port
* @author Richard Clamp <richardc@unixbeard.net> Perl version
* @copyright 2004-2005 Fabien Potencier <fabien@symfony.com>
* @copyright 2002 Richard Clamp <richardc@unixbeard.net>
*
* @see http://physics.nist.gov/cuu/Units/binary.html
*/
class NumberComparator extends Comparator
{
/**
* @param string|int $test A comparison string or an integer
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(?string $test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test));
}
$target = $matches[2];
if (!is_numeric($target)) {
throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target));
}
if (isset($matches[3])) {
// magnitude
switch (strtolower($matches[3])) {
case 'k':
$target *= 1000;
break;
case 'ki':
$target *= 1024;
break;
case 'm':
$target *= 1000000;
break;
case 'mi':
$target *= 1024 * 1024;
break;
case 'g':
$target *= 1000000000;
break;
case 'gi':
$target *= 1024 * 1024 * 1024;
break;
}
}
$this->setTarget($target);
$this->setOperator($matches[1] ?? '==');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* Comparator.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Comparator
{
private $target;
private $operator = '==';
/**
* Gets the target value.
*
* @return string The target value
*/
public function getTarget()
{
return $this->target;
}
public function setTarget(string $target)
{
$this->target = $target;
}
/**
* Gets the comparison operator.
*
* @return string The operator
*/
public function getOperator()
{
return $this->operator;
}
/**
* Sets the comparison operator.
*
* @throws \InvalidArgumentException
*/
public function setOperator(string $operator)
{
if ('' === $operator) {
$operator = '==';
}
if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) {
throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator));
}
$this->operator = $operator;
}
/**
* Tests against the target.
*
* @param mixed $test A test value
*
* @return bool
*/
public function test($test)
{
switch ($this->operator) {
case '>':
return $test > $this->target;
case '>=':
return $test >= $this->target;
case '<':
return $test < $this->target;
case '<=':
return $test <= $this->target;
case '!=':
return $test != $this->target;
}
return $test == $this->target;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Comparator;
/**
* DateCompare compiles date comparisons.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DateComparator extends Comparator
{
/**
* @param string $test A comparison string
*
* @throws \InvalidArgumentException If the test is not understood
*/
public function __construct(string $test)
{
if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) {
throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test));
}
try {
$date = new \DateTime($matches[2]);
$target = $date->format('U');
} catch (\Exception $e) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2]));
}
$operator = $matches[1] ?? '==';
if ('since' === $operator || 'after' === $operator) {
$operator = '>';
}
if ('until' === $operator || 'before' === $operator) {
$operator = '<';
}
$this->setOperator($operator);
$this->setTarget($target);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Extends \SplFileInfo to support relative paths.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SplFileInfo extends \SplFileInfo
{
private $relativePath;
private $relativePathname;
/**
* @param string $file The file name
* @param string $relativePath The relative path
* @param string $relativePathname The relative path name
*/
public function __construct(string $file, string $relativePath, string $relativePathname)
{
parent::__construct($file);
$this->relativePath = $relativePath;
$this->relativePathname = $relativePathname;
}
/**
* Returns the relative path.
*
* This path does not contain the file name.
*
* @return string the relative path
*/
public function getRelativePath()
{
return $this->relativePath;
}
/**
* Returns the relative path name.
*
* This path contains the file name.
*
* @return string the relative path name
*/
public function getRelativePathname()
{
return $this->relativePathname;
}
public function getFilenameWithoutExtension(): string
{
$filename = $this->getFilename();
return pathinfo($filename, \PATHINFO_FILENAME);
}
/**
* Returns the contents of the file.
*
* @return string the contents of the file
*
* @throws \RuntimeException
*/
public function getContents()
{
set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; });
$content = file_get_contents($this->getPathname());
restore_error_handler();
if (false === $content) {
throw new \RuntimeException($error);
}
return $content;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder;
/**
* Gitignore matches against text.
*
* @author Ahmed Abdou <mail@ahmd.io>
*/
class Gitignore
{
/**
* Returns a regexp which is the equivalent of the gitignore pattern.
*
* @return string The regexp
*/
public static function toRegex(string $gitignoreFileContent): string
{
$gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent);
$gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent);
$positives = [];
$negatives = [];
foreach ($gitignoreLines as $i => $line) {
$line = trim($line);
if ('' === $line) {
continue;
}
if (1 === preg_match('/^!/', $line)) {
$positives[$i] = null;
$negatives[$i] = self::getRegexFromGitignore(preg_replace('/^!(.*)/', '${1}', $line), true);
continue;
}
$negatives[$i] = null;
$positives[$i] = self::getRegexFromGitignore($line);
}
$index = 0;
$patterns = [];
foreach ($positives as $pattern) {
if (null === $pattern) {
continue;
}
$negativesAfter = array_filter(\array_slice($negatives, ++$index));
if ([] !== $negativesAfter) {
$pattern .= sprintf('(?<!%s)', implode('|', $negativesAfter));
}
$patterns[] = $pattern;
}
return sprintf('/^((%s))$/', implode(')|(', $patterns));
}
private static function getRegexFromGitignore(string $gitignorePattern, bool $negative = false): string
{
$regex = '';
$isRelativePath = false;
// If there is a separator at the beginning or middle (or both) of the pattern, then the pattern is relative to the directory level of the particular .gitignore file itself
$slashPosition = strpos($gitignorePattern, '/');
if (false !== $slashPosition && \strlen($gitignorePattern) - 1 !== $slashPosition) {
if (0 === $slashPosition) {
$gitignorePattern = substr($gitignorePattern, 1);
}
$isRelativePath = true;
$regex .= '^';
}
if ('/' === $gitignorePattern[\strlen($gitignorePattern) - 1]) {
$gitignorePattern = substr($gitignorePattern, 0, -1);
}
$iMax = \strlen($gitignorePattern);
for ($i = 0; $i < $iMax; ++$i) {
$tripleChars = substr($gitignorePattern, $i, 3);
if ('**/' === $tripleChars || '/**' === $tripleChars) {
$regex .= '.*';
$i += 2;
continue;
}
$doubleChars = substr($gitignorePattern, $i, 2);
if ('**' === $doubleChars) {
$regex .= '.*';
++$i;
continue;
}
if ('*/' === $doubleChars) {
$regex .= '[^\/]*\/?[^\/]*';
++$i;
continue;
}
$c = $gitignorePattern[$i];
switch ($c) {
case '*':
$regex .= $isRelativePath ? '[^\/]*' : '[^\/]*\/?[^\/]*';
break;
case '/':
case '.':
case ':':
case '(':
case ')':
case '{':
case '}':
$regex .= '\\'.$c;
break;
default:
$regex .= $c;
}
}
if ($negative) {
// a lookbehind assertion has to be a fixed width (it can not have nested '|' statements)
return sprintf('%s$|%s\/$', $regex, $regex);
}
return '(?>'.$regex.'($|\/.*))';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Andreas Erhard <andreas.erhard@i-med.ac.at>
*/
class DirectoryNotFoundException extends \InvalidArgumentException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Finder\Exception;
/**
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class AccessDeniedException extends \UnexpectedValueException
{
}
Copyright (c) 2004-2021 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Mbstring;
/**
* Partial mbstring implementation in PHP, iconv based, UTF-8 centric.
*
* Implemented:
* - mb_chr - Returns a specific character from its Unicode code point
* - mb_convert_encoding - Convert character encoding
* - mb_convert_variables - Convert character code in variable(s)
* - mb_decode_mimeheader - Decode string in MIME header field
* - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED
* - mb_decode_numericentity - Decode HTML numeric string reference to character
* - mb_encode_numericentity - Encode character to HTML numeric string reference
* - mb_convert_case - Perform case folding on a string
* - mb_detect_encoding - Detect character encoding
* - mb_get_info - Get internal settings of mbstring
* - mb_http_input - Detect HTTP input character encoding
* - mb_http_output - Set/Get HTTP output character encoding
* - mb_internal_encoding - Set/Get internal character encoding
* - mb_list_encodings - Returns an array of all supported encodings
* - mb_ord - Returns the Unicode code point of a character
* - mb_output_handler - Callback function converts character encoding in output buffer
* - mb_scrub - Replaces ill-formed byte sequences with substitute characters
* - mb_strlen - Get string length
* - mb_strpos - Find position of first occurrence of string in a string
* - mb_strrpos - Find position of last occurrence of a string in a string
* - mb_str_split - Convert a string to an array
* - mb_strtolower - Make a string lowercase
* - mb_strtoupper - Make a string uppercase
* - mb_substitute_character - Set/Get substitution character
* - mb_substr - Get part of string
* - mb_stripos - Finds position of first occurrence of a string within another, case insensitive
* - mb_stristr - Finds first occurrence of a string within another, case insensitive
* - mb_strrchr - Finds the last occurrence of a character in a string within another
* - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive
* - mb_strripos - Finds position of last occurrence of a string within another, case insensitive
* - mb_strstr - Finds first occurrence of a string within another
* - mb_strwidth - Return width of string
* - mb_substr_count - Count the number of substring occurrences
*
* Not implemented:
* - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more)
* - mb_ereg_* - Regular expression with multibyte support
* - mb_parse_str - Parse GET/POST/COOKIE data and set global variable
* - mb_preferred_mime_name - Get MIME charset string
* - mb_regex_encoding - Returns current encoding for multibyte regex as string
* - mb_regex_set_options - Set/Get the default options for mbregex functions
* - mb_send_mail - Send encoded mail
* - mb_split - Split multibyte string using regular expression
* - mb_strcut - Get part of string
* - mb_strimwidth - Get truncated string with specified width
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Mbstring
{
public const MB_CASE_FOLD = \PHP_INT_MAX;
private static $encodingList = ['ASCII', 'UTF-8'];
private static $language = 'neutral';
private static $internalEncoding = 'UTF-8';
private static $caseFold = [
['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'Ï€', 'κ', 'Ï<>', 'ε', "\xE1\xB9\xA1", 'ι'],
];
public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
{
if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) {
$fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
} else {
$fromEncoding = self::getEncoding($fromEncoding);
}
$toEncoding = self::getEncoding($toEncoding);
if ('BASE64' === $fromEncoding) {
$s = base64_decode($s);
$fromEncoding = $toEncoding;
}
if ('BASE64' === $toEncoding) {
return base64_encode($s);
}
if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) {
if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) {
$fromEncoding = 'Windows-1252';
}
if ('UTF-8' !== $fromEncoding) {
$s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
}
return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
}
if ('HTML-ENTITIES' === $fromEncoding) {
$s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8');
$fromEncoding = 'UTF-8';
}
return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
}
public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
{
$ok = true;
array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) {
if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) {
$ok = false;
}
});
return $ok ? $fromEncoding : false;
}
public static function mb_decode_mimeheader($s)
{
return iconv_mime_decode($s, 2, self::$internalEncoding);
}
public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
{
trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING);
}
public static function mb_decode_numericentity($s, $convmap, $encoding = null)
{
if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
return null;
}
if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
return false;
}
if (null !== $encoding && !is_scalar($encoding)) {
trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
return ''; // Instead of null (cf. mb_encode_numericentity).
}
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
$cnt = floor(\count($convmap) / 4) * 4;
for ($i = 0; $i < $cnt; $i += 4) {
// collector_decode_htmlnumericentity ignores $convmap[$i + 3]
$convmap[$i] += $convmap[$i + 2];
$convmap[$i + 1] += $convmap[$i + 2];
}
$s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) {
$c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1];
for ($i = 0; $i < $cnt; $i += 4) {
if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) {
return self::mb_chr($c - $convmap[$i + 2]);
}
}
return $m[0];
}, $s);
if (null === $encoding) {
return $s;
}
return iconv('UTF-8', $encoding.'//IGNORE', $s);
}
public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
{
if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
return null;
}
if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) {
return false;
}
if (null !== $encoding && !is_scalar($encoding)) {
trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
return null; // Instead of '' (cf. mb_decode_numericentity).
}
if (null !== $is_hex && !is_scalar($is_hex)) {
trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING);
return null;
}
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
$cnt = floor(\count($convmap) / 4) * 4;
$i = 0;
$len = \strlen($s);
$result = '';
while ($i < $len) {
$ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
$i += $ulen;
$c = self::mb_ord($uchr);
for ($j = 0; $j < $cnt; $j += 4) {
if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) {
$cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3];
$result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';';
continue 2;
}
}
$result .= $uchr;
}
if (null === $encoding) {
return $result;
}
return iconv('UTF-8', $encoding.'//IGNORE', $result);
}
public static function mb_convert_case($s, $mode, $encoding = null)
{
$s = (string) $s;
if ('' === $s) {
return '';
}
$encoding = self::getEncoding($encoding);
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
if (\MB_CASE_TITLE == $mode) {
static $titleRegexp = null;
if (null === $titleRegexp) {
$titleRegexp = self::getData('titleCaseRegexp');
}
$s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s);
} else {
if (\MB_CASE_UPPER == $mode) {
static $upper = null;
if (null === $upper) {
$upper = self::getData('upperCase');
}
$map = $upper;
} else {
if (self::MB_CASE_FOLD === $mode) {
$s = str_replace(self::$caseFold[0], self::$caseFold[1], $s);
}
static $lower = null;
if (null === $lower) {
$lower = self::getData('lowerCase');
}
$map = $lower;
}
static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
$i = 0;
$len = \strlen($s);
while ($i < $len) {
$ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
$i += $ulen;
if (isset($map[$uchr])) {
$uchr = $map[$uchr];
$nlen = \strlen($uchr);
if ($nlen == $ulen) {
$nlen = $i;
do {
$s[--$nlen] = $uchr[--$ulen];
} while ($ulen);
} else {
$s = substr_replace($s, $uchr, $i - $ulen, $ulen);
$len += $nlen - $ulen;
$i += $nlen - $ulen;
}
}
}
}
if (null === $encoding) {
return $s;
}
return iconv('UTF-8', $encoding.'//IGNORE', $s);
}
public static function mb_internal_encoding($encoding = null)
{
if (null === $encoding) {
return self::$internalEncoding;
}
$normalizedEncoding = self::getEncoding($encoding);
if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
self::$internalEncoding = $normalizedEncoding;
return true;
}
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding));
}
public static function mb_language($lang = null)
{
if (null === $lang) {
return self::$language;
}
switch ($normalizedLang = strtolower($lang)) {
case 'uni':
case 'neutral':
self::$language = $normalizedLang;
return true;
}
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang));
}
public static function mb_list_encodings()
{
return ['UTF-8'];
}
public static function mb_encoding_aliases($encoding)
{
switch (strtoupper($encoding)) {
case 'UTF8':
case 'UTF-8':
return ['utf8'];
}
return false;
}
public static function mb_check_encoding($var = null, $encoding = null)
{
if (null === $encoding) {
if (null === $var) {
return false;
}
$encoding = self::$internalEncoding;
}
return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var);
}
public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
{
if (null === $encodingList) {
$encodingList = self::$encodingList;
} else {
if (!\is_array($encodingList)) {
$encodingList = array_map('trim', explode(',', $encodingList));
}
$encodingList = array_map('strtoupper', $encodingList);
}
foreach ($encodingList as $enc) {
switch ($enc) {
case 'ASCII':
if (!preg_match('/[\x80-\xFF]/', $str)) {
return $enc;
}
break;
case 'UTF8':
case 'UTF-8':
if (preg_match('//u', $str)) {
return 'UTF-8';
}
break;
default:
if (0 === strncmp($enc, 'ISO-8859-', 9)) {
return $enc;
}
}
}
return false;
}
public static function mb_detect_order($encodingList = null)
{
if (null === $encodingList) {
return self::$encodingList;
}
if (!\is_array($encodingList)) {
$encodingList = array_map('trim', explode(',', $encodingList));
}
$encodingList = array_map('strtoupper', $encodingList);
foreach ($encodingList as $enc) {
switch ($enc) {
default:
if (strncmp($enc, 'ISO-8859-', 9)) {
return false;
}
// no break
case 'ASCII':
case 'UTF8':
case 'UTF-8':
}
}
self::$encodingList = $encodingList;
return true;
}
public static function mb_strlen($s, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return \strlen($s);
}
return @iconv_strlen($s, $encoding);
}
public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return strpos($haystack, $needle, $offset);
}
$needle = (string) $needle;
if ('' === $needle) {
if (80000 > \PHP_VERSION_ID) {
trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING);
return false;
}
return 0;
}
return iconv_strpos($haystack, $needle, $offset, $encoding);
}
public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return strrpos($haystack, $needle, $offset);
}
if ($offset != (int) $offset) {
$offset = 0;
} elseif ($offset = (int) $offset) {
if ($offset < 0) {
if (0 > $offset += self::mb_strlen($needle)) {
$haystack = self::mb_substr($haystack, 0, $offset, $encoding);
}
$offset = 0;
} else {
$haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding);
}
}
$pos = '' !== $needle || 80000 > \PHP_VERSION_ID
? iconv_strrpos($haystack, $needle, $encoding)
: self::mb_strlen($haystack, $encoding);
return false !== $pos ? $offset + $pos : false;
}
public static function mb_str_split($string, $split_length = 1, $encoding = null)
{
if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING);
return null;
}
if (1 > $split_length = (int) $split_length) {
if (80000 > \PHP_VERSION_ID) {
trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
return false;
}
throw new \ValueError('Argument #2 ($length) must be greater than 0');
}
if (null === $encoding) {
$encoding = mb_internal_encoding();
}
if ('UTF-8' === $encoding = self::getEncoding($encoding)) {
$rx = '/(';
while (65535 < $split_length) {
$rx .= '.{65535}';
$split_length -= 65535;
}
$rx .= '.{'.$split_length.'})/us';
return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
}
$result = [];
$length = mb_strlen($string, $encoding);
for ($i = 0; $i < $length; $i += $split_length) {
$result[] = mb_substr($string, $i, $split_length, $encoding);
}
return $result;
}
public static function mb_strtolower($s, $encoding = null)
{
return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding);
}
public static function mb_strtoupper($s, $encoding = null)
{
return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding);
}
public static function mb_substitute_character($c = null)
{
if (null === $c) {
return 'none';
}
if (0 === strcasecmp($c, 'none')) {
return true;
}
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint');
}
public static function mb_substr($s, $start, $length = null, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
return (string) substr($s, $start, null === $length ? 2147483647 : $length);
}
if ($start < 0) {
$start = iconv_strlen($s, $encoding) + $start;
if ($start < 0) {
$start = 0;
}
}
if (null === $length) {
$length = 2147483647;
} elseif ($length < 0) {
$length = iconv_strlen($s, $encoding) + $length - $start;
if ($length < 0) {
return '';
}
}
return (string) iconv_substr($s, $start, $length, $encoding);
}
public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
{
$haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
$needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
return self::mb_strpos($haystack, $needle, $offset, $encoding);
}
public static function mb_stristr($haystack, $needle, $part = false, $encoding = null)
{
$pos = self::mb_stripos($haystack, $needle, 0, $encoding);
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('CP850' === $encoding || 'ASCII' === $encoding) {
$pos = strrpos($haystack, $needle);
} else {
$needle = self::mb_substr($needle, 0, 1, $encoding);
$pos = iconv_strrpos($haystack, $needle, $encoding);
}
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null)
{
$needle = self::mb_substr($needle, 0, 1, $encoding);
$pos = self::mb_strripos($haystack, $needle, $encoding);
return self::getSubpart($pos, $part, $haystack, $encoding);
}
public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
{
$haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
$needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
return self::mb_strrpos($haystack, $needle, $offset, $encoding);
}
public static function mb_strstr($haystack, $needle, $part = false, $encoding = null)
{
$pos = strpos($haystack, $needle);
if (false === $pos) {
return false;
}
if ($part) {
return substr($haystack, 0, $pos);
}
return substr($haystack, $pos);
}
public static function mb_get_info($type = 'all')
{
$info = [
'internal_encoding' => self::$internalEncoding,
'http_output' => 'pass',
'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)',
'func_overload' => 0,
'func_overload_list' => 'no overload',
'mail_charset' => 'UTF-8',
'mail_header_encoding' => 'BASE64',
'mail_body_encoding' => 'BASE64',
'illegal_chars' => 0,
'encoding_translation' => 'Off',
'language' => self::$language,
'detect_order' => self::$encodingList,
'substitute_character' => 'none',
'strict_detection' => 'Off',
];
if ('all' === $type) {
return $info;
}
if (isset($info[$type])) {
return $info[$type];
}
return false;
}
public static function mb_http_input($type = '')
{
return false;
}
public static function mb_http_output($encoding = null)
{
return null !== $encoding ? 'pass' === $encoding : 'pass';
}
public static function mb_strwidth($s, $encoding = null)
{
$encoding = self::getEncoding($encoding);
if ('UTF-8' !== $encoding) {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
}
$s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
return ($wide << 1) + iconv_strlen($s, 'UTF-8');
}
public static function mb_substr_count($haystack, $needle, $encoding = null)
{
return substr_count($haystack, $needle);
}
public static function mb_output_handler($contents, $status)
{
return $contents;
}
public static function mb_chr($code, $encoding = null)
{
if (0x80 > $code %= 0x200000) {
$s = \chr($code);
} elseif (0x800 > $code) {
$s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
} elseif (0x10000 > $code) {
$s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
} else {
$s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
}
if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
$s = mb_convert_encoding($s, $encoding, 'UTF-8');
}
return $s;
}
public static function mb_ord($s, $encoding = null)
{
if ('UTF-8' !== $encoding = self::getEncoding($encoding)) {
$s = mb_convert_encoding($s, 'UTF-8', $encoding);
}
if (1 === \strlen($s)) {
return \ord($s);
}
$code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0;
if (0xF0 <= $code) {
return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80;
}
if (0xE0 <= $code) {
return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80;
}
if (0xC0 <= $code) {
return (($code - 0xC0) << 6) + $s[2] - 0x80;
}
return $code;
}
private static function getSubpart($pos, $part, $haystack, $encoding)
{
if (false === $pos) {
return false;
}
if ($part) {
return self::mb_substr($haystack, 0, $pos, $encoding);
}
return self::mb_substr($haystack, $pos, null, $encoding);
}
private static function html_encoding_callback(array $m)
{
$i = 1;
$entities = '';
$m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8'));
while (isset($m[$i])) {
if (0x80 > $m[$i]) {
$entities .= \chr($m[$i++]);
continue;
}
if (0xF0 <= $m[$i]) {
$c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
} elseif (0xE0 <= $m[$i]) {
$c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80;
} else {
$c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80;
}
$entities .= '&#'.$c.';';
}
return $entities;
}
private static function title_case(array $s)
{
return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8');
}
private static function getData($file)
{
if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
return require $file;
}
return false;
}
private static function getEncoding($encoding)
{
if (null === $encoding) {
return self::$internalEncoding;
}
if ('UTF-8' === $encoding) {
return 'UTF-8';
}
$encoding = strtoupper($encoding);
if ('8BIT' === $encoding || 'BINARY' === $encoding) {
return 'CP850';
}
if ('UTF8' === $encoding) {
return 'UTF-8';
}
return $encoding;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Mbstring as p;
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
if (!function_exists('mb_convert_encoding')) {
function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); }
}
if (!function_exists('mb_decode_mimeheader')) {
function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); }
}
if (!function_exists('mb_encode_mimeheader')) {
function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); }
}
if (!function_exists('mb_decode_numericentity')) {
function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); }
}
if (!function_exists('mb_encode_numericentity')) {
function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); }
}
if (!function_exists('mb_convert_case')) {
function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); }
}
if (!function_exists('mb_internal_encoding')) {
function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); }
}
if (!function_exists('mb_language')) {
function mb_language($language = null) { return p\Mbstring::mb_language($language); }
}
if (!function_exists('mb_list_encodings')) {
function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); }
}
if (!function_exists('mb_encoding_aliases')) {
function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); }
}
if (!function_exists('mb_check_encoding')) {
function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); }
}
if (!function_exists('mb_detect_encoding')) {
function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); }
}
if (!function_exists('mb_detect_order')) {
function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
}
if (!function_exists('mb_parse_str')) {
function mb_parse_str($string, &$result = []) { parse_str($string, $result); }
}
if (!function_exists('mb_strlen')) {
function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }
}
if (!function_exists('mb_strpos')) {
function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strtolower')) {
function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); }
}
if (!function_exists('mb_strtoupper')) {
function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); }
}
if (!function_exists('mb_substitute_character')) {
function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); }
}
if (!function_exists('mb_substr')) {
function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); }
}
if (!function_exists('mb_stripos')) {
function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_stristr')) {
function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrchr')) {
function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strrichr')) {
function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_strripos')) {
function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strrpos')) {
function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); }
}
if (!function_exists('mb_strstr')) {
function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); }
}
if (!function_exists('mb_get_info')) {
function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); }
}
if (!function_exists('mb_http_output')) {
function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); }
}
if (!function_exists('mb_strwidth')) {
function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); }
}
if (!function_exists('mb_substr_count')) {
function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); }
}
if (!function_exists('mb_output_handler')) {
function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); }
}
if (!function_exists('mb_http_input')) {
function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); }
}
if (!function_exists('mb_convert_variables')) {
function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); }
}
if (!function_exists('mb_ord')) {
function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); }
}
if (!function_exists('mb_chr')) {
function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); }
}
if (!function_exists('mb_scrub')) {
function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); }
}
if (!function_exists('mb_str_split')) {
function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
}
if (extension_loaded('mbstring')) {
return;
}
if (!defined('MB_CASE_UPPER')) {
define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
define('MB_CASE_TITLE', 2);
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Mbstring as p;
if (!function_exists('mb_convert_encoding')) {
function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); }
}
if (!function_exists('mb_decode_mimeheader')) {
function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); }
}
if (!function_exists('mb_encode_mimeheader')) {
function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); }
}
if (!function_exists('mb_decode_numericentity')) {
function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); }
}
if (!function_exists('mb_encode_numericentity')) {
function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); }
}
if (!function_exists('mb_convert_case')) {
function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); }
}
if (!function_exists('mb_internal_encoding')) {
function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); }
}
if (!function_exists('mb_language')) {
function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); }
}
if (!function_exists('mb_list_encodings')) {
function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); }
}
if (!function_exists('mb_encoding_aliases')) {
function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); }
}
if (!function_exists('mb_check_encoding')) {
function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); }
}
if (!function_exists('mb_detect_encoding')) {
function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); }
}
if (!function_exists('mb_detect_order')) {
function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order((string) $encoding); }
}
if (!function_exists('mb_parse_str')) {
function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); }
}
if (!function_exists('mb_strlen')) {
function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); }
}
if (!function_exists('mb_strpos')) {
function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
}
if (!function_exists('mb_strtolower')) {
function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); }
}
if (!function_exists('mb_strtoupper')) {
function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); }
}
if (!function_exists('mb_substitute_character')) {
function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); }
}
if (!function_exists('mb_substr')) {
function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); }
}
if (!function_exists('mb_stripos')) {
function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
}
if (!function_exists('mb_stristr')) {
function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_strrchr')) {
function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, $before_needle, (bool) $encoding); }
}
if (!function_exists('mb_strrichr')) {
function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_strripos')) {
function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
}
if (!function_exists('mb_strrpos')) {
function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); }
}
if (!function_exists('mb_strstr')) {
function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_get_info')) {
function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); }
}
if (!function_exists('mb_http_output')) {
function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); }
}
if (!function_exists('mb_strwidth')) {
function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); }
}
if (!function_exists('mb_substr_count')) {
function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); }
}
if (!function_exists('mb_output_handler')) {
function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); }
}
if (!function_exists('mb_http_input')) {
function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); }
}
if (!function_exists('mb_convert_variables')) {
function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); }
}
if (!function_exists('mb_ord')) {
function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); }
}
if (!function_exists('mb_chr')) {
function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); }
}
if (!function_exists('mb_scrub')) {
function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); }
}
if (!function_exists('mb_str_split')) {
function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); }
}
if (extension_loaded('mbstring')) {
return;
}
if (!defined('MB_CASE_UPPER')) {
define('MB_CASE_UPPER', 0);
}
if (!defined('MB_CASE_LOWER')) {
define('MB_CASE_LOWER', 1);
}
if (!defined('MB_CASE_TITLE')) {
define('MB_CASE_TITLE', 2);
}
Copyright (c) 2015-2019 Fabien Potencier
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.
<?php
return array (
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'E' => 'e',
'F' => 'f',
'G' => 'g',
'H' => 'h',
'I' => 'i',
'J' => 'j',
'K' => 'k',
'L' => 'l',
'M' => 'm',
'N' => 'n',
'O' => 'o',
'P' => 'p',
'Q' => 'q',
'R' => 'r',
'S' => 's',
'T' => 't',
'U' => 'u',
'V' => 'v',
'W' => 'w',
'X' => 'x',
'Y' => 'y',
'Z' => 'z',
'À' => 'à',
<>' => 'á',
'Â' => 'â',
'Ã' => 'ã',
'Ä' => 'ä',
'Ã…' => 'Ã¥',
'Æ' => 'æ',
'Ç' => 'ç',
'È' => 'è',
'É' => 'é',
'Ê' => 'ê',
'Ë' => 'ë',
'Ì' => 'ì',
<>' => 'í',
'Î' => 'î',
<>' => 'ï',
<>' => 'ð',
'Ñ' => 'ñ',
'Ò' => 'ò',
'Ó' => 'ó',
'Ô' => 'ô',
'Õ' => 'õ',
'Ö' => 'ö',
'Ø' => 'ø',
'Ù' => 'ù',
'Ú' => 'ú',
'Û' => 'û',
'Ü' => 'ü',
<>' => 'ý',
'Þ' => 'þ',
'Ä€' => 'Ä<>',
'Ă' => 'ă',
'Ä„' => 'Ä…',
'Ć' => 'ć',
'Ĉ' => 'ĉ',
'ÄŠ' => 'Ä‹',
'ÄŒ' => 'Ä<>',
'ÄŽ' => 'Ä<>',
<>' => 'Ä‘',
'Ä’' => 'Ä“',
'Ä”' => 'Ä•',
'Ä–' => 'Ä—',
'Ę' => 'ę',
'Äš' => 'Ä›',
'Äœ' => 'Ä<>',
'Äž' => 'ÄŸ',
'Ä ' => 'Ä¡',
'Ä¢' => 'Ä£',
'Ĥ' => 'ĥ',
'Ħ' => 'ħ',
'Ĩ' => 'ĩ',
'Ī' => 'ī',
'Ĭ' => 'ĭ',
'Į' => 'į',
'Ä°' => 'i',
'IJ' => 'ij',
'Ĵ' => 'ĵ',
'Ķ' => 'ķ',
'Ĺ' => 'ĺ',
'Ļ' => 'ļ',
'Ľ' => 'ľ',
'Ä¿' => 'Å€',
<>' => 'Å‚',
'Ń' => 'ń',
'Ņ' => 'ņ',
'Ň' => 'ň',
'ÅŠ' => 'Å‹',
'ÅŒ' => 'Å<>',
'ÅŽ' => 'Å<>',
<>' => 'Å‘',
'Å’' => 'Å“',
'Å”' => 'Å•',
'Å–' => 'Å—',
'Ř' => 'ř',
'Åš' => 'Å›',
'Åœ' => 'Å<>',
'Åž' => 'ÅŸ',
'Å ' => 'Å¡',
'Å¢' => 'Å£',
'Ť' => 'ť',
'Ŧ' => 'ŧ',
'Ũ' => 'ũ',
'Ū' => 'ū',
'Ŭ' => 'ŭ',
'Ů' => 'ů',
'Ű' => 'ű',
'Ų' => 'ų',
'Ŵ' => 'ŵ',
'Ŷ' => 'ŷ',
'Ÿ' => 'ÿ',
'Ź' => 'ź',
'Ż' => 'ż',
'Ž' => 'ž',
<>' => 'É“',
'Ƃ' => 'ƃ',
'Æ„' => 'Æ…',
'Ɔ' => 'ɔ',
'Ƈ' => 'ƈ',
'Ɖ' => 'ɖ',
'ÆŠ' => 'É—',
'Ƌ' => 'ƌ',
'ÆŽ' => 'Ç<>',
<>' => 'É™',
<>' => 'É›',
'Æ‘' => 'Æ’',
'Æ“' => 'É ',
'Æ”' => 'É£',
'Æ–' => 'É©',
'Ɨ' => 'ɨ',
'Ƙ' => 'ƙ',
'Ɯ' => 'ɯ',
<>' => 'ɲ',
'Ɵ' => 'ɵ',
'Æ ' => 'Æ¡',
'Æ¢' => 'Æ£',
'Ƥ' => 'ƥ',
'Ʀ' => 'ʀ',
'Ƨ' => 'ƨ',
'Ʃ' => 'ʃ',
'Ƭ' => 'ƭ',
'Ʈ' => 'ʈ',
'Ư' => 'ư',
'Ʊ' => 'ʊ',
'Ʋ' => 'ʋ',
'Ƴ' => 'ƴ',
'Ƶ' => 'ƶ',
'Æ·' => 'Ê’',
'Ƹ' => 'ƹ',
'Ƽ' => 'ƽ',
'DŽ' => 'dž',
'Dž' => 'dž',
'LJ' => 'lj',
'Lj' => 'lj',
'NJ' => 'nj',
'Nj' => 'nj',
<>' => 'ÇŽ',
<>' => 'Ç<>',
'Ç‘' => 'Ç’',
'Ç“' => 'Ç”',
'Ç•' => 'Ç–',
'Ǘ' => 'ǘ',
'Ç™' => 'Çš',
'Ǜ' => 'ǜ',
'Çž' => 'ÇŸ',
'Ç ' => 'Ç¡',
'Ç¢' => 'Ç£',
'Ǥ' => 'ǥ',
'Ǧ' => 'ǧ',
'Ǩ' => 'ǩ',
'Ǫ' => 'ǫ',
'Ǭ' => 'ǭ',
'Ǯ' => 'ǯ',
'DZ' => 'dz',
'Dz' => 'dz',
'Ǵ' => 'ǵ',
'Ƕ' => 'ƕ',
'Ç·' => 'Æ¿',
'Ǹ' => 'ǹ',
'Ǻ' => 'ǻ',
'Ǽ' => 'ǽ',
'Ǿ' => 'ǿ',
'È€' => 'È<>',
'Ȃ' => 'ȃ',
'È„' => 'È…',
'Ȇ' => 'ȇ',
'Ȉ' => 'ȉ',
'ÈŠ' => 'È‹',
'ÈŒ' => 'È<>',
'ÈŽ' => 'È<>',
<>' => 'È‘',
'È’' => 'È“',
'È”' => 'È•',
'È–' => 'È—',
'Ș' => 'ș',
'Èš' => 'È›',
'Èœ' => 'È<>',
'Èž' => 'ÈŸ',
'È ' => 'Æž',
'È¢' => 'È£',
'Ȥ' => 'ȥ',
'Ȧ' => 'ȧ',
'Ȩ' => 'ȩ',
'Ȫ' => 'ȫ',
'Ȭ' => 'ȭ',
'Ȯ' => 'ȯ',
'Ȱ' => 'ȱ',
'Ȳ' => 'ȳ',
'Ⱥ' => 'ⱥ',
'Ȼ' => 'ȼ',
'Ƚ' => 'ƚ',
'Ⱦ' => 'ⱦ',
<>' => 'É‚',
'Ƀ' => 'ƀ',
'Ʉ' => 'ʉ',
'Ʌ' => 'ʌ',
'Ɇ' => 'ɇ',
'Ɉ' => 'ɉ',
'ÉŠ' => 'É‹',
'ÉŒ' => 'É<>',
'ÉŽ' => 'É<>',
'Ͱ' => 'ͱ',
'Ͳ' => 'ͳ',
'Ͷ' => 'ͷ',
'Ϳ' => 'ϳ',
'Ά' => 'ά',
'Έ' => 'έ',
'Ή' => 'ή',
'Ί' => 'ί',
'Ό' => 'ό',
'ÎŽ' => 'Ï<>',
<>' => 'ÏŽ',
'Α' => 'α',
'Β' => 'β',
'Γ' => 'γ',
'Δ' => 'δ',
'Ε' => 'ε',
'Ζ' => 'ζ',
'Η' => 'η',
'Θ' => 'θ',
'Ι' => 'ι',
'Κ' => 'κ',
'Λ' => 'λ',
'Μ' => 'μ',
<>' => 'ν',
'Ξ' => 'ξ',
'Ο' => 'ο',
'Π' => 'π',
'Ρ' => 'Ï<>',
'Σ' => 'σ',
'Τ' => 'τ',
'Î¥' => 'Ï…',
'Φ' => 'φ',
'Χ' => 'χ',
'Ψ' => 'ψ',
'Ω' => 'ω',
'Ϊ' => 'ϊ',
'Ϋ' => 'ϋ',
<>' => 'Ï—',
'Ϙ' => 'ϙ',
'Ïš' => 'Ï›',
'Ïœ' => 'Ï<>',
'Ïž' => 'ÏŸ',
'Ï ' => 'Ï¡',
'Ï¢' => 'Ï£',
'Ϥ' => 'ϥ',
'Ϧ' => 'ϧ',
'Ϩ' => 'ϩ',
'Ϫ' => 'ϫ',
'Ϭ' => 'ϭ',
'Ϯ' => 'ϯ',
'ϴ' => 'θ',
'Ϸ' => 'ϸ',
'Ϲ' => 'ϲ',
'Ϻ' => 'ϻ',
'Ͻ' => 'ͻ',
'Ͼ' => 'ͼ',
'Ͽ' => 'ͽ',
'Ѐ' => 'Ñ<>',
<>' => 'Ñ‘',
'Ђ' => 'ђ',
'Ѓ' => 'ѓ',
'Є' => 'є',
'Ð…' => 'Ñ•',
'І' => 'і',
'Ї' => 'ї',
'Ј' => 'ј',
'Љ' => 'љ',
'Њ' => 'њ',
'Ћ' => 'ћ',
'Ќ' => 'ќ',
<>' => 'Ñ<>',
'ÐŽ' => 'Ñž',
<>' => 'ÑŸ',
<>' => 'а',
'Б' => 'б',
'В' => 'в',
'Г' => 'г',
'Д' => 'д',
'Е' => 'е',
'Ж' => 'ж',
'З' => 'з',
'И' => 'и',
'Й' => 'й',
'К' => 'к',
'Л' => 'л',
'М' => 'м',
<>' => 'н',
'О' => 'о',
'П' => 'п',
'Р' => 'р',
'С' => 'Ñ<>',
'Т' => 'т',
'У' => 'у',
'Ф' => 'ф',
'Ð¥' => 'Ñ…',
'Ц' => 'ц',
'Ч' => 'ч',
'Ш' => 'ш',
'Щ' => 'щ',
'Ъ' => 'ъ',
'Ы' => 'ы',
'Ь' => 'ь',
'Э' => 'Ñ<>',
'Ю' => 'ю',
'Я' => 'Ñ<>',
'Ñ ' => 'Ñ¡',
'Ñ¢' => 'Ñ£',
'Ѥ' => 'ѥ',
'Ѧ' => 'ѧ',
'Ѩ' => 'ѩ',
'Ѫ' => 'ѫ',
'Ѭ' => 'ѭ',
'Ѯ' => 'ѯ',
'Ѱ' => 'ѱ',
'Ѳ' => 'ѳ',
'Ѵ' => 'ѵ',
'Ѷ' => 'ѷ',
'Ѹ' => 'ѹ',
'Ѻ' => 'ѻ',
'Ѽ' => 'ѽ',
'Ѿ' => 'ѿ',
'Ò€' => 'Ò<>',
'ÒŠ' => 'Ò‹',
'ÒŒ' => 'Ò<>',
'ÒŽ' => 'Ò<>',
<>' => 'Ò‘',
'Ò’' => 'Ò“',
'Ò”' => 'Ò•',
'Ò–' => 'Ò—',
'Ò˜' => 'Ò™',
'Òš' => 'Ò›',
'Òœ' => 'Ò<>',
'Òž' => 'ÒŸ',
'Ò ' => 'Ò¡',
'Ò¢' => 'Ò£',
'Ò¤' => 'Ò¥',
'Ò¦' => 'Ò§',
'Ò¨' => 'Ò©',
'Òª' => 'Ò«',
'Ò¬' => 'Ò­',
'Ò®' => 'Ò¯',
'Ò°' => 'Ò±',
'Ò²' => 'Ò³',
'Ò´' => 'Òµ',
'Ò¶' => 'Ò·',
'Ò¸' => 'Ò¹',
'Òº' => 'Ò»',
'Ò¼' => 'Ò½',
'Ò¾' => 'Ò¿',
'Ó€' => 'Ó<>',
<>' => 'Ó‚',
'Óƒ' => 'Ó„',
'Ó…' => 'Ó†',
'Ó‡' => 'Óˆ',
'Ó‰' => 'ÓŠ',
'Ӌ' => 'ӌ',
<>' => 'ÓŽ',
<>' => 'Ó‘',
'Ó’' => 'Ó“',
'Ó”' => 'Ó•',
'Ó–' => 'Ó—',
'Ó˜' => 'Ó™',
'Óš' => 'Ó›',
'Óœ' => 'Ó<>',
'Óž' => 'ÓŸ',
'Ó ' => 'Ó¡',
'Ó¢' => 'Ó£',
'Ó¤' => 'Ó¥',
'Ó¦' => 'Ó§',
'Ó¨' => 'Ó©',
'Óª' => 'Ó«',
'Ó¬' => 'Ó­',
'Ó®' => 'Ó¯',
'Ó°' => 'Ó±',
'Ó²' => 'Ó³',
'Ó´' => 'Óµ',
'Ó¶' => 'Ó·',
'Ó¸' => 'Ó¹',
'Óº' => 'Ó»',
'Ó¼' => 'Ó½',
'Ó¾' => 'Ó¿',
'Ô€' => 'Ô<>',
'Ô‚' => 'Ôƒ',
'Ô„' => 'Ô…',
'Ô†' => 'Ô‡',
'Ôˆ' => 'Ô‰',
'ÔŠ' => 'Ô‹',
'ÔŒ' => 'Ô<>',
'ÔŽ' => 'Ô<>',
<>' => 'Ô‘',
'Ô’' => 'Ô“',
'Ô”' => 'Ô•',
'Ô–' => 'Ô—',
'Ô˜' => 'Ô™',
'Ôš' => 'Ô›',
'Ôœ' => 'Ô<>',
'Ôž' => 'ÔŸ',
'Ô ' => 'Ô¡',
'Ô¢' => 'Ô£',
'Ô¤' => 'Ô¥',
'Ô¦' => 'Ô§',
'Ô¨' => 'Ô©',
'Ôª' => 'Ô«',
'Ô¬' => 'Ô­',
'Ô®' => 'Ô¯',
'Ô±' => 'Õ¡',
'Ô²' => 'Õ¢',
'Ô³' => 'Õ£',
'Ô´' => 'Õ¤',
'Ôµ' => 'Õ¥',
'Ô¶' => 'Õ¦',
'Ô·' => 'Õ§',
'Ô¸' => 'Õ¨',
'Ô¹' => 'Õ©',
'Ôº' => 'Õª',
'Ô»' => 'Õ«',
'Ô¼' => 'Õ¬',
'Ô½' => 'Õ­',
'Ô¾' => 'Õ®',
'Ô¿' => 'Õ¯',
'Õ€' => 'Õ°',
<>' => 'Õ±',
'Õ‚' => 'Õ²',
'Õƒ' => 'Õ³',
'Õ„' => 'Õ´',
'Õ…' => 'Õµ',
'Õ†' => 'Õ¶',
'Õ‡' => 'Õ·',
'Õˆ' => 'Õ¸',
'Õ‰' => 'Õ¹',
'ÕŠ' => 'Õº',
'Õ‹' => 'Õ»',
'Ռ' => 'ռ',
<>' => 'Õ½',
'ÕŽ' => 'Õ¾',
<>' => 'Õ¿',
<>' => 'Ö€',
'Õ‘' => 'Ö<>',
'Õ’' => 'Ö‚',
'Õ“' => 'Öƒ',
'Õ”' => 'Ö„',
'Õ•' => 'Ö…',
'Õ–' => 'Ö†',
'á‚ ' => 'â´€',
'á‚¡' => 'â´<C3A2>',
'á‚¢' => 'â´‚',
'á‚£' => 'â´ƒ',
'Ⴄ' => 'ⴄ',
'á‚¥' => 'â´…',
'Ⴆ' => 'ⴆ',
'Ⴇ' => 'ⴇ',
'Ⴈ' => 'ⴈ',
'á‚©' => 'â´‰',
'Ⴊ' => 'ⴊ',
'á‚«' => 'â´‹',
'Ⴌ' => 'ⴌ',
'á‚­' => 'â´<C3A2>',
'á‚®' => 'â´Ž',
'Ⴏ' => 'â´<C3A2>',
'á‚°' => 'â´<C3A2>',
'Ⴑ' => 'ⴑ',
'Ⴒ' => 'ⴒ',
'Ⴓ' => 'ⴓ',
'á‚´' => 'â´”',
'Ⴕ' => 'ⴕ',
'Ⴖ' => 'ⴖ',
'á‚·' => 'â´—',
'Ⴘ' => 'ⴘ',
'Ⴙ' => 'ⴙ',
'Ⴚ' => 'ⴚ',
'á‚»' => 'â´›',
'Ⴜ' => 'ⴜ',
'Ⴝ' => 'â´<C3A2>',
'Ⴞ' => 'ⴞ',
'á‚¿' => 'â´Ÿ',
'Ⴠ' => 'ⴠ',
'áƒ<C3A1>' => 'â´¡',
'Ⴢ' => 'ⴢ',
'Ⴣ' => 'ⴣ',
'Ⴤ' => 'ⴤ',
'Ⴥ' => 'ⴥ',
'Ⴧ' => 'ⴧ',
'áƒ<C3A1>' => 'â´­',
'Ꭰ' => 'ꭰ',
'Ꭱ' => 'ꭱ',
'Ꭲ' => 'ꭲ',
'Ꭳ' => 'ꭳ',
'Ꭴ' => 'ꭴ',
'Ꭵ' => 'ꭵ',
'Ꭶ' => 'ꭶ',
'Ꭷ' => 'ꭷ',
'Ꭸ' => 'ꭸ',
'Ꭹ' => 'ꭹ',
'Ꭺ' => 'ꭺ',
'Ꭻ' => 'ꭻ',
'Ꭼ' => 'ꭼ',
'Ꭽ' => 'ꭽ',
'Ꭾ' => 'ꭾ',
'Ꭿ' => 'ꭿ',
'Ꮀ' => 'ꮀ',
'Ꮁ' => 'ê®<C3AA>',
'Ꮂ' => 'ꮂ',
'Ꮃ' => 'ꮃ',
'Ꮄ' => 'ꮄ',
'Ꮅ' => 'ꮅ',
'Ꮆ' => 'ꮆ',
'Ꮇ' => 'ꮇ',
'Ꮈ' => 'ꮈ',
'Ꮉ' => 'ꮉ',
'Ꮊ' => 'ꮊ',
'Ꮋ' => 'ꮋ',
'Ꮌ' => 'ꮌ',
'Ꮍ' => 'ê®<C3AA>',
'Ꮎ' => 'ꮎ',
'Ꮏ' => 'ê®<C3AA>',
<>€' => 'ê®<C3AA>',
<><C3A1>' => 'ꮑ',
<>' => 'ê®’',
<>ƒ' => 'ꮓ',
<>„' => 'ê®”',
<>…' => 'ꮕ',
<>†' => 'ê®–',
<>‡' => 'ê®—',
<>ˆ' => 'ꮘ',
<>‰' => 'ê®™',
<>Š' => 'ꮚ',
<>' => 'ê®›',
<>Œ' => 'ꮜ',
<><C3A1>' => 'ê®<C3AA>',
<>Ž' => 'ꮞ',
<><C3A1>' => 'ꮟ',
<><C3A1>' => 'ê® ',
<>' => 'ꮡ',
<>' => 'ꮢ',
<>“' => 'ꮣ',
<>”' => 'ꮤ',
<>•' => 'ꮥ',
<>' => 'ꮦ',
<>—' => 'ꮧ',
<>˜' => 'ꮨ',
<>™' => 'ꮩ',
<>š' => 'ꮪ',
<>' => 'ꮫ',
<>œ' => 'ꮬ',
<><C3A1>' => 'ê®­',
<>ž' => 'ê®®',
<>Ÿ' => 'ꮯ',
<> ' => 'ê®°',
<>¡' => 'ê®±',
<>¢' => 'ꮲ',
<>£' => 'ꮳ',
<>¤' => 'ê®´',
<>¥' => 'ꮵ',
<>¦' => 'ꮶ',
<>§' => 'ê®·',
<>¨' => 'ꮸ',
<>©' => 'ꮹ',
<>ª' => 'ꮺ',
<>«' => 'ê®»',
<>¬' => 'ꮼ',
<>­' => 'ꮽ',
<>®' => 'ꮾ',
<>¯' => 'ꮿ',
<>°' => 'á<>¸',
<>±' => 'á<>¹',
<>²' => 'á<>º',
<>³' => 'á<>»',
<>´' => 'á<>¼',
<>µ' => 'á<>½',
'á²<C3A1>' => 'áƒ<C3A1>',
'Ბ' => 'ბ',
'Გ' => 'გ',
'Დ' => 'დ',
'Ე' => 'ე',
'Ვ' => 'ვ',
'Ზ' => 'ზ',
'Თ' => 'თ',
'Ი' => 'ი',
'Კ' => 'კ',
'Ლ' => 'ლ',
'Მ' => 'მ',
'Ნ' => 'ნ',
'á²<C3A1>' => 'áƒ<C3A1>',
'Პ' => 'პ',
'Ჟ' => 'ჟ',
'Რ' => 'რ',
'Ს' => 'ს',
'Ტ' => 'ტ',
'Უ' => 'უ',
'Ფ' => 'ფ',
'Ქ' => 'ქ',
'Ღ' => 'ღ',
'Ყ' => 'ყ',
'Შ' => 'შ',
'Ჩ' => 'ჩ',
'Ც' => 'ც',
'Ძ' => 'ძ',
'Წ' => 'წ',
'Ჭ' => 'ჭ',
'Ხ' => 'ხ',
'Ჯ' => 'ჯ',
'Ჰ' => 'ჰ',
'Ჱ' => 'ჱ',
'Ჲ' => 'ჲ',
'Ჳ' => 'ჳ',
'Ჴ' => 'ჴ',
'Ჵ' => 'ჵ',
'Ჶ' => 'ჶ',
'Ჷ' => 'ჷ',
'Ჸ' => 'ჸ',
'Ჹ' => 'ჹ',
'Ჺ' => 'ჺ',
'Ჽ' => 'ჽ',
'Ჾ' => 'ჾ',
'Ჿ' => 'ჿ',
'Ḁ' => 'á¸<C3A1>',
'Ḃ' => 'ḃ',
'Ḅ' => 'ḅ',
'Ḇ' => 'ḇ',
'Ḉ' => 'ḉ',
'Ḋ' => 'ḋ',
'Ḍ' => 'á¸<C3A1>',
'Ḏ' => 'á¸<C3A1>',
¸<C3A1>' => 'ḑ',
'Ḓ' => 'ḓ',
'Ḕ' => 'ḕ',
'Ḗ' => 'ḗ',
'Ḙ' => 'ḙ',
'Ḛ' => 'ḛ',
'Ḝ' => 'á¸<C3A1>',
'Ḟ' => 'ḟ',
'Ḡ' => 'ḡ',
'Ḣ' => 'ḣ',
'Ḥ' => 'ḥ',
'Ḧ' => 'ḧ',
'Ḩ' => 'ḩ',
'Ḫ' => 'ḫ',
'Ḭ' => 'ḭ',
'Ḯ' => 'ḯ',
'Ḱ' => 'ḱ',
'Ḳ' => 'ḳ',
'Ḵ' => 'ḵ',
'Ḷ' => 'ḷ',
'Ḹ' => 'ḹ',
'Ḻ' => 'ḻ',
'Ḽ' => 'ḽ',
'Ḿ' => 'ḿ',
'á¹€' => 'á¹<C3A1>',
'Ṃ' => 'ṃ',
'Ṅ' => 'ṅ',
'Ṇ' => 'ṇ',
'Ṉ' => 'ṉ',
'Ṋ' => 'ṋ',
'Ṍ' => 'á¹<C3A1>',
'Ṏ' => 'á¹<C3A1>',
'á¹<C3A1>' => 'ṑ',
'Ṓ' => 'ṓ',
'Ṕ' => 'ṕ',
'á¹–' => 'á¹—',
'Ṙ' => 'ṙ',
'Ṛ' => 'ṛ',
'Ṝ' => 'á¹<C3A1>',
'Ṟ' => 'ṟ',
'Ṡ' => 'ṡ',
'á¹¢' => 'á¹£',
'Ṥ' => 'ṥ',
'Ṧ' => 'ṧ',
'Ṩ' => 'ṩ',
'Ṫ' => 'ṫ',
'Ṭ' => 'ṭ',
'Ṯ' => 'ṯ',
'á¹°' => 'á¹±',
'á¹²' => 'á¹³',
'á¹´' => 'á¹µ',
'Ṷ' => 'ṷ',
'Ṹ' => 'ṹ',
'Ṻ' => 'ṻ',
'á¹¼' => 'á¹½',
'Ṿ' => 'ṿ',
'Ẁ' => 'áº<C3A1>',
'Ẃ' => 'ẃ',
'Ẅ' => 'ẅ',
'Ẇ' => 'ẇ',
'Ẉ' => 'ẉ',
'Ẋ' => 'ẋ',
'Ẍ' => 'áº<C3A1>',
'Ẏ' => 'áº<C3A1>',
'áº<C3A1>' => 'ẑ',
'Ẓ' => 'ẓ',
'Ẕ' => 'ẕ',
'ẞ' => 'ß',
'Ạ' => 'ạ',
'Ả' => 'ả',
'Ấ' => 'ấ',
'Ầ' => 'ầ',
'Ẩ' => 'ẩ',
'Ẫ' => 'ẫ',
'Ậ' => 'ậ',
'Ắ' => 'ắ',
'Ằ' => 'ằ',
'Ẳ' => 'ẳ',
'Ẵ' => 'ẵ',
'Ặ' => 'ặ',
'Ẹ' => 'ẹ',
'Ẻ' => 'ẻ',
'Ẽ' => 'ẽ',
'Ế' => 'ế',
'Ề' => 'á»<C3A1>',
'Ể' => 'ể',
'Ễ' => 'ễ',
'Ệ' => 'ệ',
'Ỉ' => 'ỉ',
'Ị' => 'ị',
'Ọ' => 'á»<C3A1>',
'Ỏ' => 'á»<C3A1>',
'á»<C3A1>' => 'ố',
'Ồ' => 'ồ',
'Ổ' => 'ổ',
'á»–' => 'á»—',
'Ộ' => 'ộ',
'Ớ' => 'ớ',
'Ờ' => 'á»<C3A1>',
'Ở' => 'ở',
'Ỡ' => 'ỡ',
'Ợ' => 'ợ',
'Ụ' => 'ụ',
'Ủ' => 'ủ',
'Ứ' => 'ứ',
'Ừ' => 'ừ',
'Ử' => 'ử',
'Ữ' => 'ữ',
'á»°' => 'á»±',
'Ỳ' => 'ỳ',
'Ỵ' => 'ỵ',
'Ỷ' => 'ỷ',
'Ỹ' => 'ỹ',
'Ỻ' => 'ỻ',
'Ỽ' => 'ỽ',
'Ỿ' => 'ỿ',
'Ἀ' => 'ἀ',
'Ἁ' => 'á¼<C3A1>',
'Ἂ' => 'ἂ',
'Ἃ' => 'ἃ',
'Ἄ' => 'ἄ',
'á¼<C3A1>' => 'á¼…',
'Ἆ' => 'ἆ',
'á¼<C3A1>' => 'ἇ',
'Ἐ' => 'á¼<C3A1>',
'Ἑ' => 'ἑ',
'Ἒ' => 'ἒ',
'Ἓ' => 'ἓ',
'Ἔ' => 'ἔ',
'á¼<C3A1>' => 'ἕ',
'Ἠ' => 'ἠ',
'Ἡ' => 'ἡ',
'Ἢ' => 'ἢ',
'Ἣ' => 'ἣ',
'Ἤ' => 'ἤ',
'á¼­' => 'á¼¥',
'Ἦ' => 'ἦ',
'Ἧ' => 'ἧ',
'Ἰ' => 'ἰ',
'á¼¹' => 'á¼±',
'Ἲ' => 'ἲ',
'á¼»' => 'á¼³',
'á¼¼' => 'á¼´',
'á¼½' => 'á¼µ',
'Ἶ' => 'ἶ',
'Ἷ' => 'ἷ',
'Ὀ' => 'ὀ',
'Ὁ' => 'á½<C3A1>',
'Ὂ' => 'ὂ',
'Ὃ' => 'ὃ',
'Ὄ' => 'ὄ',
'á½<C3A1>' => 'á½…',
'Ὑ' => 'ὑ',
'Ὓ' => 'ὓ',
'á½<C3A1>' => 'ὕ',
'Ὗ' => 'ὗ',
'Ὠ' => 'ὠ',
'Ὡ' => 'ὡ',
'Ὢ' => 'ὢ',
'Ὣ' => 'ὣ',
'Ὤ' => 'ὤ',
'á½­' => 'á½¥',
'Ὦ' => 'ὦ',
'Ὧ' => 'ὧ',
'ᾈ' => 'ᾀ',
'ᾉ' => 'á¾<C3A1>',
'ᾊ' => 'ᾂ',
'ᾋ' => 'ᾃ',
'ᾌ' => 'ᾄ',
'á¾<C3A1>' => 'á¾…',
'ᾎ' => 'ᾆ',
'á¾<C3A1>' => 'ᾇ',
'ᾘ' => 'á¾<C3A1>',
'ᾙ' => 'ᾑ',
'ᾚ' => 'ᾒ',
'ᾛ' => 'ᾓ',
'ᾜ' => 'ᾔ',
'á¾<C3A1>' => 'ᾕ',
'ᾞ' => 'ᾖ',
'ᾟ' => 'ᾗ',
'ᾨ' => 'ᾠ',
'ᾩ' => 'ᾡ',
'ᾪ' => 'ᾢ',
'ᾫ' => 'ᾣ',
'ᾬ' => 'ᾤ',
'á¾­' => 'á¾¥',
'ᾮ' => 'ᾦ',
'ᾯ' => 'ᾧ',
'Ᾰ' => 'ᾰ',
'á¾¹' => 'á¾±',
'Ὰ' => 'ὰ',
'á¾»' => 'á½±',
'á¾¼' => 'á¾³',
'Ὲ' => 'ὲ',
'Έ' => 'έ',
'á¿Š' => 'á½´',
'á¿‹' => 'á½µ',
'ῌ' => 'ῃ',
'Ῐ' => 'á¿<C3A1>',
'á¿™' => 'á¿‘',
'Ὶ' => 'ὶ',
'á¿›' => 'á½·',
'Ῠ' => 'ῠ',
'á¿©' => 'á¿¡',
'Ὺ' => 'ὺ',
'á¿«' => 'á½»',
'Ῥ' => 'ῥ',
'Ὸ' => 'ὸ',
'Ό' => 'ό',
'Ὼ' => 'ὼ',
'á¿»' => 'á½½',
'ῼ' => 'ῳ',
'Ω' => 'ω',
'K' => 'k',
'â„«' => 'Ã¥',
'Ⅎ' => 'ⅎ',
'â… ' => 'â…°',
'â…¡' => 'â…±',
'â…¢' => 'â…²',
'â…£' => 'â…³',
'â…¤' => 'â…´',
'â…¥' => 'â…µ',
'â…¦' => 'â…¶',
'â…§' => 'â…·',
'â…¨' => 'â…¸',
'â…©' => 'â…¹',
'â…ª' => 'â…º',
'â…«' => 'â…»',
'â…¬' => 'â…¼',
'â…­' => 'â…½',
'â…®' => 'â…¾',
'â…¯' => 'â…¿',
'Ↄ' => 'ↄ',
'â’¶' => 'â“<C3A2>',
'â’·' => 'â“‘',
'â’¸' => 'â“’',
'â’¹' => 'â““',
'â’º' => 'â“”',
'â’»' => 'â“•',
'â’¼' => 'â“–',
'â’½' => 'â“—',
'Ⓘ' => 'ⓘ',
'â’¿' => 'â“™',
'â“€' => 'â“š',
'â“<C3A2>' => 'â“›',
'Ⓜ' => 'ⓜ',
'Ⓝ' => 'â“<C3A2>',
'â“„' => 'â“ž',
'â“…' => 'â“Ÿ',
'Ⓠ' => 'ⓠ',
'Ⓡ' => 'ⓡ',
'Ⓢ' => 'ⓢ',
'Ⓣ' => 'ⓣ',
'Ⓤ' => 'ⓤ',
'â“‹' => 'â“¥',
'Ⓦ' => 'ⓦ',
'â“<C3A2>' => 'ⓧ',
'Ⓨ' => 'ⓨ',
'â“<C3A2>' => 'â“©',
'â°€' => 'â°°',
'â°<C3A2>' => 'â°±',
'â°‚' => 'â°²',
'â°ƒ' => 'â°³',
'â°„' => 'â°´',
'â°…' => 'â°µ',
'â°†' => 'â°¶',
'â°‡' => 'â°·',
'â°ˆ' => 'â°¸',
'â°‰' => 'â°¹',
'â°Š' => 'â°º',
'â°‹' => 'â°»',
'Ⰼ' => 'ⰼ',
'â°<C3A2>' => 'â°½',
'â°Ž' => 'â°¾',
'â°<C3A2>' => 'â°¿',
'â°<C3A2>' => 'â±€',
'â°‘' => 'â±<C3A2>',
'Ⱂ' => 'ⱂ',
'Ⱃ' => 'ⱃ',
'Ⱄ' => 'ⱄ',
'â°•' => 'â±…',
'Ⱆ' => 'ⱆ',
'Ⱇ' => 'ⱇ',
'Ⱈ' => 'ⱈ',
'Ⱉ' => 'ⱉ',
'Ⱊ' => 'ⱊ',
'Ⱋ' => 'ⱋ',
'Ⱌ' => 'ⱌ',
'â°<C3A2>' => 'â±<C3A2>',
'Ⱎ' => 'ⱎ',
'â°Ÿ' => 'â±<C3A2>',
'â° ' => 'â±<C3A2>',
'Ⱑ' => 'ⱑ',
'â°¢' => 'â±’',
'Ⱓ' => 'ⱓ',
'â°¤' => 'â±”',
'Ⱕ' => 'ⱕ',
'â°¦' => 'â±–',
'â°§' => 'â±—',
'Ⱘ' => 'ⱘ',
'â°©' => 'â±™',
'Ⱚ' => 'ⱚ',
'â°«' => 'â±›',
'Ⱜ' => 'ⱜ',
'â°­' => 'â±<C3A2>',
'Ⱞ' => 'ⱞ',
'Ⱡ' => 'ⱡ',
'â±¢' => 'É«',
'â±£' => 'áµ½',
'Ɽ' => 'ɽ',
'Ⱨ' => 'ⱨ',
'Ⱪ' => 'ⱪ',
'Ⱬ' => 'ⱬ',
'â±­' => 'É‘',
'Ɱ' => 'ɱ',
'Ɐ' => 'É<>',
'â±°' => 'É’',
'â±²' => 'â±³',
'Ⱶ' => 'ⱶ',
'â±¾' => 'È¿',
'Ɀ' => 'ɀ',
'â²€' => 'â²<C3A2>',
'Ⲃ' => 'ⲃ',
'Ⲅ' => 'ⲅ',
'Ⲇ' => 'ⲇ',
'Ⲉ' => 'ⲉ',
'Ⲋ' => 'ⲋ',
'Ⲍ' => 'â²<C3A2>',
'Ⲏ' => 'â²<C3A2>',
'â²<C3A2>' => 'ⲑ',
'Ⲓ' => 'ⲓ',
'Ⲕ' => 'ⲕ',
'â²–' => 'â²—',
'Ⲙ' => 'ⲙ',
'Ⲛ' => 'ⲛ',
'Ⲝ' => 'â²<C3A2>',
'Ⲟ' => 'ⲟ',
'Ⲡ' => 'ⲡ',
'â²¢' => 'â²£',
'Ⲥ' => 'ⲥ',
'Ⲧ' => 'ⲧ',
'Ⲩ' => 'ⲩ',
'Ⲫ' => 'ⲫ',
'Ⲭ' => 'ⲭ',
'Ⲯ' => 'ⲯ',
'â²°' => 'â²±',
'â²²' => 'â²³',
'â²´' => 'â²µ',
'Ⲷ' => 'ⲷ',
'Ⲹ' => 'ⲹ',
'Ⲻ' => 'ⲻ',
'â²¼' => 'â²½',
'Ⲿ' => 'ⲿ',
'â³€' => 'â³<C3A2>',
'Ⳃ' => 'ⳃ',
'Ⳅ' => 'ⳅ',
'Ⳇ' => 'ⳇ',
'Ⳉ' => 'ⳉ',
'Ⳋ' => 'ⳋ',
'Ⳍ' => 'â³<C3A2>',
'Ⳏ' => 'â³<C3A2>',
'â³<C3A2>' => 'ⳑ',
'Ⳓ' => 'ⳓ',
'Ⳕ' => 'ⳕ',
'â³–' => 'â³—',
'Ⳙ' => 'ⳙ',
'Ⳛ' => 'ⳛ',
'Ⳝ' => 'â³<C3A2>',
'Ⳟ' => 'ⳟ',
'Ⳡ' => 'ⳡ',
'â³¢' => 'â³£',
'Ⳬ' => 'ⳬ',
'â³­' => 'â³®',
'â³²' => 'â³³',
'Ꙁ' => 'ê™<C3AA>',
'Ꙃ' => 'ꙃ',
'Ꙅ' => 'ꙅ',
'Ꙇ' => 'ꙇ',
'Ꙉ' => 'ꙉ',
'Ꙋ' => 'ꙋ',
'Ꙍ' => 'ê™<C3AA>',
'Ꙏ' => 'ê™<C3AA>',
'ê™<C3AA>' => 'ꙑ',
'Ꙓ' => 'ꙓ',
'Ꙕ' => 'ꙕ',
'ê™–' => 'ê™—',
'Ꙙ' => 'ꙙ',
'Ꙛ' => 'ꙛ',
'Ꙝ' => 'ê™<C3AA>',
'Ꙟ' => 'ꙟ',
'Ꙡ' => 'ꙡ',
'Ꙣ' => 'ꙣ',
'Ꙥ' => 'ꙥ',
'Ꙧ' => 'ꙧ',
'Ꙩ' => 'ꙩ',
'Ꙫ' => 'ꙫ',
'Ꙭ' => 'ꙭ',
'Ꚁ' => 'êš<C3AA>',
'Ꚃ' => 'ꚃ',
'êš„' => 'êš…',
'Ꚇ' => 'ꚇ',
'Ꚉ' => 'ꚉ',
'Ꚋ' => 'ꚋ',
'Ꚍ' => 'êš<C3AA>',
'Ꚏ' => 'êš<C3AA>',
'êš<C3AA>' => 'êš‘',
'êš’' => 'êš“',
'êš”' => 'êš•',
'êš–' => 'êš—',
'Ꚙ' => 'ꚙ',
'êšš' => 'êš›',
'Ꜣ' => 'ꜣ',
'Ꜥ' => 'ꜥ',
'Ꜧ' => 'ꜧ',
'Ꜩ' => 'ꜩ',
'Ꜫ' => 'ꜫ',
'Ꜭ' => 'ꜭ',
'Ꜯ' => 'ꜯ',
'Ꜳ' => 'ꜳ',
'Ꜵ' => 'ꜵ',
'Ꜷ' => 'ꜷ',
'Ꜹ' => 'ꜹ',
'Ꜻ' => 'ꜻ',
'Ꜽ' => 'ꜽ',
'Ꜿ' => 'ꜿ',
<>€' => 'ê<><C3AA>',
<>' => 'ê<>ƒ',
<>„' => 'ê<>…',
<>†' => 'ê<>‡',
<>ˆ' => 'ê<>‰',
<>Š' => 'ê<>',
<>Œ' => 'ê<><C3AA>',
<>Ž' => 'ê<><C3AA>',
<><C3AA>' => 'ê<>',
<>' => 'ê<>“',
<>”' => 'ê<>•',
<>' => 'ê<>—',
<>˜' => 'ê<>™',
<>š' => 'ê<>',
<>œ' => 'ê<><C3AA>',
<>ž' => 'ê<>Ÿ',
<> ' => 'ê<>¡',
<>¢' => 'ê<>£',
<>¤' => 'ê<>¥',
<>¦' => 'ê<>§',
<>¨' => 'ê<>©',
<>ª' => 'ê<>«',
<>¬' => 'ê<>­',
<>®' => 'ê<>¯',
<>¹' => 'ê<>º',
<>»' => 'ê<>¼',
<>½' => 'áµ¹',
<>¾' => 'ê<>¿',
'Ꞁ' => 'êž<C3AA>',
'Ꞃ' => 'ꞃ',
'êž„' => 'êž…',
'Ꞇ' => 'ꞇ',
'Ꞌ' => 'ꞌ',
'êž<C3AA>' => 'É¥',
'êž<C3AA>' => 'êž‘',
'êž’' => 'êž“',
'êž–' => 'êž—',
'Ꞙ' => 'ꞙ',
'êžš' => 'êž›',
'êžœ' => 'êž<C3AA>',
'Ꞟ' => 'ꞟ',
'êž ' => 'êž¡',
'Ꞣ' => 'ꞣ',
'Ꞥ' => 'ꞥ',
'Ꞧ' => 'ꞧ',
'Ꞩ' => 'ꞩ',
'Ɦ' => 'ɦ',
'Ɜ' => 'ɜ',
'Ɡ' => 'ɡ',
'Ɬ' => 'ɬ',
'Ɪ' => 'ɪ',
'êž°' => 'Êž',
'Ʇ' => 'ʇ',
'êž²' => 'Ê<>',
'êž³' => 'ê­“',
'êž´' => 'êžµ',
'Ꞷ' => 'ꞷ',
'Ꞹ' => 'ꞹ',
'Ꞻ' => 'ꞻ',
'êž¼' => 'êž½',
'êž¾' => 'êž¿',
'Ꟃ' => 'ꟃ',
'Ꞔ' => 'ꞔ',
'Ʂ' => 'ʂ',
'Ᶎ' => 'ᶎ',
'Ꟈ' => 'ꟈ',
'Ꟊ' => 'ꟊ',
'Ꟶ' => 'ꟶ',
'A' => 'ï½<C3AF>',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'ï¼¥' => 'ï½…',
'F' => 'f',
'G' => 'g',
'H' => 'h',
'I' => 'i',
'J' => 'j',
'K' => 'k',
'L' => 'l',
'ï¼­' => 'ï½<C3AF>',
'N' => 'n',
'O' => 'ï½<C3AF>',
'ï¼°' => 'ï½<C3AF>',
'Q' => 'q',
'ï¼²' => 'ï½’',
'S' => 's',
'ï¼´' => 'ï½”',
'U' => 'u',
'V' => 'v',
'ï¼·' => 'ï½—',
'X' => 'x',
'ï¼¹' => 'ï½™',
'Z' => 'z',
<><C3B0>€' => 'ð<><C3B0>¨',
<><C3B0><EFBFBD>' => 'ð<><C3B0>©',
<><C3B0>' => 'ð<><C3B0>ª',
<><C3B0>ƒ' => 'ð<><C3B0>«',
<><C3B0>„' => 'ð<><C3B0>¬',
<><C3B0>…' => 'ð<><C3B0>­',
<><C3B0>†' => 'ð<><C3B0>®',
<><C3B0>‡' => 'ð<><C3B0>¯',
<><C3B0>ˆ' => 'ð<><C3B0>°',
<><C3B0>‰' => 'ð<><C3B0>±',
<><C3B0>Š' => 'ð<><C3B0>²',
<><C3B0>' => 'ð<><C3B0>³',
<><C3B0>Œ' => 'ð<><C3B0>´',
<><C3B0><EFBFBD>' => 'ð<><C3B0>µ',
<><C3B0>Ž' => 'ð<><C3B0>¶',
<><C3B0><EFBFBD>' => 'ð<><C3B0>·',
<><C3B0><EFBFBD>' => 'ð<><C3B0>¸',
<><C3B0>' => 'ð<><C3B0>¹',
<><C3B0>' => 'ð<><C3B0>º',
<><C3B0>“' => 'ð<><C3B0>»',
<><C3B0>”' => 'ð<><C3B0>¼',
<><C3B0>•' => 'ð<><C3B0>½',
<><C3B0>' => 'ð<><C3B0>¾',
<><C3B0>—' => 'ð<><C3B0>¿',
<><C3B0>˜' => 'ð<>€',
<><C3B0>™' => 'ð<><EFBFBD>',
<><C3B0>š' => 'ð<>',
<><C3B0>' => 'ð<>ƒ',
<><C3B0>œ' => 'ð<>„',
<><C3B0><EFBFBD>' => 'ð<>…',
<><C3B0>ž' => 'ð<>†',
<><C3B0>Ÿ' => 'ð<>‡',
<><C3B0> ' => 'ð<>ˆ',
<><C3B0>¡' => 'ð<>‰',
<><C3B0>¢' => 'ð<>Š',
<><C3B0>£' => 'ð<>',
<><C3B0>¤' => 'ð<>Œ',
<><C3B0>¥' => 'ð<><EFBFBD>',
<><C3B0>¦' => 'ð<>Ž',
<><C3B0>§' => 'ð<><EFBFBD>',
<>°' => 'ð<>˜',
<>±' => 'ð<>“™',
<>²' => 'ð<>“š',
<>³' => 'ð<>',
<>´' => 'ð<>“œ',
<>µ' => 'ð<><EFBFBD>',
<>¶' => 'ð<>“ž',
<>·' => 'ð<>“Ÿ',
<>¸' => 'ð<> ',
<>¹' => 'ð<>“¡',
<>º' => 'ð<>“¢',
<>»' => 'ð<>“£',
<>¼' => 'ð<>“¤',
<>½' => 'ð<>“¥',
<>¾' => 'ð<>“¦',
<>¿' => 'ð<>“§',
<>“€' => 'ð<>“¨',
<><EFBFBD>' => 'ð<>“©',
<>' => 'ð<>“ª',
<>“ƒ' => 'ð<>“«',
<>“„' => 'ð<>“¬',
<>“…' => 'ð<>­',
<>“†' => 'ð<>“®',
<>“‡' => 'ð<>“¯',
<>ˆ' => 'ð<>“°',
<>“‰' => 'ð<>“±',
<>“Š' => 'ð<>“²',
<>' => 'ð<>“³',
<>“Œ' => 'ð<>´',
<><EFBFBD>' => 'ð<>“µ',
<>“Ž' => 'ð<>“¶',
<><EFBFBD>' => 'ð<>“·',
<><EFBFBD>' => 'ð<>¸',
<>' => 'ð<>“¹',
<>' => 'ð<>“º',
<>““' => 'ð<>“»',
<>²€' => 'ð<>³€',
<>²<EFBFBD>' => 'ð<>³<EFBFBD>',
<>²' => 'ð<>³',
<>²ƒ' => 'ð<>³ƒ',
<>²„' => 'ð<>³„',
<>²…' => 'ð<>³…',
<>²†' => 'ð<>³†',
<>²‡' => 'ð<>³‡',
<>²ˆ' => 'ð<>³ˆ',
<>²‰' => 'ð<>³‰',
<>²Š' => 'ð<>³Š',
<>²' => 'ð<>³',
<>²Œ' => 'ð<>³Œ',
<>²<EFBFBD>' => 'ð<>³<EFBFBD>',
<>²Ž' => 'ð<>³Ž',
<>²<EFBFBD>' => 'ð<>³<EFBFBD>',
<>²<EFBFBD>' => 'ð<>³<EFBFBD>',
<>²' => 'ð<>³',
<>²' => 'ð<>³',
<>²“' => 'ð<>³“',
<>²”' => 'ð<>³”',
<>²•' => 'ð<>³•',
<>²' => 'ð<>³',
<>²—' => 'ð<>³—',
<>²˜' => 'ð<>³˜',
<>²™' => 'ð<>³™',
<>²š' => 'ð<>³š',
<>²' => 'ð<>³',
<>²œ' => 'ð<>³œ',
<>²<EFBFBD>' => 'ð<>³<EFBFBD>',
<>²ž' => 'ð<>³ž',
<>²Ÿ' => 'ð<>³Ÿ',
<>² ' => 'ð<>³ ',
<>²¡' => 'ð<>³¡',
<>²¢' => 'ð<>³¢',
<>²£' => 'ð<>³£',
<>²¤' => 'ð<>³¤',
<>²¥' => 'ð<>³¥',
<>²¦' => 'ð<>³¦',
<>²§' => 'ð<>³§',
<>²¨' => 'ð<>³¨',
<>²©' => 'ð<>³©',
<>²ª' => 'ð<>³ª',
<>²«' => 'ð<>³«',
<>²¬' => 'ð<>³¬',
<>²­' => 'ð<>³­',
<>²®' => 'ð<>³®',
<>²¯' => 'ð<>³¯',
<>²°' => 'ð<>³°',
<>²±' => 'ð<>³±',
<>²²' => 'ð<>³²',
'ð‘¢ ' => 'ð‘£€',
'𑢡' => 'ð£<E28098>',
'𑢢' => '𑣂',
'𑢣' => '𑣃',
'𑢤' => '𑣄',
'ð‘¢¥' => 'ð‘£…',
'𑢦' => '𑣆',
'𑢧' => '𑣇',
'𑢨' => '𑣈',
'𑢩' => '𑣉',
'𑢪' => '𑣊',
'𑢫' => '𑣋',
'𑢬' => '𑣌',
'ð‘¢­' => 'ð£<E28098>',
'𑢮' => '𑣎',
'𑢯' => 'ð£<E28098>',
'ð‘¢°' => 'ð£<E28098>',
'𑢱' => '𑣑',
'ð‘¢²' => 'ð‘£’',
'𑢳' => '𑣓',
'ð‘¢´' => 'ð‘£”',
'𑢵' => '𑣕',
'𑢶' => '𑣖',
'ð‘¢·' => 'ð‘£—',
'𑢸' => '𑣘',
'ð‘¢¹' => 'ð‘£™',
'𑢺' => '𑣚',
'ð‘¢»' => 'ð‘£›',
'𑢼' => '𑣜',
'ð‘¢½' => 'ð£<E28098>',
'𑢾' => '𑣞',
'𑢿' => '𑣟',
'ð–¹€' => 'ð–¹ ',
¹<E28093>' => '𖹡',
'𖹂' => '𖹢',
'𖹃' => '𖹣',
'𖹄' => '𖹤',
'ð–¹…' => 'ð–¹¥',
'𖹆' => '𖹦',
'𖹇' => '𖹧',
'𖹈' => '𖹨',
'𖹉' => '𖹩',
'𖹊' => '𖹪',
'𖹋' => '𖹫',
'𖹌' => '𖹬',
¹<E28093>' => 'ð–¹­',
'𖹎' => '𖹮',
¹<E28093>' => '𖹯',
¹<E28093>' => 'ð–¹°',
'𖹑' => '𖹱',
'ð–¹’' => 'ð–¹²',
'𖹓' => '𖹳',
'ð–¹”' => 'ð–¹´',
'𖹕' => '𖹵',
'𖹖' => '𖹶',
'ð–¹—' => 'ð–¹·',
'𖹘' => '𖹸',
'ð–¹™' => 'ð–¹¹',
'𖹚' => '𖹺',
'ð–¹›' => 'ð–¹»',
'𖹜' => '𖹼',
¹<E28093>' => 'ð–¹½',
'𖹞' => '𖹾',
'𖹟' => '𖹿',
'𞤀' => '𞤢',
'ðž¤<C5BE>' => '𞤣',
'𞤂' => '𞤤',
'𞤃' => '𞤥',
'𞤄' => '𞤦',
'𞤅' => '𞤧',
'𞤆' => '𞤨',
'𞤇' => '𞤩',
'𞤈' => '𞤪',
'𞤉' => '𞤫',
'𞤊' => '𞤬',
'𞤋' => '𞤭',
'𞤌' => '𞤮',
'ðž¤<C5BE>' => '𞤯',
'𞤎' => '𞤰',
'ðž¤<C5BE>' => '𞤱',
'ðž¤<C5BE>' => '𞤲',
'𞤑' => '𞤳',
'𞤒' => '𞤴',
'𞤓' => '𞤵',
'𞤔' => '𞤶',
'𞤕' => '𞤷',
'𞤖' => '𞤸',
'𞤗' => '𞤹',
'𞤘' => '𞤺',
'𞤙' => '𞤻',
'𞤚' => '𞤼',
'𞤛' => '𞤽',
'𞤜' => '𞤾',
'ðž¤<C5BE>' => '𞤿',
'𞤞' => '𞥀',
'𞤟' => 'ðž¥<C5BE>',
'𞤠' => '𞥂',
'𞤡' => '𞥃',
);
<?php
// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt
return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u';
<?php
return array (
'a' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'e' => 'E',
'f' => 'F',
'g' => 'G',
'h' => 'H',
'i' => 'I',
'j' => 'J',
'k' => 'K',
'l' => 'L',
'm' => 'M',
'n' => 'N',
'o' => 'O',
'p' => 'P',
'q' => 'Q',
'r' => 'R',
's' => 'S',
't' => 'T',
'u' => 'U',
'v' => 'V',
'w' => 'W',
'x' => 'X',
'y' => 'Y',
'z' => 'Z',
'µ' => 'Μ',
'à' => 'À',
'á' => 'Ã<>',
'â' => 'Â',
'ã' => 'Ã',
'ä' => 'Ä',
'Ã¥' => 'Ã…',
'æ' => 'Æ',
'ç' => 'Ç',
'è' => 'È',
'é' => 'É',
'ê' => 'Ê',
'ë' => 'Ë',
'ì' => 'Ì',
'í' => 'Ã<>',
'î' => 'Î',
'ï' => 'Ã<>',
'ð' => 'Ã<>',
'ñ' => 'Ñ',
'ò' => 'Ò',
'ó' => 'Ó',
'ô' => 'Ô',
'õ' => 'Õ',
'ö' => 'Ö',
'ø' => 'Ø',
'ù' => 'Ù',
'ú' => 'Ú',
'û' => 'Û',
'ü' => 'Ü',
'ý' => 'Ã<>',
'þ' => 'Þ',
'ÿ' => 'Ÿ',
<>' => 'Ä€',
'ă' => 'Ă',
'Ä…' => 'Ä„',
'ć' => 'Ć',
'ĉ' => 'Ĉ',
'Ä‹' => 'ÄŠ',
<>' => 'Č',
<>' => 'ÄŽ',
'Ä‘' => 'Ä<>',
'Ä“' => 'Ä’',
'Ä•' => 'Ä”',
'Ä—' => 'Ä–',
'ę' => 'Ę',
'Ä›' => 'Äš',
<>' => 'Ĝ',
'ÄŸ' => 'Äž',
'Ä¡' => 'Ä ',
'Ä£' => 'Ä¢',
'ĥ' => 'Ĥ',
'ħ' => 'Ħ',
'ĩ' => 'Ĩ',
'ī' => 'Ī',
'ĭ' => 'Ĭ',
'į' => 'Į',
'ı' => 'I',
'ij' => 'IJ',
'ĵ' => 'Ĵ',
'ķ' => 'Ķ',
'ĺ' => 'Ĺ',
'ļ' => 'Ļ',
'ľ' => 'Ľ',
'Å€' => 'Ä¿',
'Å‚' => 'Å<>',
'ń' => 'Ń',
'ņ' => 'Ņ',
'ň' => 'Ň',
'Å‹' => 'ÅŠ',
<>' => 'Ō',
<>' => 'ÅŽ',
'Å‘' => 'Å<>',
'Å“' => 'Å’',
'Å•' => 'Å”',
'Å—' => 'Å–',
'ř' => 'Ř',
'Å›' => 'Åš',
<>' => 'Ŝ',
'ÅŸ' => 'Åž',
'Å¡' => 'Å ',
'Å£' => 'Å¢',
'ť' => 'Ť',
'ŧ' => 'Ŧ',
'ũ' => 'Ũ',
'ū' => 'Ū',
'ŭ' => 'Ŭ',
'ů' => 'Ů',
'ű' => 'Ű',
'ų' => 'Ų',
'ŵ' => 'Ŵ',
'ŷ' => 'Ŷ',
'ź' => 'Ź',
'ż' => 'Ż',
'ž' => 'Ž',
'Å¿' => 'S',
'ƀ' => 'Ƀ',
'ƃ' => 'Ƃ',
'Æ…' => 'Æ„',
'ƈ' => 'Ƈ',
'ƌ' => 'Ƌ',
'Æ’' => 'Æ‘',
'ƕ' => 'Ƕ',
'ƙ' => 'Ƙ',
'ƚ' => 'Ƚ',
'Æž' => 'È ',
'Æ¡' => 'Æ ',
'Æ£' => 'Æ¢',
'ƥ' => 'Ƥ',
'ƨ' => 'Ƨ',
'ƭ' => 'Ƭ',
'ư' => 'Ư',
'ƴ' => 'Ƴ',
'ƶ' => 'Ƶ',
'ƹ' => 'Ƹ',
'ƽ' => 'Ƽ',
'Æ¿' => 'Ç·',
'Ç…' => 'Ç„',
'dž' => 'DŽ',
'Lj' => 'LJ',
'lj' => 'LJ',
'Ç‹' => 'ÇŠ',
'nj' => 'NJ',
'ÇŽ' => 'Ç<>',
<>' => 'Ç<>',
'Ç’' => 'Ç‘',
'Ç”' => 'Ç“',
'Ç–' => 'Ç•',
'ǘ' => 'Ǘ',
'Çš' => 'Ç™',
'ǜ' => 'Ǜ',
<>' => 'ÆŽ',
'ÇŸ' => 'Çž',
'Ç¡' => 'Ç ',
'Ç£' => 'Ç¢',
'ǥ' => 'Ǥ',
'ǧ' => 'Ǧ',
'ǩ' => 'Ǩ',
'ǫ' => 'Ǫ',
'ǭ' => 'Ǭ',
'ǯ' => 'Ǯ',
'Dz' => 'DZ',
'dz' => 'DZ',
'ǵ' => 'Ǵ',
'ǹ' => 'Ǹ',
'ǻ' => 'Ǻ',
'ǽ' => 'Ǽ',
'ǿ' => 'Ǿ',
<>' => 'È€',
'ȃ' => 'Ȃ',
'È…' => 'È„',
'ȇ' => 'Ȇ',
'ȉ' => 'Ȉ',
'È‹' => 'ÈŠ',
<>' => 'Ȍ',
<>' => 'ÈŽ',
'È‘' => 'È<>',
'È“' => 'È’',
'È•' => 'È”',
'È—' => 'È–',
'ș' => 'Ș',
'È›' => 'Èš',
<>' => 'Ȝ',
'ÈŸ' => 'Èž',
'È£' => 'È¢',
'ȥ' => 'Ȥ',
'ȧ' => 'Ȧ',
'ȩ' => 'Ȩ',
'ȫ' => 'Ȫ',
'ȭ' => 'Ȭ',
'ȯ' => 'Ȯ',
'ȱ' => 'Ȱ',
'ȳ' => 'Ȳ',
'ȼ' => 'Ȼ',
'È¿' => 'â±¾',
'ɀ' => 'Ɀ',
'É‚' => 'É<>',
'ɇ' => 'Ɇ',
'ɉ' => 'Ɉ',
'É‹' => 'ÉŠ',
<>' => 'Ɍ',
<>' => 'ÉŽ',
<>' => 'Ɐ',
'É‘' => 'â±­',
'É’' => 'â±°',
'É“' => 'Æ<>',
'ɔ' => 'Ɔ',
'ɖ' => 'Ɖ',
'É—' => 'ÆŠ',
'É™' => 'Æ<>',
'É›' => 'Æ<>',
'ɜ' => 'Ɜ',
'É ' => 'Æ“',
'ɡ' => 'Ɡ',
'É£' => 'Æ”',
'É¥' => 'êž<C3AA>',
'ɦ' => 'Ɦ',
'ɨ' => 'Ɨ',
'É©' => 'Æ–',
'ɪ' => 'Ɪ',
'É«' => 'â±¢',
'ɬ' => 'Ɬ',
'ɯ' => 'Ɯ',
'ɱ' => 'Ɱ',
'ɲ' => 'Æ<>',
'ɵ' => 'Ɵ',
'ɽ' => 'Ɽ',
'ʀ' => 'Ʀ',
'ʂ' => 'Ʂ',
'ʃ' => 'Ʃ',
'ʇ' => 'Ʇ',
'ʈ' => 'Ʈ',
'ʉ' => 'Ʉ',
'ʊ' => 'Ʊ',
'ʋ' => 'Ʋ',
'ʌ' => 'Ʌ',
'Ê’' => 'Æ·',
<>' => 'êž²',
'Êž' => 'êž°',
'ͅ' => 'Ι',
'ͱ' => 'Ͱ',
'ͳ' => 'Ͳ',
'ͷ' => 'Ͷ',
'ͻ' => 'Ͻ',
'ͼ' => 'Ͼ',
'ͽ' => 'Ͽ',
'ά' => 'Ά',
'έ' => 'Έ',
'ή' => 'Ή',
'ί' => 'Ί',
'α' => 'Α',
'β' => 'Β',
'γ' => 'Γ',
'δ' => 'Δ',
'ε' => 'Ε',
'ζ' => 'Ζ',
'η' => 'Η',
'θ' => 'Θ',
'ι' => 'Ι',
'κ' => 'Κ',
'λ' => 'Λ',
'μ' => 'Μ',
'ν' => 'Î<>',
'ξ' => 'Ξ',
'ο' => 'Ο',
'π' => 'Π',
<>' => 'Ρ',
'ς' => 'Σ',
'σ' => 'Σ',
'τ' => 'Τ',
'Ï…' => 'Î¥',
'φ' => 'Φ',
'χ' => 'Χ',
'ψ' => 'Ψ',
'ω' => 'Ω',
'ϊ' => 'Ϊ',
'ϋ' => 'Ϋ',
'ό' => 'Ό',
<>' => 'ÎŽ',
'ÏŽ' => 'Î<>',
<>' => 'Î’',
'ϑ' => 'Θ',
'ϕ' => 'Φ',
'ϖ' => 'Π',
'Ï—' => 'Ï<>',
'ϙ' => 'Ϙ',
'Ï›' => 'Ïš',
<>' => 'Ϝ',
'ÏŸ' => 'Ïž',
'Ï¡' => 'Ï ',
'Ï£' => 'Ï¢',
'ϥ' => 'Ϥ',
'ϧ' => 'Ϧ',
'ϩ' => 'Ϩ',
'ϫ' => 'Ϫ',
'ϭ' => 'Ϭ',
'ϯ' => 'Ϯ',
'ϰ' => 'Κ',
'ϱ' => 'Ρ',
'ϲ' => 'Ϲ',
'ϳ' => 'Ϳ',
'ϵ' => 'Ε',
'ϸ' => 'Ϸ',
'ϻ' => 'Ϻ',
'а' => 'Ð<>',
'б' => 'Б',
'в' => 'В',
'г' => 'Г',
'д' => 'Д',
'е' => 'Е',
'ж' => 'Ж',
'з' => 'З',
'и' => 'И',
'й' => 'Й',
'к' => 'К',
'л' => 'Л',
'м' => 'М',
'н' => 'Ð<>',
'о' => 'О',
'п' => 'П',
'р' => 'Р',
<>' => 'С',
'т' => 'Т',
'у' => 'У',
'ф' => 'Ф',
'Ñ…' => 'Ð¥',
'ц' => 'Ц',
'ч' => 'Ч',
'ш' => 'Ш',
'щ' => 'Щ',
'ъ' => 'Ъ',
'ы' => 'Ы',
'ь' => 'Ь',
<>' => 'Э',
'ю' => 'Ю',
<>' => 'Я',
<>' => 'Ѐ',
'Ñ‘' => 'Ð<>',
'ђ' => 'Ђ',
'ѓ' => 'Ѓ',
'є' => 'Є',
'Ñ•' => 'Ð…',
'і' => 'І',
'ї' => 'Ї',
'ј' => 'Ј',
'љ' => 'Љ',
'њ' => 'Њ',
'ћ' => 'Ћ',
'ќ' => 'Ќ',
<>' => 'Ð<>',
'Ñž' => 'ÐŽ',
'ÑŸ' => 'Ð<>',
'Ñ¡' => 'Ñ ',
'Ñ£' => 'Ñ¢',
'ѥ' => 'Ѥ',
'ѧ' => 'Ѧ',
'ѩ' => 'Ѩ',
'ѫ' => 'Ѫ',
'ѭ' => 'Ѭ',
'ѯ' => 'Ѯ',
'ѱ' => 'Ѱ',
'ѳ' => 'Ѳ',
'ѵ' => 'Ѵ',
'ѷ' => 'Ѷ',
'ѹ' => 'Ѹ',
'ѻ' => 'Ѻ',
'ѽ' => 'Ѽ',
'ѿ' => 'Ѿ',
<>' => 'Ò€',
'Ò‹' => 'ÒŠ',
<>' => 'Ҍ',
<>' => 'ÒŽ',
'Ò‘' => 'Ò<>',
'Ò“' => 'Ò’',
'Ò•' => 'Ò”',
'Ò—' => 'Ò–',
'Ò™' => 'Ò˜',
'Ò›' => 'Òš',
<>' => 'Ҝ',
'ÒŸ' => 'Òž',
'Ò¡' => 'Ò ',
'Ò£' => 'Ò¢',
'Ò¥' => 'Ò¤',
'Ò§' => 'Ò¦',
'Ò©' => 'Ò¨',
'Ò«' => 'Òª',
'Ò­' => 'Ò¬',
'Ò¯' => 'Ò®',
'Ò±' => 'Ò°',
'Ò³' => 'Ò²',
'Òµ' => 'Ò´',
'Ò·' => 'Ò¶',
'Ò¹' => 'Ò¸',
'Ò»' => 'Òº',
'Ò½' => 'Ò¼',
'Ò¿' => 'Ò¾',
'Ó‚' => 'Ó<>',
'Ó„' => 'Óƒ',
'Ó†' => 'Ó…',
'Óˆ' => 'Ó‡',
'ÓŠ' => 'Ó‰',
'ӌ' => 'Ӌ',
'ÓŽ' => 'Ó<>',
<>' => 'Ó€',
'Ó‘' => 'Ó<>',
'Ó“' => 'Ó’',
'Ó•' => 'Ó”',
'Ó—' => 'Ó–',
'Ó™' => 'Ó˜',
'Ó›' => 'Óš',
<>' => 'Ӝ',
'ÓŸ' => 'Óž',
'Ó¡' => 'Ó ',
'Ó£' => 'Ó¢',
'Ó¥' => 'Ó¤',
'Ó§' => 'Ó¦',
'Ó©' => 'Ó¨',
'Ó«' => 'Óª',
'Ó­' => 'Ó¬',
'Ó¯' => 'Ó®',
'Ó±' => 'Ó°',
'Ó³' => 'Ó²',
'Óµ' => 'Ó´',
'Ó·' => 'Ó¶',
'Ó¹' => 'Ó¸',
'Ó»' => 'Óº',
'Ó½' => 'Ó¼',
'Ó¿' => 'Ó¾',
<>' => 'Ô€',
'Ôƒ' => 'Ô‚',
'Ô…' => 'Ô„',
'Ô‡' => 'Ô†',
'Ô‰' => 'Ôˆ',
'Ô‹' => 'ÔŠ',
<>' => 'Ԍ',
<>' => 'ÔŽ',
'Ô‘' => 'Ô<>',
'Ô“' => 'Ô’',
'Ô•' => 'Ô”',
'Ô—' => 'Ô–',
'Ô™' => 'Ô˜',
'Ô›' => 'Ôš',
<>' => 'Ԝ',
'ÔŸ' => 'Ôž',
'Ô¡' => 'Ô ',
'Ô£' => 'Ô¢',
'Ô¥' => 'Ô¤',
'Ô§' => 'Ô¦',
'Ô©' => 'Ô¨',
'Ô«' => 'Ôª',
'Ô­' => 'Ô¬',
'Ô¯' => 'Ô®',
'Õ¡' => 'Ô±',
'Õ¢' => 'Ô²',
'Õ£' => 'Ô³',
'Õ¤' => 'Ô´',
'Õ¥' => 'Ôµ',
'Õ¦' => 'Ô¶',
'Õ§' => 'Ô·',
'Õ¨' => 'Ô¸',
'Õ©' => 'Ô¹',
'Õª' => 'Ôº',
'Õ«' => 'Ô»',
'Õ¬' => 'Ô¼',
'Õ­' => 'Ô½',
'Õ®' => 'Ô¾',
'Õ¯' => 'Ô¿',
'Õ°' => 'Õ€',
'Õ±' => 'Õ<>',
'Õ²' => 'Õ‚',
'Õ³' => 'Õƒ',
'Õ´' => 'Õ„',
'Õµ' => 'Õ…',
'Õ¶' => 'Õ†',
'Õ·' => 'Õ‡',
'Õ¸' => 'Õˆ',
'Õ¹' => 'Õ‰',
'Õº' => 'ÕŠ',
'Õ»' => 'Õ‹',
'ռ' => 'Ռ',
'Õ½' => 'Õ<>',
'Õ¾' => 'ÕŽ',
'Õ¿' => 'Õ<>',
'Ö€' => 'Õ<>',
<>' => 'Õ‘',
'Ö‚' => 'Õ’',
'Öƒ' => 'Õ“',
'Ö„' => 'Õ”',
'Ö…' => 'Õ•',
'Ö†' => 'Õ–',
'áƒ<C3A1>' => 'á²<C3A1>',
'ბ' => 'Ბ',
'გ' => 'Გ',
'დ' => 'Დ',
'ე' => 'Ე',
'ვ' => 'Ვ',
'ზ' => 'Ზ',
'თ' => 'Თ',
'ი' => 'Ი',
'კ' => 'Კ',
'ლ' => 'Ლ',
'მ' => 'Მ',
'ნ' => 'Ნ',
'áƒ<C3A1>' => 'á²<C3A1>',
'პ' => 'Პ',
'ჟ' => 'Ჟ',
'რ' => 'Რ',
'ს' => 'Ს',
'ტ' => 'Ტ',
'უ' => 'Უ',
'ფ' => 'Ფ',
'ქ' => 'Ქ',
'ღ' => 'Ღ',
'ყ' => 'Ყ',
'შ' => 'Შ',
'ჩ' => 'Ჩ',
'ც' => 'Ც',
'ძ' => 'Ძ',
'წ' => 'Წ',
'ჭ' => 'Ჭ',
'ხ' => 'Ხ',
'ჯ' => 'Ჯ',
'ჰ' => 'Ჰ',
'ჱ' => 'Ჱ',
'ჲ' => 'Ჲ',
'ჳ' => 'Ჳ',
'ჴ' => 'Ჴ',
'ჵ' => 'Ჵ',
'ჶ' => 'Ჶ',
'ჷ' => 'Ჷ',
'ჸ' => 'Ჸ',
'ჹ' => 'Ჹ',
'ჺ' => 'Ჺ',
'ჽ' => 'Ჽ',
'ჾ' => 'Ჾ',
'ჿ' => 'Ჿ',
<>¸' => 'á<>°',
<>¹' => 'á<>±',
<>º' => 'á<>²',
<>»' => 'á<>³',
<>¼' => 'á<>´',
<>½' => 'á<>µ',
'á²€' => 'Ð’',
'á²<C3A1>' => 'Д',
'ᲂ' => 'О',
'ᲃ' => 'С',
'ᲄ' => 'Т',
'ᲅ' => 'Т',
'ᲆ' => 'Ъ',
'ᲇ' => 'Ѣ',
'ᲈ' => 'Ꙋ',
'áµ¹' => 'ê<>½',
'áµ½' => 'â±£',
'ᶎ' => 'Ᶎ',
¸<C3A1>' => 'Ḁ',
'ḃ' => 'Ḃ',
'ḅ' => 'Ḅ',
'ḇ' => 'Ḇ',
'ḉ' => 'Ḉ',
'ḋ' => 'Ḋ',
¸<C3A1>' => 'Ḍ',
¸<C3A1>' => 'Ḏ',
'ḑ' => 'á¸<C3A1>',
'ḓ' => 'Ḓ',
'ḕ' => 'Ḕ',
'ḗ' => 'Ḗ',
'ḙ' => 'Ḙ',
'ḛ' => 'Ḛ',
¸<C3A1>' => 'Ḝ',
'ḟ' => 'Ḟ',
'ḡ' => 'Ḡ',
'ḣ' => 'Ḣ',
'ḥ' => 'Ḥ',
'ḧ' => 'Ḧ',
'ḩ' => 'Ḩ',
'ḫ' => 'Ḫ',
'ḭ' => 'Ḭ',
'ḯ' => 'Ḯ',
'ḱ' => 'Ḱ',
'ḳ' => 'Ḳ',
'ḵ' => 'Ḵ',
'ḷ' => 'Ḷ',
'ḹ' => 'Ḹ',
'ḻ' => 'Ḻ',
'ḽ' => 'Ḽ',
'ḿ' => 'Ḿ',
'á¹<C3A1>' => 'á¹€',
'ṃ' => 'Ṃ',
'ṅ' => 'Ṅ',
'ṇ' => 'Ṇ',
'ṉ' => 'Ṉ',
'ṋ' => 'Ṋ',
'á¹<C3A1>' => 'Ṍ',
'á¹<C3A1>' => 'Ṏ',
'ṑ' => 'á¹<C3A1>',
'ṓ' => 'Ṓ',
'ṕ' => 'Ṕ',
'á¹—' => 'á¹–',
'ṙ' => 'Ṙ',
'ṛ' => 'Ṛ',
'á¹<C3A1>' => 'Ṝ',
'ṟ' => 'Ṟ',
'ṡ' => 'Ṡ',
'á¹£' => 'á¹¢',
'ṥ' => 'Ṥ',
'ṧ' => 'Ṧ',
'ṩ' => 'Ṩ',
'ṫ' => 'Ṫ',
'ṭ' => 'Ṭ',
'ṯ' => 'Ṯ',
'á¹±' => 'á¹°',
'á¹³' => 'á¹²',
'á¹µ' => 'á¹´',
'ṷ' => 'Ṷ',
'ṹ' => 'Ṹ',
'ṻ' => 'Ṻ',
'á¹½' => 'á¹¼',
'ṿ' => 'Ṿ',
'áº<C3A1>' => 'Ẁ',
'ẃ' => 'Ẃ',
'ẅ' => 'Ẅ',
'ẇ' => 'Ẇ',
'ẉ' => 'Ẉ',
'ẋ' => 'Ẋ',
'áº<C3A1>' => 'Ẍ',
'áº<C3A1>' => 'Ẏ',
'ẑ' => 'áº<C3A1>',
'ẓ' => 'Ẓ',
'ẕ' => 'Ẕ',
'ẛ' => 'Ṡ',
'ạ' => 'Ạ',
'ả' => 'Ả',
'ấ' => 'Ấ',
'ầ' => 'Ầ',
'ẩ' => 'Ẩ',
'ẫ' => 'Ẫ',
'ậ' => 'Ậ',
'ắ' => 'Ắ',
'ằ' => 'Ằ',
'ẳ' => 'Ẳ',
'ẵ' => 'Ẵ',
'ặ' => 'Ặ',
'ẹ' => 'Ẹ',
'ẻ' => 'Ẻ',
'ẽ' => 'Ẽ',
'ế' => 'Ế',
'á»<C3A1>' => 'Ề',
'ể' => 'Ể',
'ễ' => 'Ễ',
'ệ' => 'Ệ',
'ỉ' => 'Ỉ',
'ị' => 'Ị',
'á»<C3A1>' => 'Ọ',
'á»<C3A1>' => 'Ỏ',
'ố' => 'á»<C3A1>',
'ồ' => 'Ồ',
'ổ' => 'Ổ',
'á»—' => 'á»–',
'ộ' => 'Ộ',
'ớ' => 'Ớ',
'á»<C3A1>' => 'Ờ',
'ở' => 'Ở',
'ỡ' => 'Ỡ',
'ợ' => 'Ợ',
'ụ' => 'Ụ',
'ủ' => 'Ủ',
'ứ' => 'Ứ',
'ừ' => 'Ừ',
'ử' => 'Ử',
'ữ' => 'Ữ',
'á»±' => 'á»°',
'ỳ' => 'Ỳ',
'ỵ' => 'Ỵ',
'ỷ' => 'Ỷ',
'ỹ' => 'Ỹ',
'ỻ' => 'Ỻ',
'ỽ' => 'Ỽ',
'ỿ' => 'Ỿ',
'ἀ' => 'Ἀ',
'á¼<C3A1>' => 'Ἁ',
'ἂ' => 'Ἂ',
'ἃ' => 'Ἃ',
'ἄ' => 'Ἄ',
'á¼…' => 'á¼<C3A1>',
'ἆ' => 'Ἆ',
'ἇ' => 'á¼<C3A1>',
'á¼<C3A1>' => 'Ἐ',
'ἑ' => 'Ἑ',
'ἒ' => 'Ἒ',
'ἓ' => 'Ἓ',
'ἔ' => 'Ἔ',
'ἕ' => 'á¼<C3A1>',
'ἠ' => 'Ἠ',
'ἡ' => 'Ἡ',
'ἢ' => 'Ἢ',
'ἣ' => 'Ἣ',
'ἤ' => 'Ἤ',
'á¼¥' => 'á¼­',
'ἦ' => 'Ἦ',
'ἧ' => 'Ἧ',
'ἰ' => 'Ἰ',
'á¼±' => 'á¼¹',
'ἲ' => 'Ἲ',
'á¼³' => 'á¼»',
'á¼´' => 'á¼¼',
'á¼µ' => 'á¼½',
'ἶ' => 'Ἶ',
'ἷ' => 'Ἷ',
'ὀ' => 'Ὀ',
'á½<C3A1>' => 'Ὁ',
'ὂ' => 'Ὂ',
'ὃ' => 'Ὃ',
'ὄ' => 'Ὄ',
'á½…' => 'á½<C3A1>',
'ὑ' => 'Ὑ',
'ὓ' => 'Ὓ',
'ὕ' => 'á½<C3A1>',
'ὗ' => 'Ὗ',
'ὠ' => 'Ὠ',
'ὡ' => 'Ὡ',
'ὢ' => 'Ὢ',
'ὣ' => 'Ὣ',
'ὤ' => 'Ὤ',
'á½¥' => 'á½­',
'ὦ' => 'Ὦ',
'ὧ' => 'Ὧ',
'ὰ' => 'Ὰ',
'á½±' => 'á¾»',
'ὲ' => 'Ὲ',
'έ' => 'Έ',
'á½´' => 'á¿Š',
'á½µ' => 'á¿‹',
'ὶ' => 'Ὶ',
'á½·' => 'á¿›',
'ὸ' => 'Ὸ',
'ό' => 'Ό',
'ὺ' => 'Ὺ',
'á½»' => 'á¿«',
'ὼ' => 'Ὼ',
'á½½' => 'á¿»',
'ᾀ' => 'ᾈ',
'á¾<C3A1>' => 'ᾉ',
'ᾂ' => 'ᾊ',
'ᾃ' => 'ᾋ',
'ᾄ' => 'ᾌ',
'á¾…' => 'á¾<C3A1>',
'ᾆ' => 'ᾎ',
'ᾇ' => 'á¾<C3A1>',
'á¾<C3A1>' => 'ᾘ',
'ᾑ' => 'ᾙ',
'ᾒ' => 'ᾚ',
'ᾓ' => 'ᾛ',
'ᾔ' => 'ᾜ',
'ᾕ' => 'á¾<C3A1>',
'ᾖ' => 'ᾞ',
'ᾗ' => 'ᾟ',
'ᾠ' => 'ᾨ',
'ᾡ' => 'ᾩ',
'ᾢ' => 'ᾪ',
'ᾣ' => 'ᾫ',
'ᾤ' => 'ᾬ',
'á¾¥' => 'á¾­',
'ᾦ' => 'ᾮ',
'ᾧ' => 'ᾯ',
'ᾰ' => 'Ᾰ',
'á¾±' => 'á¾¹',
'á¾³' => 'á¾¼',
'ι' => 'Ι',
'ῃ' => 'ῌ',
'á¿<C3A1>' => 'Ῐ',
'á¿‘' => 'á¿™',
'ῠ' => 'Ῠ',
'á¿¡' => 'á¿©',
'ῥ' => 'Ῥ',
'ῳ' => 'ῼ',
'ⅎ' => 'Ⅎ',
'â…°' => 'â… ',
'â…±' => 'â…¡',
'â…²' => 'â…¢',
'â…³' => 'â…£',
'â…´' => 'â…¤',
'â…µ' => 'â…¥',
'â…¶' => 'â…¦',
'â…·' => 'â…§',
'â…¸' => 'â…¨',
'â…¹' => 'â…©',
'â…º' => 'â…ª',
'â…»' => 'â…«',
'â…¼' => 'â…¬',
'â…½' => 'â…­',
'â…¾' => 'â…®',
'â…¿' => 'â…¯',
'ↄ' => 'Ↄ',
'â“<C3A2>' => 'â’¶',
'â“‘' => 'â’·',
'â“’' => 'â’¸',
'â““' => 'â’¹',
'â“”' => 'â’º',
'â“•' => 'â’»',
'â“–' => 'â’¼',
'â“—' => 'â’½',
'ⓘ' => 'Ⓘ',
'â“™' => 'â’¿',
'â“š' => 'â“€',
'â“›' => 'â“<C3A2>',
'ⓜ' => 'Ⓜ',
'â“<C3A2>' => 'Ⓝ',
'â“ž' => 'â“„',
'â“Ÿ' => 'â“…',
'ⓠ' => 'Ⓠ',
'ⓡ' => 'Ⓡ',
'ⓢ' => 'Ⓢ',
'ⓣ' => 'Ⓣ',
'ⓤ' => 'Ⓤ',
'â“¥' => 'â“‹',
'ⓦ' => 'Ⓦ',
'ⓧ' => 'â“<C3A2>',
'ⓨ' => 'Ⓨ',
'â“©' => 'â“<C3A2>',
'â°°' => 'â°€',
'â°±' => 'â°<C3A2>',
'â°²' => 'â°‚',
'â°³' => 'â°ƒ',
'â°´' => 'â°„',
'â°µ' => 'â°…',
'â°¶' => 'â°†',
'â°·' => 'â°‡',
'â°¸' => 'â°ˆ',
'â°¹' => 'â°‰',
'â°º' => 'â°Š',
'â°»' => 'â°‹',
'ⰼ' => 'Ⰼ',
'â°½' => 'â°<C3A2>',
'â°¾' => 'â°Ž',
'â°¿' => 'â°<C3A2>',
'â±€' => 'â°<C3A2>',
'â±<C3A2>' => 'â°‘',
'ⱂ' => 'Ⱂ',
'ⱃ' => 'Ⱃ',
'ⱄ' => 'Ⱄ',
'â±…' => 'â°•',
'ⱆ' => 'Ⱆ',
'ⱇ' => 'Ⱇ',
'ⱈ' => 'Ⱈ',
'ⱉ' => 'Ⱉ',
'ⱊ' => 'Ⱊ',
'ⱋ' => 'Ⱋ',
'ⱌ' => 'Ⱌ',
'â±<C3A2>' => 'â°<C3A2>',
'ⱎ' => 'Ⱎ',
'â±<C3A2>' => 'â°Ÿ',
'â±<C3A2>' => 'â° ',
'ⱑ' => 'Ⱑ',
'â±’' => 'â°¢',
'ⱓ' => 'Ⱓ',
'â±”' => 'â°¤',
'ⱕ' => 'Ⱕ',
'â±–' => 'â°¦',
'â±—' => 'â°§',
'ⱘ' => 'Ⱘ',
'â±™' => 'â°©',
'ⱚ' => 'Ⱚ',
'â±›' => 'â°«',
'ⱜ' => 'Ⱜ',
'â±<C3A2>' => 'â°­',
'ⱞ' => 'Ⱞ',
'ⱡ' => 'Ⱡ',
'ⱥ' => 'Ⱥ',
'ⱦ' => 'Ⱦ',
'ⱨ' => 'Ⱨ',
'ⱪ' => 'Ⱪ',
'ⱬ' => 'Ⱬ',
'â±³' => 'â±²',
'ⱶ' => 'Ⱶ',
'â²<C3A2>' => 'â²€',
'ⲃ' => 'Ⲃ',
'ⲅ' => 'Ⲅ',
'ⲇ' => 'Ⲇ',
'ⲉ' => 'Ⲉ',
'ⲋ' => 'Ⲋ',
'â²<C3A2>' => 'Ⲍ',
'â²<C3A2>' => 'Ⲏ',
'ⲑ' => 'â²<C3A2>',
'ⲓ' => 'Ⲓ',
'ⲕ' => 'Ⲕ',
'â²—' => 'â²–',
'ⲙ' => 'Ⲙ',
'ⲛ' => 'Ⲛ',
'â²<C3A2>' => 'Ⲝ',
'ⲟ' => 'Ⲟ',
'ⲡ' => 'Ⲡ',
'â²£' => 'â²¢',
'ⲥ' => 'Ⲥ',
'ⲧ' => 'Ⲧ',
'ⲩ' => 'Ⲩ',
'ⲫ' => 'Ⲫ',
'ⲭ' => 'Ⲭ',
'ⲯ' => 'Ⲯ',
'â²±' => 'â²°',
'â²³' => 'â²²',
'â²µ' => 'â²´',
'ⲷ' => 'Ⲷ',
'ⲹ' => 'Ⲹ',
'ⲻ' => 'Ⲻ',
'â²½' => 'â²¼',
'ⲿ' => 'Ⲿ',
'â³<C3A2>' => 'â³€',
'ⳃ' => 'Ⳃ',
'ⳅ' => 'Ⳅ',
'ⳇ' => 'Ⳇ',
'ⳉ' => 'Ⳉ',
'ⳋ' => 'Ⳋ',
'â³<C3A2>' => 'Ⳍ',
'â³<C3A2>' => 'Ⳏ',
'ⳑ' => 'â³<C3A2>',
'ⳓ' => 'Ⳓ',
'ⳕ' => 'Ⳕ',
'â³—' => 'â³–',
'ⳙ' => 'Ⳙ',
'ⳛ' => 'Ⳛ',
'â³<C3A2>' => 'Ⳝ',
'ⳟ' => 'Ⳟ',
'ⳡ' => 'Ⳡ',
'â³£' => 'â³¢',
'ⳬ' => 'Ⳬ',
'â³®' => 'â³­',
'â³³' => 'â³²',
'â´€' => 'á‚ ',
´<C3A2>' => 'á‚¡',
'â´‚' => 'á‚¢',
'â´ƒ' => 'á‚£',
'ⴄ' => 'Ⴄ',
'â´…' => 'á‚¥',
'ⴆ' => 'Ⴆ',
'ⴇ' => 'Ⴇ',
'ⴈ' => 'Ⴈ',
'â´‰' => 'á‚©',
'ⴊ' => 'Ⴊ',
'â´‹' => 'á‚«',
'ⴌ' => 'Ⴌ',
´<C3A2>' => 'á‚­',
'â´Ž' => 'á‚®',
´<C3A2>' => 'Ⴏ',
´<C3A2>' => 'á‚°',
'ⴑ' => 'Ⴑ',
'ⴒ' => 'Ⴒ',
'ⴓ' => 'Ⴓ',
'â´”' => 'á‚´',
'ⴕ' => 'Ⴕ',
'ⴖ' => 'Ⴖ',
'â´—' => 'á‚·',
'ⴘ' => 'Ⴘ',
'ⴙ' => 'Ⴙ',
'ⴚ' => 'Ⴚ',
'â´›' => 'á‚»',
'ⴜ' => 'Ⴜ',
´<C3A2>' => 'Ⴝ',
'ⴞ' => 'Ⴞ',
'â´Ÿ' => 'á‚¿',
'ⴠ' => 'Ⴠ',
'â´¡' => 'áƒ<C3A1>',
'ⴢ' => 'Ⴢ',
'ⴣ' => 'Ⴣ',
'ⴤ' => 'Ⴤ',
'ⴥ' => 'Ⴥ',
'ⴧ' => 'Ⴧ',
'â´­' => 'áƒ<C3A1>',
'ê™<C3AA>' => 'Ꙁ',
'ꙃ' => 'Ꙃ',
'ꙅ' => 'Ꙅ',
'ꙇ' => 'Ꙇ',
'ꙉ' => 'Ꙉ',
'ꙋ' => 'Ꙋ',
'ê™<C3AA>' => 'Ꙍ',
'ê™<C3AA>' => 'Ꙏ',
'ꙑ' => 'ê™<C3AA>',
'ꙓ' => 'Ꙓ',
'ꙕ' => 'Ꙕ',
'ê™—' => 'ê™–',
'ꙙ' => 'Ꙙ',
'ꙛ' => 'Ꙛ',
'ê™<C3AA>' => 'Ꙝ',
'ꙟ' => 'Ꙟ',
'ꙡ' => 'Ꙡ',
'ꙣ' => 'Ꙣ',
'ꙥ' => 'Ꙥ',
'ꙧ' => 'Ꙧ',
'ꙩ' => 'Ꙩ',
'ꙫ' => 'Ꙫ',
'ꙭ' => 'Ꙭ',
'êš<C3AA>' => 'Ꚁ',
'ꚃ' => 'Ꚃ',
'êš…' => 'êš„',
'ꚇ' => 'Ꚇ',
'ꚉ' => 'Ꚉ',
'ꚋ' => 'Ꚋ',
'êš<C3AA>' => 'Ꚍ',
'êš<C3AA>' => 'Ꚏ',
'êš‘' => 'êš<C3AA>',
'êš“' => 'êš’',
'êš•' => 'êš”',
'êš—' => 'êš–',
'ꚙ' => 'Ꚙ',
'êš›' => 'êšš',
'ꜣ' => 'Ꜣ',
'ꜥ' => 'Ꜥ',
'ꜧ' => 'Ꜧ',
'ꜩ' => 'Ꜩ',
'ꜫ' => 'Ꜫ',
'ꜭ' => 'Ꜭ',
'ꜯ' => 'Ꜯ',
'ꜳ' => 'Ꜳ',
'ꜵ' => 'Ꜵ',
'ꜷ' => 'Ꜷ',
'ꜹ' => 'Ꜹ',
'ꜻ' => 'Ꜻ',
'ꜽ' => 'Ꜽ',
'ꜿ' => 'Ꜿ',
<><C3AA>' => 'ê<>€',
<>ƒ' => 'ê<>',
<>…' => 'ê<>„',
<>‡' => 'ê<>†',
<>‰' => 'ê<>ˆ',
<>' => 'ê<>Š',
<><C3AA>' => 'ê<>Œ',
<><C3AA>' => 'ê<>Ž',
<>' => 'ê<><C3AA>',
<>“' => 'ê<>',
<>•' => 'ê<>”',
<>—' => 'ê<>',
<>™' => 'ê<>˜',
<>' => 'ê<>š',
<><C3AA>' => 'ê<>œ',
<>Ÿ' => 'ê<>ž',
<>¡' => 'ê<> ',
<>£' => 'ê<>¢',
<>¥' => 'ê<>¤',
<>§' => 'ê<>¦',
<>©' => 'ê<>¨',
<>«' => 'ê<>ª',
<>­' => 'ê<>¬',
<>¯' => 'ê<>®',
<>º' => 'ê<>¹',
<>¼' => 'ê<>»',
<>¿' => 'ê<>¾',
'êž<C3AA>' => 'Ꞁ',
'ꞃ' => 'Ꞃ',
'êž…' => 'êž„',
'ꞇ' => 'Ꞇ',
'ꞌ' => 'Ꞌ',
'êž‘' => 'êž<C3AA>',
'êž“' => 'êž’',
'ꞔ' => 'Ꞔ',
'êž—' => 'êž–',
'ꞙ' => 'Ꞙ',
'êž›' => 'êžš',
'êž<C3AA>' => 'êžœ',
'ꞟ' => 'Ꞟ',
'êž¡' => 'êž ',
'ꞣ' => 'Ꞣ',
'ꞥ' => 'Ꞥ',
'ꞧ' => 'Ꞧ',
'ꞩ' => 'Ꞩ',
'êžµ' => 'êž´',
'ꞷ' => 'Ꞷ',
'ꞹ' => 'Ꞹ',
'ꞻ' => 'Ꞻ',
'êž½' => 'êž¼',
'êž¿' => 'êž¾',
'ꟃ' => 'Ꟃ',
'ꟈ' => 'Ꟈ',
'ꟊ' => 'Ꟊ',
'ꟶ' => 'Ꟶ',
'ê­“' => 'êž³',
'ꭰ' => 'Ꭰ',
'ꭱ' => 'Ꭱ',
'ꭲ' => 'Ꭲ',
'ꭳ' => 'Ꭳ',
'ꭴ' => 'Ꭴ',
'ꭵ' => 'Ꭵ',
'ꭶ' => 'Ꭶ',
'ꭷ' => 'Ꭷ',
'ꭸ' => 'Ꭸ',
'ꭹ' => 'Ꭹ',
'ꭺ' => 'Ꭺ',
'ꭻ' => 'Ꭻ',
'ꭼ' => 'Ꭼ',
'ꭽ' => 'Ꭽ',
'ꭾ' => 'Ꭾ',
'ꭿ' => 'Ꭿ',
'ꮀ' => 'Ꮀ',
'ê®<C3AA>' => 'Ꮁ',
'ꮂ' => 'Ꮂ',
'ꮃ' => 'Ꮃ',
'ꮄ' => 'Ꮄ',
'ꮅ' => 'Ꮅ',
'ꮆ' => 'Ꮆ',
'ꮇ' => 'Ꮇ',
'ꮈ' => 'Ꮈ',
'ꮉ' => 'Ꮉ',
'ꮊ' => 'Ꮊ',
'ꮋ' => 'Ꮋ',
'ꮌ' => 'Ꮌ',
'ê®<C3AA>' => 'Ꮍ',
'ꮎ' => 'Ꮎ',
'ê®<C3AA>' => 'Ꮏ',
'ê®<C3AA>' => 'á<>€',
'ꮑ' => 'á<><C3A1>',
'ê®’' => 'á<>',
'ꮓ' => 'á<>ƒ',
'ê®”' => 'á<>„',
'ꮕ' => 'á<>…',
'ê®–' => 'á<>†',
'ê®—' => 'á<>‡',
'ꮘ' => 'á<>ˆ',
'ê®™' => 'á<>‰',
'ꮚ' => 'á<>Š',
'ê®›' => 'á<>',
'ꮜ' => 'á<>Œ',
'ê®<C3AA>' => 'á<><C3A1>',
'ꮞ' => 'á<>Ž',
'ꮟ' => 'á<><C3A1>',
'ê® ' => 'á<><C3A1>',
'ꮡ' => 'á<>',
'ꮢ' => 'á<>',
'ꮣ' => 'á<>“',
'ꮤ' => 'á<>”',
'ꮥ' => 'á<>•',
'ꮦ' => 'á<>',
'ꮧ' => 'á<>—',
'ꮨ' => 'á<>˜',
'ꮩ' => 'á<>™',
'ꮪ' => 'á<>š',
'ꮫ' => 'á<>',
'ꮬ' => 'á<>œ',
'ê®­' => 'á<><C3A1>',
'ê®®' => 'á<>ž',
'ꮯ' => 'á<>Ÿ',
'ê®°' => 'á<> ',
'ê®±' => 'á<>¡',
'ꮲ' => 'á<>¢',
'ꮳ' => 'á<>£',
'ê®´' => 'á<>¤',
'ꮵ' => 'á<>¥',
'ꮶ' => 'á<>¦',
'ê®·' => 'á<>§',
'ꮸ' => 'á<>¨',
'ꮹ' => 'á<>©',
'ꮺ' => 'á<>ª',
'ê®»' => 'á<>«',
'ꮼ' => 'á<>¬',
'ꮽ' => 'á<>­',
'ꮾ' => 'á<>®',
'ꮿ' => 'á<>¯',
'ï½<C3AF>' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
'ï½…' => 'ï¼¥',
'f' => 'F',
'g' => 'G',
'h' => 'H',
'i' => 'I',
'j' => 'J',
'k' => 'K',
'l' => 'L',
'ï½<C3AF>' => 'ï¼­',
'n' => 'N',
'ï½<C3AF>' => 'O',
'ï½<C3AF>' => 'ï¼°',
'q' => 'Q',
'ï½’' => 'ï¼²',
's' => 'S',
'ï½”' => 'ï¼´',
'u' => 'U',
'v' => 'V',
'ï½—' => 'ï¼·',
'x' => 'X',
'ï½™' => 'ï¼¹',
'z' => 'Z',
<><C3B0>¨' => 'ð<><C3B0>€',
<><C3B0>©' => 'ð<><C3B0><EFBFBD>',
<><C3B0>ª' => 'ð<><C3B0>',
<><C3B0>«' => 'ð<><C3B0>ƒ',
<><C3B0>¬' => 'ð<><C3B0>„',
<><C3B0>­' => 'ð<><C3B0>…',
<><C3B0>®' => 'ð<><C3B0>†',
<><C3B0>¯' => 'ð<><C3B0>‡',
<><C3B0>°' => 'ð<><C3B0>ˆ',
<><C3B0>±' => 'ð<><C3B0>‰',
<><C3B0>²' => 'ð<><C3B0>Š',
<><C3B0>³' => 'ð<><C3B0>',
<><C3B0>´' => 'ð<><C3B0>Œ',
<><C3B0>µ' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¶' => 'ð<><C3B0>Ž',
<><C3B0>·' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¸' => 'ð<><C3B0><EFBFBD>',
<><C3B0>¹' => 'ð<><C3B0>',
<><C3B0>º' => 'ð<><C3B0>',
<><C3B0>»' => 'ð<><C3B0>“',
<><C3B0>¼' => 'ð<><C3B0>”',
<><C3B0>½' => 'ð<><C3B0>•',
<><C3B0>¾' => 'ð<><C3B0>',
<><C3B0>¿' => 'ð<><C3B0>—',
<>€' => 'ð<><C3B0>˜',
<><EFBFBD>' => 'ð<><C3B0>™',
<>' => 'ð<><C3B0>š',
<>ƒ' => 'ð<><C3B0>',
<>„' => 'ð<><C3B0>œ',
<>…' => 'ð<><C3B0><EFBFBD>',
<>†' => 'ð<><C3B0>ž',
<>‡' => 'ð<><C3B0>Ÿ',
<>ˆ' => 'ð<><C3B0> ',
<>‰' => 'ð<><C3B0>¡',
<>Š' => 'ð<><C3B0>¢',
<>' => 'ð<><C3B0>£',
<>Œ' => 'ð<><C3B0>¤',
<><EFBFBD>' => 'ð<><C3B0>¥',
<>Ž' => 'ð<><C3B0>¦',
<><EFBFBD>' => 'ð<><C3B0>§',
<>˜' => 'ð<>°',
<>“™' => 'ð<>±',
<>“š' => 'ð<>²',
<>' => 'ð<>³',
<>“œ' => 'ð<>´',
<><EFBFBD>' => 'ð<>µ',
<>“ž' => 'ð<>¶',
<>“Ÿ' => 'ð<>·',
<> ' => 'ð<>¸',
<>“¡' => 'ð<>¹',
<>“¢' => 'ð<>º',
<>“£' => 'ð<>»',
<>“¤' => 'ð<>¼',
<>“¥' => 'ð<>½',
<>“¦' => 'ð<>¾',
<>“§' => 'ð<>¿',
<>“¨' => 'ð<>“€',
<>“©' => 'ð<><EFBFBD>',
<>“ª' => 'ð<>',
<>“«' => 'ð<>“ƒ',
<>“¬' => 'ð<>“„',
<>­' => 'ð<>“…',
<>“®' => 'ð<>“†',
<>“¯' => 'ð<>“‡',
<>“°' => 'ð<>ˆ',
<>“±' => 'ð<>“‰',
<>“²' => 'ð<>“Š',
<>“³' => 'ð<>',
<>´' => 'ð<>“Œ',
<>“µ' => 'ð<><EFBFBD>',
<>“¶' => 'ð<>“Ž',
<>“·' => 'ð<><EFBFBD>',
<>¸' => 'ð<><EFBFBD>',
<>“¹' => 'ð<>',
<>“º' => 'ð<>',
<>“»' => 'ð<>““',
<>³€' => 'ð<>²€',
<>³<EFBFBD>' => 'ð<>²<EFBFBD>',
<>³' => 'ð<>²',
<>³ƒ' => 'ð<>²ƒ',
<>³„' => 'ð<>²„',
<>³…' => 'ð<>²…',
<>³†' => 'ð<>²†',
<>³‡' => 'ð<>²‡',
<>³ˆ' => 'ð<>²ˆ',
<>³‰' => 'ð<>²‰',
<>³Š' => 'ð<>²Š',
<>³' => 'ð<>²',
<>³Œ' => 'ð<>²Œ',
<>³<EFBFBD>' => 'ð<>²<EFBFBD>',
<>³Ž' => 'ð<>²Ž',
<>³<EFBFBD>' => 'ð<>²<EFBFBD>',
<>³<EFBFBD>' => 'ð<>²<EFBFBD>',
<>³' => 'ð<>²',
<>³' => 'ð<>²',
<>³“' => 'ð<>²“',
<>³”' => 'ð<>²”',
<>³•' => 'ð<>²•',
<>³' => 'ð<>²',
<>³—' => 'ð<>²—',
<>³˜' => 'ð<>²˜',
<>³™' => 'ð<>²™',
<>³š' => 'ð<>²š',
<>³' => 'ð<>²',
<>³œ' => 'ð<>²œ',
<>³<EFBFBD>' => 'ð<>²<EFBFBD>',
<>³ž' => 'ð<>²ž',
<>³Ÿ' => 'ð<>²Ÿ',
<>³ ' => 'ð<>² ',
<>³¡' => 'ð<>²¡',
<>³¢' => 'ð<>²¢',
<>³£' => 'ð<>²£',
<>³¤' => 'ð<>²¤',
<>³¥' => 'ð<>²¥',
<>³¦' => 'ð<>²¦',
<>³§' => 'ð<>²§',
<>³¨' => 'ð<>²¨',
<>³©' => 'ð<>²©',
<>³ª' => 'ð<>²ª',
<>³«' => 'ð<>²«',
<>³¬' => 'ð<>²¬',
<>³­' => 'ð<>²­',
<>³®' => 'ð<>²®',
<>³¯' => 'ð<>²¯',
<>³°' => 'ð<>²°',
<>³±' => 'ð<>²±',
<>³²' => 'ð<>²²',
'ð‘£€' => 'ð‘¢ ',
£<E28098>' => '𑢡',
'𑣂' => '𑢢',
'𑣃' => '𑢣',
'𑣄' => '𑢤',
'ð‘£…' => 'ð‘¢¥',
'𑣆' => '𑢦',
'𑣇' => '𑢧',
'𑣈' => '𑢨',
'𑣉' => '𑢩',
'𑣊' => '𑢪',
'𑣋' => '𑢫',
'𑣌' => '𑢬',
£<E28098>' => 'ð‘¢­',
'𑣎' => '𑢮',
£<E28098>' => '𑢯',
£<E28098>' => 'ð‘¢°',
'𑣑' => '𑢱',
'ð‘£’' => 'ð‘¢²',
'𑣓' => '𑢳',
'ð‘£”' => 'ð‘¢´',
'𑣕' => '𑢵',
'𑣖' => '𑢶',
'ð‘£—' => 'ð‘¢·',
'𑣘' => '𑢸',
'ð‘£™' => 'ð‘¢¹',
'𑣚' => '𑢺',
'ð‘£›' => 'ð‘¢»',
'𑣜' => '𑢼',
£<E28098>' => 'ð‘¢½',
'𑣞' => '𑢾',
'𑣟' => '𑢿',
'ð–¹ ' => 'ð–¹€',
'𖹡' => 'ð¹<E28093>',
'𖹢' => '𖹂',
'𖹣' => '𖹃',
'𖹤' => '𖹄',
'ð–¹¥' => 'ð–¹…',
'𖹦' => '𖹆',
'𖹧' => '𖹇',
'𖹨' => '𖹈',
'𖹩' => '𖹉',
'𖹪' => '𖹊',
'𖹫' => '𖹋',
'𖹬' => '𖹌',
'ð–¹­' => 'ð¹<E28093>',
'𖹮' => '𖹎',
'𖹯' => 'ð¹<E28093>',
'ð–¹°' => 'ð¹<E28093>',
'𖹱' => '𖹑',
'ð–¹²' => 'ð–¹’',
'𖹳' => '𖹓',
'ð–¹´' => 'ð–¹”',
'𖹵' => '𖹕',
'𖹶' => '𖹖',
'ð–¹·' => 'ð–¹—',
'𖹸' => '𖹘',
'ð–¹¹' => 'ð–¹™',
'𖹺' => '𖹚',
'ð–¹»' => 'ð–¹›',
'𖹼' => '𖹜',
'ð–¹½' => 'ð¹<E28093>',
'𖹾' => '𖹞',
'𖹿' => '𖹟',
'𞤢' => '𞤀',
'𞤣' => 'ðž¤<C5BE>',
'𞤤' => '𞤂',
'𞤥' => '𞤃',
'𞤦' => '𞤄',
'𞤧' => '𞤅',
'𞤨' => '𞤆',
'𞤩' => '𞤇',
'𞤪' => '𞤈',
'𞤫' => '𞤉',
'𞤬' => '𞤊',
'𞤭' => '𞤋',
'𞤮' => '𞤌',
'𞤯' => 'ðž¤<C5BE>',
'𞤰' => '𞤎',
'𞤱' => 'ðž¤<C5BE>',
'𞤲' => 'ðž¤<C5BE>',
'𞤳' => '𞤑',
'𞤴' => '𞤒',
'𞤵' => '𞤓',
'𞤶' => '𞤔',
'𞤷' => '𞤕',
'𞤸' => '𞤖',
'𞤹' => '𞤗',
'𞤺' => '𞤘',
'𞤻' => '𞤙',
'𞤼' => '𞤚',
'𞤽' => '𞤛',
'𞤾' => '𞤜',
'𞤿' => 'ðž¤<C5BE>',
'𞥀' => '𞤞',
'ðž¥<C5BE>' => '𞤟',
'𞥂' => '𞤠',
'𞥃' => '𞤡',
);
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Php80 as p;
if (\PHP_VERSION_ID >= 80000) {
return;
}
if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) {
define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN);
}
if (!function_exists('fdiv')) {
function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); }
}
if (!function_exists('preg_last_error_msg')) {
function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); }
}
if (!function_exists('str_contains')) {
function str_contains(string $haystack, string $needle): bool { return p\Php80::str_contains($haystack, $needle); }
}
if (!function_exists('str_starts_with')) {
function str_starts_with(string $haystack, string $needle): bool { return p\Php80::str_starts_with($haystack, $needle); }
}
if (!function_exists('str_ends_with')) {
function str_ends_with(string $haystack, string $needle): bool { return p\Php80::str_ends_with($haystack, $needle); }
}
if (!function_exists('get_debug_type')) {
function get_debug_type($value): string { return p\Php80::get_debug_type($value); }
}
if (!function_exists('get_resource_id')) {
function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); }
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Php80;
/**
* @author Ion Bazan <ion.bazan@gmail.com>
* @author Nico Oelgart <nicoswd@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Php80
{
public static function fdiv(float $dividend, float $divisor): float
{
return @($dividend / $divisor);
}
public static function get_debug_type($value): string
{
switch (true) {
case null === $value: return 'null';
case \is_bool($value): return 'bool';
case \is_string($value): return 'string';
case \is_array($value): return 'array';
case \is_int($value): return 'int';
case \is_float($value): return 'float';
case \is_object($value): break;
case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class';
default:
if (null === $type = @get_resource_type($value)) {
return 'unknown';
}
if ('Unknown' === $type) {
$type = 'closed';
}
return "resource ($type)";
}
$class = \get_class($value);
if (false === strpos($class, '@')) {
return $class;
}
return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous';
}
public static function get_resource_id($res): int
{
if (!\is_resource($res) && null === @get_resource_type($res)) {
throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res)));
}
return (int) $res;
}
public static function preg_last_error_msg(): string
{
switch (preg_last_error()) {
case \PREG_INTERNAL_ERROR:
return 'Internal error';
case \PREG_BAD_UTF8_ERROR:
return 'Malformed UTF-8 characters, possibly incorrectly encoded';
case \PREG_BAD_UTF8_OFFSET_ERROR:
return 'The offset did not correspond to the beginning of a valid UTF-8 code point';
case \PREG_BACKTRACK_LIMIT_ERROR:
return 'Backtrack limit exhausted';
case \PREG_RECURSION_LIMIT_ERROR:
return 'Recursion limit exhausted';
case \PREG_JIT_STACKLIMIT_ERROR:
return 'JIT stack limit exhausted';
case \PREG_NO_ERROR:
return 'No error';
default:
return 'Unknown error';
}
}
public static function str_contains(string $haystack, string $needle): bool
{
return '' === $needle || false !== strpos($haystack, $needle);
}
public static function str_starts_with(string $haystack, string $needle): bool
{
return 0 === strncmp($haystack, $needle, \strlen($needle));
}
public static function str_ends_with(string $haystack, string $needle): bool
{
return '' === $needle || ('' !== $haystack && 0 === substr_compare($haystack, $needle, -\strlen($needle)));
}
}
Copyright (c) 2020 Fabien Potencier
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.
<?php
class UnhandledMatchError extends Error
{
}
<?php
class ValueError extends Error
{
}
<?php
if (\PHP_VERSION_ID < 80000) {
interface Stringable
{
/**
* @return string
*/
public function __toString();
}
}
<?php
#[Attribute(Attribute::TARGET_CLASS)]
final class Attribute
{
public const TARGET_CLASS = 1;
public const TARGET_FUNCTION = 2;
public const TARGET_METHOD = 4;
public const TARGET_PROPERTY = 8;
public const TARGET_CLASS_CONSTANT = 16;
public const TARGET_PARAMETER = 32;
public const TARGET_ALL = 63;
public const IS_REPEATABLE = 64;
/** @var int */
public $flags;
public function __construct(int $flags = self::TARGET_ALL)
{
$this->flags = $flags;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Ctype;
/**
* Ctype implementation through regex.
*
* @internal
*
* @author Gert de Pagter <BackEndTea@gmail.com>
*/
final class Ctype
{
/**
* Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
*
* @see https://php.net/ctype-alnum
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_alnum($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
}
/**
* Returns TRUE if every character in text is a letter, FALSE otherwise.
*
* @see https://php.net/ctype-alpha
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_alpha($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
}
/**
* Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
*
* @see https://php.net/ctype-cntrl
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_cntrl($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
}
/**
* Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
*
* @see https://php.net/ctype-digit
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_digit($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
}
/**
* Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
*
* @see https://php.net/ctype-graph
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_graph($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
}
/**
* Returns TRUE if every character in text is a lowercase letter.
*
* @see https://php.net/ctype-lower
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_lower($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
}
/**
* Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
*
* @see https://php.net/ctype-print
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_print($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
}
/**
* Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
*
* @see https://php.net/ctype-punct
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_punct($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
}
/**
* Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
*
* @see https://php.net/ctype-space
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_space($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
}
/**
* Returns TRUE if every character in text is an uppercase letter.
*
* @see https://php.net/ctype-upper
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_upper($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
}
/**
* Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
*
* @see https://php.net/ctype-xdigit
*
* @param string|int $text
*
* @return bool
*/
public static function ctype_xdigit($text)
{
$text = self::convert_int_to_char_for_ctype($text);
return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
}
/**
* Converts integers to their char versions according to normal ctype behaviour, if needed.
*
* If an integer between -128 and 255 inclusive is provided,
* it is interpreted as the ASCII value of a single character
* (negative values have 256 added in order to allow characters in the Extended ASCII range).
* Any other integer is interpreted as a string containing the decimal digits of the integer.
*
* @param string|int $int
*
* @return mixed
*/
private static function convert_int_to_char_for_ctype($int)
{
if (!\is_int($int)) {
return $int;
}
if ($int < -128 || $int > 255) {
return (string) $int;
}
if ($int < 0) {
$int += 256;
}
return \chr($int);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Ctype as p;
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
if (!function_exists('ctype_alnum')) {
function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
}
if (!function_exists('ctype_alpha')) {
function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
}
if (!function_exists('ctype_cntrl')) {
function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
}
if (!function_exists('ctype_digit')) {
function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
}
if (!function_exists('ctype_graph')) {
function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
}
if (!function_exists('ctype_lower')) {
function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
}
if (!function_exists('ctype_print')) {
function ctype_print($text) { return p\Ctype::ctype_print($text); }
}
if (!function_exists('ctype_punct')) {
function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
}
if (!function_exists('ctype_space')) {
function ctype_space($text) { return p\Ctype::ctype_space($text); }
}
if (!function_exists('ctype_upper')) {
function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
}
if (!function_exists('ctype_xdigit')) {
function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Ctype as p;
if (!function_exists('ctype_alnum')) {
function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); }
}
if (!function_exists('ctype_alpha')) {
function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); }
}
if (!function_exists('ctype_cntrl')) {
function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); }
}
if (!function_exists('ctype_digit')) {
function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); }
}
if (!function_exists('ctype_graph')) {
function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); }
}
if (!function_exists('ctype_lower')) {
function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); }
}
if (!function_exists('ctype_print')) {
function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); }
}
if (!function_exists('ctype_punct')) {
function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); }
}
if (!function_exists('ctype_space')) {
function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); }
}
if (!function_exists('ctype_upper')) {
function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); }
}
if (!function_exists('ctype_xdigit')) {
function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); }
}
Copyright (c) 2018-2019 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
/**
* A ServiceSubscriber exposes its dependencies via the static {@link getSubscribedServices} method.
*
* The getSubscribedServices method returns an array of service types required by such instances,
* optionally keyed by the service names used internally. Service types that start with an interrogation
* mark "?" are optional, while the other ones are mandatory service dependencies.
*
* The injected service locators SHOULD NOT allow access to any other services not specified by the method.
*
* It is expected that ServiceSubscriber instances consume PSR-11-based service locators internally.
* This interface does not dictate any injection method for these service locators, although constructor
* injection is recommended.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface ServiceSubscriberInterface
{
/**
* Returns an array of service types required by such instances, optionally keyed by the service names used internally.
*
* For mandatory dependencies:
*
* * ['logger' => 'Psr\Log\LoggerInterface'] means the objects use the "logger" name
* internally to fetch a service which must implement Psr\Log\LoggerInterface.
* * ['loggers' => 'Psr\Log\LoggerInterface[]'] means the objects use the "loggers" name
* internally to fetch an iterable of Psr\Log\LoggerInterface instances.
* * ['Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => 'Psr\Log\LoggerInterface']
*
* otherwise:
*
* * ['logger' => '?Psr\Log\LoggerInterface'] denotes an optional dependency
* * ['loggers' => '?Psr\Log\LoggerInterface[]'] denotes an optional iterable dependency
* * ['?Psr\Log\LoggerInterface'] is a shortcut for
* * ['Psr\Log\LoggerInterface' => '?Psr\Log\LoggerInterface']
*
* @return string[] The required service types, optionally keyed by service names
*/
public static function getSubscribedServices();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
/**
* A ServiceProviderInterface exposes the identifiers and the types of services provided by a container.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Mateusz Sip <mateusz.sip@gmail.com>
*/
interface ServiceProviderInterface extends ContainerInterface
{
/**
* Returns an associative array of service types keyed by the identifiers provided by the current container.
*
* Examples:
*
* * ['logger' => 'Psr\Log\LoggerInterface'] means the object provides a service named "logger" that implements Psr\Log\LoggerInterface
* * ['foo' => '?'] means the container provides service name "foo" of unspecified type
* * ['bar' => '?Bar\Baz'] means the container provides a service "bar" of type Bar\Baz|null
*
* @return string[] The provided service types, keyed by service names
*/
public function getProvidedServices(): array;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service\Attribute;
/**
* A required dependency.
*
* This attribute indicates that a property holds a required dependency. The annotated property or method should be
* considered during the instantiation process of the containing class.
*
* @author Alexander M. Turek <me@derrabus.de>
*/
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::TARGET_PROPERTY)]
final class Required
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
/**
* Provides a way to reset an object to its initial state.
*
* When calling the "reset()" method on an object, it should be put back to its
* initial state. This usually means clearing any internal buffers and forwarding
* the call to internal dependencies. All properties of the object should be put
* back to the same state it had when it was first ready to use.
*
* This method could be called, for example, to recycle objects that are used as
* services, so that they can be used to handle several requests in the same
* process loop (note that we advise making your services stateless instead of
* implementing this interface when possible.)
*/
interface ResetInterface
{
public function reset();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service\Test;
use PHPUnit\Framework\TestCase;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceLocatorTrait;
abstract class ServiceLocatorTest extends TestCase
{
protected function getServiceLocator(array $factories)
{
return new class($factories) implements ContainerInterface {
use ServiceLocatorTrait;
};
}
public function testHas()
{
$locator = $this->getServiceLocator([
'foo' => function () { return 'bar'; },
'bar' => function () { return 'baz'; },
function () { return 'dummy'; },
]);
$this->assertTrue($locator->has('foo'));
$this->assertTrue($locator->has('bar'));
$this->assertFalse($locator->has('dummy'));
}
public function testGet()
{
$locator = $this->getServiceLocator([
'foo' => function () { return 'bar'; },
'bar' => function () { return 'baz'; },
]);
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame('baz', $locator->get('bar'));
}
public function testGetDoesNotMemoize()
{
$i = 0;
$locator = $this->getServiceLocator([
'foo' => function () use (&$i) {
++$i;
return 'bar';
},
]);
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame('bar', $locator->get('foo'));
$this->assertSame(2, $i);
}
public function testThrowsOnUndefinedInternalService()
{
if (!$this->getExpectedException()) {
$this->expectException(\Psr\Container\NotFoundExceptionInterface::class);
$this->expectExceptionMessage('The service "foo" has a dependency on a non-existent service "bar". This locator only knows about the "foo" service.');
}
$locator = $this->getServiceLocator([
'foo' => function () use (&$locator) { return $locator->get('bar'); },
]);
$locator->get('foo');
}
public function testThrowsOnCircularReference()
{
$this->expectException(\Psr\Container\ContainerExceptionInterface::class);
$this->expectExceptionMessage('Circular reference detected for service "bar", path: "bar -> baz -> bar".');
$locator = $this->getServiceLocator([
'foo' => function () use (&$locator) { return $locator->get('bar'); },
'bar' => function () use (&$locator) { return $locator->get('baz'); },
'baz' => function () use (&$locator) { return $locator->get('bar'); },
]);
$locator->get('foo');
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerInterface;
/**
* Implementation of ServiceSubscriberInterface that determines subscribed services from
* private method return types. Service ids are available as "ClassName::methodName".
*
* @author Kevin Bond <kevinbond@gmail.com>
*/
trait ServiceSubscriberTrait
{
/** @var ContainerInterface */
protected $container;
/**
* {@inheritdoc}
*/
public static function getSubscribedServices(): array
{
static $services;
if (null !== $services) {
return $services;
}
$services = \is_callable(['parent', __FUNCTION__]) ? parent::getSubscribedServices() : [];
foreach ((new \ReflectionClass(self::class))->getMethods() as $method) {
if ($method->isStatic() || $method->isAbstract() || $method->isGenerator() || $method->isInternal() || $method->getNumberOfRequiredParameters()) {
continue;
}
if (self::class === $method->getDeclaringClass()->name && ($returnType = $method->getReturnType()) && !$returnType->isBuiltin()) {
$services[self::class.'::'.$method->name] = '?'.($returnType instanceof \ReflectionNamedType ? $returnType->getName() : $returnType);
}
}
return $services;
}
/**
* @required
*/
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
if (\is_callable(['parent', __FUNCTION__])) {
return parent::setContainer($container);
}
return null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Service;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
// Help opcache.preload discover always-needed symbols
class_exists(ContainerExceptionInterface::class);
class_exists(NotFoundExceptionInterface::class);
/**
* A trait to help implement ServiceProviderInterface.
*
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*/
trait ServiceLocatorTrait
{
private $factories;
private $loading = [];
private $providedTypes;
/**
* @param callable[] $factories
*/
public function __construct(array $factories)
{
$this->factories = $factories;
}
/**
* {@inheritdoc}
*
* @return bool
*/
public function has(string $id)
{
return isset($this->factories[$id]);
}
/**
* {@inheritdoc}
*
* @return mixed
*/
public function get(string $id)
{
if (!isset($this->factories[$id])) {
throw $this->createNotFoundException($id);
}
if (isset($this->loading[$id])) {
$ids = array_values($this->loading);
$ids = \array_slice($this->loading, array_search($id, $ids));
$ids[] = $id;
throw $this->createCircularReferenceException($id, $ids);
}
$this->loading[$id] = $id;
try {
return $this->factories[$id]($this);
} finally {
unset($this->loading[$id]);
}
}
/**
* {@inheritdoc}
*/
public function getProvidedServices(): array
{
if (null === $this->providedTypes) {
$this->providedTypes = [];
foreach ($this->factories as $name => $factory) {
if (!\is_callable($factory)) {
$this->providedTypes[$name] = '?';
} else {
$type = (new \ReflectionFunction($factory))->getReturnType();
$this->providedTypes[$name] = $type ? ($type->allowsNull() ? '?' : '').($type instanceof \ReflectionNamedType ? $type->getName() : $type) : '?';
}
}
}
return $this->providedTypes;
}
private function createNotFoundException(string $id): NotFoundExceptionInterface
{
if (!$alternatives = array_keys($this->factories)) {
$message = 'is empty...';
} else {
$last = array_pop($alternatives);
if ($alternatives) {
$message = sprintf('only knows about the "%s" and "%s" services.', implode('", "', $alternatives), $last);
} else {
$message = sprintf('only knows about the "%s" service.', $last);
}
}
if ($this->loading) {
$message = sprintf('The service "%s" has a dependency on a non-existent service "%s". This locator %s', end($this->loading), $id, $message);
} else {
$message = sprintf('Service "%s" not found: the current service locator %s', $id, $message);
}
return new class($message) extends \InvalidArgumentException implements NotFoundExceptionInterface {
};
}
private function createCircularReferenceException(string $id, array $path): ContainerExceptionInterface
{
return new class(sprintf('Circular reference detected for service "%s", path: "%s".', $id, implode(' -> ', $path))) extends \RuntimeException implements ContainerExceptionInterface {
};
}
}
Copyright (c) 2018-2021 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
/**
* PipesInterface manages descriptors and pipes for the use of proc_open.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
interface PipesInterface
{
public const CHUNK_SIZE = 16384;
/**
* Returns an array of descriptors for the use of proc_open.
*/
public function getDescriptors(): array;
/**
* Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
*
* @return string[]
*/
public function getFiles(): array;
/**
* Reads data in file handles and pipes.
*
* @param bool $blocking Whether to use blocking calls or not
* @param bool $close Whether to close pipes if they've reached EOF
*
* @return string[] An array of read data indexed by their fd
*/
public function readAndWrite(bool $blocking, bool $close = false): array;
/**
* Returns if the current state has open file handles or pipes.
*/
public function areOpen(): bool;
/**
* Returns if pipes are able to read output.
*/
public function haveReadSupport(): bool;
/**
* Closes file handles and pipes.
*/
public function close();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Process;
/**
* UnixPipes implementation uses unix pipes as handles.
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class UnixPipes extends AbstractPipes
{
private $ttyMode;
private $ptyMode;
private $haveReadSupport;
public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
{
$this->ttyMode = $ttyMode;
$this->ptyMode = $ptyMode;
$this->haveReadSupport = $haveReadSupport;
parent::__construct($input);
}
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
$this->close();
}
/**
* {@inheritdoc}
*/
public function getDescriptors(): array
{
if (!$this->haveReadSupport) {
$nullstream = fopen('/dev/null', 'c');
return [
['pipe', 'r'],
$nullstream,
$nullstream,
];
}
if ($this->ttyMode) {
return [
['file', '/dev/tty', 'r'],
['file', '/dev/tty', 'w'],
['file', '/dev/tty', 'w'],
];
}
if ($this->ptyMode && Process::isPtySupported()) {
return [
['pty'],
['pty'],
['pty'],
];
}
return [
['pipe', 'r'],
['pipe', 'w'], // stdout
['pipe', 'w'], // stderr
];
}
/**
* {@inheritdoc}
*/
public function getFiles(): array
{
return [];
}
/**
* {@inheritdoc}
*/
public function readAndWrite(bool $blocking, bool $close = false): array
{
$this->unblock();
$w = $this->write();
$read = $e = [];
$r = $this->pipes;
unset($r[0]);
// let's have a look if something changed in streams
set_error_handler([$this, 'handleError']);
if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
restore_error_handler();
// if a system call has been interrupted, forget about it, let's try again
// otherwise, an error occurred, let's reset pipes
if (!$this->hasSystemCallBeenInterrupted()) {
$this->pipes = [];
}
return $read;
}
restore_error_handler();
foreach ($r as $pipe) {
// prior PHP 5.4 the array passed to stream_select is modified and
// lose key association, we have to find back the key
$read[$type = array_search($pipe, $this->pipes, true)] = '';
do {
$data = @fread($pipe, self::CHUNK_SIZE);
$read[$type] .= $data;
} while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
if (!isset($read[$type][0])) {
unset($read[$type]);
}
if ($close && feof($pipe)) {
fclose($pipe);
unset($this->pipes[$type]);
}
}
return $read;
}
/**
* {@inheritdoc}
*/
public function haveReadSupport(): bool
{
return $this->haveReadSupport;
}
/**
* {@inheritdoc}
*/
public function areOpen(): bool
{
return (bool) $this->pipes;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Process;
/**
* WindowsPipes implementation uses temporary files as handles.
*
* @see https://bugs.php.net/51800
* @see https://bugs.php.net/65650
*
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
class WindowsPipes extends AbstractPipes
{
private $files = [];
private $fileHandles = [];
private $lockHandles = [];
private $readBytes = [
Process::STDOUT => 0,
Process::STDERR => 0,
];
private $haveReadSupport;
public function __construct($input, bool $haveReadSupport)
{
$this->haveReadSupport = $haveReadSupport;
if ($this->haveReadSupport) {
// Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
// Workaround for this problem is to use temporary files instead of pipes on Windows platform.
//
// @see https://bugs.php.net/51800
$pipes = [
Process::STDOUT => Process::OUT,
Process::STDERR => Process::ERR,
];
$tmpDir = sys_get_temp_dir();
$lastError = 'unknown reason';
set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
for ($i = 0;; ++$i) {
foreach ($pipes as $pipe => $name) {
$file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
if (!$h = fopen($file.'.lock', 'w')) {
if (file_exists($file.'.lock')) {
continue 2;
}
restore_error_handler();
throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
}
if (!flock($h, \LOCK_EX | \LOCK_NB)) {
continue 2;
}
if (isset($this->lockHandles[$pipe])) {
flock($this->lockHandles[$pipe], \LOCK_UN);
fclose($this->lockHandles[$pipe]);
}
$this->lockHandles[$pipe] = $h;
if (!fclose(fopen($file, 'w')) || !$h = fopen($file, 'r')) {
flock($this->lockHandles[$pipe], \LOCK_UN);
fclose($this->lockHandles[$pipe]);
unset($this->lockHandles[$pipe]);
continue 2;
}
$this->fileHandles[$pipe] = $h;
$this->files[$pipe] = $file;
}
break;
}
restore_error_handler();
}
parent::__construct($input);
}
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
$this->close();
}
/**
* {@inheritdoc}
*/
public function getDescriptors(): array
{
if (!$this->haveReadSupport) {
$nullstream = fopen('NUL', 'c');
return [
['pipe', 'r'],
$nullstream,
$nullstream,
];
}
// We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
// We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
// So we redirect output within the commandline and pass the nul device to the process
return [
['pipe', 'r'],
['file', 'NUL', 'w'],
['file', 'NUL', 'w'],
];
}
/**
* {@inheritdoc}
*/
public function getFiles(): array
{
return $this->files;
}
/**
* {@inheritdoc}
*/
public function readAndWrite(bool $blocking, bool $close = false): array
{
$this->unblock();
$w = $this->write();
$read = $r = $e = [];
if ($blocking) {
if ($w) {
@stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
} elseif ($this->fileHandles) {
usleep(Process::TIMEOUT_PRECISION * 1E6);
}
}
foreach ($this->fileHandles as $type => $fileHandle) {
$data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
if (isset($data[0])) {
$this->readBytes[$type] += \strlen($data);
$read[$type] = $data;
}
if ($close) {
ftruncate($fileHandle, 0);
fclose($fileHandle);
flock($this->lockHandles[$type], \LOCK_UN);
fclose($this->lockHandles[$type]);
unset($this->fileHandles[$type], $this->lockHandles[$type]);
}
}
return $read;
}
/**
* {@inheritdoc}
*/
public function haveReadSupport(): bool
{
return $this->haveReadSupport;
}
/**
* {@inheritdoc}
*/
public function areOpen(): bool
{
return $this->pipes && $this->fileHandles;
}
/**
* {@inheritdoc}
*/
public function close()
{
parent::close();
foreach ($this->fileHandles as $type => $handle) {
ftruncate($handle, 0);
fclose($handle);
flock($this->lockHandles[$type], \LOCK_UN);
fclose($this->lockHandles[$type]);
}
$this->fileHandles = $this->lockHandles = [];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Pipes;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* @author Romain Neutron <imprec@gmail.com>
*
* @internal
*/
abstract class AbstractPipes implements PipesInterface
{
public $pipes = [];
private $inputBuffer = '';
private $input;
private $blocked = true;
private $lastError;
/**
* @param resource|string|int|float|bool|\Iterator|null $input
*/
public function __construct($input)
{
if (\is_resource($input) || $input instanceof \Iterator) {
$this->input = $input;
} elseif (\is_string($input)) {
$this->inputBuffer = $input;
} else {
$this->inputBuffer = (string) $input;
}
}
/**
* {@inheritdoc}
*/
public function close()
{
foreach ($this->pipes as $pipe) {
fclose($pipe);
}
$this->pipes = [];
}
/**
* Returns true if a system call has been interrupted.
*/
protected function hasSystemCallBeenInterrupted(): bool
{
$lastError = $this->lastError;
$this->lastError = null;
// stream_select returns false when the `select` system call is interrupted by an incoming signal
return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
}
/**
* Unblocks streams.
*/
protected function unblock()
{
if (!$this->blocked) {
return;
}
foreach ($this->pipes as $pipe) {
stream_set_blocking($pipe, 0);
}
if (\is_resource($this->input)) {
stream_set_blocking($this->input, 0);
}
$this->blocked = false;
}
/**
* Writes input to stdin.
*
* @throws InvalidArgumentException When an input iterator yields a non supported value
*/
protected function write(): ?array
{
if (!isset($this->pipes[0])) {
return null;
}
$input = $this->input;
if ($input instanceof \Iterator) {
if (!$input->valid()) {
$input = null;
} elseif (\is_resource($input = $input->current())) {
stream_set_blocking($input, 0);
} elseif (!isset($this->inputBuffer[0])) {
if (!\is_string($input)) {
if (!is_scalar($input)) {
throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
}
$input = (string) $input;
}
$this->inputBuffer = $input;
$this->input->next();
$input = null;
} else {
$input = null;
}
}
$r = $e = [];
$w = [$this->pipes[0]];
// let's have a look if something changed in streams
if (false === @stream_select($r, $w, $e, 0, 0)) {
return null;
}
foreach ($w as $stdin) {
if (isset($this->inputBuffer[0])) {
$written = fwrite($stdin, $this->inputBuffer);
$this->inputBuffer = substr($this->inputBuffer, $written);
if (isset($this->inputBuffer[0])) {
return [$this->pipes[0]];
}
}
if ($input) {
for (;;) {
$data = fread($input, self::CHUNK_SIZE);
if (!isset($data[0])) {
break;
}
$written = fwrite($stdin, $data);
$data = substr($data, $written);
if (isset($data[0])) {
$this->inputBuffer = $data;
return [$this->pipes[0]];
}
}
if (feof($input)) {
if ($this->input instanceof \Iterator) {
$this->input->next();
} else {
$this->input = null;
}
}
}
}
// no input to read on resource, buffer is empty
if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
$this->input = null;
fclose($this->pipes[0]);
unset($this->pipes[0]);
} elseif (!$w) {
return [$this->pipes[0]];
}
return null;
}
/**
* @internal
*/
public function handleError($type, $msg)
{
$this->lastError = $msg;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* PhpProcess runs a PHP script in an independent process.
*
* $p = new PhpProcess('<?php echo "foo"; ?>');
* $p->run();
* print $p->getOutput()."\n";
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PhpProcess extends Process
{
/**
* @param string $script The PHP script to run (as a string)
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param int $timeout The timeout in seconds
* @param array|null $php Path to the PHP binary to use with any additional arguments
*/
public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
{
if (null === $php) {
$executableFinder = new PhpExecutableFinder();
$php = $executableFinder->find(false);
$php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
}
if ('phpdbg' === \PHP_SAPI) {
$file = tempnam(sys_get_temp_dir(), 'dbg');
file_put_contents($file, $script);
register_shutdown_function('unlink', $file);
$php[] = $file;
$script = null;
}
parent::__construct($php, $cwd, $env, $script, $timeout);
}
/**
* {@inheritdoc}
*/
public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
}
/**
* {@inheritdoc}
*/
public function start(callable $callback = null, array $env = [])
{
if (null === $this->getCommandLine()) {
throw new RuntimeException('Unable to find the PHP executable.');
}
parent::start($callback, $env);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* Provides a way to continuously write to the input of a Process until the InputStream is closed.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class InputStream implements \IteratorAggregate
{
/** @var callable|null */
private $onEmpty = null;
private $input = [];
private $open = true;
/**
* Sets a callback that is called when the write buffer becomes empty.
*/
public function onEmpty(callable $onEmpty = null)
{
$this->onEmpty = $onEmpty;
}
/**
* Appends an input to the write buffer.
*
* @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
* stream resource or \Traversable
*/
public function write($input)
{
if (null === $input) {
return;
}
if ($this->isClosed()) {
throw new RuntimeException(sprintf('"%s" is closed.', static::class));
}
$this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
}
/**
* Closes the write buffer.
*/
public function close()
{
$this->open = false;
}
/**
* Tells whether the write buffer is closed or not.
*/
public function isClosed()
{
return !$this->open;
}
/**
* @return \Traversable
*/
public function getIterator()
{
$this->open = true;
while ($this->open || $this->input) {
if (!$this->input) {
yield '';
continue;
}
$current = array_shift($this->input);
if ($current instanceof \Iterator) {
yield from $current;
} else {
yield $current;
}
if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
$this->write($onEmpty($this));
}
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* An executable finder specifically designed for the PHP executable.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class PhpExecutableFinder
{
private $executableFinder;
public function __construct()
{
$this->executableFinder = new ExecutableFinder();
}
/**
* Finds The PHP executable.
*
* @return string|false The PHP executable path or false if it cannot be found
*/
public function find(bool $includeArgs = true)
{
if ($php = getenv('PHP_BINARY')) {
if (!is_executable($php)) {
$command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) {
if (!is_executable($php)) {
return false;
}
} else {
return false;
}
}
return $php;
}
$args = $this->findArguments();
$args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
// PHP_BINARY return the current sapi executable
if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cgi-fcgi', 'cli', 'cli-server', 'phpdbg'], true)) {
return \PHP_BINARY.$args;
}
if ($php = getenv('PHP_PATH')) {
if (!@is_executable($php)) {
return false;
}
return $php;
}
if ($php = getenv('PHP_PEAR_PHP_BIN')) {
if (@is_executable($php)) {
return $php;
}
}
if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php'))) {
return $php;
}
$dirs = [\PHP_BINDIR];
if ('\\' === \DIRECTORY_SEPARATOR) {
$dirs[] = 'C:\xampp\php\\';
}
return $this->executableFinder->find('php', false, $dirs);
}
/**
* Finds the PHP executable arguments.
*
* @return array The PHP executable arguments
*/
public function findArguments()
{
$arguments = [];
if ('phpdbg' === \PHP_SAPI) {
$arguments[] = '-qrr';
}
return $arguments;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* Marker Interface for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception that is thrown when a process has been signaled.
*
* @author Sullivan Senechal <soullivaneuh@gmail.com>
*/
final class ProcessSignaledException extends RuntimeException
{
private $process;
public function __construct(Process $process)
{
$this->process = $process;
parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
}
public function getProcess(): Process
{
return $this->process;
}
public function getSignal(): int
{
return $this->getProcess()->getTermSignal();
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* LogicException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception for failed processes.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessFailedException extends RuntimeException
{
private $process;
public function __construct(Process $process)
{
if ($process->isSuccessful()) {
throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
}
$error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
$process->getCommandLine(),
$process->getExitCode(),
$process->getExitCodeText(),
$process->getWorkingDirectory()
);
if (!$process->isOutputDisabled()) {
$error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
$process->getOutput(),
$process->getErrorOutput()
);
}
parent::__construct($error);
$this->process = $process;
}
public function getProcess()
{
return $this->process;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
use Symfony\Component\Process\Process;
/**
* Exception that is thrown when a process times out.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ProcessTimedOutException extends RuntimeException
{
public const TYPE_GENERAL = 1;
public const TYPE_IDLE = 2;
private $process;
private $timeoutType;
public function __construct(Process $process, int $timeoutType)
{
$this->process = $process;
$this->timeoutType = $timeoutType;
parent::__construct(sprintf(
'The process "%s" exceeded the timeout of %s seconds.',
$process->getCommandLine(),
$this->getExceededTimeout()
));
}
public function getProcess()
{
return $this->process;
}
public function isGeneralTimeout()
{
return self::TYPE_GENERAL === $this->timeoutType;
}
public function isIdleTimeout()
{
return self::TYPE_IDLE === $this->timeoutType;
}
public function getExceededTimeout()
{
switch ($this->timeoutType) {
case self::TYPE_GENERAL:
return $this->process->getTimeout();
case self::TYPE_IDLE:
return $this->process->getIdleTimeout();
default:
throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* RuntimeException for the Process Component.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process\Exception;
/**
* InvalidArgumentException for the Process Component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
Copyright (c) 2004-2021 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
/**
* ProcessUtils is a bunch of utility methods.
*
* This class contains static methods only and is not meant to be instantiated.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
class ProcessUtils
{
/**
* This class should not be instantiated.
*/
private function __construct()
{
}
/**
* Validates and normalizes a Process input.
*
* @param string $caller The name of method call that validates the input
* @param mixed $input The input to validate
*
* @return mixed The validated input
*
* @throws InvalidArgumentException In case the input is not valid
*/
public static function validateInput(string $caller, $input)
{
if (null !== $input) {
if (\is_resource($input)) {
return $input;
}
if (\is_string($input)) {
return $input;
}
if (is_scalar($input)) {
return (string) $input;
}
if ($input instanceof Process) {
return $input->getIterator($input::ITER_SKIP_ERR);
}
if ($input instanceof \Iterator) {
return $input;
}
if ($input instanceof \Traversable) {
return new \IteratorIterator($input);
}
throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
}
return $input;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
use Symfony\Component\Process\Exception\InvalidArgumentException;
use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessFailedException;
use Symfony\Component\Process\Exception\ProcessSignaledException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Pipes\UnixPipes;
use Symfony\Component\Process\Pipes\WindowsPipes;
/**
* Process is a thin wrapper around proc_* functions to easily
* start independent PHP processes.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Romain Neutron <imprec@gmail.com>
*/
class Process implements \IteratorAggregate
{
public const ERR = 'err';
public const OUT = 'out';
public const STATUS_READY = 'ready';
public const STATUS_STARTED = 'started';
public const STATUS_TERMINATED = 'terminated';
public const STDIN = 0;
public const STDOUT = 1;
public const STDERR = 2;
// Timeout Precision in seconds.
public const TIMEOUT_PRECISION = 0.2;
public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
private $callback;
private $hasCallback = false;
private $commandline;
private $cwd;
private $env;
private $input;
private $starttime;
private $lastOutputTime;
private $timeout;
private $idleTimeout;
private $exitcode;
private $fallbackStatus = [];
private $processInformation;
private $outputDisabled = false;
private $stdout;
private $stderr;
private $process;
private $status = self::STATUS_READY;
private $incrementalOutputOffset = 0;
private $incrementalErrorOutputOffset = 0;
private $tty = false;
private $pty;
private $options = ['suppress_errors' => true, 'bypass_shell' => true];
private $useFileHandles = false;
/** @var PipesInterface */
private $processPipes;
private $latestSignal;
private static $sigchild;
/**
* Exit codes translation table.
*
* User-defined errors must use exit codes in the 64-113 range.
*/
public static $exitCodes = [
0 => 'OK',
1 => 'General error',
2 => 'Misuse of shell builtins',
126 => 'Invoked command cannot execute',
127 => 'Command not found',
128 => 'Invalid exit argument',
// signals
129 => 'Hangup',
130 => 'Interrupt',
131 => 'Quit and dump core',
132 => 'Illegal instruction',
133 => 'Trace/breakpoint trap',
134 => 'Process aborted',
135 => 'Bus error: "access to undefined portion of memory object"',
136 => 'Floating point exception: "erroneous arithmetic operation"',
137 => 'Kill (terminate immediately)',
138 => 'User-defined 1',
139 => 'Segmentation violation',
140 => 'User-defined 2',
141 => 'Write to pipe with no one reading',
142 => 'Signal raised by alarm',
143 => 'Termination (request to terminate)',
// 144 - not defined
145 => 'Child process terminated, stopped (or continued*)',
146 => 'Continue if stopped',
147 => 'Stop executing temporarily',
148 => 'Terminal stop signal',
149 => 'Background process attempting to read from tty ("in")',
150 => 'Background process attempting to write to tty ("out")',
151 => 'Urgent data available on socket',
152 => 'CPU time limit exceeded',
153 => 'File size limit exceeded',
154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
155 => 'Profiling timer expired',
// 156 - not defined
157 => 'Pollable event',
// 158 - not defined
159 => 'Bad syscall',
];
/**
* @param array $command The command to run and its arguments listed as separate entries
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
*
* @throws LogicException When proc_open is not installed
*/
public function __construct(array $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
if (!\function_exists('proc_open')) {
throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
}
$this->commandline = $command;
$this->cwd = $cwd;
// on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
// on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
// @see : https://bugs.php.net/51800
// @see : https://bugs.php.net/50524
if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
$this->cwd = getcwd();
}
if (null !== $env) {
$this->setEnv($env);
}
$this->setInput($input);
$this->setTimeout($timeout);
$this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
$this->pty = false;
}
/**
* Creates a Process instance as a command-line to be run in a shell wrapper.
*
* Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
* This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
* shell wrapper and not to your commands.
*
* In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
* This will save escaping values, which is not portable nor secure anyway:
*
* $process = Process::fromShellCommandline('my_command "$MY_VAR"');
* $process->run(null, ['MY_VAR' => $theValue]);
*
* @param string $command The command line to pass to the shell of the OS
* @param string|null $cwd The working directory or null to use the working dir of the current PHP process
* @param array|null $env The environment variables or null to use the same environment as the current PHP process
* @param mixed|null $input The input as stream resource, scalar or \Traversable, or null for no input
* @param int|float|null $timeout The timeout in seconds or null to disable
*
* @return static
*
* @throws LogicException When proc_open is not installed
*/
public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
{
$process = new static([], $cwd, $env, $input, $timeout);
$process->commandline = $command;
return $process;
}
public function __sleep()
{
throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
}
public function __wakeup()
{
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
public function __destruct()
{
if ($this->options['create_new_console'] ?? false) {
$this->processPipes->close();
} else {
$this->stop(0);
}
}
public function __clone()
{
$this->resetProcessData();
}
/**
* Runs the process.
*
* The callback receives the type of output (out or err) and
* some bytes from the output in real-time. It allows to have feedback
* from the independent process during execution.
*
* The STDOUT and STDERR are also available after the process is finished
* via the getOutput() and getErrorOutput() methods.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return int The exit status code
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException In case a callback is provided and output has been disabled
*
* @final
*/
public function run(callable $callback = null, array $env = []): int
{
$this->start($callback, $env);
return $this->wait();
}
/**
* Runs the process.
*
* This is identical to run() except that an exception is thrown if the process
* exits with a non-zero exit code.
*
* @return $this
*
* @throws ProcessFailedException if the process didn't terminate successfully
*
* @final
*/
public function mustRun(callable $callback = null, array $env = []): self
{
if (0 !== $this->run($callback, $env)) {
throw new ProcessFailedException($this);
}
return $this;
}
/**
* Starts the process and returns after writing the input to STDIN.
*
* This method blocks until all STDIN data is sent to the process then it
* returns while the process runs in the background.
*
* The termination of the process can be awaited with wait().
*
* The callback receives the type of output (out or err) and some bytes from
* the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
* @throws LogicException In case a callback is provided and output has been disabled
*/
public function start(callable $callback = null, array $env = [])
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running.');
}
$this->resetProcessData();
$this->starttime = $this->lastOutputTime = microtime(true);
$this->callback = $this->buildCallback($callback);
$this->hasCallback = null !== $callback;
$descriptors = $this->getDescriptors();
if ($this->env) {
$env += $this->env;
}
$env += $this->getDefaultEnv();
if (\is_array($commandline = $this->commandline)) {
$commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
if ('\\' !== \DIRECTORY_SEPARATOR) {
// exec is mandatory to deal with sending a signal to the process
$commandline = 'exec '.$commandline;
}
} else {
$commandline = $this->replacePlaceholders($commandline, $env);
}
if ('\\' === \DIRECTORY_SEPARATOR) {
$commandline = $this->prepareWindowsCommandLine($commandline, $env);
} elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
$descriptors[3] = ['pipe', 'w'];
// See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
$commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
$commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
// Workaround for the bug, when PTS functionality is enabled.
// @see : https://bugs.php.net/69442
$ptsWorkaround = fopen(__FILE__, 'r');
}
$envPairs = [];
foreach ($env as $k => $v) {
if (false !== $v) {
$envPairs[] = $k.'='.$v;
}
}
if (!is_dir($this->cwd)) {
throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
}
$this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
if (!\is_resource($this->process)) {
throw new RuntimeException('Unable to launch a new process.');
}
$this->status = self::STATUS_STARTED;
if (isset($descriptors[3])) {
$this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
}
if ($this->tty) {
return;
}
$this->updateStatus(false);
$this->checkTimeout();
}
/**
* Restarts the process.
*
* Be warned that the process is cloned before being started.
*
* @param callable|null $callback A PHP callback to run whenever there is some
* output available on STDOUT or STDERR
*
* @return static
*
* @throws RuntimeException When process can't be launched
* @throws RuntimeException When process is already running
*
* @see start()
*
* @final
*/
public function restart(callable $callback = null, array $env = []): self
{
if ($this->isRunning()) {
throw new RuntimeException('Process is already running.');
}
$process = clone $this;
$process->start($callback, $env);
return $process;
}
/**
* Waits for the process to terminate.
*
* The callback receives the type of output (out or err) and some bytes
* from the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable|null $callback A valid PHP callback
*
* @return int The exitcode of the process
*
* @throws ProcessTimedOutException When process timed out
* @throws ProcessSignaledException When process stopped after receiving signal
* @throws LogicException When process is not yet started
*/
public function wait(callable $callback = null)
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (null !== $callback) {
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".');
}
$this->callback = $this->buildCallback($callback);
}
do {
$this->checkTimeout();
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
} while ($running);
while ($this->isRunning()) {
$this->checkTimeout();
usleep(1000);
}
if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
throw new ProcessSignaledException($this);
}
return $this->exitcode;
}
/**
* Waits until the callback returns true.
*
* The callback receives the type of output (out or err) and some bytes
* from the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @throws RuntimeException When process timed out
* @throws LogicException When process is not yet started
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function waitUntil(callable $callback): bool
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
}
$callback = $this->buildCallback($callback);
$ready = false;
while (true) {
$this->checkTimeout();
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
foreach ($output as $type => $data) {
if (3 !== $type) {
$ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
if ($ready) {
return true;
}
if (!$running) {
return false;
}
usleep(1000);
}
}
/**
* Returns the Pid (process identifier), if applicable.
*
* @return int|null The process id if running, null otherwise
*/
public function getPid()
{
return $this->isRunning() ? $this->processInformation['pid'] : null;
}
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
*
* @return $this
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
public function signal(int $signal)
{
$this->doSignal($signal, true);
return $this;
}
/**
* Disables fetching output and error output from the underlying process.
*
* @return $this
*
* @throws RuntimeException In case the process is already running
* @throws LogicException if an idle timeout is set
*/
public function disableOutput()
{
if ($this->isRunning()) {
throw new RuntimeException('Disabling output while the process is running is not possible.');
}
if (null !== $this->idleTimeout) {
throw new LogicException('Output can not be disabled while an idle timeout is set.');
}
$this->outputDisabled = true;
return $this;
}
/**
* Enables fetching output and error output from the underlying process.
*
* @return $this
*
* @throws RuntimeException In case the process is already running
*/
public function enableOutput()
{
if ($this->isRunning()) {
throw new RuntimeException('Enabling output while the process is running is not possible.');
}
$this->outputDisabled = false;
return $this;
}
/**
* Returns true in case the output is disabled, false otherwise.
*
* @return bool
*/
public function isOutputDisabled()
{
return $this->outputDisabled;
}
/**
* Returns the current output of the process (STDOUT).
*
* @return string The process output
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getOutput()
{
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
return '';
}
return $ret;
}
/**
* Returns the output incrementally.
*
* In comparison with the getOutput method which always return the whole
* output, this one returns the new output since the last call.
*
* @return string The process output since the last call
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getIncrementalOutput()
{
$this->readPipesForOutput(__FUNCTION__);
$latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
$this->incrementalOutputOffset = ftell($this->stdout);
if (false === $latest) {
return '';
}
return $latest;
}
/**
* Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
*
* @param int $flags A bit field of Process::ITER_* flags
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*
* @return \Generator
*/
public function getIterator(int $flags = 0)
{
$this->readPipesForOutput(__FUNCTION__, false);
$clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
$blocking = !(self::ITER_NON_BLOCKING & $flags);
$yieldOut = !(self::ITER_SKIP_OUT & $flags);
$yieldErr = !(self::ITER_SKIP_ERR & $flags);
while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
if ($yieldOut) {
$out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
if (isset($out[0])) {
if ($clearOutput) {
$this->clearOutput();
} else {
$this->incrementalOutputOffset = ftell($this->stdout);
}
yield self::OUT => $out;
}
}
if ($yieldErr) {
$err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
if (isset($err[0])) {
if ($clearOutput) {
$this->clearErrorOutput();
} else {
$this->incrementalErrorOutputOffset = ftell($this->stderr);
}
yield self::ERR => $err;
}
}
if (!$blocking && !isset($out[0]) && !isset($err[0])) {
yield self::OUT => '';
}
$this->checkTimeout();
$this->readPipesForOutput(__FUNCTION__, $blocking);
}
}
/**
* Clears the process output.
*
* @return $this
*/
public function clearOutput()
{
ftruncate($this->stdout, 0);
fseek($this->stdout, 0);
$this->incrementalOutputOffset = 0;
return $this;
}
/**
* Returns the current error output of the process (STDERR).
*
* @return string The process error output
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getErrorOutput()
{
$this->readPipesForOutput(__FUNCTION__);
if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
return '';
}
return $ret;
}
/**
* Returns the errorOutput incrementally.
*
* In comparison with the getErrorOutput method which always return the
* whole error output, this one returns the new error output since the last
* call.
*
* @return string The process error output since the last call
*
* @throws LogicException in case the output has been disabled
* @throws LogicException In case the process is not started
*/
public function getIncrementalErrorOutput()
{
$this->readPipesForOutput(__FUNCTION__);
$latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
$this->incrementalErrorOutputOffset = ftell($this->stderr);
if (false === $latest) {
return '';
}
return $latest;
}
/**
* Clears the process output.
*
* @return $this
*/
public function clearErrorOutput()
{
ftruncate($this->stderr, 0);
fseek($this->stderr, 0);
$this->incrementalErrorOutputOffset = 0;
return $this;
}
/**
* Returns the exit code returned by the process.
*
* @return int|null The exit status code, null if the Process is not terminated
*/
public function getExitCode()
{
$this->updateStatus(false);
return $this->exitcode;
}
/**
* Returns a string representation for the exit code returned by the process.
*
* This method relies on the Unix exit code status standardization
* and might not be relevant for other operating systems.
*
* @return string|null A string representation for the exit status code, null if the Process is not terminated
*
* @see http://tldp.org/LDP/abs/html/exitcodes.html
* @see http://en.wikipedia.org/wiki/Unix_signal
*/
public function getExitCodeText()
{
if (null === $exitcode = $this->getExitCode()) {
return null;
}
return self::$exitCodes[$exitcode] ?? 'Unknown error';
}
/**
* Checks if the process ended successfully.
*
* @return bool true if the process ended successfully, false otherwise
*/
public function isSuccessful()
{
return 0 === $this->getExitCode();
}
/**
* Returns true if the child process has been terminated by an uncaught signal.
*
* It always returns false on Windows.
*
* @return bool
*
* @throws LogicException In case the process is not terminated
*/
public function hasBeenSignaled()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['signaled'];
}
/**
* Returns the number of the signal that caused the child process to terminate its execution.
*
* It is only meaningful if hasBeenSignaled() returns true.
*
* @return int
*
* @throws RuntimeException In case --enable-sigchild is activated
* @throws LogicException In case the process is not terminated
*/
public function getTermSignal()
{
$this->requireProcessIsTerminated(__FUNCTION__);
if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
}
return $this->processInformation['termsig'];
}
/**
* Returns true if the child process has been stopped by a signal.
*
* It always returns false on Windows.
*
* @return bool
*
* @throws LogicException In case the process is not terminated
*/
public function hasBeenStopped()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['stopped'];
}
/**
* Returns the number of the signal that caused the child process to stop its execution.
*
* It is only meaningful if hasBeenStopped() returns true.
*
* @return int
*
* @throws LogicException In case the process is not terminated
*/
public function getStopSignal()
{
$this->requireProcessIsTerminated(__FUNCTION__);
return $this->processInformation['stopsig'];
}
/**
* Checks if the process is currently running.
*
* @return bool true if the process is currently running, false otherwise
*/
public function isRunning()
{
if (self::STATUS_STARTED !== $this->status) {
return false;
}
$this->updateStatus(false);
return $this->processInformation['running'];
}
/**
* Checks if the process has been started with no regard to the current state.
*
* @return bool true if status is ready, false otherwise
*/
public function isStarted()
{
return self::STATUS_READY != $this->status;
}
/**
* Checks if the process is terminated.
*
* @return bool true if process is terminated, false otherwise
*/
public function isTerminated()
{
$this->updateStatus(false);
return self::STATUS_TERMINATED == $this->status;
}
/**
* Gets the process status.
*
* The status is one of: ready, started, terminated.
*
* @return string The current process status
*/
public function getStatus()
{
$this->updateStatus(false);
return $this->status;
}
/**
* Stops the process.
*
* @param int|float $timeout The timeout in seconds
* @param int $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
*
* @return int|null The exit-code of the process or null if it's not running
*/
public function stop(float $timeout = 10, int $signal = null)
{
$timeoutMicro = microtime(true) + $timeout;
if ($this->isRunning()) {
// given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
$this->doSignal(15, false);
do {
usleep(1000);
} while ($this->isRunning() && microtime(true) < $timeoutMicro);
if ($this->isRunning()) {
// Avoid exception here: process is supposed to be running, but it might have stopped just
// after this line. In any case, let's silently discard the error, we cannot do anything.
$this->doSignal($signal ?: 9, false);
}
}
if ($this->isRunning()) {
if (isset($this->fallbackStatus['pid'])) {
unset($this->fallbackStatus['pid']);
return $this->stop(0, $signal);
}
$this->close();
}
return $this->exitcode;
}
/**
* Adds a line to the STDOUT stream.
*
* @internal
*/
public function addOutput(string $line)
{
$this->lastOutputTime = microtime(true);
fseek($this->stdout, 0, \SEEK_END);
fwrite($this->stdout, $line);
fseek($this->stdout, $this->incrementalOutputOffset);
}
/**
* Adds a line to the STDERR stream.
*
* @internal
*/
public function addErrorOutput(string $line)
{
$this->lastOutputTime = microtime(true);
fseek($this->stderr, 0, \SEEK_END);
fwrite($this->stderr, $line);
fseek($this->stderr, $this->incrementalErrorOutputOffset);
}
/**
* Gets the last output time in seconds.
*
* @return float|null The last output time in seconds or null if it isn't started
*/
public function getLastOutputTime(): ?float
{
return $this->lastOutputTime;
}
/**
* Gets the command line to be executed.
*
* @return string The command to execute
*/
public function getCommandLine()
{
return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
}
/**
* Gets the process timeout (max. runtime).
*
* @return float|null The timeout in seconds or null if it's disabled
*/
public function getTimeout()
{
return $this->timeout;
}
/**
* Gets the process idle timeout (max. time since last output).
*
* @return float|null The timeout in seconds or null if it's disabled
*/
public function getIdleTimeout()
{
return $this->idleTimeout;
}
/**
* Sets the process timeout (max. runtime) in seconds.
*
* To disable the timeout, set this value to null.
*
* @return $this
*
* @throws InvalidArgumentException if the timeout is negative
*/
public function setTimeout(?float $timeout)
{
$this->timeout = $this->validateTimeout($timeout);
return $this;
}
/**
* Sets the process idle timeout (max. time since last output) in seconds.
*
* To disable the timeout, set this value to null.
*
* @return $this
*
* @throws LogicException if the output is disabled
* @throws InvalidArgumentException if the timeout is negative
*/
public function setIdleTimeout(?float $timeout)
{
if (null !== $timeout && $this->outputDisabled) {
throw new LogicException('Idle timeout can not be set while the output is disabled.');
}
$this->idleTimeout = $this->validateTimeout($timeout);
return $this;
}
/**
* Enables or disables the TTY mode.
*
* @return $this
*
* @throws RuntimeException In case the TTY mode is not supported
*/
public function setTty(bool $tty)
{
if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
throw new RuntimeException('TTY mode is not supported on Windows platform.');
}
if ($tty && !self::isTtySupported()) {
throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
}
$this->tty = $tty;
return $this;
}
/**
* Checks if the TTY mode is enabled.
*
* @return bool true if the TTY mode is enabled, false otherwise
*/
public function isTty()
{
return $this->tty;
}
/**
* Sets PTY mode.
*
* @return $this
*/
public function setPty(bool $bool)
{
$this->pty = $bool;
return $this;
}
/**
* Returns PTY state.
*
* @return bool
*/
public function isPty()
{
return $this->pty;
}
/**
* Gets the working directory.
*
* @return string|null The current working directory or null on failure
*/
public function getWorkingDirectory()
{
if (null === $this->cwd) {
// getcwd() will return false if any one of the parent directories does not have
// the readable or search mode set, even if the current directory does
return getcwd() ?: null;
}
return $this->cwd;
}
/**
* Sets the current working directory.
*
* @return $this
*/
public function setWorkingDirectory(string $cwd)
{
$this->cwd = $cwd;
return $this;
}
/**
* Gets the environment variables.
*
* @return array The current environment variables
*/
public function getEnv()
{
return $this->env;
}
/**
* Sets the environment variables.
*
* Each environment variable value should be a string.
* If it is an array, the variable is ignored.
* If it is false or null, it will be removed when
* env vars are otherwise inherited.
*
* That happens in PHP when 'argv' is registered into
* the $_ENV array for instance.
*
* @param array $env The new environment variables
*
* @return $this
*/
public function setEnv(array $env)
{
// Process can not handle env values that are arrays
$env = array_filter($env, function ($value) {
return !\is_array($value);
});
$this->env = $env;
return $this;
}
/**
* Gets the Process input.
*
* @return resource|string|\Iterator|null The Process input
*/
public function getInput()
{
return $this->input;
}
/**
* Sets the input.
*
* This content will be passed to the underlying process standard input.
*
* @param string|int|float|bool|resource|\Traversable|null $input The content
*
* @return $this
*
* @throws LogicException In case the process is running
*/
public function setInput($input)
{
if ($this->isRunning()) {
throw new LogicException('Input can not be set while the process is running.');
}
$this->input = ProcessUtils::validateInput(__METHOD__, $input);
return $this;
}
/**
* Performs a check between the timeout definition and the time the process started.
*
* In case you run a background process (with the start method), you should
* trigger this method regularly to ensure the process timeout
*
* @throws ProcessTimedOutException In case the timeout was reached
*/
public function checkTimeout()
{
if (self::STATUS_STARTED !== $this->status) {
return;
}
if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
$this->stop(0);
throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
}
if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
$this->stop(0);
throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
}
}
/**
* @throws LogicException in case process is not started
*/
public function getStartTime(): float
{
if (!$this->isStarted()) {
throw new LogicException('Start time is only available after process start.');
}
return $this->starttime;
}
/**
* Defines options to pass to the underlying proc_open().
*
* @see https://php.net/proc_open for the options supported by PHP.
*
* Enabling the "create_new_console" option allows a subprocess to continue
* to run after the main process exited, on both Windows and *nix
*/
public function setOptions(array $options)
{
if ($this->isRunning()) {
throw new RuntimeException('Setting options while the process is running is not possible.');
}
$defaultOptions = $this->options;
$existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];
foreach ($options as $key => $value) {
if (!\in_array($key, $existingOptions)) {
$this->options = $defaultOptions;
throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
}
$this->options[$key] = $value;
}
}
/**
* Returns whether TTY is supported on the current operating system.
*/
public static function isTtySupported(): bool
{
static $isTtySupported;
if (null === $isTtySupported) {
$isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
}
return $isTtySupported;
}
/**
* Returns whether PTY is supported on the current operating system.
*
* @return bool
*/
public static function isPtySupported()
{
static $result;
if (null !== $result) {
return $result;
}
if ('\\' === \DIRECTORY_SEPARATOR) {
return $result = false;
}
return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
}
/**
* Creates the descriptors needed by the proc_open.
*/
private function getDescriptors(): array
{
if ($this->input instanceof \Iterator) {
$this->input->rewind();
}
if ('\\' === \DIRECTORY_SEPARATOR) {
$this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
} else {
$this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
}
return $this->processPipes->getDescriptors();
}
/**
* Builds up the callback used by wait().
*
* The callbacks adds all occurred output to the specific buffer and calls
* the user callback (if present) with the received output.
*
* @param callable|null $callback The user defined PHP callback
*
* @return \Closure A PHP closure
*/
protected function buildCallback(callable $callback = null)
{
if ($this->outputDisabled) {
return function ($type, $data) use ($callback): bool {
return null !== $callback && $callback($type, $data);
};
}
$out = self::OUT;
return function ($type, $data) use ($callback, $out): bool {
if ($out == $type) {
$this->addOutput($data);
} else {
$this->addErrorOutput($data);
}
return null !== $callback && $callback($type, $data);
};
}
/**
* Updates the status of the process, reads pipes.
*
* @param bool $blocking Whether to use a blocking read call
*/
protected function updateStatus(bool $blocking)
{
if (self::STATUS_STARTED !== $this->status) {
return;
}
$this->processInformation = proc_get_status($this->process);
$running = $this->processInformation['running'];
$this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
if ($this->fallbackStatus && $this->isSigchildEnabled()) {
$this->processInformation = $this->fallbackStatus + $this->processInformation;
}
if (!$running) {
$this->close();
}
}
/**
* Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
*
* @return bool
*/
protected function isSigchildEnabled()
{
if (null !== self::$sigchild) {
return self::$sigchild;
}
if (!\function_exists('phpinfo')) {
return self::$sigchild = false;
}
ob_start();
phpinfo(\INFO_GENERAL);
return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild');
}
/**
* Reads pipes for the freshest output.
*
* @param string $caller The name of the method that needs fresh outputs
* @param bool $blocking Whether to use blocking calls or not
*
* @throws LogicException in case output has been disabled or process is not started
*/
private function readPipesForOutput(string $caller, bool $blocking = false)
{
if ($this->outputDisabled) {
throw new LogicException('Output has been disabled.');
}
$this->requireProcessIsStarted($caller);
$this->updateStatus($blocking);
}
/**
* Validates and returns the filtered timeout.
*
* @throws InvalidArgumentException if the given timeout is a negative number
*/
private function validateTimeout(?float $timeout): ?float
{
$timeout = (float) $timeout;
if (0.0 === $timeout) {
$timeout = null;
} elseif ($timeout < 0) {
throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
}
return $timeout;
}
/**
* Reads pipes, executes callback.
*
* @param bool $blocking Whether to use blocking calls or not
* @param bool $close Whether to close file handles or not
*/
private function readPipes(bool $blocking, bool $close)
{
$result = $this->processPipes->readAndWrite($blocking, $close);
$callback = $this->callback;
foreach ($result as $type => $data) {
if (3 !== $type) {
$callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
}
/**
* Closes process resource, closes file handles, sets the exitcode.
*
* @return int The exitcode
*/
private function close(): int
{
$this->processPipes->close();
if (\is_resource($this->process)) {
proc_close($this->process);
}
$this->exitcode = $this->processInformation['exitcode'];
$this->status = self::STATUS_TERMINATED;
if (-1 === $this->exitcode) {
if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
// if process has been signaled, no exitcode but a valid termsig, apply Unix convention
$this->exitcode = 128 + $this->processInformation['termsig'];
} elseif ($this->isSigchildEnabled()) {
$this->processInformation['signaled'] = true;
$this->processInformation['termsig'] = -1;
}
}
// Free memory from self-reference callback created by buildCallback
// Doing so in other contexts like __destruct or by garbage collector is ineffective
// Now pipes are closed, so the callback is no longer necessary
$this->callback = null;
return $this->exitcode;
}
/**
* Resets data related to the latest run of the process.
*/
private function resetProcessData()
{
$this->starttime = null;
$this->callback = null;
$this->exitcode = null;
$this->fallbackStatus = [];
$this->processInformation = null;
$this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
$this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
$this->process = null;
$this->latestSignal = null;
$this->status = self::STATUS_READY;
$this->incrementalOutputOffset = 0;
$this->incrementalErrorOutputOffset = 0;
}
/**
* Sends a POSIX signal to the process.
*
* @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
* @param bool $throwException Whether to throw exception in case signal failed
*
* @return bool True if the signal was sent successfully, false otherwise
*
* @throws LogicException In case the process is not running
* @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
* @throws RuntimeException In case of failure
*/
private function doSignal(int $signal, bool $throwException): bool
{
if (null === $pid = $this->getPid()) {
if ($throwException) {
throw new LogicException('Can not send signal on a non running process.');
}
return false;
}
if ('\\' === \DIRECTORY_SEPARATOR) {
exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
if ($exitCode && $this->isRunning()) {
if ($throwException) {
throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
}
return false;
}
} else {
if (!$this->isSigchildEnabled()) {
$ok = @proc_terminate($this->process, $signal);
} elseif (\function_exists('posix_kill')) {
$ok = @posix_kill($pid, $signal);
} elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
$ok = false === fgets($pipes[2]);
}
if (!$ok) {
if ($throwException) {
throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
}
return false;
}
}
$this->latestSignal = $signal;
$this->fallbackStatus['signaled'] = true;
$this->fallbackStatus['exitcode'] = -1;
$this->fallbackStatus['termsig'] = $this->latestSignal;
return true;
}
private function prepareWindowsCommandLine(string $cmd, array &$env): string
{
$uid = uniqid('', true);
$varCount = 0;
$varCache = [];
$cmd = preg_replace_callback(
'/"(?:(
[^"%!^]*+
(?:
(?: !LF! | "(?:\^[%!^])?+" )
[^"%!^]*+
)++
) | [^"]*+ )"/x',
function ($m) use (&$env, &$varCache, &$varCount, $uid) {
if (!isset($m[1])) {
return $m[0];
}
if (isset($varCache[$m[0]])) {
return $varCache[$m[0]];
}
if (false !== strpos($value = $m[1], "\0")) {
$value = str_replace("\0", '?', $value);
}
if (false === strpbrk($value, "\"%!\n")) {
return '"'.$value.'"';
}
$value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
$value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
$var = $uid.++$varCount;
$env[$var] = $value;
return $varCache[$m[0]] = '!'.$var.'!';
},
$cmd
);
$cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
foreach ($this->processPipes->getFiles() as $offset => $filename) {
$cmd .= ' '.$offset.'>"'.$filename.'"';
}
return $cmd;
}
/**
* Ensures the process is running or terminated, throws a LogicException if the process has a not started.
*
* @throws LogicException if the process has not run
*/
private function requireProcessIsStarted(string $functionName)
{
if (!$this->isStarted()) {
throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
}
}
/**
* Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
*
* @throws LogicException if the process is not yet terminated
*/
private function requireProcessIsTerminated(string $functionName)
{
if (!$this->isTerminated()) {
throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
}
}
/**
* Escapes a string to be used as a shell argument.
*/
private function escapeArgument(?string $argument): string
{
if ('' === $argument || null === $argument) {
return '""';
}
if ('\\' !== \DIRECTORY_SEPARATOR) {
return "'".str_replace("'", "'\\''", $argument)."'";
}
if (false !== strpos($argument, "\0")) {
$argument = str_replace("\0", '?', $argument);
}
if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
return $argument;
}
$argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
}
private function replacePlaceholders(string $commandline, array $env)
{
return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
}
return $this->escapeArgument($env[$matches[1]]);
}, $commandline);
}
private function getDefaultEnv(): array
{
$env = [];
foreach ($_SERVER as $k => $v) {
if (\is_string($v) && false !== $v = getenv($k)) {
$env[$k] = $v;
}
}
foreach ($_ENV as $k => $v) {
if (\is_string($v)) {
$env[$k] = $v;
}
}
return $env;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Process;
/**
* Generic executable finder.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
class ExecutableFinder
{
private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
/**
* Replaces default suffixes of executable.
*/
public function setSuffixes(array $suffixes)
{
$this->suffixes = $suffixes;
}
/**
* Adds new possible suffix to check for executable.
*/
public function addSuffix(string $suffix)
{
$this->suffixes[] = $suffix;
}
/**
* Finds an executable by name.
*
* @param string $name The executable name (without the extension)
* @param string|null $default The default to return if no executable is found
* @param array $extraDirs Additional dirs to check into
*
* @return string|null The executable path or default value
*/
public function find(string $name, string $default = null, array $extraDirs = [])
{
if (ini_get('open_basedir')) {
$searchPath = array_merge(explode(\PATH_SEPARATOR, ini_get('open_basedir')), $extraDirs);
$dirs = [];
foreach ($searchPath as $path) {
// Silencing against https://bugs.php.net/69240
if (@is_dir($path)) {
$dirs[] = $path;
} else {
if (basename($path) == $name && @is_executable($path)) {
return $path;
}
}
}
} else {
$dirs = array_merge(
explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
$extraDirs
);
}
$suffixes = [''];
if ('\\' === \DIRECTORY_SEPARATOR) {
$pathExt = getenv('PATHEXT');
$suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
}
foreach ($suffixes as $suffix) {
foreach ($dirs as $dir) {
if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
return $file;
}
}
}
return $default;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!function_exists('trigger_deprecation')) {
/**
* Triggers a silenced deprecation notice.
*
* @param string $package The name of the Composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The message of the deprecation
* @param mixed ...$args Values to insert in the message using printf() formatting
*
* @author Nicolas Grekas <p@tchwork.com>
*/
function trigger_deprecation(string $package, string $version, string $message, ...$args): void
{
@trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
}
}
Copyright (c) 2020-2021 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Intl\Grapheme;
\define('SYMFONY_GRAPHEME_CLUSTER_RX', \PCRE_VERSION >= '8.32' ? '\X' : Grapheme::GRAPHEME_CLUSTER_RX);
/**
* Partial intl implementation in pure PHP.
*
* Implemented:
* - grapheme_extract - Extract a sequence of grapheme clusters from a text buffer, which must be encoded in UTF-8
* - grapheme_stripos - Find position (in grapheme units) of first occurrence of a case-insensitive string
* - grapheme_stristr - Returns part of haystack string from the first occurrence of case-insensitive needle to the end of haystack
* - grapheme_strlen - Get string length in grapheme units
* - grapheme_strpos - Find position (in grapheme units) of first occurrence of a string
* - grapheme_strripos - Find position (in grapheme units) of last occurrence of a case-insensitive string
* - grapheme_strrpos - Find position (in grapheme units) of last occurrence of a string
* - grapheme_strstr - Returns part of haystack string from the first occurrence of needle to the end of haystack
* - grapheme_substr - Return part of a string
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
final class Grapheme
{
// (CRLF|([ZWNJ-ZWJ]|T+|L*(LV?V+|LV|LVT)T*|L+|[^Control])[Extend]*|[Control])
// This regular expression is a work around for http://bugs.exim.org/1279
public const GRAPHEME_CLUSTER_RX = '(?:\r\n|(?:[ -~\x{200C}\x{200D}]|[ᆨ-ᇹ]+|[á„€-á…Ÿ]*(?:[가개갸걔거게겨계고과괘괴êµ<C3AA>구궈궤귀규그긔기까깨꺄꺠꺼께껴ê¼<C3AA>꼬꽈꽤꾀꾜꾸꿔꿰뀌뀨ë<C2A8>„ë<E2809E> ë<C2A0>¼ë˜ë´ëƒ<C3AB>냬너네녀녜노놔놰뇌뇨누눠눼뉘뉴ëŠ<C3AB>늬ëˆë¤ëŒ€ëŒœëŒ¸ë<C2B8>”ë<E2809D>°ëŽŒëŽ¨ë<C2A8>„ë<E2809E> ë<C2A0>¼ë<C2BC>˜ë<CB9C>´ë<C3AB>ë¬ëˆë¤ë“€ë“œë“¸ë””따때땨ë„ë ë¼ë—˜ë—´ë˜<C3AB>똬뙈뙤뚀뚜뚸ë”ë°ëœŒëœ¨ë<C2A8>„ë<E2809E> ë<C2A0>¼ëž˜ëž´ëŸ<C3AB>러레려례로롸뢔뢰료루뤄뤠뤼류르ë¦<C3AB>리마매먀먜머메며몌모뫄뫠뫼묘무ë­<C3AB>뭬뮈뮤므믜미바배뱌뱨버베벼볘보ë´<C3AB>봬뵈뵤부붜붸뷔뷰브븨비빠빼뺘뺴ë»<C3AB>뻬뼈뼤뽀뽜뽸뾔뾰뿌뿨쀄쀠쀼ì<C2BC>˜ì<CB9C>´ì<C3AC>ì¬ìƒˆìƒ¤ì„€ì„œì„¸ì…”셰소솨쇄쇠쇼수숴ì‰<C3AC>쉬슈스ì€ìœì¸ìŒ”쌰ì<C2B0>Œì<C592>¨ìŽ„쎠쎼ì<C2BC>˜ì<CB9C>´ì<C2B4><C3AC>ì<EFBFBD>¬ìˆì¤ì€ìœì¸ì“”쓰씌씨아애야ì˜ì´ì—<C3AC>여예오와왜외요우ìŒì¨ìœ„유으ì<C2BC>˜ì<CB9C>´ìž<C3AC>재쟈쟤저제져졔조좌좨죄죠주줘줴ì¥<C3AC>쥬즈즤지짜째쨔쨰쩌쩨쪄쪠쪼쫘쫴ì¬<C3AC>쬬쭈쭤쮀쮜쮸쯔쯰찌차채챠챼처체ì³<C3AC>쳬초촤쵀최쵸추춰췌취츄츠츼치카ìº<C3AC>캬컈커케켜켸코콰쾌쾨쿄쿠쿼퀘퀴í<C2B4><C3AD>í<EFBFBD>¬íˆí¤íƒ€íƒœíƒ¸í„”터테텨톄토톼퇘퇴íˆ<C3AD>투퉈퉤튀튜트í”í°íŒŒíŒ¨í<C2A8>„í<E2809E> í<C2A0>¼íŽ˜íŽ´í<C2B4><C3AD>í<EFBFBD>¬í<C2AC>ˆí<CB86>¤í€íœí¸í”í°í“Œí“¨í”„픠피하해í<C3AD>í¬í—ˆí—¤í˜€í˜œí˜¸í™”홰회효í„í í¼íœ˜íœ´í<C2B4><C3AD>í<EFBFBD>¬ížˆ]?[á… -ᆢ]+|[ê°€-힣])[ᆨ-ᇹ]*|[á„€-á…Ÿ]+|[^\p{Cc}\p{Cf}\p{Zl}\p{Zp}])[\p{Mn}\p{Me}\x{09BE}\x{09D7}\x{0B3E}\x{0B57}\x{0BBE}\x{0BD7}\x{0CC2}\x{0CD5}\x{0CD6}\x{0D3E}\x{0D57}\x{0DCF}\x{0DDF}\x{200C}\x{200D}\x{1D165}\x{1D16E}-\x{1D172}]*|[\p{Cc}\p{Cf}\p{Zl}\p{Zp}])';
public static function grapheme_extract($s, $size, $type = \GRAPHEME_EXTR_COUNT, $start = 0, &$next = 0)
{
if (0 > $start) {
$start = \strlen($s) + $start;
}
if (!is_scalar($s)) {
$hasError = false;
set_error_handler(function () use (&$hasError) { $hasError = true; });
$next = substr($s, $start);
restore_error_handler();
if ($hasError) {
substr($s, $start);
$s = '';
} else {
$s = $next;
}
} else {
$s = substr($s, $start);
}
$size = (int) $size;
$type = (int) $type;
$start = (int) $start;
if (\GRAPHEME_EXTR_COUNT !== $type && \GRAPHEME_EXTR_MAXBYTES !== $type && \GRAPHEME_EXTR_MAXCHARS !== $type) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('grapheme_extract(): Argument #3 ($type) must be one of GRAPHEME_EXTR_COUNT, GRAPHEME_EXTR_MAXBYTES, or GRAPHEME_EXTR_MAXCHARS');
}
if (!isset($s[0]) || 0 > $size || 0 > $start) {
return false;
}
if (0 === $size) {
return '';
}
$next = $start;
$s = preg_split('/('.SYMFONY_GRAPHEME_CLUSTER_RX.')/u', "\r\n".$s, $size + 1, \PREG_SPLIT_NO_EMPTY | \PREG_SPLIT_DELIM_CAPTURE);
if (!isset($s[1])) {
return false;
}
$i = 1;
$ret = '';
do {
if (\GRAPHEME_EXTR_COUNT === $type) {
--$size;
} elseif (\GRAPHEME_EXTR_MAXBYTES === $type) {
$size -= \strlen($s[$i]);
} else {
$size -= iconv_strlen($s[$i], 'UTF-8//IGNORE');
}
if ($size >= 0) {
$ret .= $s[$i];
}
} while (isset($s[++$i]) && $size > 0);
$next += \strlen($ret);
return $ret;
}
public static function grapheme_strlen($s)
{
preg_replace('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', '', $s, -1, $len);
return 0 === $len && '' !== $s ? null : $len;
}
public static function grapheme_substr($s, $start, $len = null)
{
if (null === $len) {
$len = 2147483647;
}
preg_match_all('/'.SYMFONY_GRAPHEME_CLUSTER_RX.'/u', $s, $s);
$slen = \count($s[0]);
$start = (int) $start;
if (0 > $start) {
$start += $slen;
}
if (0 > $start) {
if (\PHP_VERSION_ID < 80000) {
return false;
}
$start = 0;
}
if ($start >= $slen) {
return \PHP_VERSION_ID >= 80000 ? '' : false;
}
$rem = $slen - $start;
if (0 > $len) {
$len += $rem;
}
if (0 === $len) {
return '';
}
if (0 > $len) {
return \PHP_VERSION_ID >= 80000 ? '' : false;
}
if ($len > $rem) {
$len = $rem;
}
return implode('', \array_slice($s[0], $start, $len));
}
public static function grapheme_strpos($s, $needle, $offset = 0)
{
return self::grapheme_position($s, $needle, $offset, 0);
}
public static function grapheme_stripos($s, $needle, $offset = 0)
{
return self::grapheme_position($s, $needle, $offset, 1);
}
public static function grapheme_strrpos($s, $needle, $offset = 0)
{
return self::grapheme_position($s, $needle, $offset, 2);
}
public static function grapheme_strripos($s, $needle, $offset = 0)
{
return self::grapheme_position($s, $needle, $offset, 3);
}
public static function grapheme_stristr($s, $needle, $beforeNeedle = false)
{
return mb_stristr($s, $needle, $beforeNeedle, 'UTF-8');
}
public static function grapheme_strstr($s, $needle, $beforeNeedle = false)
{
return mb_strstr($s, $needle, $beforeNeedle, 'UTF-8');
}
private static function grapheme_position($s, $needle, $offset, $mode)
{
$needle = (string) $needle;
if (80000 > \PHP_VERSION_ID && !preg_match('/./us', $needle)) {
return false;
}
$s = (string) $s;
if (!preg_match('/./us', $s)) {
return false;
}
if ($offset > 0) {
$s = self::grapheme_substr($s, $offset);
} elseif ($offset < 0) {
if (2 > $mode) {
$offset += self::grapheme_strlen($s);
$s = self::grapheme_substr($s, $offset);
if (0 > $offset) {
$offset = 0;
}
} elseif (0 > $offset += self::grapheme_strlen($needle)) {
$s = self::grapheme_substr($s, 0, $offset);
$offset = 0;
} else {
$offset = 0;
}
}
// As UTF-8 is self-synchronizing, and we have ensured the strings are valid UTF-8,
// we can use normal binary string functions here. For case-insensitive searches,
// case fold the strings first.
$caseInsensitive = $mode & 1;
$reverse = $mode & 2;
if ($caseInsensitive) {
// Use the same case folding mode as mbstring does for mb_stripos().
// Stick to SIMPLE case folding to avoid changing the length of the string, which
// might result in offsets being shifted.
$mode = \defined('MB_CASE_FOLD_SIMPLE') ? \MB_CASE_FOLD_SIMPLE : \MB_CASE_UPPER;
$s = mb_convert_case($s, $mode, 'UTF-8');
$needle = mb_convert_case($needle, $mode, 'UTF-8');
}
if ($reverse) {
$needlePos = strrpos($s, $needle);
} else {
$needlePos = strpos($s, $needle);
}
return false !== $needlePos ? self::grapheme_strlen(substr($s, 0, $needlePos)) + $offset : false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Intl\Grapheme as p;
if (extension_loaded('intl')) {
return;
}
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
if (!defined('GRAPHEME_EXTR_COUNT')) {
define('GRAPHEME_EXTR_COUNT', 0);
}
if (!defined('GRAPHEME_EXTR_MAXBYTES')) {
define('GRAPHEME_EXTR_MAXBYTES', 1);
}
if (!defined('GRAPHEME_EXTR_MAXCHARS')) {
define('GRAPHEME_EXTR_MAXCHARS', 2);
}
if (!function_exists('grapheme_extract')) {
function grapheme_extract($haystack, $size, $type = 0, $start = 0, &$next = 0) { return p\Grapheme::grapheme_extract($haystack, $size, $type, $start, $next); }
}
if (!function_exists('grapheme_stripos')) {
function grapheme_stripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_stripos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_stristr')) {
function grapheme_stristr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_stristr($haystack, $needle, $beforeNeedle); }
}
if (!function_exists('grapheme_strlen')) {
function grapheme_strlen($input) { return p\Grapheme::grapheme_strlen($input); }
}
if (!function_exists('grapheme_strpos')) {
function grapheme_strpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strpos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strripos')) {
function grapheme_strripos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strripos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strrpos')) {
function grapheme_strrpos($haystack, $needle, $offset = 0) { return p\Grapheme::grapheme_strrpos($haystack, $needle, $offset); }
}
if (!function_exists('grapheme_strstr')) {
function grapheme_strstr($haystack, $needle, $beforeNeedle = false) { return p\Grapheme::grapheme_strstr($haystack, $needle, $beforeNeedle); }
}
if (!function_exists('grapheme_substr')) {
function grapheme_substr($string, $offset, $length = null) { return p\Grapheme::grapheme_substr($string, $offset, $length); }
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Intl\Grapheme as p;
if (!defined('GRAPHEME_EXTR_COUNT')) {
define('GRAPHEME_EXTR_COUNT', 0);
}
if (!defined('GRAPHEME_EXTR_MAXBYTES')) {
define('GRAPHEME_EXTR_MAXBYTES', 1);
}
if (!defined('GRAPHEME_EXTR_MAXCHARS')) {
define('GRAPHEME_EXTR_MAXCHARS', 2);
}
if (!function_exists('grapheme_extract')) {
function grapheme_extract(?string $haystack, ?int $size, ?int $type = GRAPHEME_EXTR_COUNT, ?int $offset = 0, &$next = null): string|false { return p\Grapheme::grapheme_extract((string) $haystack, (int) $size, (int) $type, (int) $offset, $next); }
}
if (!function_exists('grapheme_stripos')) {
function grapheme_stripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_stripos((string) $haystack, (string) $needle, (int) $offset); }
}
if (!function_exists('grapheme_stristr')) {
function grapheme_stristr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_stristr((string) $haystack, (string) $needle, (bool) $beforeNeedle); }
}
if (!function_exists('grapheme_strlen')) {
function grapheme_strlen(?string $string): int|false|null { return p\Grapheme::grapheme_strlen((string) $string); }
}
if (!function_exists('grapheme_strpos')) {
function grapheme_strpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strpos((string) $haystack, (string) $needle, (int) $offset); }
}
if (!function_exists('grapheme_strripos')) {
function grapheme_strripos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strripos((string) $haystack, (string) $needle, (int) $offset); }
}
if (!function_exists('grapheme_strrpos')) {
function grapheme_strrpos(?string $haystack, ?string $needle, ?int $offset = 0): int|false { return p\Grapheme::grapheme_strrpos((string) $haystack, (string) $needle, (int) $offset); }
}
if (!function_exists('grapheme_strstr')) {
function grapheme_strstr(?string $haystack, ?string $needle, ?bool $beforeNeedle = false): string|false { return p\Grapheme::grapheme_strstr((string) $haystack, (string) $needle, (bool) $beforeNeedle); }
}
if (!function_exists('grapheme_substr')) {
function grapheme_substr(?string $string, ?int $offset, ?int $length = null): string|false { return p\Grapheme::grapheme_substr((string) $string, (int) $offset, (int) $length); }
}
Copyright (c) 2015-2019 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;
/**
* Represents a string of abstract Unicode characters.
*
* Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
* This class is the abstract type to use as a type-hint when the logic you want to
* implement is Unicode-aware but doesn't care about code points vs grapheme clusters.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @throws ExceptionInterface
*/
abstract class AbstractUnicodeString extends AbstractString
{
public const NFC = \Normalizer::NFC;
public const NFD = \Normalizer::NFD;
public const NFKC = \Normalizer::NFKC;
public const NFKD = \Normalizer::NFKD;
// all ASCII letters sorted by typical frequency of occurrence
private const ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
// the subset of folded case mappings that is not in lower case mappings
private const FOLD_FROM = ['Ä°', 'µ', 'Å¿', "\xCD\x85", 'Ï‚', 'Ï<>', 'Ï‘', 'Ï•', 'Ï–', 'Ï°', 'ϱ', 'ϵ', 'ẛ', "\xE1\xBE\xBE", 'ß', 'Ä°', 'ʼn', 'Ç°', 'Î<>', 'ΰ', 'Ö‡', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'ẞ', 'á½<C3A1>', 'á½’', 'á½”', 'á½–', 'á¾€', 'á¾<C3A1>', 'ᾂ', 'ᾃ', 'ᾄ', 'á¾…', 'ᾆ', 'ᾇ', 'ᾈ', 'ᾉ', 'ᾊ', 'ᾋ', 'ᾌ', 'á¾<C3A1>', 'ᾎ', 'á¾<C3A1>', 'á¾<C3A1>', 'ᾑ', 'á¾’', 'ᾓ', 'á¾”', 'ᾕ', 'á¾–', 'á¾—', 'ᾘ', 'á¾™', 'ᾚ', 'á¾›', 'ᾜ', 'á¾<C3A1>', 'ᾞ', 'ᾟ', 'á¾ ', 'ᾡ', 'á¾¢', 'á¾£', 'ᾤ', 'á¾¥', 'ᾦ', 'ᾧ', 'ᾨ', 'ᾩ', 'ᾪ', 'ᾫ', 'ᾬ', 'á¾­', 'á¾®', 'ᾯ', 'á¾²', 'á¾³', 'á¾´', 'ᾶ', 'á¾·', 'á¾¼', 'á¿‚', 'ῃ', 'á¿„', 'ῆ', 'ῇ', 'á¿Œ', 'á¿’', 'Î<>', 'á¿–', 'á¿—', 'á¿¢', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ῲ', 'ῳ', 'á¿´', 'ῶ', 'á¿·', 'ῼ', 'ff', 'ï¬<C3AF>', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ'];
private const FOLD_TO = ['i̇', 'μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'Ï€', 'κ', 'Ï<>', 'ε', 'ṡ', 'ι', 'ss', 'i̇', 'ʼn', 'Ç°', 'Î<>', 'ΰ', 'Õ¥Ö‚', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'aʾ', 'ss', 'á½<C3A1>', 'á½’', 'á½”', 'á½–', 'ἀι', 'á¼<C3A1>ι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἀι', 'á¼<C3A1>ι', 'ἂι', 'ἃι', 'ἄι', 'ἅι', 'ἆι', 'ἇι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ἠι', 'ἡι', 'ἢι', 'ἣι', 'ἤι', 'ἥι', 'ἦι', 'ἧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὠι', 'ὡι', 'ὢι', 'ὣι', 'ὤι', 'ὥι', 'ὦι', 'ὧι', 'ὰι', 'αι', 'άι', 'ᾶ', 'ᾶι', 'αι', 'ὴι', 'ηι', 'ήι', 'ῆ', 'ῆι', 'ηι', 'á¿’', 'Î<>', 'á¿–', 'á¿—', 'á¿¢', 'ΰ', 'ῤ', 'ῦ', 'ῧ', 'ὼι', 'ωι', 'ώι', 'ῶ', 'ῶι', 'ωι', 'ff', 'fi', 'fl', 'ffi', 'ffl', 'st', 'st', 'Õ´Õ¶', 'Õ´Õ¥', 'Õ´Õ«', 'Õ¾Õ¶', 'Õ´Õ­'];
// the subset of upper case mappings that map one code point to many code points
private const UPPER_FROM = ['ß', 'ff', 'ï¬<C3AF>', 'fl', 'ffi', 'ffl', 'ſt', 'st', 'Ö‡', 'ﬓ', 'ﬔ', 'ﬕ', 'ﬖ', 'ﬗ', 'ʼn', 'Î<>', 'ΰ', 'Ç°', 'ẖ', 'ẗ', 'ẘ', 'ẙ', 'ẚ', 'á½<C3A1>', 'á½’', 'á½”', 'á½–', 'ᾶ', 'ῆ', 'á¿’', 'á¿“', 'á¿–', 'á¿—', 'á¿¢', 'á¿£', 'ῤ', 'ῦ', 'ῧ', 'ῶ'];
private const UPPER_TO = ['SS', 'FF', 'FI', 'FL', 'FFI', 'FFL', 'ST', 'ST', 'ÔµÕ’', 'Õ„Õ†', 'Õ„Ôµ', 'Õ„Ô»', 'ÕŽÕ†', 'Õ„Ô½', 'ʼN', 'ΪÌ<CB86>', 'ΫÌ<CB86>', 'JÌŒ', 'H̱', 'T̈', 'WÌŠ', 'YÌŠ', 'Aʾ', 'Υ̓', 'Υ̓̀', 'Υ̓Ì<E2809C>', 'Υ̓͂', 'Α͂', 'Η͂', 'Ϊ̀', 'ΪÌ<CB86>', 'Ι͂', 'Ϊ͂', 'Ϋ̀', 'ΫÌ<CB86>', 'Ρ̓', 'Υ͂', 'Ϋ͂', 'Ω͂'];
// the subset of https://github.com/unicode-org/cldr/blob/master/common/transforms/Latin-ASCII.xml that is not in NFKD
private const TRANSLIT_FROM = ['Æ', 'Ã<>', 'Ø', 'Þ', 'ß', 'æ', 'ð', 'ø', 'þ', 'Ä<>', 'Ä‘', 'Ħ', 'ħ', 'ı', 'ĸ', 'Ä¿', 'Å€', 'Å<>', 'Å‚', 'ʼn', 'ÅŠ', 'Å‹', 'Å’', 'Å“', 'Ŧ', 'ŧ', 'Æ€', 'Æ<>', 'Æ‚', 'ƃ', 'Ƈ', 'ƈ', 'Ɖ', 'ÆŠ', 'Æ‹', 'ÆŒ', 'Æ<>', 'Æ‘', 'Æ’', 'Æ“', 'Æ•', 'Æ–', 'Æ—', 'Ƙ', 'Æ™', 'Æš', 'Æ<>', 'Æž', 'Æ¢', 'Æ£', 'Ƥ', 'Æ¥', 'Æ«', 'Ƭ', 'Æ­', 'Æ®', 'Ʋ', 'Ƴ', 'Æ´', 'Ƶ', 'ƶ', 'Ç„', 'Ç…', 'dž', 'Ǥ', 'Ç¥', 'È¡', 'Ȥ', 'È¥', 'È´', 'ȵ', 'ȶ', 'È·', 'ȸ', 'ȹ', 'Ⱥ', 'È»', 'ȼ', 'Ƚ', 'Ⱦ', 'È¿', 'É€', 'Ƀ', 'É„', 'Ɇ', 'ɇ', 'Ɉ', 'ɉ', 'ÉŒ', 'É<>', 'ÉŽ', 'É<>', 'É“', 'É•', 'É–', 'É—', 'É›', 'ÉŸ', 'É ', 'É¡', 'É¢', 'ɦ', 'ɧ', 'ɨ', 'ɪ', 'É«', 'ɬ', 'É­', 'ɱ', 'ɲ', 'ɳ', 'É´', 'ɶ', 'ɼ', 'ɽ', 'ɾ', 'Ê€', 'Ê‚', 'ʈ', 'ʉ', 'Ê‹', 'Ê<>', 'Ê<>', 'Ê‘', 'Ê™', 'Ê›', 'Êœ', 'Ê<>', 'ÊŸ', 'Ê ', 'Ê£', 'Ê¥', 'ʦ', 'ʪ', 'Ê«', 'á´€', 'á´<C3A1>', 'á´ƒ', 'á´„', 'á´…', 'á´†', 'á´‡', 'á´Š', 'á´‹', 'á´Œ', 'á´<C3A1>', 'á´<C3A1>', 'á´˜', 'á´›', 'á´œ', 'á´ ', 'á´¡', 'á´¢', 'ᵫ', 'ᵬ', 'áµ­', 'áµ®', 'ᵯ', 'áµ°', 'áµ±', 'áµ²', 'áµ³', 'áµ´', 'áµµ', 'ᵶ', 'ᵺ', 'áµ»', 'áµ½', 'áµ¾', 'ᶀ', 'á¶<C3A1>', 'ᶂ', 'ᶃ', 'ᶄ', 'ᶅ', 'ᶆ', 'ᶇ', 'ᶈ', 'ᶉ', 'ᶊ', 'ᶌ', 'á¶<C3A1>', 'ᶎ', 'á¶<C3A1>', 'ᶑ', 'ᶒ', 'ᶓ', 'ᶖ', 'ᶙ', 'ẚ', 'ẜ', 'áº<C3A1>', 'ẞ', 'Ỻ', 'á»»', 'Ỽ', 'ỽ', 'Ỿ', 'ỿ', '©', '®', 'â‚ ', 'â‚¢', 'â‚£', '₤', '₧', '₺', '₹', 'â„Œ', 'â„ž', '㎧', '㎮', 'ã<>†', 'ã<>—', 'ã<>ž', 'ã<>Ÿ', '¼', '½', '¾', 'â…“', 'â…”', 'â…•', 'â…–', 'â…—', 'â…˜', 'â…™', 'â…š', 'â…›', 'â…œ', 'â…<C3A2>', 'â…ž', 'â…Ÿ', '〇', '‘', '’', '‚', '‛', '“', 'â€<C3A2>', '„', '‟', '′', '″', 'ã€<C3A3>', '〞', '«', '»', '‹', '›', 'â€<C3A2>', '‑', '‒', '–', '—', '―', '︱', '︲', '﹘', '‖', 'â<>„', 'â<>…', 'â<>†', 'â<>Ž', 'ã€<C3A3>', '。', '〈', '〉', '《', '》', '〔', '〕', '〘', '〙', '〚', '〛', '︑', '︒', '︹', '︺', '︽', '︾', '︿', 'ï¹€', '﹑', 'ï¹<C3AF>', '﹞', '⦅', 'ï½ ', '。', '、', '×', '÷', '−', '∕', '∖', '∣', '∥', '≪', '≫', '⦅', '⦆'];
private const TRANSLIT_TO = ['AE', 'D', 'O', 'TH', 'ss', 'ae', 'd', 'o', 'th', 'D', 'd', 'H', 'h', 'i', 'q', 'L', 'l', 'L', 'l', '\'n', 'N', 'n', 'OE', 'oe', 'T', 't', 'b', 'B', 'B', 'b', 'C', 'c', 'D', 'D', 'D', 'd', 'E', 'F', 'f', 'G', 'hv', 'I', 'I', 'K', 'k', 'l', 'N', 'n', 'OI', 'oi', 'P', 'p', 't', 'T', 't', 'T', 'V', 'Y', 'y', 'Z', 'z', 'DZ', 'Dz', 'dz', 'G', 'g', 'd', 'Z', 'z', 'l', 'n', 't', 'j', 'db', 'qp', 'A', 'C', 'c', 'L', 'T', 's', 'z', 'B', 'U', 'E', 'e', 'J', 'j', 'R', 'r', 'Y', 'y', 'b', 'c', 'd', 'd', 'e', 'j', 'g', 'g', 'G', 'h', 'h', 'i', 'I', 'l', 'l', 'l', 'm', 'n', 'n', 'N', 'OE', 'r', 'r', 'r', 'R', 's', 't', 'u', 'v', 'Y', 'z', 'z', 'B', 'G', 'H', 'j', 'L', 'q', 'dz', 'dz', 'ts', 'ls', 'lz', 'A', 'AE', 'B', 'C', 'D', 'D', 'E', 'J', 'K', 'L', 'M', 'O', 'P', 'T', 'U', 'V', 'W', 'Z', 'ue', 'b', 'd', 'f', 'm', 'n', 'p', 'r', 'r', 's', 't', 'z', 'th', 'I', 'p', 'U', 'b', 'd', 'f', 'g', 'k', 'l', 'm', 'n', 'p', 'r', 's', 'v', 'x', 'z', 'a', 'd', 'e', 'e', 'i', 'u', 'a', 's', 's', 'SS', 'LL', 'll', 'V', 'v', 'Y', 'y', '(C)', '(R)', 'CE', 'Cr', 'Fr.', 'L.', 'Pts', 'TL', 'Rs', 'x', 'Rx', 'm/s', 'rad/s', 'C/kg', 'pH', 'V/m', 'A/m', ' 1/4', ' 1/2', ' 3/4', ' 1/3', ' 2/3', ' 1/5', ' 2/5', ' 3/5', ' 4/5', ' 1/6', ' 5/6', ' 1/8', ' 3/8', ' 5/8', ' 7/8', ' 1/', '0', '\'', '\'', ',', '\'', '"', '"', ',,', '"', '\'', '"', '"', '"', '<<', '>>', '<', '>', '-', '-', '-', '-', '-', '-', '-', '-', '-', '||', '/', '[', ']', '*', ',', '.', '<', '>', '<<', '>>', '[', ']', '[', ']', '[', ']', ',', '.', '[', ']', '<<', '>>', '<', '>', ',', '[', ']', '((', '))', '.', ',', '*', '/', '-', '/', '\\', '|', '||', '<<', '>>', '((', '))'];
private static $transliterators = [];
/**
* @return static
*/
public static function fromCodePoints(int ...$codes): self
{
$string = '';
foreach ($codes as $code) {
if (0x80 > $code %= 0x200000) {
$string .= \chr($code);
} elseif (0x800 > $code) {
$string .= \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F);
} elseif (0x10000 > $code) {
$string .= \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
} else {
$string .= \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F);
}
}
return new static($string);
}
/**
* Generic UTF-8 to ASCII transliteration.
*
* Install the intl extension for best results.
*
* @param string[]|\Transliterator[]|\Closure[] $rules See "*-Latin" rules from Transliterator::listIDs()
*/
public function ascii(array $rules = []): self
{
$str = clone $this;
$s = $str->string;
$str->string = '';
array_unshift($rules, 'nfd');
$rules[] = 'latin-ascii';
if (\function_exists('transliterator_transliterate')) {
$rules[] = 'any-latin/bgn';
}
$rules[] = 'nfkd';
$rules[] = '[:nonspacing mark:] remove';
while (\strlen($s) - 1 > $i = strspn($s, self::ASCII)) {
if (0 < --$i) {
$str->string .= substr($s, 0, $i);
$s = substr($s, $i);
}
if (!$rule = array_shift($rules)) {
$rules = []; // An empty rule interrupts the next ones
}
if ($rule instanceof \Transliterator) {
$s = $rule->transliterate($s);
} elseif ($rule instanceof \Closure) {
$s = $rule($s);
} elseif ($rule) {
if ('nfd' === $rule = strtolower($rule)) {
normalizer_is_normalized($s, self::NFD) ?: $s = normalizer_normalize($s, self::NFD);
} elseif ('nfkd' === $rule) {
normalizer_is_normalized($s, self::NFKD) ?: $s = normalizer_normalize($s, self::NFKD);
} elseif ('[:nonspacing mark:] remove' === $rule) {
$s = preg_replace('/\p{Mn}++/u', '', $s);
} elseif ('latin-ascii' === $rule) {
$s = str_replace(self::TRANSLIT_FROM, self::TRANSLIT_TO, $s);
} elseif ('de-ascii' === $rule) {
$s = preg_replace("/([AUO])\u{0308}(?=\p{Ll})/u", '$1e', $s);
$s = str_replace(["a\u{0308}", "o\u{0308}", "u\u{0308}", "A\u{0308}", "O\u{0308}", "U\u{0308}"], ['ae', 'oe', 'ue', 'AE', 'OE', 'UE'], $s);
} elseif (\function_exists('transliterator_transliterate')) {
if (null === $transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule)) {
if ('any-latin/bgn' === $rule) {
$rule = 'any-latin';
$transliterator = self::$transliterators[$rule] ?? self::$transliterators[$rule] = \Transliterator::create($rule);
}
if (null === $transliterator) {
throw new InvalidArgumentException(sprintf('Unknown transliteration rule "%s".', $rule));
}
self::$transliterators['any-latin/bgn'] = $transliterator;
}
$s = $transliterator->transliterate($s);
}
} elseif (!\function_exists('iconv')) {
$s = preg_replace('/[^\x00-\x7F]/u', '?', $s);
} else {
$s = @preg_replace_callback('/[^\x00-\x7F]/u', static function ($c) {
$c = (string) iconv('UTF-8', 'ASCII//TRANSLIT', $c[0]);
if ('' === $c && '' === iconv('UTF-8', 'ASCII//TRANSLIT', '²')) {
throw new \LogicException(sprintf('"%s" requires a translit-able iconv implementation, try installing "gnu-libiconv" if you\'re using Alpine Linux.', static::class));
}
return 1 < \strlen($c) ? ltrim($c, '\'`"^~') : ('' !== $c ? $c : '?');
}, $s);
}
}
$str->string .= $s;
return $str;
}
public function camel(): parent
{
$str = clone $this;
$str->string = str_replace(' ', '', preg_replace_callback('/\b./u', static function ($m) use (&$i) {
return 1 === ++$i ? ('İ' === $m[0] ? 'i̇' : mb_strtolower($m[0], 'UTF-8')) : mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
}, preg_replace('/[^\pL0-9]++/u', ' ', $this->string)));
return $str;
}
/**
* @return int[]
*/
public function codePointsAt(int $offset): array
{
$str = $this->slice($offset, 1);
if ('' === $str->string) {
return [];
}
$codePoints = [];
foreach (preg_split('//u', $str->string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
$codePoints[] = mb_ord($c, 'UTF-8');
}
return $codePoints;
}
public function folded(bool $compat = true): parent
{
$str = clone $this;
if (!$compat || \PHP_VERSION_ID < 70300 || !\defined('Normalizer::NFKC_CF')) {
$str->string = normalizer_normalize($str->string, $compat ? \Normalizer::NFKC : \Normalizer::NFC);
$str->string = mb_strtolower(str_replace(self::FOLD_FROM, self::FOLD_TO, $this->string), 'UTF-8');
} else {
$str->string = normalizer_normalize($str->string, \Normalizer::NFKC_CF);
}
return $str;
}
public function join(array $strings, string $lastGlue = null): parent
{
$str = clone $this;
$tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
$str->string = implode($this->string, $strings).$tail;
if (!preg_match('//u', $str->string)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function lower(): parent
{
$str = clone $this;
$str->string = mb_strtolower(str_replace('İ', 'i̇', $str->string), 'UTF-8');
return $str;
}
public function match(string $regexp, int $flags = 0, int $offset = 0): array
{
$match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
if ($this->ignoreCase) {
$regexp .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (false === $match($regexp.'u', $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
}
} finally {
restore_error_handler();
}
return $matches;
}
/**
* @return static
*/
public function normalize(int $form = self::NFC): self
{
if (!\in_array($form, [self::NFC, self::NFD, self::NFKC, self::NFKD])) {
throw new InvalidArgumentException('Unsupported normalization form.');
}
$str = clone $this;
normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);
return $str;
}
public function padBoth(int $length, string $padStr = ' '): parent
{
if ('' === $padStr || !preg_match('//u', $padStr)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$pad = clone $this;
$pad->string = $padStr;
return $this->pad($length, $pad, \STR_PAD_BOTH);
}
public function padEnd(int $length, string $padStr = ' '): parent
{
if ('' === $padStr || !preg_match('//u', $padStr)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$pad = clone $this;
$pad->string = $padStr;
return $this->pad($length, $pad, \STR_PAD_RIGHT);
}
public function padStart(int $length, string $padStr = ' '): parent
{
if ('' === $padStr || !preg_match('//u', $padStr)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$pad = clone $this;
$pad->string = $padStr;
return $this->pad($length, $pad, \STR_PAD_LEFT);
}
public function replaceMatches(string $fromRegexp, $to): parent
{
if ($this->ignoreCase) {
$fromRegexp .= 'i';
}
if (\is_array($to) || $to instanceof \Closure) {
if (!\is_callable($to)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
}
$replace = 'preg_replace_callback';
$to = static function (array $m) use ($to): string {
$to = $to($m);
if ('' !== $to && (!\is_string($to) || !preg_match('//u', $to))) {
throw new InvalidArgumentException('Replace callback must return a valid UTF-8 string.');
}
return $to;
};
} elseif ('' !== $to && !preg_match('//u', $to)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
} else {
$replace = 'preg_replace';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (null === $string = $replace($fromRegexp.'u', $to, $this->string)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
}
} finally {
restore_error_handler();
}
$str = clone $this;
$str->string = $string;
return $str;
}
public function reverse(): parent
{
$str = clone $this;
$str->string = implode('', array_reverse(preg_split('/(\X)/u', $str->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY)));
return $str;
}
public function snake(): parent
{
$str = $this->camel()->title();
$str->string = mb_strtolower(preg_replace(['/(\p{Lu}+)(\p{Lu}\p{Ll})/u', '/([\p{Ll}0-9])(\p{Lu})/u'], '\1_\2', $str->string), 'UTF-8');
return $str;
}
public function title(bool $allWords = false): parent
{
$str = clone $this;
$limit = $allWords ? -1 : 1;
$str->string = preg_replace_callback('/\b./u', static function (array $m): string {
return mb_convert_case($m[0], \MB_CASE_TITLE, 'UTF-8');
}, $str->string, $limit);
return $str;
}
public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
{
if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
throw new InvalidArgumentException('Invalid UTF-8 chars.');
}
$chars = preg_quote($chars);
$str = clone $this;
$str->string = preg_replace("{^[$chars]++|[$chars]++$}uD", '', $str->string);
return $str;
}
public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
{
if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
throw new InvalidArgumentException('Invalid UTF-8 chars.');
}
$chars = preg_quote($chars);
$str = clone $this;
$str->string = preg_replace("{[$chars]++$}uD", '', $str->string);
return $str;
}
public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): parent
{
if (" \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}" !== $chars && !preg_match('//u', $chars)) {
throw new InvalidArgumentException('Invalid UTF-8 chars.');
}
$chars = preg_quote($chars);
$str = clone $this;
$str->string = preg_replace("{^[$chars]++}uD", '', $str->string);
return $str;
}
public function upper(): parent
{
$str = clone $this;
$str->string = mb_strtoupper($str->string, 'UTF-8');
if (\PHP_VERSION_ID < 70300) {
$str->string = str_replace(self::UPPER_FROM, self::UPPER_TO, $str->string);
}
return $str;
}
public function width(bool $ignoreAnsiDecoration = true): int
{
$width = 0;
$s = str_replace(["\x00", "\x05", "\x07"], '', $this->string);
if (false !== strpos($s, "\r")) {
$s = str_replace(["\r\n", "\r"], "\n", $s);
}
if (!$ignoreAnsiDecoration) {
$s = preg_replace('/[\p{Cc}\x7F]++/u', '', $s);
}
foreach (explode("\n", $s) as $s) {
if ($ignoreAnsiDecoration) {
$s = preg_replace('/(?:\x1B(?:
\[ [\x30-\x3F]*+ [\x20-\x2F]*+ [0x40-\x7E]
| [P\]X^_] .*? \x1B\\\\
| [\x41-\x7E]
)|[\p{Cc}\x7F]++)/xu', '', $s);
}
// Non printable characters have been dropped, so wcswidth cannot logically return -1.
$width += $this->wcswidth($s);
}
return $width;
}
/**
* @return static
*/
private function pad(int $len, self $pad, int $type): parent
{
$sLen = $this->length();
if ($len <= $sLen) {
return clone $this;
}
$padLen = $pad->length();
$freeLen = $len - $sLen;
$len = $freeLen % $padLen;
switch ($type) {
case \STR_PAD_RIGHT:
return $this->append(str_repeat($pad->string, $freeLen / $padLen).($len ? $pad->slice(0, $len) : ''));
case \STR_PAD_LEFT:
return $this->prepend(str_repeat($pad->string, $freeLen / $padLen).($len ? $pad->slice(0, $len) : ''));
case \STR_PAD_BOTH:
$freeLen /= 2;
$rightLen = ceil($freeLen);
$len = $rightLen % $padLen;
$str = $this->append(str_repeat($pad->string, $rightLen / $padLen).($len ? $pad->slice(0, $len) : ''));
$leftLen = floor($freeLen);
$len = $leftLen % $padLen;
return $str->prepend(str_repeat($pad->string, $leftLen / $padLen).($len ? $pad->slice(0, $len) : ''));
default:
throw new InvalidArgumentException('Invalid padding type.');
}
}
/**
* Based on https://github.com/jquast/wcwidth, a Python implementation of https://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c.
*/
private function wcswidth(string $string): int
{
$width = 0;
foreach (preg_split('//u', $string, -1, \PREG_SPLIT_NO_EMPTY) as $c) {
$codePoint = mb_ord($c, 'UTF-8');
if (0 === $codePoint // NULL
|| 0x034F === $codePoint // COMBINING GRAPHEME JOINER
|| (0x200B <= $codePoint && 0x200F >= $codePoint) // ZERO WIDTH SPACE to RIGHT-TO-LEFT MARK
|| 0x2028 === $codePoint // LINE SEPARATOR
|| 0x2029 === $codePoint // PARAGRAPH SEPARATOR
|| (0x202A <= $codePoint && 0x202E >= $codePoint) // LEFT-TO-RIGHT EMBEDDING to RIGHT-TO-LEFT OVERRIDE
|| (0x2060 <= $codePoint && 0x2063 >= $codePoint) // WORD JOINER to INVISIBLE SEPARATOR
) {
continue;
}
// Non printable characters
if (32 > $codePoint // C0 control characters
|| (0x07F <= $codePoint && 0x0A0 > $codePoint) // C1 control characters and DEL
) {
return -1;
}
static $tableZero;
if (null === $tableZero) {
$tableZero = require __DIR__.'/Resources/data/wcswidth_table_zero.php';
}
if ($codePoint >= $tableZero[0][0] && $codePoint <= $tableZero[$ubound = \count($tableZero) - 1][1]) {
$lbound = 0;
while ($ubound >= $lbound) {
$mid = floor(($lbound + $ubound) / 2);
if ($codePoint > $tableZero[$mid][1]) {
$lbound = $mid + 1;
} elseif ($codePoint < $tableZero[$mid][0]) {
$ubound = $mid - 1;
} else {
continue 2;
}
}
}
static $tableWide;
if (null === $tableWide) {
$tableWide = require __DIR__.'/Resources/data/wcswidth_table_wide.php';
}
if ($codePoint >= $tableWide[0][0] && $codePoint <= $tableWide[$ubound = \count($tableWide) - 1][1]) {
$lbound = 0;
while ($ubound >= $lbound) {
$mid = floor(($lbound + $ubound) / 2);
if ($codePoint > $tableWide[$mid][1]) {
$lbound = $mid + 1;
} elseif ($codePoint < $tableWide[$mid][0]) {
$ubound = $mid - 1;
} else {
$width += 2;
continue 2;
}
}
}
++$width;
}
return $width;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Inflector;
final class EnglishInflector implements InflectorInterface
{
/**
* Map English plural to singular suffixes.
*
* @see http://english-zone.com/spelling/plurals.html
*/
private const PLURAL_MAP = [
// First entry: plural suffix, reversed
// Second entry: length of plural suffix
// Third entry: Whether the suffix may succeed a vocal
// Fourth entry: Whether the suffix may succeed a consonant
// Fifth entry: singular suffix, normal
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['a', 1, true, true, ['on', 'um']],
// nebulae (nebula)
['ea', 2, true, true, 'a'],
// services (service)
['secivres', 8, true, true, 'service'],
// mice (mouse), lice (louse)
['eci', 3, false, true, 'ouse'],
// geese (goose)
['esee', 4, false, true, 'oose'],
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
['i', 1, true, true, 'us'],
// men (man), women (woman)
['nem', 3, true, true, 'man'],
// children (child)
['nerdlihc', 8, true, true, 'child'],
// oxen (ox)
['nexo', 4, false, false, 'ox'],
// indices (index), appendices (appendix), prices (price)
['seci', 4, false, true, ['ex', 'ix', 'ice']],
// selfies (selfie)
['seifles', 7, true, true, 'selfie'],
// movies (movie)
['seivom', 6, true, true, 'movie'],
// feet (foot)
['teef', 4, true, true, 'foot'],
// geese (goose)
['eseeg', 5, true, true, 'goose'],
// teeth (tooth)
['hteet', 5, true, true, 'tooth'],
// news (news)
['swen', 4, true, true, 'news'],
// series (series)
['seires', 6, true, true, 'series'],
// babies (baby)
['sei', 3, false, true, 'y'],
// accesses (access), addresses (address), kisses (kiss)
['sess', 4, true, false, 'ss'],
// analyses (analysis), ellipses (ellipsis), fungi (fungus),
// neuroses (neurosis), theses (thesis), emphases (emphasis),
// oases (oasis), crises (crisis), houses (house), bases (base),
// atlases (atlas)
['ses', 3, true, true, ['s', 'se', 'sis']],
// objectives (objective), alternative (alternatives)
['sevit', 5, true, true, 'tive'],
// drives (drive)
['sevird', 6, false, true, 'drive'],
// lives (life), wives (wife)
['sevi', 4, false, true, 'ife'],
// moves (move)
['sevom', 5, true, true, 'move'],
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf), caves (cave), staves (staff)
['sev', 3, true, true, ['f', 've', 'ff']],
// axes (axis), axes (ax), axes (axe)
['sexa', 4, false, false, ['ax', 'axe', 'axis']],
// indexes (index), matrixes (matrix)
['sex', 3, true, false, 'x'],
// quizzes (quiz)
['sezz', 4, true, false, 'z'],
// bureaus (bureau)
['suae', 4, false, true, 'eau'],
// fees (fee), trees (tree), employees (employee)
['see', 3, true, true, 'ee'],
// roses (rose), garages (garage), cassettes (cassette),
// waltzes (waltz), heroes (hero), bushes (bush), arches (arch),
// shoes (shoe)
['se', 2, true, true, ['', 'e']],
// tags (tag)
['s', 1, true, true, ''],
// chateaux (chateau)
['xuae', 4, false, true, 'eau'],
// people (person)
['elpoep', 6, true, true, 'person'],
];
/**
* Map English singular to plural suffixes.
*
* @see http://english-zone.com/spelling/plurals.html
*/
private const SINGULAR_MAP = [
// First entry: singular suffix, reversed
// Second entry: length of singular suffix
// Third entry: Whether the suffix may succeed a vocal
// Fourth entry: Whether the suffix may succeed a consonant
// Fifth entry: plural suffix, normal
// criterion (criteria)
['airetirc', 8, false, false, 'criterion'],
// nebulae (nebula)
['aluben', 6, false, false, 'nebulae'],
// children (child)
['dlihc', 5, true, true, 'children'],
// prices (price)
['eci', 3, false, true, 'ices'],
// services (service)
['ecivres', 7, true, true, 'services'],
// lives (life), wives (wife)
['efi', 3, false, true, 'ives'],
// selfies (selfie)
['eifles', 6, true, true, 'selfies'],
// movies (movie)
['eivom', 5, true, true, 'movies'],
// lice (louse)
['esuol', 5, false, true, 'lice'],
// mice (mouse)
['esuom', 5, false, true, 'mice'],
// geese (goose)
['esoo', 4, false, true, 'eese'],
// houses (house), bases (base)
['es', 2, true, true, 'ses'],
// geese (goose)
['esoog', 5, true, true, 'geese'],
// caves (cave)
['ev', 2, true, true, 'ves'],
// drives (drive)
['evird', 5, false, true, 'drives'],
// objectives (objective), alternative (alternatives)
['evit', 4, true, true, 'tives'],
// moves (move)
['evom', 4, true, true, 'moves'],
// staves (staff)
['ffats', 5, true, true, 'staves'],
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
['ff', 2, true, true, 'ffs'],
// hooves (hoof), dwarves (dwarf), elves (elf), leaves (leaf)
['f', 1, true, true, ['fs', 'ves']],
// arches (arch)
['hc', 2, true, true, 'ches'],
// bushes (bush)
['hs', 2, true, true, 'shes'],
// teeth (tooth)
['htoot', 5, true, true, 'teeth'],
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['mu', 2, true, true, 'a'],
// men (man), women (woman)
['nam', 3, true, true, 'men'],
// people (person)
['nosrep', 6, true, true, ['persons', 'people']],
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['noi', 3, true, true, 'ions'],
// coupon (coupons)
['nop', 3, true, true, 'pons'],
// seasons (season), treasons (treason), poisons (poison), lessons (lesson)
['nos', 3, true, true, 'sons'],
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['no', 2, true, true, 'a'],
// echoes (echo)
['ohce', 4, true, true, 'echoes'],
// heroes (hero)
['oreh', 4, true, true, 'heroes'],
// atlases (atlas)
['salta', 5, true, true, 'atlases'],
// irises (iris)
['siri', 4, true, true, 'irises'],
// analyses (analysis), ellipses (ellipsis), neuroses (neurosis)
// theses (thesis), emphases (emphasis), oases (oasis),
// crises (crisis)
['sis', 3, true, true, 'ses'],
// accesses (access), addresses (address), kisses (kiss)
['ss', 2, true, false, 'sses'],
// syllabi (syllabus)
['suballys', 8, true, true, 'syllabi'],
// buses (bus)
['sub', 3, true, true, 'buses'],
// circuses (circus)
['suc', 3, true, true, 'cuses'],
// fungi (fungus), alumni (alumnus), syllabi (syllabus), radii (radius)
['su', 2, true, true, 'i'],
// news (news)
['swen', 4, true, true, 'news'],
// feet (foot)
['toof', 4, true, true, 'feet'],
// chateaux (chateau), bureaus (bureau)
['uae', 3, false, true, ['eaus', 'eaux']],
// oxen (ox)
['xo', 2, false, false, 'oxen'],
// hoaxes (hoax)
['xaoh', 4, true, false, 'hoaxes'],
// indices (index)
['xedni', 5, false, true, ['indicies', 'indexes']],
// boxes (box)
['xo', 2, false, true, 'oxes'],
// indexes (index), matrixes (matrix)
['x', 1, true, false, ['cies', 'xes']],
// appendices (appendix)
['xi', 2, false, true, 'ices'],
// babies (baby)
['y', 1, false, true, 'ies'],
// quizzes (quiz)
['ziuq', 4, true, false, 'quizzes'],
// waltzes (waltz)
['z', 1, true, true, 'zes'],
];
/**
* A list of words which should not be inflected, reversed.
*/
private const UNINFLECTED = [
'',
'atad',
'reed',
'kcabdeef',
'hsif',
'ofni',
'esoom',
'seires',
'peehs',
'seiceps',
];
/**
* {@inheritdoc}
*/
public function singularize(string $plural): array
{
$pluralRev = strrev($plural);
$lowerPluralRev = strtolower($pluralRev);
$pluralLength = \strlen($lowerPluralRev);
// Check if the word is one which is not inflected, return early if so
if (\in_array($lowerPluralRev, self::UNINFLECTED, true)) {
return [$plural];
}
// The outer loop iterates over the entries of the plural table
// The inner loop $j iterates over the characters of the plural suffix
// in the plural table to compare them with the characters of the actual
// given plural suffix
foreach (self::PLURAL_MAP as $map) {
$suffix = $map[0];
$suffixLength = $map[1];
$j = 0;
// Compare characters in the plural table and of the suffix of the
// given plural one by one
while ($suffix[$j] === $lowerPluralRev[$j]) {
// Let $j point to the next character
++$j;
// Successfully compared the last character
// Add an entry with the singular suffix to the singular array
if ($j === $suffixLength) {
// Is there any character preceding the suffix in the plural string?
if ($j < $pluralLength) {
$nextIsVocal = false !== strpos('aeiou', $lowerPluralRev[$j]);
if (!$map[2] && $nextIsVocal) {
// suffix may not succeed a vocal but next char is one
break;
}
if (!$map[3] && !$nextIsVocal) {
// suffix may not succeed a consonant but next char is one
break;
}
}
$newBase = substr($plural, 0, $pluralLength - $suffixLength);
$newSuffix = $map[4];
// Check whether the first character in the plural suffix
// is uppercased. If yes, uppercase the first character in
// the singular suffix too
$firstUpper = ctype_upper($pluralRev[$j - 1]);
if (\is_array($newSuffix)) {
$singulars = [];
foreach ($newSuffix as $newSuffixEntry) {
$singulars[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
}
return $singulars;
}
return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
}
// Suffix is longer than word
if ($j === $pluralLength) {
break;
}
}
}
// Assume that plural and singular is identical
return [$plural];
}
/**
* {@inheritdoc}
*/
public function pluralize(string $singular): array
{
$singularRev = strrev($singular);
$lowerSingularRev = strtolower($singularRev);
$singularLength = \strlen($lowerSingularRev);
// Check if the word is one which is not inflected, return early if so
if (\in_array($lowerSingularRev, self::UNINFLECTED, true)) {
return [$singular];
}
// The outer loop iterates over the entries of the singular table
// The inner loop $j iterates over the characters of the singular suffix
// in the singular table to compare them with the characters of the actual
// given singular suffix
foreach (self::SINGULAR_MAP as $map) {
$suffix = $map[0];
$suffixLength = $map[1];
$j = 0;
// Compare characters in the singular table and of the suffix of the
// given plural one by one
while ($suffix[$j] === $lowerSingularRev[$j]) {
// Let $j point to the next character
++$j;
// Successfully compared the last character
// Add an entry with the plural suffix to the plural array
if ($j === $suffixLength) {
// Is there any character preceding the suffix in the plural string?
if ($j < $singularLength) {
$nextIsVocal = false !== strpos('aeiou', $lowerSingularRev[$j]);
if (!$map[2] && $nextIsVocal) {
// suffix may not succeed a vocal but next char is one
break;
}
if (!$map[3] && !$nextIsVocal) {
// suffix may not succeed a consonant but next char is one
break;
}
}
$newBase = substr($singular, 0, $singularLength - $suffixLength);
$newSuffix = $map[4];
// Check whether the first character in the singular suffix
// is uppercased. If yes, uppercase the first character in
// the singular suffix too
$firstUpper = ctype_upper($singularRev[$j - 1]);
if (\is_array($newSuffix)) {
$plurals = [];
foreach ($newSuffix as $newSuffixEntry) {
$plurals[] = $newBase.($firstUpper ? ucfirst($newSuffixEntry) : $newSuffixEntry);
}
return $plurals;
}
return [$newBase.($firstUpper ? ucfirst($newSuffix) : $newSuffix)];
}
// Suffix is longer than word
if ($j === $singularLength) {
break;
}
}
}
// Assume that plural is singular with a trailing `s`
return [$singular.'s'];
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Inflector;
interface InflectorInterface
{
/**
* Returns the singular forms of a string.
*
* If the method can't determine the form with certainty, several possible singulars are returned.
*
* @return string[] An array of possible singular forms
*/
public function singularize(string $plural): array;
/**
* Returns the plural forms of a string.
*
* If the method can't determine the form with certainty, several possible plurals are returned.
*
* @return string[] An array of possible plural forms
*/
public function pluralize(string $singular): array;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Inflector;
/**
* French inflector.
*
* This class does only inflect nouns; not adjectives nor composed words like "soixante-dix".
*/
final class FrenchInflector implements InflectorInterface
{
/**
* A list of all rules for pluralise.
*
* @see https://la-conjugaison.nouvelobs.com/regles/grammaire/le-pluriel-des-noms-121.php
*/
private const PLURALIZE_REGEXP = [
// First entry: regexp
// Second entry: replacement
// Words finishing with "s", "x" or "z" are invariables
// Les mots finissant par "s", "x" ou "z" sont invariables
['/(s|x|z)$/i', '\1'],
// Words finishing with "eau" are pluralized with a "x"
// Les mots finissant par "eau" prennent tous un "x" au pluriel
['/(eau)$/i', '\1x'],
// Words finishing with "au" are pluralized with a "x" excepted "landau"
// Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
['/^(landau)$/i', '\1s'],
['/(au)$/i', '\1x'],
// Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
// Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
['/^(pneu|bleu|émeu)$/i', '\1s'],
['/(eu)$/i', '\1x'],
// Words finishing with "al" are pluralized with a "aux" excepted
// Les mots finissant en "al" se terminent en "aux" sauf
['/^(bal|carnaval|caracal|chacal|choral|corral|étal|festival|récital|val)$/i', '\1s'],
['/al$/i', '\1aux'],
// Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
['/^(aspir|b|cor|ém|ferm|soupir|trav|vant|vitr)ail$/i', '\1aux'],
// Bijou, caillou, chou, genou, hibou, joujou et pou qui prennent un x au pluriel
['/^(bij|caill|ch|gen|hib|jouj|p)ou$/i', '\1oux'],
// Invariable words
['/^(cinquante|soixante|mille)$/i', '\1'],
// French titles
['/^(mon|ma)(sieur|dame|demoiselle|seigneur)$/', 'mes\2s'],
['/^(Mon|Ma)(sieur|dame|demoiselle|seigneur)$/', 'Mes\2s'],
];
/**
* A list of all rules for singularize.
*/
private const SINGULARIZE_REGEXP = [
// First entry: regexp
// Second entry: replacement
// Aspirail, bail, corail, émail, fermail, soupirail, travail, vantail et vitrail font leur pluriel en -aux
['/((aspir|b|cor|ém|ferm|soupir|trav|vant|vitr))aux$/i', '\1ail'],
// Words finishing with "eau" are pluralized with a "x"
// Les mots finissant par "eau" prennent tous un "x" au pluriel
['/(eau)x$/i', '\1'],
// Words finishing with "al" are pluralized with a "aux" expected
// Les mots finissant en "al" se terminent en "aux" sauf
['/(amir|anim|arsen|boc|can|capit|capor|chev|crist|génér|hopit|hôpit|idé|journ|littor|loc|m|mét|minér|princip|radic|termin)aux$/i', '\1al'],
// Words finishing with "au" are pluralized with a "x" excepted "landau"
// Les mots finissant par "au" prennent un "x" au pluriel sauf "landau"
['/(au)x$/i', '\1'],
// Words finishing with "eu" are pluralized with a "x" excepted "pneu", "bleu", "émeu"
// Les mots finissant en "eu" prennent un "x" au pluriel sauf "pneu", "bleu", "émeu"
['/(eu)x$/i', '\1'],
// Words finishing with "ou" are pluralized with a "s" excepted bijou, caillou, chou, genou, hibou, joujou, pou
// Les mots finissant par "ou" prennent un "s" sauf bijou, caillou, chou, genou, hibou, joujou, pou
['/(bij|caill|ch|gen|hib|jouj|p)oux$/i', '\1ou'],
// French titles
['/^mes(dame|demoiselle)s$/', 'ma\1'],
['/^Mes(dame|demoiselle)s$/', 'Ma\1'],
['/^mes(sieur|seigneur)s$/', 'mon\1'],
['/^Mes(sieur|seigneur)s$/', 'Mon\1'],
//Default rule
['/s$/i', ''],
];
/**
* A list of words which should not be inflected.
* This list is only used by singularize.
*/
private const UNINFLECTED = '/^(abcès|accès|abus|albatros|anchois|anglais|autobus|bois|brebis|carquois|cas|chas|colis|concours|corps|cours|cyprès|décès|devis|discours|dos|embarras|engrais|entrelacs|excès|fils|fois|gâchis|gars|glas|héros|intrus|jars|jus|kermès|lacis|legs|lilas|marais|mars|matelas|mépris|mets|mois|mors|obus|os|palais|paradis|parcours|pardessus|pays|plusieurs|poids|pois|pouls|printemps|processus|progrès|puits|pus|rabais|radis|recors|recours|refus|relais|remords|remous|rictus|rhinocéros|repas|rubis|sas|secours|sens|souris|succès|talus|tapis|tas|taudis|temps|tiers|univers|velours|verglas|vernis|virus)$/i';
/**
* {@inheritdoc}
*/
public function singularize(string $plural): array
{
if ($this->isInflectedWord($plural)) {
return [$plural];
}
foreach (self::SINGULARIZE_REGEXP as $rule) {
[$regexp, $replace] = $rule;
if (1 === preg_match($regexp, $plural)) {
return [preg_replace($regexp, $replace, $plural)];
}
}
return [$plural];
}
/**
* {@inheritdoc}
*/
public function pluralize(string $singular): array
{
if ($this->isInflectedWord($singular)) {
return [$singular];
}
foreach (self::PLURALIZE_REGEXP as $rule) {
[$regexp, $replace] = $rule;
if (1 === preg_match($regexp, $singular)) {
return [preg_replace($regexp, $replace, $singular)];
}
}
return [$singular.'s'];
}
private function isInflectedWord(string $word): bool
{
return 1 === preg_match(self::UNINFLECTED, $word);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
/**
* Represents a string of Unicode code points encoded as UTF-8.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*
* @throws ExceptionInterface
*/
class CodePointString extends AbstractUnicodeString
{
public function __construct(string $string = '')
{
if ('' !== $string && !preg_match('//u', $string)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$this->string = $string;
}
public function append(string ...$suffix): AbstractString
{
$str = clone $this;
$str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
if (!preg_match('//u', $str->string)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function chunk(int $length = 1): array
{
if (1 > $length) {
throw new InvalidArgumentException('The chunk length must be greater than zero.');
}
if ('' === $this->string) {
return [];
}
$rx = '/(';
while (65535 < $length) {
$rx .= '.{65535}';
$length -= 65535;
}
$rx .= '.{'.$length.'})/us';
$str = clone $this;
$chunks = [];
foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
$str->string = $chunk;
$chunks[] = clone $str;
}
return $chunks;
}
public function codePointsAt(int $offset): array
{
$str = $offset ? $this->slice($offset, 1) : $this;
return '' === $str->string ? [] : [mb_ord($str->string, 'UTF-8')];
}
public function endsWith($suffix): bool
{
if ($suffix instanceof AbstractString) {
$suffix = $suffix->string;
} elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
return parent::endsWith($suffix);
} else {
$suffix = (string) $suffix;
}
if ('' === $suffix || !preg_match('//u', $suffix)) {
return false;
}
if ($this->ignoreCase) {
return preg_match('{'.preg_quote($suffix).'$}iuD', $this->string);
}
return \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix));
}
public function equalsTo($string): bool
{
if ($string instanceof AbstractString) {
$string = $string->string;
} elseif (\is_array($string) || $string instanceof \Traversable) {
return parent::equalsTo($string);
} else {
$string = (string) $string;
}
if ('' !== $string && $this->ignoreCase) {
return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
}
return $string === $this->string;
}
public function indexOf($needle, int $offset = 0): ?int
{
if ($needle instanceof AbstractString) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOf($needle, $offset);
} else {
$needle = (string) $needle;
}
if ('' === $needle) {
return null;
}
$i = $this->ignoreCase ? mb_stripos($this->string, $needle, $offset, 'UTF-8') : mb_strpos($this->string, $needle, $offset, 'UTF-8');
return false === $i ? null : $i;
}
public function indexOfLast($needle, int $offset = 0): ?int
{
if ($needle instanceof AbstractString) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOfLast($needle, $offset);
} else {
$needle = (string) $needle;
}
if ('' === $needle) {
return null;
}
$i = $this->ignoreCase ? mb_strripos($this->string, $needle, $offset, 'UTF-8') : mb_strrpos($this->string, $needle, $offset, 'UTF-8');
return false === $i ? null : $i;
}
public function length(): int
{
return mb_strlen($this->string, 'UTF-8');
}
public function prepend(string ...$prefix): AbstractString
{
$str = clone $this;
$str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;
if (!preg_match('//u', $str->string)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function replace(string $from, string $to): AbstractString
{
$str = clone $this;
if ('' === $from || !preg_match('//u', $from)) {
return $str;
}
if ('' !== $to && !preg_match('//u', $to)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
if ($this->ignoreCase) {
$str->string = implode($to, preg_split('{'.preg_quote($from).'}iuD', $this->string));
} else {
$str->string = str_replace($from, $to, $this->string);
}
return $str;
}
public function slice(int $start = 0, int $length = null): AbstractString
{
$str = clone $this;
$str->string = mb_substr($this->string, $start, $length, 'UTF-8');
return $str;
}
public function splice(string $replacement, int $start = 0, int $length = null): AbstractString
{
if (!preg_match('//u', $replacement)) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
$str = clone $this;
$start = $start ? \strlen(mb_substr($this->string, 0, $start, 'UTF-8')) : 0;
$length = $length ? \strlen(mb_substr($this->string, $start, $length, 'UTF-8')) : $length;
$str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
return $str;
}
public function split(string $delimiter, int $limit = null, int $flags = null): array
{
if (1 > $limit = $limit ?? \PHP_INT_MAX) {
throw new InvalidArgumentException('Split limit must be a positive integer.');
}
if ('' === $delimiter) {
throw new InvalidArgumentException('Split delimiter is empty.');
}
if (null !== $flags) {
return parent::split($delimiter.'u', $limit, $flags);
}
if (!preg_match('//u', $delimiter)) {
throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
}
$str = clone $this;
$chunks = $this->ignoreCase
? preg_split('{'.preg_quote($delimiter).'}iuD', $this->string, $limit)
: explode($delimiter, $this->string, $limit);
foreach ($chunks as &$chunk) {
$str->string = $chunk;
$chunk = clone $str;
}
return $chunks;
}
public function startsWith($prefix): bool
{
if ($prefix instanceof AbstractString) {
$prefix = $prefix->string;
} elseif (\is_array($prefix) || $prefix instanceof \Traversable) {
return parent::startsWith($prefix);
} else {
$prefix = (string) $prefix;
}
if ('' === $prefix || !preg_match('//u', $prefix)) {
return false;
}
if ($this->ignoreCase) {
return 0 === mb_stripos($this->string, $prefix, 0, 'UTF-8');
}
return 0 === strncmp($this->string, $prefix, \strlen($prefix));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;
/**
* Represents a string of abstract characters.
*
* Unicode defines 3 types of "characters" (bytes, code points and grapheme clusters).
* This class is the abstract type to use as a type-hint when the logic you want to
* implement doesn't care about the exact variant it deals with.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*
* @throws ExceptionInterface
*/
abstract class AbstractString implements \Stringable, \JsonSerializable
{
public const PREG_PATTERN_ORDER = \PREG_PATTERN_ORDER;
public const PREG_SET_ORDER = \PREG_SET_ORDER;
public const PREG_OFFSET_CAPTURE = \PREG_OFFSET_CAPTURE;
public const PREG_UNMATCHED_AS_NULL = \PREG_UNMATCHED_AS_NULL;
public const PREG_SPLIT = 0;
public const PREG_SPLIT_NO_EMPTY = \PREG_SPLIT_NO_EMPTY;
public const PREG_SPLIT_DELIM_CAPTURE = \PREG_SPLIT_DELIM_CAPTURE;
public const PREG_SPLIT_OFFSET_CAPTURE = \PREG_SPLIT_OFFSET_CAPTURE;
protected $string = '';
protected $ignoreCase = false;
abstract public function __construct(string $string = '');
/**
* Unwraps instances of AbstractString back to strings.
*
* @return string[]|array
*/
public static function unwrap(array $values): array
{
foreach ($values as $k => $v) {
if ($v instanceof self) {
$values[$k] = $v->__toString();
} elseif (\is_array($v) && $values[$k] !== $v = static::unwrap($v)) {
$values[$k] = $v;
}
}
return $values;
}
/**
* Wraps (and normalizes) strings in instances of AbstractString.
*
* @return static[]|array
*/
public static function wrap(array $values): array
{
$i = 0;
$keys = null;
foreach ($values as $k => $v) {
if (\is_string($k) && '' !== $k && $k !== $j = (string) new static($k)) {
$keys = $keys ?? array_keys($values);
$keys[$i] = $j;
}
if (\is_string($v)) {
$values[$k] = new static($v);
} elseif (\is_array($v) && $values[$k] !== $v = static::wrap($v)) {
$values[$k] = $v;
}
++$i;
}
return null !== $keys ? array_combine($keys, $values) : $values;
}
/**
* @param string|string[] $needle
*
* @return static
*/
public function after($needle, bool $includeNeedle = false, int $offset = 0): self
{
$str = clone $this;
$i = \PHP_INT_MAX;
foreach ((array) $needle as $n) {
$n = (string) $n;
$j = $this->indexOf($n, $offset);
if (null !== $j && $j < $i) {
$i = $j;
$str->string = $n;
}
}
if (\PHP_INT_MAX === $i) {
return $str;
}
if (!$includeNeedle) {
$i += $str->length();
}
return $this->slice($i);
}
/**
* @param string|string[] $needle
*
* @return static
*/
public function afterLast($needle, bool $includeNeedle = false, int $offset = 0): self
{
$str = clone $this;
$i = null;
foreach ((array) $needle as $n) {
$n = (string) $n;
$j = $this->indexOfLast($n, $offset);
if (null !== $j && $j >= $i) {
$i = $offset = $j;
$str->string = $n;
}
}
if (null === $i) {
return $str;
}
if (!$includeNeedle) {
$i += $str->length();
}
return $this->slice($i);
}
/**
* @return static
*/
abstract public function append(string ...$suffix): self;
/**
* @param string|string[] $needle
*
* @return static
*/
public function before($needle, bool $includeNeedle = false, int $offset = 0): self
{
$str = clone $this;
$i = \PHP_INT_MAX;
foreach ((array) $needle as $n) {
$n = (string) $n;
$j = $this->indexOf($n, $offset);
if (null !== $j && $j < $i) {
$i = $j;
$str->string = $n;
}
}
if (\PHP_INT_MAX === $i) {
return $str;
}
if ($includeNeedle) {
$i += $str->length();
}
return $this->slice(0, $i);
}
/**
* @param string|string[] $needle
*
* @return static
*/
public function beforeLast($needle, bool $includeNeedle = false, int $offset = 0): self
{
$str = clone $this;
$i = null;
foreach ((array) $needle as $n) {
$n = (string) $n;
$j = $this->indexOfLast($n, $offset);
if (null !== $j && $j >= $i) {
$i = $offset = $j;
$str->string = $n;
}
}
if (null === $i) {
return $str;
}
if ($includeNeedle) {
$i += $str->length();
}
return $this->slice(0, $i);
}
/**
* @return int[]
*/
public function bytesAt(int $offset): array
{
$str = $this->slice($offset, 1);
return '' === $str->string ? [] : array_values(unpack('C*', $str->string));
}
/**
* @return static
*/
abstract public function camel(): self;
/**
* @return static[]
*/
abstract public function chunk(int $length = 1): array;
/**
* @return static
*/
public function collapseWhitespace(): self
{
$str = clone $this;
$str->string = trim(preg_replace('/(?:\s{2,}+|[^\S ])/', ' ', $str->string));
return $str;
}
/**
* @param string|string[] $needle
*/
public function containsAny($needle): bool
{
return null !== $this->indexOf($needle);
}
/**
* @param string|string[] $suffix
*/
public function endsWith($suffix): bool
{
if (!\is_array($suffix) && !$suffix instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
foreach ($suffix as $s) {
if ($this->endsWith((string) $s)) {
return true;
}
}
return false;
}
/**
* @return static
*/
public function ensureEnd(string $suffix): self
{
if (!$this->endsWith($suffix)) {
return $this->append($suffix);
}
$suffix = preg_quote($suffix);
$regex = '{('.$suffix.')(?:'.$suffix.')++$}D';
return $this->replaceMatches($regex.($this->ignoreCase ? 'i' : ''), '$1');
}
/**
* @return static
*/
public function ensureStart(string $prefix): self
{
$prefix = new static($prefix);
if (!$this->startsWith($prefix)) {
return $this->prepend($prefix);
}
$str = clone $this;
$i = $prefixLen = $prefix->length();
while ($this->indexOf($prefix, $i) === $i) {
$str = $str->slice($prefixLen);
$i += $prefixLen;
}
return $str;
}
/**
* @param string|string[] $string
*/
public function equalsTo($string): bool
{
if (!\is_array($string) && !$string instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
foreach ($string as $s) {
if ($this->equalsTo((string) $s)) {
return true;
}
}
return false;
}
/**
* @return static
*/
abstract public function folded(): self;
/**
* @return static
*/
public function ignoreCase(): self
{
$str = clone $this;
$str->ignoreCase = true;
return $str;
}
/**
* @param string|string[] $needle
*/
public function indexOf($needle, int $offset = 0): ?int
{
if (!\is_array($needle) && !$needle instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
$i = \PHP_INT_MAX;
foreach ($needle as $n) {
$j = $this->indexOf((string) $n, $offset);
if (null !== $j && $j < $i) {
$i = $j;
}
}
return \PHP_INT_MAX === $i ? null : $i;
}
/**
* @param string|string[] $needle
*/
public function indexOfLast($needle, int $offset = 0): ?int
{
if (!\is_array($needle) && !$needle instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
$i = null;
foreach ($needle as $n) {
$j = $this->indexOfLast((string) $n, $offset);
if (null !== $j && $j >= $i) {
$i = $offset = $j;
}
}
return $i;
}
public function isEmpty(): bool
{
return '' === $this->string;
}
/**
* @return static
*/
abstract public function join(array $strings, string $lastGlue = null): self;
public function jsonSerialize(): string
{
return $this->string;
}
abstract public function length(): int;
/**
* @return static
*/
abstract public function lower(): self;
/**
* Matches the string using a regular expression.
*
* Pass PREG_PATTERN_ORDER or PREG_SET_ORDER as $flags to get all occurrences matching the regular expression.
*
* @return array All matches in a multi-dimensional array ordered according to flags
*/
abstract public function match(string $regexp, int $flags = 0, int $offset = 0): array;
/**
* @return static
*/
abstract public function padBoth(int $length, string $padStr = ' '): self;
/**
* @return static
*/
abstract public function padEnd(int $length, string $padStr = ' '): self;
/**
* @return static
*/
abstract public function padStart(int $length, string $padStr = ' '): self;
/**
* @return static
*/
abstract public function prepend(string ...$prefix): self;
/**
* @return static
*/
public function repeat(int $multiplier): self
{
if (0 > $multiplier) {
throw new InvalidArgumentException(sprintf('Multiplier must be positive, %d given.', $multiplier));
}
$str = clone $this;
$str->string = str_repeat($str->string, $multiplier);
return $str;
}
/**
* @return static
*/
abstract public function replace(string $from, string $to): self;
/**
* @param string|callable $to
*
* @return static
*/
abstract public function replaceMatches(string $fromRegexp, $to): self;
/**
* @return static
*/
abstract public function reverse(): self;
/**
* @return static
*/
abstract public function slice(int $start = 0, int $length = null): self;
/**
* @return static
*/
abstract public function snake(): self;
/**
* @return static
*/
abstract public function splice(string $replacement, int $start = 0, int $length = null): self;
/**
* @return static[]
*/
public function split(string $delimiter, int $limit = null, int $flags = null): array
{
if (null === $flags) {
throw new \TypeError('Split behavior when $flags is null must be implemented by child classes.');
}
if ($this->ignoreCase) {
$delimiter .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (false === $chunks = preg_split($delimiter, $this->string, $limit, $flags)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Splitting failed with '.$k.'.');
}
}
throw new RuntimeException('Splitting failed with unknown error code.');
}
} finally {
restore_error_handler();
}
$str = clone $this;
if (self::PREG_SPLIT_OFFSET_CAPTURE & $flags) {
foreach ($chunks as &$chunk) {
$str->string = $chunk[0];
$chunk[0] = clone $str;
}
} else {
foreach ($chunks as &$chunk) {
$str->string = $chunk;
$chunk = clone $str;
}
}
return $chunks;
}
/**
* @param string|string[] $prefix
*/
public function startsWith($prefix): bool
{
if (!\is_array($prefix) && !$prefix instanceof \Traversable) {
throw new \TypeError(sprintf('Method "%s()" must be overridden by class "%s" to deal with non-iterable values.', __FUNCTION__, static::class));
}
foreach ($prefix as $prefix) {
if ($this->startsWith((string) $prefix)) {
return true;
}
}
return false;
}
/**
* @return static
*/
abstract public function title(bool $allWords = false): self;
public function toByteString(string $toEncoding = null): ByteString
{
$b = new ByteString();
$toEncoding = \in_array($toEncoding, ['utf8', 'utf-8', 'UTF8'], true) ? 'UTF-8' : $toEncoding;
if (null === $toEncoding || $toEncoding === $fromEncoding = $this instanceof AbstractUnicodeString || preg_match('//u', $b->string) ? 'UTF-8' : 'Windows-1252') {
$b->string = $this->string;
return $b;
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
try {
$b->string = mb_convert_encoding($this->string, $toEncoding, 'UTF-8');
} catch (InvalidArgumentException $e) {
if (!\function_exists('iconv')) {
throw $e;
}
$b->string = iconv('UTF-8', $toEncoding, $this->string);
}
} finally {
restore_error_handler();
}
return $b;
}
public function toCodePointString(): CodePointString
{
return new CodePointString($this->string);
}
public function toString(): string
{
return $this->string;
}
public function toUnicodeString(): UnicodeString
{
return new UnicodeString($this->string);
}
/**
* @return static
*/
abstract public function trim(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
/**
* @return static
*/
abstract public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
/**
* @return static
*/
abstract public function trimStart(string $chars = " \t\n\r\0\x0B\x0C\u{A0}\u{FEFF}"): self;
/**
* @return static
*/
public function truncate(int $length, string $ellipsis = '', bool $cut = true): self
{
$stringLength = $this->length();
if ($stringLength <= $length) {
return clone $this;
}
$ellipsisLength = '' !== $ellipsis ? (new static($ellipsis))->length() : 0;
if ($length < $ellipsisLength) {
$ellipsisLength = 0;
}
if (!$cut) {
if (null === $length = $this->indexOf([' ', "\r", "\n", "\t"], ($length ?: 1) - 1)) {
return clone $this;
}
$length += $ellipsisLength;
}
$str = $this->slice(0, $length - $ellipsisLength);
return $ellipsisLength ? $str->trimEnd()->append($ellipsis) : $str;
}
/**
* @return static
*/
abstract public function upper(): self;
/**
* Returns the printable length on a terminal.
*/
abstract public function width(bool $ignoreAnsiDecoration = true): int;
/**
* @return static
*/
public function wordwrap(int $width = 75, string $break = "\n", bool $cut = false): self
{
$lines = '' !== $break ? $this->split($break) : [clone $this];
$chars = [];
$mask = '';
if (1 === \count($lines) && '' === $lines[0]->string) {
return $lines[0];
}
foreach ($lines as $i => $line) {
if ($i) {
$chars[] = $break;
$mask .= '#';
}
foreach ($line->chunk() as $char) {
$chars[] = $char->string;
$mask .= ' ' === $char->string ? ' ' : '?';
}
}
$string = '';
$j = 0;
$b = $i = -1;
$mask = wordwrap($mask, $width, '#', $cut);
while (false !== $b = strpos($mask, '#', $b + 1)) {
for (++$i; $i < $b; ++$i) {
$string .= $chars[$j];
unset($chars[$j++]);
}
if ($break === $chars[$j] || ' ' === $chars[$j]) {
unset($chars[$j++]);
}
$string .= $break;
}
$str = clone $this;
$str->string = $string.implode('', $chars);
return $str;
}
public function __sleep(): array
{
return ['string'];
}
public function __clone()
{
$this->ignoreCase = false;
}
public function __toString(): string
{
return $this->string;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
/**
* Represents a string of Unicode grapheme clusters encoded as UTF-8.
*
* A letter followed by combining characters (accents typically) form what Unicode defines
* as a grapheme cluster: a character as humans mean it in written texts. This class knows
* about the concept and won't split a letter apart from its combining accents. It also
* ensures all string comparisons happen on their canonically-composed representation,
* ignoring e.g. the order in which accents are listed when a letter has many of them.
*
* @see https://unicode.org/reports/tr15/
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*
* @throws ExceptionInterface
*/
class UnicodeString extends AbstractUnicodeString
{
public function __construct(string $string = '')
{
$this->string = normalizer_is_normalized($string) ? $string : normalizer_normalize($string);
if (false === $this->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
}
public function append(string ...$suffix): AbstractString
{
$str = clone $this;
$str->string = $this->string.(1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix));
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
if (false === $str->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function chunk(int $length = 1): array
{
if (1 > $length) {
throw new InvalidArgumentException('The chunk length must be greater than zero.');
}
if ('' === $this->string) {
return [];
}
$rx = '/(';
while (65535 < $length) {
$rx .= '\X{65535}';
$length -= 65535;
}
$rx .= '\X{'.$length.'})/u';
$str = clone $this;
$chunks = [];
foreach (preg_split($rx, $this->string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY) as $chunk) {
$str->string = $chunk;
$chunks[] = clone $str;
}
return $chunks;
}
public function endsWith($suffix): bool
{
if ($suffix instanceof AbstractString) {
$suffix = $suffix->string;
} elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
return parent::endsWith($suffix);
} else {
$suffix = (string) $suffix;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($suffix, $form) ?: $suffix = normalizer_normalize($suffix, $form);
if ('' === $suffix || false === $suffix) {
return false;
}
if ($this->ignoreCase) {
return 0 === mb_stripos(grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix)), $suffix, 0, 'UTF-8');
}
return $suffix === grapheme_extract($this->string, \strlen($suffix), \GRAPHEME_EXTR_MAXBYTES, \strlen($this->string) - \strlen($suffix));
}
public function equalsTo($string): bool
{
if ($string instanceof AbstractString) {
$string = $string->string;
} elseif (\is_array($string) || $string instanceof \Traversable) {
return parent::equalsTo($string);
} else {
$string = (string) $string;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($string, $form) ?: $string = normalizer_normalize($string, $form);
if ('' !== $string && false !== $string && $this->ignoreCase) {
return \strlen($string) === \strlen($this->string) && 0 === mb_stripos($this->string, $string, 0, 'UTF-8');
}
return $string === $this->string;
}
public function indexOf($needle, int $offset = 0): ?int
{
if ($needle instanceof AbstractString) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOf($needle, $offset);
} else {
$needle = (string) $needle;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form);
if ('' === $needle || false === $needle) {
return null;
}
try {
$i = $this->ignoreCase ? grapheme_stripos($this->string, $needle, $offset) : grapheme_strpos($this->string, $needle, $offset);
} catch (\ValueError $e) {
return null;
}
return false === $i ? null : $i;
}
public function indexOfLast($needle, int $offset = 0): ?int
{
if ($needle instanceof AbstractString) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOfLast($needle, $offset);
} else {
$needle = (string) $needle;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($needle, $form) ?: $needle = normalizer_normalize($needle, $form);
if ('' === $needle || false === $needle) {
return null;
}
$string = $this->string;
if (0 > $offset) {
// workaround https://bugs.php.net/74264
if (0 > $offset += grapheme_strlen($needle)) {
$string = grapheme_substr($string, 0, $offset);
}
$offset = 0;
}
$i = $this->ignoreCase ? grapheme_strripos($string, $needle, $offset) : grapheme_strrpos($string, $needle, $offset);
return false === $i ? null : $i;
}
public function join(array $strings, string $lastGlue = null): AbstractString
{
$str = parent::join($strings, $lastGlue);
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
return $str;
}
public function length(): int
{
return grapheme_strlen($this->string);
}
/**
* @return static
*/
public function normalize(int $form = self::NFC): parent
{
$str = clone $this;
if (\in_array($form, [self::NFC, self::NFKC], true)) {
normalizer_is_normalized($str->string, $form) ?: $str->string = normalizer_normalize($str->string, $form);
} elseif (!\in_array($form, [self::NFD, self::NFKD], true)) {
throw new InvalidArgumentException('Unsupported normalization form.');
} elseif (!normalizer_is_normalized($str->string, $form)) {
$str->string = normalizer_normalize($str->string, $form);
$str->ignoreCase = null;
}
return $str;
}
public function prepend(string ...$prefix): AbstractString
{
$str = clone $this;
$str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$this->string;
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
if (false === $str->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function replace(string $from, string $to): AbstractString
{
$str = clone $this;
normalizer_is_normalized($from) ?: $from = normalizer_normalize($from);
if ('' !== $from && false !== $from) {
$tail = $str->string;
$result = '';
$indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos';
while ('' !== $tail && false !== $i = $indexOf($tail, $from)) {
$slice = grapheme_substr($tail, 0, $i);
$result .= $slice.$to;
$tail = substr($tail, \strlen($slice) + \strlen($from));
}
$str->string = $result.$tail;
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
if (false === $str->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
}
return $str;
}
public function replaceMatches(string $fromRegexp, $to): AbstractString
{
$str = parent::replaceMatches($fromRegexp, $to);
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
return $str;
}
public function slice(int $start = 0, int $length = null): AbstractString
{
$str = clone $this;
if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) {
$start = 0;
}
$str->string = (string) grapheme_substr($this->string, $start, $length ?? 2147483647);
return $str;
}
public function splice(string $replacement, int $start = 0, int $length = null): AbstractString
{
$str = clone $this;
if (\PHP_VERSION_ID < 80000 && 0 > $start && grapheme_strlen($this->string) < -$start) {
$start = 0;
}
$start = $start ? \strlen(grapheme_substr($this->string, 0, $start)) : 0;
$length = $length ? \strlen(grapheme_substr($this->string, $start, $length ?? 2147483647)) : $length;
$str->string = substr_replace($this->string, $replacement, $start, $length ?? 2147483647);
normalizer_is_normalized($str->string) ?: $str->string = normalizer_normalize($str->string);
if (false === $str->string) {
throw new InvalidArgumentException('Invalid UTF-8 string.');
}
return $str;
}
public function split(string $delimiter, int $limit = null, int $flags = null): array
{
if (1 > $limit = $limit ?? 2147483647) {
throw new InvalidArgumentException('Split limit must be a positive integer.');
}
if ('' === $delimiter) {
throw new InvalidArgumentException('Split delimiter is empty.');
}
if (null !== $flags) {
return parent::split($delimiter.'u', $limit, $flags);
}
normalizer_is_normalized($delimiter) ?: $delimiter = normalizer_normalize($delimiter);
if (false === $delimiter) {
throw new InvalidArgumentException('Split delimiter is not a valid UTF-8 string.');
}
$str = clone $this;
$tail = $this->string;
$chunks = [];
$indexOf = $this->ignoreCase ? 'grapheme_stripos' : 'grapheme_strpos';
while (1 < $limit && false !== $i = $indexOf($tail, $delimiter)) {
$str->string = grapheme_substr($tail, 0, $i);
$chunks[] = clone $str;
$tail = substr($tail, \strlen($str->string) + \strlen($delimiter));
--$limit;
}
$str->string = $tail;
$chunks[] = clone $str;
return $chunks;
}
public function startsWith($prefix): bool
{
if ($prefix instanceof AbstractString) {
$prefix = $prefix->string;
} elseif (\is_array($prefix) || $prefix instanceof \Traversable) {
return parent::startsWith($prefix);
} else {
$prefix = (string) $prefix;
}
$form = null === $this->ignoreCase ? \Normalizer::NFD : \Normalizer::NFC;
normalizer_is_normalized($prefix, $form) ?: $prefix = normalizer_normalize($prefix, $form);
if ('' === $prefix || false === $prefix) {
return false;
}
if ($this->ignoreCase) {
return 0 === mb_stripos(grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES), $prefix, 0, 'UTF-8');
}
return $prefix === grapheme_extract($this->string, \strlen($prefix), \GRAPHEME_EXTR_MAXBYTES);
}
public function __wakeup()
{
if (!\is_string($this->string)) {
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
}
public function __clone()
{
if (null === $this->ignoreCase) {
normalizer_is_normalized($this->string) ?: $this->string = normalizer_normalize($this->string);
}
$this->ignoreCase = false;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
use Symfony\Component\String\Exception\ExceptionInterface;
use Symfony\Component\String\Exception\InvalidArgumentException;
use Symfony\Component\String\Exception\RuntimeException;
/**
* Represents a binary-safe string of bytes.
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Hugo Hamon <hugohamon@neuf.fr>
*
* @throws ExceptionInterface
*/
class ByteString extends AbstractString
{
private const ALPHABET_ALPHANUMERIC = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
public function __construct(string $string = '')
{
$this->string = $string;
}
/*
* The following method was derived from code of the Hack Standard Library (v4.40 - 2020-05-03)
*
* https://github.com/hhvm/hsl/blob/80a42c02f036f72a42f0415e80d6b847f4bf62d5/src/random/private.php#L16
*
* Code subject to the MIT license (https://github.com/hhvm/hsl/blob/master/LICENSE).
*
* Copyright (c) 2004-2020, Facebook, Inc. (https://www.facebook.com/)
*/
public static function fromRandom(int $length = 16, string $alphabet = null): self
{
if ($length <= 0) {
throw new InvalidArgumentException(sprintf('A strictly positive length is expected, "%d" given.', $length));
}
$alphabet = $alphabet ?? self::ALPHABET_ALPHANUMERIC;
$alphabetSize = \strlen($alphabet);
$bits = (int) ceil(log($alphabetSize, 2.0));
if ($bits <= 0 || $bits > 56) {
throw new InvalidArgumentException('The length of the alphabet must in the [2^1, 2^56] range.');
}
$ret = '';
while ($length > 0) {
$urandomLength = (int) ceil(2 * $length * $bits / 8.0);
$data = random_bytes($urandomLength);
$unpackedData = 0;
$unpackedBits = 0;
for ($i = 0; $i < $urandomLength && $length > 0; ++$i) {
// Unpack 8 bits
$unpackedData = ($unpackedData << 8) | \ord($data[$i]);
$unpackedBits += 8;
// While we have enough bits to select a character from the alphabet, keep
// consuming the random data
for (; $unpackedBits >= $bits && $length > 0; $unpackedBits -= $bits) {
$index = ($unpackedData & ((1 << $bits) - 1));
$unpackedData >>= $bits;
// Unfortunately, the alphabet size is not necessarily a power of two.
// Worst case, it is 2^k + 1, which means we need (k+1) bits and we
// have around a 50% chance of missing as k gets larger
if ($index < $alphabetSize) {
$ret .= $alphabet[$index];
--$length;
}
}
}
}
return new static($ret);
}
public function bytesAt(int $offset): array
{
$str = $this->string[$offset] ?? '';
return '' === $str ? [] : [\ord($str)];
}
public function append(string ...$suffix): parent
{
$str = clone $this;
$str->string .= 1 >= \count($suffix) ? ($suffix[0] ?? '') : implode('', $suffix);
return $str;
}
public function camel(): parent
{
$str = clone $this;
$str->string = lcfirst(str_replace(' ', '', ucwords(preg_replace('/[^a-zA-Z0-9\x7f-\xff]++/', ' ', $this->string))));
return $str;
}
public function chunk(int $length = 1): array
{
if (1 > $length) {
throw new InvalidArgumentException('The chunk length must be greater than zero.');
}
if ('' === $this->string) {
return [];
}
$str = clone $this;
$chunks = [];
foreach (str_split($this->string, $length) as $chunk) {
$str->string = $chunk;
$chunks[] = clone $str;
}
return $chunks;
}
public function endsWith($suffix): bool
{
if ($suffix instanceof parent) {
$suffix = $suffix->string;
} elseif (\is_array($suffix) || $suffix instanceof \Traversable) {
return parent::endsWith($suffix);
} else {
$suffix = (string) $suffix;
}
return '' !== $suffix && \strlen($this->string) >= \strlen($suffix) && 0 === substr_compare($this->string, $suffix, -\strlen($suffix), null, $this->ignoreCase);
}
public function equalsTo($string): bool
{
if ($string instanceof parent) {
$string = $string->string;
} elseif (\is_array($string) || $string instanceof \Traversable) {
return parent::equalsTo($string);
} else {
$string = (string) $string;
}
if ('' !== $string && $this->ignoreCase) {
return 0 === strcasecmp($string, $this->string);
}
return $string === $this->string;
}
public function folded(): parent
{
$str = clone $this;
$str->string = strtolower($str->string);
return $str;
}
public function indexOf($needle, int $offset = 0): ?int
{
if ($needle instanceof parent) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOf($needle, $offset);
} else {
$needle = (string) $needle;
}
if ('' === $needle) {
return null;
}
$i = $this->ignoreCase ? stripos($this->string, $needle, $offset) : strpos($this->string, $needle, $offset);
return false === $i ? null : $i;
}
public function indexOfLast($needle, int $offset = 0): ?int
{
if ($needle instanceof parent) {
$needle = $needle->string;
} elseif (\is_array($needle) || $needle instanceof \Traversable) {
return parent::indexOfLast($needle, $offset);
} else {
$needle = (string) $needle;
}
if ('' === $needle) {
return null;
}
$i = $this->ignoreCase ? strripos($this->string, $needle, $offset) : strrpos($this->string, $needle, $offset);
return false === $i ? null : $i;
}
public function isUtf8(): bool
{
return '' === $this->string || preg_match('//u', $this->string);
}
public function join(array $strings, string $lastGlue = null): parent
{
$str = clone $this;
$tail = null !== $lastGlue && 1 < \count($strings) ? $lastGlue.array_pop($strings) : '';
$str->string = implode($this->string, $strings).$tail;
return $str;
}
public function length(): int
{
return \strlen($this->string);
}
public function lower(): parent
{
$str = clone $this;
$str->string = strtolower($str->string);
return $str;
}
public function match(string $regexp, int $flags = 0, int $offset = 0): array
{
$match = ((\PREG_PATTERN_ORDER | \PREG_SET_ORDER) & $flags) ? 'preg_match_all' : 'preg_match';
if ($this->ignoreCase) {
$regexp .= 'i';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (false === $match($regexp, $this->string, $matches, $flags | \PREG_UNMATCHED_AS_NULL, $offset)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
}
} finally {
restore_error_handler();
}
return $matches;
}
public function padBoth(int $length, string $padStr = ' '): parent
{
$str = clone $this;
$str->string = str_pad($this->string, $length, $padStr, \STR_PAD_BOTH);
return $str;
}
public function padEnd(int $length, string $padStr = ' '): parent
{
$str = clone $this;
$str->string = str_pad($this->string, $length, $padStr, \STR_PAD_RIGHT);
return $str;
}
public function padStart(int $length, string $padStr = ' '): parent
{
$str = clone $this;
$str->string = str_pad($this->string, $length, $padStr, \STR_PAD_LEFT);
return $str;
}
public function prepend(string ...$prefix): parent
{
$str = clone $this;
$str->string = (1 >= \count($prefix) ? ($prefix[0] ?? '') : implode('', $prefix)).$str->string;
return $str;
}
public function replace(string $from, string $to): parent
{
$str = clone $this;
if ('' !== $from) {
$str->string = $this->ignoreCase ? str_ireplace($from, $to, $this->string) : str_replace($from, $to, $this->string);
}
return $str;
}
public function replaceMatches(string $fromRegexp, $to): parent
{
if ($this->ignoreCase) {
$fromRegexp .= 'i';
}
if (\is_array($to)) {
if (!\is_callable($to)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s::replaceMatches()" must be callable, array given.', static::class));
}
$replace = 'preg_replace_callback';
} else {
$replace = $to instanceof \Closure ? 'preg_replace_callback' : 'preg_replace';
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
if (null === $string = $replace($fromRegexp, $to, $this->string)) {
$lastError = preg_last_error();
foreach (get_defined_constants(true)['pcre'] as $k => $v) {
if ($lastError === $v && '_ERROR' === substr($k, -6)) {
throw new RuntimeException('Matching failed with '.$k.'.');
}
}
throw new RuntimeException('Matching failed with unknown error code.');
}
} finally {
restore_error_handler();
}
$str = clone $this;
$str->string = $string;
return $str;
}
public function reverse(): parent
{
$str = clone $this;
$str->string = strrev($str->string);
return $str;
}
public function slice(int $start = 0, int $length = null): parent
{
$str = clone $this;
$str->string = (string) substr($this->string, $start, $length ?? \PHP_INT_MAX);
return $str;
}
public function snake(): parent
{
$str = $this->camel()->title();
$str->string = strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], '\1_\2', $str->string));
return $str;
}
public function splice(string $replacement, int $start = 0, int $length = null): parent
{
$str = clone $this;
$str->string = substr_replace($this->string, $replacement, $start, $length ?? \PHP_INT_MAX);
return $str;
}
public function split(string $delimiter, int $limit = null, int $flags = null): array
{
if (1 > $limit = $limit ?? \PHP_INT_MAX) {
throw new InvalidArgumentException('Split limit must be a positive integer.');
}
if ('' === $delimiter) {
throw new InvalidArgumentException('Split delimiter is empty.');
}
if (null !== $flags) {
return parent::split($delimiter, $limit, $flags);
}
$str = clone $this;
$chunks = $this->ignoreCase
? preg_split('{'.preg_quote($delimiter).'}iD', $this->string, $limit)
: explode($delimiter, $this->string, $limit);
foreach ($chunks as &$chunk) {
$str->string = $chunk;
$chunk = clone $str;
}
return $chunks;
}
public function startsWith($prefix): bool
{
if ($prefix instanceof parent) {
$prefix = $prefix->string;
} elseif (!\is_string($prefix)) {
return parent::startsWith($prefix);
}
return '' !== $prefix && 0 === ($this->ignoreCase ? strncasecmp($this->string, $prefix, \strlen($prefix)) : strncmp($this->string, $prefix, \strlen($prefix)));
}
public function title(bool $allWords = false): parent
{
$str = clone $this;
$str->string = $allWords ? ucwords($str->string) : ucfirst($str->string);
return $str;
}
public function toUnicodeString(string $fromEncoding = null): UnicodeString
{
return new UnicodeString($this->toCodePointString($fromEncoding)->string);
}
public function toCodePointString(string $fromEncoding = null): CodePointString
{
$u = new CodePointString();
if (\in_array($fromEncoding, [null, 'utf8', 'utf-8', 'UTF8', 'UTF-8'], true) && preg_match('//u', $this->string)) {
$u->string = $this->string;
return $u;
}
set_error_handler(static function ($t, $m) { throw new InvalidArgumentException($m); });
try {
try {
$validEncoding = false !== mb_detect_encoding($this->string, $fromEncoding ?? 'Windows-1252', true);
} catch (InvalidArgumentException $e) {
if (!\function_exists('iconv')) {
throw $e;
}
$u->string = iconv($fromEncoding ?? 'Windows-1252', 'UTF-8', $this->string);
return $u;
}
} finally {
restore_error_handler();
}
if (!$validEncoding) {
throw new InvalidArgumentException(sprintf('Invalid "%s" string.', $fromEncoding ?? 'Windows-1252'));
}
$u->string = mb_convert_encoding($this->string, 'UTF-8', $fromEncoding ?? 'Windows-1252');
return $u;
}
public function trim(string $chars = " \t\n\r\0\x0B\x0C"): parent
{
$str = clone $this;
$str->string = trim($str->string, $chars);
return $str;
}
public function trimEnd(string $chars = " \t\n\r\0\x0B\x0C"): parent
{
$str = clone $this;
$str->string = rtrim($str->string, $chars);
return $str;
}
public function trimStart(string $chars = " \t\n\r\0\x0B\x0C"): parent
{
$str = clone $this;
$str->string = ltrim($str->string, $chars);
return $str;
}
public function upper(): parent
{
$str = clone $this;
$str->string = strtoupper($str->string);
return $str;
}
public function width(bool $ignoreAnsiDecoration = true): int
{
$string = preg_match('//u', $this->string) ? $this->string : preg_replace('/[\x80-\xFF]/', '?', $this->string);
return (new CodePointString($string))->width($ignoreAnsiDecoration);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
/**
* A string whose value is computed lazily by a callback.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class LazyString implements \Stringable, \JsonSerializable
{
private $value;
/**
* @param callable|array $callback A callable or a [Closure, method] lazy-callable
*
* @return static
*/
public static function fromCallable($callback, ...$arguments): self
{
if (!\is_callable($callback) && !(\is_array($callback) && isset($callback[0]) && $callback[0] instanceof \Closure && 2 >= \count($callback))) {
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a callable or a [Closure, method] lazy-callable, "%s" given.', __METHOD__, get_debug_type($callback)));
}
$lazyString = new static();
$lazyString->value = static function () use (&$callback, &$arguments, &$value): string {
if (null !== $arguments) {
if (!\is_callable($callback)) {
$callback[0] = $callback[0]();
$callback[1] = $callback[1] ?? '__invoke';
}
$value = $callback(...$arguments);
$callback = self::getPrettyName($callback);
$arguments = null;
}
return $value ?? '';
};
return $lazyString;
}
/**
* @param string|int|float|bool|\Stringable $value
*
* @return static
*/
public static function fromStringable($value): self
{
if (!self::isStringable($value)) {
throw new \TypeError(sprintf('Argument 1 passed to "%s()" must be a scalar or a stringable object, "%s" given.', __METHOD__, get_debug_type($value)));
}
if (\is_object($value)) {
return static::fromCallable([$value, '__toString']);
}
$lazyString = new static();
$lazyString->value = (string) $value;
return $lazyString;
}
/**
* Tells whether the provided value can be cast to string.
*/
final public static function isStringable($value): bool
{
return \is_string($value) || $value instanceof self || (\is_object($value) ? method_exists($value, '__toString') : is_scalar($value));
}
/**
* Casts scalars and stringable objects to strings.
*
* @param object|string|int|float|bool $value
*
* @throws \TypeError When the provided value is not stringable
*/
final public static function resolve($value): string
{
return $value;
}
/**
* @return string
*/
public function __toString()
{
if (\is_string($this->value)) {
return $this->value;
}
try {
return $this->value = ($this->value)();
} catch (\Throwable $e) {
if (\TypeError::class === \get_class($e) && __FILE__ === $e->getFile()) {
$type = explode(', ', $e->getMessage());
$type = substr(array_pop($type), 0, -\strlen(' returned'));
$r = new \ReflectionFunction($this->value);
$callback = $r->getStaticVariables()['callback'];
$e = new \TypeError(sprintf('Return value of %s() passed to %s::fromCallable() must be of the type string, %s returned.', $callback, static::class, $type));
}
if (\PHP_VERSION_ID < 70400) {
// leverage the ErrorHandler component with graceful fallback when it's not available
return trigger_error($e, \E_USER_ERROR);
}
throw $e;
}
}
public function __sleep(): array
{
$this->__toString();
return ['value'];
}
public function jsonSerialize(): string
{
return $this->__toString();
}
private function __construct()
{
}
private static function getPrettyName(callable $callback): string
{
if (\is_string($callback)) {
return $callback;
}
if (\is_array($callback)) {
$class = \is_object($callback[0]) ? get_debug_type($callback[0]) : $callback[0];
$method = $callback[1];
} elseif ($callback instanceof \Closure) {
$r = new \ReflectionFunction($callback);
if (false !== strpos($r->name, '{closure}') || !$class = $r->getClosureScopeClass()) {
return $r->name;
}
$class = $class->name;
$method = $r->name;
} else {
$class = get_debug_type($callback);
$method = '__invoke';
}
return $class.'::'.$method;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Exception;
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
Copyright (c) 2019-2021 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Slugger;
use Symfony\Component\String\AbstractUnicodeString;
use Symfony\Component\String\UnicodeString;
use Symfony\Contracts\Translation\LocaleAwareInterface;
if (!interface_exists(LocaleAwareInterface::class)) {
throw new \LogicException('You cannot use the "Symfony\Component\String\Slugger\AsciiSlugger" as the "symfony/translation-contracts" package is not installed. Try running "composer require symfony/translation-contracts".');
}
/**
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class AsciiSlugger implements SluggerInterface, LocaleAwareInterface
{
private const LOCALE_TO_TRANSLITERATOR_ID = [
'am' => 'Amharic-Latin',
'ar' => 'Arabic-Latin',
'az' => 'Azerbaijani-Latin',
'be' => 'Belarusian-Latin',
'bg' => 'Bulgarian-Latin',
'bn' => 'Bengali-Latin',
'de' => 'de-ASCII',
'el' => 'Greek-Latin',
'fa' => 'Persian-Latin',
'he' => 'Hebrew-Latin',
'hy' => 'Armenian-Latin',
'ka' => 'Georgian-Latin',
'kk' => 'Kazakh-Latin',
'ky' => 'Kirghiz-Latin',
'ko' => 'Korean-Latin',
'mk' => 'Macedonian-Latin',
'mn' => 'Mongolian-Latin',
'or' => 'Oriya-Latin',
'ps' => 'Pashto-Latin',
'ru' => 'Russian-Latin',
'sr' => 'Serbian-Latin',
'sr_Cyrl' => 'Serbian-Latin',
'th' => 'Thai-Latin',
'tk' => 'Turkmen-Latin',
'uk' => 'Ukrainian-Latin',
'uz' => 'Uzbek-Latin',
'zh' => 'Han-Latin',
];
private $defaultLocale;
private $symbolsMap = [
'en' => ['@' => 'at', '&' => 'and'],
];
/**
* Cache of transliterators per locale.
*
* @var \Transliterator[]
*/
private $transliterators = [];
/**
* @param array|\Closure|null $symbolsMap
*/
public function __construct(string $defaultLocale = null, $symbolsMap = null)
{
if (null !== $symbolsMap && !\is_array($symbolsMap) && !$symbolsMap instanceof \Closure) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be array, Closure or null, "%s" given.', __METHOD__, \gettype($symbolsMap)));
}
$this->defaultLocale = $defaultLocale;
$this->symbolsMap = $symbolsMap ?? $this->symbolsMap;
}
/**
* {@inheritdoc}
*/
public function setLocale($locale)
{
$this->defaultLocale = $locale;
}
/**
* {@inheritdoc}
*/
public function getLocale()
{
return $this->defaultLocale;
}
/**
* {@inheritdoc}
*/
public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString
{
$locale = $locale ?? $this->defaultLocale;
$transliterator = [];
if ('de' === $locale || 0 === strpos($locale, 'de_')) {
// Use the shortcut for German in UnicodeString::ascii() if possible (faster and no requirement on intl)
$transliterator = ['de-ASCII'];
} elseif (\function_exists('transliterator_transliterate') && $locale) {
$transliterator = (array) $this->createTransliterator($locale);
}
if ($this->symbolsMap instanceof \Closure) {
$symbolsMap = $this->symbolsMap;
array_unshift($transliterator, static function ($s) use ($symbolsMap, $locale) {
return $symbolsMap($s, $locale);
});
}
$unicodeString = (new UnicodeString($string))->ascii($transliterator);
if (\is_array($this->symbolsMap) && isset($this->symbolsMap[$locale])) {
foreach ($this->symbolsMap[$locale] as $char => $replace) {
$unicodeString = $unicodeString->replace($char, ' '.$replace.' ');
}
}
return $unicodeString
->replaceMatches('/[^A-Za-z0-9]++/', $separator)
->trim($separator)
;
}
private function createTransliterator(string $locale): ?\Transliterator
{
if (\array_key_exists($locale, $this->transliterators)) {
return $this->transliterators[$locale];
}
// Exact locale supported, cache and return
if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$locale] ?? null) {
return $this->transliterators[$locale] = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
}
// Locale not supported and no parent, fallback to any-latin
if (false === $str = strrchr($locale, '_')) {
return $this->transliterators[$locale] = null;
}
// Try to use the parent locale (ie. try "de" for "de_AT") and cache both locales
$parent = substr($locale, 0, -\strlen($str));
if ($id = self::LOCALE_TO_TRANSLITERATOR_ID[$parent] ?? null) {
$transliterator = \Transliterator::create($id.'/BGN') ?? \Transliterator::create($id);
}
return $this->transliterators[$locale] = $this->transliterators[$parent] = $transliterator ?? null;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String\Slugger;
use Symfony\Component\String\AbstractUnicodeString;
/**
* Creates a URL-friendly slug from a given string.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
interface SluggerInterface
{
/**
* Creates a slug for the given string and locale, using appropriate transliteration when needed.
*/
public function slug(string $string, string $separator = '-', string $locale = null): AbstractUnicodeString;
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\String;
if (!\function_exists(u::class)) {
function u(?string $string = ''): UnicodeString
{
return new UnicodeString($string ?? '');
}
}
if (!\function_exists(b::class)) {
function b(?string $string = ''): ByteString
{
return new ByteString($string ?? '');
}
}
if (!\function_exists(s::class)) {
/**
* @return UnicodeString|ByteString
*/
function s(?string $string = ''): AbstractString
{
$string = $string ?? '';
return preg_match('//u', $string) ? new UnicodeString($string) : new ByteString($string);
}
}
<?php
/*
* This file has been auto-generated by the Symfony String Component for internal use.
*
* Unicode version: 13.0.0
* Date: 2020-03-12T08:04:33+00:00
*/
return [
[
4352,
4447,
],
[
8986,
8987,
],
[
9001,
9001,
],
[
9002,
9002,
],
[
9193,
9196,
],
[
9200,
9200,
],
[
9203,
9203,
],
[
9725,
9726,
],
[
9748,
9749,
],
[
9800,
9811,
],
[
9855,
9855,
],
[
9875,
9875,
],
[
9889,
9889,
],
[
9898,
9899,
],
[
9917,
9918,
],
[
9924,
9925,
],
[
9934,
9934,
],
[
9940,
9940,
],
[
9962,
9962,
],
[
9970,
9971,
],
[
9973,
9973,
],
[
9978,
9978,
],
[
9981,
9981,
],
[
9989,
9989,
],
[
9994,
9995,
],
[
10024,
10024,
],
[
10060,
10060,
],
[
10062,
10062,
],
[
10067,
10069,
],
[
10071,
10071,
],
[
10133,
10135,
],
[
10160,
10160,
],
[
10175,
10175,
],
[
11035,
11036,
],
[
11088,
11088,
],
[
11093,
11093,
],
[
11904,
11929,
],
[
11931,
12019,
],
[
12032,
12245,
],
[
12272,
12283,
],
[
12288,
12288,
],
[
12289,
12291,
],
[
12292,
12292,
],
[
12293,
12293,
],
[
12294,
12294,
],
[
12295,
12295,
],
[
12296,
12296,
],
[
12297,
12297,
],
[
12298,
12298,
],
[
12299,
12299,
],
[
12300,
12300,
],
[
12301,
12301,
],
[
12302,
12302,
],
[
12303,
12303,
],
[
12304,
12304,
],
[
12305,
12305,
],
[
12306,
12307,
],
[
12308,
12308,
],
[
12309,
12309,
],
[
12310,
12310,
],
[
12311,
12311,
],
[
12312,
12312,
],
[
12313,
12313,
],
[
12314,
12314,
],
[
12315,
12315,
],
[
12316,
12316,
],
[
12317,
12317,
],
[
12318,
12319,
],
[
12320,
12320,
],
[
12321,
12329,
],
[
12330,
12333,
],
[
12334,
12335,
],
[
12336,
12336,
],
[
12337,
12341,
],
[
12342,
12343,
],
[
12344,
12346,
],
[
12347,
12347,
],
[
12348,
12348,
],
[
12349,
12349,
],
[
12350,
12350,
],
[
12353,
12438,
],
[
12441,
12442,
],
[
12443,
12444,
],
[
12445,
12446,
],
[
12447,
12447,
],
[
12448,
12448,
],
[
12449,
12538,
],
[
12539,
12539,
],
[
12540,
12542,
],
[
12543,
12543,
],
[
12549,
12591,
],
[
12593,
12686,
],
[
12688,
12689,
],
[
12690,
12693,
],
[
12694,
12703,
],
[
12704,
12735,
],
[
12736,
12771,
],
[
12784,
12799,
],
[
12800,
12830,
],
[
12832,
12841,
],
[
12842,
12871,
],
[
12880,
12880,
],
[
12881,
12895,
],
[
12896,
12927,
],
[
12928,
12937,
],
[
12938,
12976,
],
[
12977,
12991,
],
[
12992,
13055,
],
[
13056,
13311,
],
[
13312,
19903,
],
[
19968,
40956,
],
[
40957,
40959,
],
[
40960,
40980,
],
[
40981,
40981,
],
[
40982,
42124,
],
[
42128,
42182,
],
[
43360,
43388,
],
[
44032,
55203,
],
[
63744,
64109,
],
[
64110,
64111,
],
[
64112,
64217,
],
[
64218,
64255,
],
[
65040,
65046,
],
[
65047,
65047,
],
[
65048,
65048,
],
[
65049,
65049,
],
[
65072,
65072,
],
[
65073,
65074,
],
[
65075,
65076,
],
[
65077,
65077,
],
[
65078,
65078,
],
[
65079,
65079,
],
[
65080,
65080,
],
[
65081,
65081,
],
[
65082,
65082,
],
[
65083,
65083,
],
[
65084,
65084,
],
[
65085,
65085,
],
[
65086,
65086,
],
[
65087,
65087,
],
[
65088,
65088,
],
[
65089,
65089,
],
[
65090,
65090,
],
[
65091,
65091,
],
[
65092,
65092,
],
[
65093,
65094,
],
[
65095,
65095,
],
[
65096,
65096,
],
[
65097,
65100,
],
[
65101,
65103,
],
[
65104,
65106,
],
[
65108,
65111,
],
[
65112,
65112,
],
[
65113,
65113,
],
[
65114,
65114,
],
[
65115,
65115,
],
[
65116,
65116,
],
[
65117,
65117,
],
[
65118,
65118,
],
[
65119,
65121,
],
[
65122,
65122,
],
[
65123,
65123,
],
[
65124,
65126,
],
[
65128,
65128,
],
[
65129,
65129,
],
[
65130,
65131,
],
[
65281,
65283,
],
[
65284,
65284,
],
[
65285,
65287,
],
[
65288,
65288,
],
[
65289,
65289,
],
[
65290,
65290,
],
[
65291,
65291,
],
[
65292,
65292,
],
[
65293,
65293,
],
[
65294,
65295,
],
[
65296,
65305,
],
[
65306,
65307,
],
[
65308,
65310,
],
[
65311,
65312,
],
[
65313,
65338,
],
[
65339,
65339,
],
[
65340,
65340,
],
[
65341,
65341,
],
[
65342,
65342,
],
[
65343,
65343,
],
[
65344,
65344,
],
[
65345,
65370,
],
[
65371,
65371,
],
[
65372,
65372,
],
[
65373,
65373,
],
[
65374,
65374,
],
[
65375,
65375,
],
[
65376,
65376,
],
[
65504,
65505,
],
[
65506,
65506,
],
[
65507,
65507,
],
[
65508,
65508,
],
[
65509,
65510,
],
[
94176,
94177,
],
[
94178,
94178,
],
[
94179,
94179,
],
[
94180,
94180,
],
[
94192,
94193,
],
[
94208,
100343,
],
[
100352,
101119,
],
[
101120,
101589,
],
[
101632,
101640,
],
[
110592,
110847,
],
[
110848,
110878,
],
[
110928,
110930,
],
[
110948,
110951,
],
[
110960,
111355,
],
[
126980,
126980,
],
[
127183,
127183,
],
[
127374,
127374,
],
[
127377,
127386,
],
[
127488,
127490,
],
[
127504,
127547,
],
[
127552,
127560,
],
[
127568,
127569,
],
[
127584,
127589,
],
[
127744,
127776,
],
[
127789,
127797,
],
[
127799,
127868,
],
[
127870,
127891,
],
[
127904,
127946,
],
[
127951,
127955,
],
[
127968,
127984,
],
[
127988,
127988,
],
[
127992,
127994,
],
[
127995,
127999,
],
[
128000,
128062,
],
[
128064,
128064,
],
[
128066,
128252,
],
[
128255,
128317,
],
[
128331,
128334,
],
[
128336,
128359,
],
[
128378,
128378,
],
[
128405,
128406,
],
[
128420,
128420,
],
[
128507,
128511,
],
[
128512,
128591,
],
[
128640,
128709,
],
[
128716,
128716,
],
[
128720,
128722,
],
[
128725,
128727,
],
[
128747,
128748,
],
[
128756,
128764,
],
[
128992,
129003,
],
[
129292,
129338,
],
[
129340,
129349,
],
[
129351,
129400,
],
[
129402,
129483,
],
[
129485,
129535,
],
[
129648,
129652,
],
[
129656,
129658,
],
[
129664,
129670,
],
[
129680,
129704,
],
[
129712,
129718,
],
[
129728,
129730,
],
[
129744,
129750,
],
[
131072,
173789,
],
[
173790,
173823,
],
[
173824,
177972,
],
[
177973,
177983,
],
[
177984,
178205,
],
[
178206,
178207,
],
[
178208,
183969,
],
[
183970,
183983,
],
[
183984,
191456,
],
[
191457,
194559,
],
[
194560,
195101,
],
[
195102,
195103,
],
[
195104,
196605,
],
[
196608,
201546,
],
[
201547,
262141,
],
];
<?php
/*
* This file has been auto-generated by the Symfony String Component for internal use.
*
* Unicode version: 13.0.0
* Date: 2020-03-12T08:04:34+00:00
*/
return [
[
768,
879,
],
[
1155,
1159,
],
[
1160,
1161,
],
[
1425,
1469,
],
[
1471,
1471,
],
[
1473,
1474,
],
[
1476,
1477,
],
[
1479,
1479,
],
[
1552,
1562,
],
[
1611,
1631,
],
[
1648,
1648,
],
[
1750,
1756,
],
[
1759,
1764,
],
[
1767,
1768,
],
[
1770,
1773,
],
[
1809,
1809,
],
[
1840,
1866,
],
[
1958,
1968,
],
[
2027,
2035,
],
[
2045,
2045,
],
[
2070,
2073,
],
[
2075,
2083,
],
[
2085,
2087,
],
[
2089,
2093,
],
[
2137,
2139,
],
[
2259,
2273,
],
[
2275,
2306,
],
[
2362,
2362,
],
[
2364,
2364,
],
[
2369,
2376,
],
[
2381,
2381,
],
[
2385,
2391,
],
[
2402,
2403,
],
[
2433,
2433,
],
[
2492,
2492,
],
[
2497,
2500,
],
[
2509,
2509,
],
[
2530,
2531,
],
[
2558,
2558,
],
[
2561,
2562,
],
[
2620,
2620,
],
[
2625,
2626,
],
[
2631,
2632,
],
[
2635,
2637,
],
[
2641,
2641,
],
[
2672,
2673,
],
[
2677,
2677,
],
[
2689,
2690,
],
[
2748,
2748,
],
[
2753,
2757,
],
[
2759,
2760,
],
[
2765,
2765,
],
[
2786,
2787,
],
[
2810,
2815,
],
[
2817,
2817,
],
[
2876,
2876,
],
[
2879,
2879,
],
[
2881,
2884,
],
[
2893,
2893,
],
[
2901,
2902,
],
[
2914,
2915,
],
[
2946,
2946,
],
[
3008,
3008,
],
[
3021,
3021,
],
[
3072,
3072,
],
[
3076,
3076,
],
[
3134,
3136,
],
[
3142,
3144,
],
[
3146,
3149,
],
[
3157,
3158,
],
[
3170,
3171,
],
[
3201,
3201,
],
[
3260,
3260,
],
[
3263,
3263,
],
[
3270,
3270,
],
[
3276,
3277,
],
[
3298,
3299,
],
[
3328,
3329,
],
[
3387,
3388,
],
[
3393,
3396,
],
[
3405,
3405,
],
[
3426,
3427,
],
[
3457,
3457,
],
[
3530,
3530,
],
[
3538,
3540,
],
[
3542,
3542,
],
[
3633,
3633,
],
[
3636,
3642,
],
[
3655,
3662,
],
[
3761,
3761,
],
[
3764,
3772,
],
[
3784,
3789,
],
[
3864,
3865,
],
[
3893,
3893,
],
[
3895,
3895,
],
[
3897,
3897,
],
[
3953,
3966,
],
[
3968,
3972,
],
[
3974,
3975,
],
[
3981,
3991,
],
[
3993,
4028,
],
[
4038,
4038,
],
[
4141,
4144,
],
[
4146,
4151,
],
[
4153,
4154,
],
[
4157,
4158,
],
[
4184,
4185,
],
[
4190,
4192,
],
[
4209,
4212,
],
[
4226,
4226,
],
[
4229,
4230,
],
[
4237,
4237,
],
[
4253,
4253,
],
[
4957,
4959,
],
[
5906,
5908,
],
[
5938,
5940,
],
[
5970,
5971,
],
[
6002,
6003,
],
[
6068,
6069,
],
[
6071,
6077,
],
[
6086,
6086,
],
[
6089,
6099,
],
[
6109,
6109,
],
[
6155,
6157,
],
[
6277,
6278,
],
[
6313,
6313,
],
[
6432,
6434,
],
[
6439,
6440,
],
[
6450,
6450,
],
[
6457,
6459,
],
[
6679,
6680,
],
[
6683,
6683,
],
[
6742,
6742,
],
[
6744,
6750,
],
[
6752,
6752,
],
[
6754,
6754,
],
[
6757,
6764,
],
[
6771,
6780,
],
[
6783,
6783,
],
[
6832,
6845,
],
[
6846,
6846,
],
[
6847,
6848,
],
[
6912,
6915,
],
[
6964,
6964,
],
[
6966,
6970,
],
[
6972,
6972,
],
[
6978,
6978,
],
[
7019,
7027,
],
[
7040,
7041,
],
[
7074,
7077,
],
[
7080,
7081,
],
[
7083,
7085,
],
[
7142,
7142,
],
[
7144,
7145,
],
[
7149,
7149,
],
[
7151,
7153,
],
[
7212,
7219,
],
[
7222,
7223,
],
[
7376,
7378,
],
[
7380,
7392,
],
[
7394,
7400,
],
[
7405,
7405,
],
[
7412,
7412,
],
[
7416,
7417,
],
[
7616,
7673,
],
[
7675,
7679,
],
[
8400,
8412,
],
[
8413,
8416,
],
[
8417,
8417,
],
[
8418,
8420,
],
[
8421,
8432,
],
[
11503,
11505,
],
[
11647,
11647,
],
[
11744,
11775,
],
[
12330,
12333,
],
[
12441,
12442,
],
[
42607,
42607,
],
[
42608,
42610,
],
[
42612,
42621,
],
[
42654,
42655,
],
[
42736,
42737,
],
[
43010,
43010,
],
[
43014,
43014,
],
[
43019,
43019,
],
[
43045,
43046,
],
[
43052,
43052,
],
[
43204,
43205,
],
[
43232,
43249,
],
[
43263,
43263,
],
[
43302,
43309,
],
[
43335,
43345,
],
[
43392,
43394,
],
[
43443,
43443,
],
[
43446,
43449,
],
[
43452,
43453,
],
[
43493,
43493,
],
[
43561,
43566,
],
[
43569,
43570,
],
[
43573,
43574,
],
[
43587,
43587,
],
[
43596,
43596,
],
[
43644,
43644,
],
[
43696,
43696,
],
[
43698,
43700,
],
[
43703,
43704,
],
[
43710,
43711,
],
[
43713,
43713,
],
[
43756,
43757,
],
[
43766,
43766,
],
[
44005,
44005,
],
[
44008,
44008,
],
[
44013,
44013,
],
[
64286,
64286,
],
[
65024,
65039,
],
[
65056,
65071,
],
[
66045,
66045,
],
[
66272,
66272,
],
[
66422,
66426,
],
[
68097,
68099,
],
[
68101,
68102,
],
[
68108,
68111,
],
[
68152,
68154,
],
[
68159,
68159,
],
[
68325,
68326,
],
[
68900,
68903,
],
[
69291,
69292,
],
[
69446,
69456,
],
[
69633,
69633,
],
[
69688,
69702,
],
[
69759,
69761,
],
[
69811,
69814,
],
[
69817,
69818,
],
[
69888,
69890,
],
[
69927,
69931,
],
[
69933,
69940,
],
[
70003,
70003,
],
[
70016,
70017,
],
[
70070,
70078,
],
[
70089,
70092,
],
[
70095,
70095,
],
[
70191,
70193,
],
[
70196,
70196,
],
[
70198,
70199,
],
[
70206,
70206,
],
[
70367,
70367,
],
[
70371,
70378,
],
[
70400,
70401,
],
[
70459,
70460,
],
[
70464,
70464,
],
[
70502,
70508,
],
[
70512,
70516,
],
[
70712,
70719,
],
[
70722,
70724,
],
[
70726,
70726,
],
[
70750,
70750,
],
[
70835,
70840,
],
[
70842,
70842,
],
[
70847,
70848,
],
[
70850,
70851,
],
[
71090,
71093,
],
[
71100,
71101,
],
[
71103,
71104,
],
[
71132,
71133,
],
[
71219,
71226,
],
[
71229,
71229,
],
[
71231,
71232,
],
[
71339,
71339,
],
[
71341,
71341,
],
[
71344,
71349,
],
[
71351,
71351,
],
[
71453,
71455,
],
[
71458,
71461,
],
[
71463,
71467,
],
[
71727,
71735,
],
[
71737,
71738,
],
[
71995,
71996,
],
[
71998,
71998,
],
[
72003,
72003,
],
[
72148,
72151,
],
[
72154,
72155,
],
[
72160,
72160,
],
[
72193,
72202,
],
[
72243,
72248,
],
[
72251,
72254,
],
[
72263,
72263,
],
[
72273,
72278,
],
[
72281,
72283,
],
[
72330,
72342,
],
[
72344,
72345,
],
[
72752,
72758,
],
[
72760,
72765,
],
[
72767,
72767,
],
[
72850,
72871,
],
[
72874,
72880,
],
[
72882,
72883,
],
[
72885,
72886,
],
[
73009,
73014,
],
[
73018,
73018,
],
[
73020,
73021,
],
[
73023,
73029,
],
[
73031,
73031,
],
[
73104,
73105,
],
[
73109,
73109,
],
[
73111,
73111,
],
[
73459,
73460,
],
[
92912,
92916,
],
[
92976,
92982,
],
[
94031,
94031,
],
[
94095,
94098,
],
[
94180,
94180,
],
[
113821,
113822,
],
[
119143,
119145,
],
[
119163,
119170,
],
[
119173,
119179,
],
[
119210,
119213,
],
[
119362,
119364,
],
[
121344,
121398,
],
[
121403,
121452,
],
[
121461,
121461,
],
[
121476,
121476,
],
[
121499,
121503,
],
[
121505,
121519,
],
[
122880,
122886,
],
[
122888,
122904,
],
[
122907,
122913,
],
[
122915,
122916,
],
[
122918,
122922,
],
[
123184,
123190,
],
[
123628,
123631,
],
[
125136,
125142,
],
[
125252,
125258,
],
[
917760,
917999,
],
];
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Tag;
/**
* @author Nicolas Grekas <p@tchwork.com>
* @author Guilhem N. <egetick@gmail.com>
*/
final class TaggedValue
{
private $tag;
private $value;
public function __construct(string $tag, $value)
{
$this->tag = $tag;
$this->value = $value;
}
public function getTag(): string
{
return $this->tag;
}
public function getValue()
{
return $this->value;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Yaml offers convenience methods to load and dump YAML.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Yaml
{
public const DUMP_OBJECT = 1;
public const PARSE_EXCEPTION_ON_INVALID_TYPE = 2;
public const PARSE_OBJECT = 4;
public const PARSE_OBJECT_FOR_MAP = 8;
public const DUMP_EXCEPTION_ON_INVALID_TYPE = 16;
public const PARSE_DATETIME = 32;
public const DUMP_OBJECT_AS_MAP = 64;
public const DUMP_MULTI_LINE_LITERAL_BLOCK = 128;
public const PARSE_CONSTANT = 256;
public const PARSE_CUSTOM_TAGS = 512;
public const DUMP_EMPTY_ARRAY_AS_SEQUENCE = 1024;
public const DUMP_NULL_AS_TILDE = 2048;
/**
* Parses a YAML file into a PHP value.
*
* Usage:
*
* $array = Yaml::parseFile('config.yml');
* print_r($array);
*
* @param string $filename The path to the YAML file to be parsed
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public static function parseFile(string $filename, int $flags = 0)
{
$yaml = new Parser();
return $yaml->parseFile($filename, $flags);
}
/**
* Parses YAML into a PHP value.
*
* Usage:
* <code>
* $array = Yaml::parse(file_get_contents('config.yml'));
* print_r($array);
* </code>
*
* @param string $input A string containing YAML
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the YAML is not valid
*/
public static function parse(string $input, int $flags = 0)
{
$yaml = new Parser();
return $yaml->parse($input, $flags);
}
/**
* Dumps a PHP value to a YAML string.
*
* The dump method, when supplied with an array, will do its best
* to convert the array into friendly YAML.
*
* @param mixed $input The PHP value
* @param int $inline The level where you switch to inline YAML
* @param int $indent The amount of spaces to use for indentation of nested nodes
* @param int $flags A bit field of DUMP_* constants to customize the dumped YAML string
*
* @return string A YAML string representing the original PHP value
*/
public static function dump($input, int $inline = 2, int $indent = 4, int $flags = 0): string
{
$yaml = new Dumper($indent);
return $yaml->dump($input, $inline, 0, $flags);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Yaml;
/**
* Validates YAML files syntax and outputs encountered errors.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class LintCommand extends Command
{
protected static $defaultName = 'lint:yaml';
private $parser;
private $format;
private $displayCorrectFiles;
private $directoryIteratorProvider;
private $isReadableProvider;
public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null)
{
parent::__construct($name);
$this->directoryIteratorProvider = $directoryIteratorProvider;
$this->isReadableProvider = $isReadableProvider;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription('Lint a file and outputs encountered errors')
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
->addOption('parse-tags', null, InputOption::VALUE_NONE, 'Parse custom tags')
->setHelp(<<<EOF
The <info>%command.name%</info> command lints a YAML file and outputs to STDOUT
the first encountered syntax error.
You can validates YAML contents passed from STDIN:
<info>cat filename | php %command.full_name% -</info>
You can also validate the syntax of a file:
<info>php %command.full_name% filename</info>
Or of a whole directory:
<info>php %command.full_name% dirname</info>
<info>php %command.full_name% dirname --format=json</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$filenames = (array) $input->getArgument('filename');
$this->format = $input->getOption('format');
$this->displayCorrectFiles = $output->isVerbose();
$flags = $input->getOption('parse-tags') ? Yaml::PARSE_CUSTOM_TAGS : 0;
if (['-'] === $filenames) {
return $this->display($io, [$this->validate(file_get_contents('php://stdin'), $flags)]);
}
if (!$filenames) {
throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
}
$filesInfo = [];
foreach ($filenames as $filename) {
if (!$this->isReadable($filename)) {
throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
}
foreach ($this->getFiles($filename) as $file) {
$filesInfo[] = $this->validate(file_get_contents($file), $flags, $file);
}
}
return $this->display($io, $filesInfo);
}
private function validate(string $content, int $flags, string $file = null)
{
$prevErrorHandler = set_error_handler(function ($level, $message, $file, $line) use (&$prevErrorHandler) {
if (\E_USER_DEPRECATED === $level) {
throw new ParseException($message, $this->getParser()->getRealCurrentLineNb() + 1);
}
return $prevErrorHandler ? $prevErrorHandler($level, $message, $file, $line) : false;
});
try {
$this->getParser()->parse($content, Yaml::PARSE_CONSTANT | $flags);
} catch (ParseException $e) {
return ['file' => $file, 'line' => $e->getParsedLine(), 'valid' => false, 'message' => $e->getMessage()];
} finally {
restore_error_handler();
}
return ['file' => $file, 'valid' => true];
}
private function display(SymfonyStyle $io, array $files): int
{
switch ($this->format) {
case 'txt':
return $this->displayTxt($io, $files);
case 'json':
return $this->displayJson($io, $files);
default:
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
}
}
private function displayTxt(SymfonyStyle $io, array $filesInfo): int
{
$countFiles = \count($filesInfo);
$erroredFiles = 0;
$suggestTagOption = false;
foreach ($filesInfo as $info) {
if ($info['valid'] && $this->displayCorrectFiles) {
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
} elseif (!$info['valid']) {
++$erroredFiles;
$io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
$io->text(sprintf('<error> >> %s</error>', $info['message']));
if (false !== strpos($info['message'], 'PARSE_CUSTOM_TAGS')) {
$suggestTagOption = true;
}
}
}
if (0 === $erroredFiles) {
$io->success(sprintf('All %d YAML files contain valid syntax.', $countFiles));
} else {
$io->warning(sprintf('%d YAML files have valid syntax and %d contain errors.%s', $countFiles - $erroredFiles, $erroredFiles, $suggestTagOption ? ' Use the --parse-tags option if you want parse custom tags.' : ''));
}
return min($erroredFiles, 1);
}
private function displayJson(SymfonyStyle $io, array $filesInfo): int
{
$errors = 0;
array_walk($filesInfo, function (&$v) use (&$errors) {
$v['file'] = (string) $v['file'];
if (!$v['valid']) {
++$errors;
}
if (isset($v['message']) && false !== strpos($v['message'], 'PARSE_CUSTOM_TAGS')) {
$v['message'] .= ' Use the --parse-tags option if you want parse custom tags.';
}
});
$io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
return min($errors, 1);
}
private function getFiles(string $fileOrDirectory): iterable
{
if (is_file($fileOrDirectory)) {
yield new \SplFileInfo($fileOrDirectory);
return;
}
foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
if (!\in_array($file->getExtension(), ['yml', 'yaml'])) {
continue;
}
yield $file;
}
}
private function getParser(): Parser
{
if (!$this->parser) {
$this->parser = new Parser();
}
return $this->parser;
}
private function getDirectoryIterator(string $directory): iterable
{
$default = function ($directory) {
return new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
};
if (null !== $this->directoryIteratorProvider) {
return ($this->directoryIteratorProvider)($directory, $default);
}
return $default($directory);
}
private function isReadable(string $fileOrDirectory): bool
{
$default = function ($fileOrDirectory) {
return is_readable($fileOrDirectory);
};
if (null !== $this->isReadableProvider) {
return ($this->isReadableProvider)($fileOrDirectory, $default);
}
return $default($fileOrDirectory);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Dumper dumps PHP variables to YAML strings.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Dumper
{
/**
* The amount of spaces to use for indentation of nested nodes.
*
* @var int
*/
protected $indentation;
public function __construct(int $indentation = 4)
{
if ($indentation < 1) {
throw new \InvalidArgumentException('The indentation must be greater than zero.');
}
$this->indentation = $indentation;
}
/**
* Dumps a PHP value to YAML.
*
* @param mixed $input The PHP value
* @param int $inline The level where you switch to inline YAML
* @param int $indent The level of indentation (used internally)
* @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
*
* @return string The YAML representation of the PHP value
*/
public function dump($input, int $inline = 0, int $indent = 0, int $flags = 0): string
{
$output = '';
$prefix = $indent ? str_repeat(' ', $indent) : '';
$dumpObjectAsInlineMap = true;
if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($input instanceof \ArrayObject || $input instanceof \stdClass)) {
$dumpObjectAsInlineMap = empty((array) $input);
}
if ($inline <= 0 || (!\is_array($input) && !$input instanceof TaggedValue && $dumpObjectAsInlineMap) || empty($input)) {
$output .= $prefix.Inline::dump($input, $flags);
} else {
$dumpAsMap = Inline::isHash($input);
foreach ($input as $key => $value) {
if ('' !== $output && "\n" !== $output[-1]) {
$output .= "\n";
}
if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value) && false !== strpos($value, "\n") && false === strpos($value, "\r")) {
// If the first line starts with a space character, the spec requires a blockIndicationIndicator
// http://www.yaml.org/spec/1.2/spec.html#id2793979
$blockIndentationIndicator = (' ' === substr($value, 0, 1)) ? (string) $this->indentation : '';
if (isset($value[-2]) && "\n" === $value[-2] && "\n" === $value[-1]) {
$blockChompingIndicator = '+';
} elseif ("\n" === $value[-1]) {
$blockChompingIndicator = '';
} else {
$blockChompingIndicator = '-';
}
$output .= sprintf('%s%s%s |%s%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', '', $blockIndentationIndicator, $blockChompingIndicator);
foreach (explode("\n", $value) as $row) {
if ('' === $row) {
$output .= "\n";
} else {
$output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row);
}
}
continue;
}
if ($value instanceof TaggedValue) {
$output .= sprintf('%s%s !%s', $prefix, $dumpAsMap ? Inline::dump($key, $flags).':' : '-', $value->getTag());
if (Yaml::DUMP_MULTI_LINE_LITERAL_BLOCK & $flags && \is_string($value->getValue()) && false !== strpos($value->getValue(), "\n") && false === strpos($value->getValue(), "\r\n")) {
// If the first line starts with a space character, the spec requires a blockIndicationIndicator
// http://www.yaml.org/spec/1.2/spec.html#id2793979
$blockIndentationIndicator = (' ' === substr($value->getValue(), 0, 1)) ? (string) $this->indentation : '';
$output .= sprintf(' |%s', $blockIndentationIndicator);
foreach (explode("\n", $value->getValue()) as $row) {
$output .= sprintf("\n%s%s%s", $prefix, str_repeat(' ', $this->indentation), $row);
}
continue;
}
if ($inline - 1 <= 0 || null === $value->getValue() || is_scalar($value->getValue())) {
$output .= ' '.$this->dump($value->getValue(), $inline - 1, 0, $flags)."\n";
} else {
$output .= "\n";
$output .= $this->dump($value->getValue(), $inline - 1, $dumpAsMap ? $indent + $this->indentation : $indent + 2, $flags);
}
continue;
}
$dumpObjectAsInlineMap = true;
if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \ArrayObject || $value instanceof \stdClass)) {
$dumpObjectAsInlineMap = empty((array) $value);
}
$willBeInlined = $inline - 1 <= 0 || !\is_array($value) && $dumpObjectAsInlineMap || empty($value);
$output .= sprintf('%s%s%s%s',
$prefix,
$dumpAsMap ? Inline::dump($key, $flags).':' : '-',
$willBeInlined ? ' ' : "\n",
$this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $flags)
).($willBeInlined ? "\n" : '');
}
}
return $output;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* Unescaper encapsulates unescaping rules for single and double-quoted
* YAML strings.
*
* @author Matthew Lewinski <matthew@lewinski.org>
*
* @internal
*/
class Unescaper
{
/**
* Regex fragment that matches an escaped character in a double quoted string.
*/
public const REGEX_ESCAPED_CHARACTER = '\\\\(x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8}|.)';
/**
* Unescapes a single quoted string.
*
* @param string $value A single quoted string
*
* @return string The unescaped string
*/
public function unescapeSingleQuotedString(string $value): string
{
return str_replace('\'\'', '\'', $value);
}
/**
* Unescapes a double quoted string.
*
* @param string $value A double quoted string
*
* @return string The unescaped string
*/
public function unescapeDoubleQuotedString(string $value): string
{
$callback = function ($match) {
return $this->unescapeCharacter($match[0]);
};
// evaluate the string
return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value);
}
/**
* Unescapes a character that was found in a double-quoted string.
*
* @param string $value An escaped character
*
* @return string The unescaped character
*/
private function unescapeCharacter(string $value): string
{
switch ($value[1]) {
case '0':
return "\x0";
case 'a':
return "\x7";
case 'b':
return "\x8";
case 't':
return "\t";
case "\t":
return "\t";
case 'n':
return "\n";
case 'v':
return "\xB";
case 'f':
return "\xC";
case 'r':
return "\r";
case 'e':
return "\x1B";
case ' ':
return ' ';
case '"':
return '"';
case '/':
return '/';
case '\\':
return '\\';
case 'N':
// U+0085 NEXT LINE
return "\xC2\x85";
case '_':
// U+00A0 NO-BREAK SPACE
return "\xC2\xA0";
case 'L':
// U+2028 LINE SEPARATOR
return "\xE2\x80\xA8";
case 'P':
// U+2029 PARAGRAPH SEPARATOR
return "\xE2\x80\xA9";
case 'x':
return self::utf8chr(hexdec(substr($value, 2, 2)));
case 'u':
return self::utf8chr(hexdec(substr($value, 2, 4)));
case 'U':
return self::utf8chr(hexdec(substr($value, 2, 8)));
default:
throw new ParseException(sprintf('Found unknown escape character "%s".', $value));
}
}
/**
* Get the UTF-8 character for the given code point.
*/
private static function utf8chr(int $c): string
{
if (0x80 > $c %= 0x200000) {
return \chr($c);
}
if (0x800 > $c) {
return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F);
}
if (0x10000 > $c) {
return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
}
return \chr(0xF0 | $c >> 18).\chr(0x80 | $c >> 12 & 0x3F).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Parser parses YAML strings to convert them to PHP arrays.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @final
*/
class Parser
{
public const TAG_PATTERN = '(?P<tag>![\w!.\/:-]+)';
public const BLOCK_SCALAR_HEADER_PATTERN = '(?P<separator>\||>)(?P<modifiers>\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P<comments> +#.*)?';
private $filename;
private $offset = 0;
private $numberOfParsedLines = 0;
private $totalNumberOfLines;
private $lines = [];
private $currentLineNb = -1;
private $currentLine = '';
private $refs = [];
private $skippedLineNumbers = [];
private $locallySkippedLineNumbers = [];
private $refsBeingParsed = [];
/**
* Parses a YAML file into a PHP value.
*
* @param string $filename The path to the YAML file to be parsed
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed The YAML converted to a PHP value
*
* @throws ParseException If the file could not be read or the YAML is not valid
*/
public function parseFile(string $filename, int $flags = 0)
{
if (!is_file($filename)) {
throw new ParseException(sprintf('File "%s" does not exist.', $filename));
}
if (!is_readable($filename)) {
throw new ParseException(sprintf('File "%s" cannot be read.', $filename));
}
$this->filename = $filename;
try {
return $this->parse(file_get_contents($filename), $flags);
} finally {
$this->filename = null;
}
}
/**
* Parses a YAML string to a PHP value.
*
* @param string $value A YAML string
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
*
* @return mixed A PHP value
*
* @throws ParseException If the YAML is not valid
*/
public function parse(string $value, int $flags = 0)
{
if (false === preg_match('//u', $value)) {
throw new ParseException('The YAML value does not appear to be valid UTF-8.', -1, null, $this->filename);
}
$this->refs = [];
$mbEncoding = null;
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8');
}
try {
$data = $this->doParse($value, $flags);
} finally {
if (null !== $mbEncoding) {
mb_internal_encoding($mbEncoding);
}
$this->lines = [];
$this->currentLine = '';
$this->numberOfParsedLines = 0;
$this->refs = [];
$this->skippedLineNumbers = [];
$this->locallySkippedLineNumbers = [];
$this->totalNumberOfLines = null;
}
return $data;
}
private function doParse(string $value, int $flags)
{
$this->currentLineNb = -1;
$this->currentLine = '';
$value = $this->cleanup($value);
$this->lines = explode("\n", $value);
$this->numberOfParsedLines = \count($this->lines);
$this->locallySkippedLineNumbers = [];
if (null === $this->totalNumberOfLines) {
$this->totalNumberOfLines = $this->numberOfParsedLines;
}
if (!$this->moveToNextLine()) {
return null;
}
$data = [];
$context = null;
$allowOverwrite = false;
while ($this->isCurrentLineEmpty()) {
if (!$this->moveToNextLine()) {
return null;
}
}
// Resolves the tag and returns if end of the document
if (null !== ($tag = $this->getLineTag($this->currentLine, $flags, false)) && !$this->moveToNextLine()) {
return new TaggedValue($tag, '');
}
do {
if ($this->isCurrentLineEmpty()) {
continue;
}
// tab?
if ("\t" === $this->currentLine[0]) {
throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
Inline::initialize($flags, $this->getRealCurrentLineNb(), $this->filename);
$isRef = $mergeNode = false;
if ('-' === $this->currentLine[0] && self::preg_match('#^\-((?P<leadspaces>\s+)(?P<value>.+))?$#u', rtrim($this->currentLine), $values)) {
if ($context && 'mapping' == $context) {
throw new ParseException('You cannot define a sequence item when in a mapping.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$context = 'sequence';
if (isset($values['value']) && '&' === $values['value'][0] && self::preg_match('#^&(?P<ref>[^ ]+) *(?P<value>.*)#u', $values['value'], $matches)) {
$isRef = $matches['ref'];
$this->refsBeingParsed[] = $isRef;
$values['value'] = $matches['value'];
}
if (isset($values['value'][1]) && '?' === $values['value'][0] && ' ' === $values['value'][1]) {
throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
// array
if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
$data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true) ?? '', $flags);
} elseif (null !== $subTag = $this->getLineTag(ltrim($values['value'], ' '), $flags)) {
$data[] = new TaggedValue(
$subTag,
$this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $flags)
);
} else {
if (
isset($values['leadspaces'])
&& (
'!' === $values['value'][0]
|| self::preg_match('#^(?P<key>'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P<value>.+?))?\s*$#u', $this->trimTag($values['value']), $matches)
)
) {
// this is a compact notation element, add to next block and parse
$block = $values['value'];
if ($this->isNextLineIndented()) {
$block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
}
$data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $flags);
} else {
$data[] = $this->parseValue($values['value'], $flags, $context);
}
}
if ($isRef) {
$this->refs[$isRef] = end($data);
array_pop($this->refsBeingParsed);
}
} elseif (
self::preg_match('#^(?P<key>(?:![^\s]++\s++)?(?:'.Inline::REGEX_QUOTED_STRING.'|(?:!?!php/const:)?[^ \'"\[\{!].*?)) *\:( ++(?P<value>.+))?$#u', rtrim($this->currentLine), $values)
&& (false === strpos($values['key'], ' #') || \in_array($values['key'][0], ['"', "'"]))
) {
if ($context && 'sequence' == $context) {
throw new ParseException('You cannot define a mapping item when in a sequence.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
$context = 'mapping';
try {
$key = Inline::parseScalar($values['key']);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
if (!\is_string($key) && !\is_int($key)) {
throw new ParseException(sprintf('%s keys are not supported. Quote your evaluable mapping keys instead.', is_numeric($key) ? 'Numeric' : 'Non-string'), $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
// Convert float keys to strings, to avoid being converted to integers by PHP
if (\is_float($key)) {
$key = (string) $key;
}
if ('<<' === $key && (!isset($values['value']) || '&' !== $values['value'][0] || !self::preg_match('#^&(?P<ref>[^ ]+)#u', $values['value'], $refMatches))) {
$mergeNode = true;
$allowOverwrite = true;
if (isset($values['value'][0]) && '*' === $values['value'][0]) {
$refName = substr(rtrim($values['value']), 1);
if (!\array_key_exists($refName, $this->refs)) {
if (false !== $pos = array_search($refName, $this->refsBeingParsed, true)) {
throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $refName, $refName), $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$refValue = $this->refs[$refName];
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $refValue instanceof \stdClass) {
$refValue = (array) $refValue;
}
if (!\is_array($refValue)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
$data += $refValue; // array union
} else {
if (isset($values['value']) && '' !== $values['value']) {
$value = $values['value'];
} else {
$value = $this->getNextEmbedBlock();
}
$parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $flags);
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsed instanceof \stdClass) {
$parsed = (array) $parsed;
}
if (!\is_array($parsed)) {
throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
if (isset($parsed[0])) {
// If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
// and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
// in the sequence override keys specified in later mapping nodes.
foreach ($parsed as $parsedItem) {
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $parsedItem instanceof \stdClass) {
$parsedItem = (array) $parsedItem;
}
if (!\is_array($parsedItem)) {
throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem, $this->filename);
}
$data += $parsedItem; // array union
}
} else {
// If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
// current mapping, unless the key already exists in it.
$data += $parsed; // array union
}
}
} elseif ('<<' !== $key && isset($values['value']) && '&' === $values['value'][0] && self::preg_match('#^&(?P<ref>[^ ]++) *+(?P<value>.*)#u', $values['value'], $matches)) {
$isRef = $matches['ref'];
$this->refsBeingParsed[] = $isRef;
$values['value'] = $matches['value'];
}
$subTag = null;
if ($mergeNode) {
// Merge keys
} elseif (!isset($values['value']) || '' === $values['value'] || '#' === ($values['value'][0] ?? '') || (null !== $subTag = $this->getLineTag($values['value'], $flags)) || '<<' === $key) {
// hash
// if next line is less indented or equal, then it means that the current value is null
if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
// Spec: Keys MUST be unique; first one wins.
// But overwriting is allowed when a merge node is used in current block.
if ($allowOverwrite || !isset($data[$key])) {
if (null !== $subTag) {
$data[$key] = new TaggedValue($subTag, '');
} else {
$data[$key] = null;
}
} else {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
} else {
// remember the parsed line number here in case we need it to provide some contexts in error messages below
$realCurrentLineNbKey = $this->getRealCurrentLineNb();
$value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $flags);
if ('<<' === $key) {
$this->refs[$refMatches['ref']] = $value;
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && $value instanceof \stdClass) {
$value = (array) $value;
}
$data += $value;
} elseif ($allowOverwrite || !isset($data[$key])) {
// Spec: Keys MUST be unique; first one wins.
// But overwriting is allowed when a merge node is used in current block.
if (null !== $subTag) {
$data[$key] = new TaggedValue($subTag, $value);
} else {
$data[$key] = $value;
}
} else {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $realCurrentLineNbKey + 1, $this->currentLine);
}
}
} else {
$value = $this->parseValue(rtrim($values['value']), $flags, $context);
// Spec: Keys MUST be unique; first one wins.
// But overwriting is allowed when a merge node is used in current block.
if ($allowOverwrite || !isset($data[$key])) {
$data[$key] = $value;
} else {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
}
if ($isRef) {
$this->refs[$isRef] = $data[$key];
array_pop($this->refsBeingParsed);
}
} elseif ('"' === $this->currentLine[0] || "'" === $this->currentLine[0]) {
if (null !== $context) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
try {
return Inline::parse($this->lexInlineQuotedString(), $flags, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
} elseif ('{' === $this->currentLine[0]) {
if (null !== $context) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
try {
$parsedMapping = Inline::parse($this->lexInlineMapping(), $flags, $this->refs);
while ($this->moveToNextLine()) {
if (!$this->isCurrentLineEmpty()) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
}
return $parsedMapping;
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
} elseif ('[' === $this->currentLine[0]) {
if (null !== $context) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
try {
$parsedSequence = Inline::parse($this->lexInlineSequence(), $flags, $this->refs);
while ($this->moveToNextLine()) {
if (!$this->isCurrentLineEmpty()) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
}
return $parsedSequence;
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
} else {
// multiple documents are not supported
if ('---' === $this->currentLine) {
throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
if ($deprecatedUsage = (isset($this->currentLine[1]) && '?' === $this->currentLine[0] && ' ' === $this->currentLine[1])) {
throw new ParseException('Complex mappings are not supported.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
}
// 1-liner optionally followed by newline(s)
if (\is_string($value) && $this->lines[0] === trim($value)) {
try {
$value = Inline::parse($this->lines[0], $flags, $this->refs);
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
return $value;
}
// try to parse the value as a multi-line string as a last resort
if (0 === $this->currentLineNb) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
$value = '';
foreach ($this->lines as $line) {
$trimmedLine = trim($line);
if ('#' === ($trimmedLine[0] ?? '')) {
continue;
}
// If the indentation is not consistent at offset 0, it is to be considered as a ParseError
if (0 === $this->offset && !$deprecatedUsage && isset($line[0]) && ' ' === $line[0]) {
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
if (false !== strpos($line, ': ')) {
throw new ParseException('Mapping values are not allowed in multi-line blocks.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
if ('' === $trimmedLine) {
$value .= "\n";
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
$value .= ' ';
}
if ('' !== $trimmedLine && '\\' === $line[-1]) {
$value .= ltrim(substr($line, 0, -1));
} elseif ('' !== $trimmedLine) {
$value .= $trimmedLine;
}
if ('' === $trimmedLine) {
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === $line[-1]) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true;
} else {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
}
}
try {
return Inline::parse(trim($value));
} catch (ParseException $e) {
// fall-through to the ParseException thrown below
}
}
throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
} while ($this->moveToNextLine());
if (null !== $tag) {
$data = new TaggedValue($tag, $data);
}
if (Yaml::PARSE_OBJECT_FOR_MAP & $flags && 'mapping' === $context && !\is_object($data)) {
$object = new \stdClass();
foreach ($data as $key => $value) {
$object->$key = $value;
}
$data = $object;
}
return empty($data) ? null : $data;
}
private function parseBlock(int $offset, string $yaml, int $flags)
{
$skippedLineNumbers = $this->skippedLineNumbers;
foreach ($this->locallySkippedLineNumbers as $lineNumber) {
if ($lineNumber < $offset) {
continue;
}
$skippedLineNumbers[] = $lineNumber;
}
$parser = new self();
$parser->offset = $offset;
$parser->totalNumberOfLines = $this->totalNumberOfLines;
$parser->skippedLineNumbers = $skippedLineNumbers;
$parser->refs = &$this->refs;
$parser->refsBeingParsed = $this->refsBeingParsed;
return $parser->doParse($yaml, $flags);
}
/**
* Returns the current line number (takes the offset into account).
*
* @internal
*
* @return int The current line number
*/
public function getRealCurrentLineNb(): int
{
$realCurrentLineNumber = $this->currentLineNb + $this->offset;
foreach ($this->skippedLineNumbers as $skippedLineNumber) {
if ($skippedLineNumber > $realCurrentLineNumber) {
break;
}
++$realCurrentLineNumber;
}
return $realCurrentLineNumber;
}
/**
* Returns the current line indentation.
*
* @return int The current line indentation
*/
private function getCurrentLineIndentation(): int
{
if (' ' !== ($this->currentLine[0] ?? '')) {
return 0;
}
return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
}
/**
* Returns the next embed block of YAML.
*
* @param int|null $indentation The indent level at which the block is to be read, or null for default
* @param bool $inSequence True if the enclosing data structure is a sequence
*
* @return string A YAML string
*
* @throws ParseException When indentation problem are detected
*/
private function getNextEmbedBlock(int $indentation = null, bool $inSequence = false): string
{
$oldLineIndentation = $this->getCurrentLineIndentation();
if (!$this->moveToNextLine()) {
return '';
}
if (null === $indentation) {
$newIndent = null;
$movements = 0;
do {
$EOF = false;
// empty and comment-like lines do not influence the indentation depth
if ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
$EOF = !$this->moveToNextLine();
if (!$EOF) {
++$movements;
}
} else {
$newIndent = $this->getCurrentLineIndentation();
}
} while (!$EOF && null === $newIndent);
for ($i = 0; $i < $movements; ++$i) {
$this->moveToPreviousLine();
}
$unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
} else {
$newIndent = $indentation;
}
$data = [];
if ($this->getCurrentLineIndentation() >= $newIndent) {
$data[] = substr($this->currentLine, $newIndent);
} elseif ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()) {
$data[] = $this->currentLine;
} else {
$this->moveToPreviousLine();
return '';
}
if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
// the previous line contained a dash but no item content, this line is a sequence item with the same indentation
// and therefore no nested list or mapping
$this->moveToPreviousLine();
return '';
}
$isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
$isItComment = $this->isCurrentLineComment();
while ($this->moveToNextLine()) {
if ($isItComment && !$isItUnindentedCollection) {
$isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
$isItComment = $this->isCurrentLineComment();
}
$indent = $this->getCurrentLineIndentation();
if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
$this->moveToPreviousLine();
break;
}
if ($this->isCurrentLineBlank()) {
$data[] = substr($this->currentLine, $newIndent);
continue;
}
if ($indent >= $newIndent) {
$data[] = substr($this->currentLine, $newIndent);
} elseif ($this->isCurrentLineComment()) {
$data[] = $this->currentLine;
} elseif (0 == $indent) {
$this->moveToPreviousLine();
break;
} else {
throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine, $this->filename);
}
}
return implode("\n", $data);
}
private function hasMoreLines(): bool
{
return (\count($this->lines) - 1) > $this->currentLineNb;
}
/**
* Moves the parser to the next line.
*/
private function moveToNextLine(): bool
{
if ($this->currentLineNb >= $this->numberOfParsedLines - 1) {
return false;
}
$this->currentLine = $this->lines[++$this->currentLineNb];
return true;
}
/**
* Moves the parser to the previous line.
*/
private function moveToPreviousLine(): bool
{
if ($this->currentLineNb < 1) {
return false;
}
$this->currentLine = $this->lines[--$this->currentLineNb];
return true;
}
/**
* Parses a YAML value.
*
* @param string $value A YAML value
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
* @param string $context The parser context (either sequence or mapping)
*
* @return mixed A PHP value
*
* @throws ParseException When reference does not exist
*/
private function parseValue(string $value, int $flags, string $context)
{
if ('*' === ($value[0] ?? '')) {
if (false !== $pos = strpos($value, '#')) {
$value = substr($value, 1, $pos - 2);
} else {
$value = substr($value, 1);
}
if (!\array_key_exists($value, $this->refs)) {
if (false !== $pos = array_search($value, $this->refsBeingParsed, true)) {
throw new ParseException(sprintf('Circular reference [%s, %s] detected for reference "%s".', implode(', ', \array_slice($this->refsBeingParsed, $pos)), $value, $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine, $this->filename);
}
return $this->refs[$value];
}
if (\in_array($value[0], ['!', '|', '>'], true) && self::preg_match('/^(?:'.self::TAG_PATTERN.' +)?'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
$modifiers = $matches['modifiers'] ?? '';
$data = $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), abs((int) $modifiers));
if ('' !== $matches['tag'] && '!' !== $matches['tag']) {
if ('!!binary' === $matches['tag']) {
return Inline::evaluateBinaryScalar($data);
}
return new TaggedValue(substr($matches['tag'], 1), $data);
}
return $data;
}
try {
if ('' !== $value && '{' === $value[0]) {
$cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value));
return Inline::parse($this->lexInlineMapping($cursor), $flags, $this->refs);
} elseif ('' !== $value && '[' === $value[0]) {
$cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value));
return Inline::parse($this->lexInlineSequence($cursor), $flags, $this->refs);
}
switch ($value[0] ?? '') {
case '"':
case "'":
$cursor = \strlen(rtrim($this->currentLine)) - \strlen(rtrim($value));
$parsedValue = Inline::parse($this->lexInlineQuotedString($cursor), $flags, $this->refs);
if (isset($this->currentLine[$cursor]) && preg_replace('/\s*(#.*)?$/A', '', substr($this->currentLine, $cursor))) {
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($this->currentLine, $cursor)));
}
return $parsedValue;
default:
$lines = [];
while ($this->moveToNextLine()) {
// unquoted strings end before the first unindented line
if (0 === $this->getCurrentLineIndentation()) {
$this->moveToPreviousLine();
break;
}
$lines[] = trim($this->currentLine);
}
for ($i = 0, $linesCount = \count($lines), $previousLineBlank = false; $i < $linesCount; ++$i) {
if ('' === $lines[$i]) {
$value .= "\n";
$previousLineBlank = true;
} elseif ($previousLineBlank) {
$value .= $lines[$i];
$previousLineBlank = false;
} else {
$value .= ' '.$lines[$i];
$previousLineBlank = false;
}
}
Inline::$parsedLineNumber = $this->getRealCurrentLineNb();
$parsedValue = Inline::parse($value, $flags, $this->refs);
if ('mapping' === $context && \is_string($parsedValue) && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
throw new ParseException('A colon cannot be used in an unquoted mapping value.', $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
return $parsedValue;
}
} catch (ParseException $e) {
$e->setParsedLine($this->getRealCurrentLineNb() + 1);
$e->setSnippet($this->currentLine);
throw $e;
}
}
/**
* Parses a block scalar.
*
* @param string $style The style indicator that was used to begin this block scalar (| or >)
* @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -)
* @param int $indentation The indentation indicator that was used to begin this block scalar
*/
private function parseBlockScalar(string $style, string $chomping = '', int $indentation = 0): string
{
$notEOF = $this->moveToNextLine();
if (!$notEOF) {
return '';
}
$isCurrentLineBlank = $this->isCurrentLineBlank();
$blockLines = [];
// leading blank lines are consumed before determining indentation
while ($notEOF && $isCurrentLineBlank) {
// newline only if not EOF
if ($notEOF = $this->moveToNextLine()) {
$blockLines[] = '';
$isCurrentLineBlank = $this->isCurrentLineBlank();
}
}
// determine indentation if not specified
if (0 === $indentation) {
$currentLineLength = \strlen($this->currentLine);
for ($i = 0; $i < $currentLineLength && ' ' === $this->currentLine[$i]; ++$i) {
++$indentation;
}
}
if ($indentation > 0) {
$pattern = sprintf('/^ {%d}(.*)$/', $indentation);
while (
$notEOF && (
$isCurrentLineBlank ||
self::preg_match($pattern, $this->currentLine, $matches)
)
) {
if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
$blockLines[] = substr($this->currentLine, $indentation);
} elseif ($isCurrentLineBlank) {
$blockLines[] = '';
} else {
$blockLines[] = $matches[1];
}
// newline only if not EOF
if ($notEOF = $this->moveToNextLine()) {
$isCurrentLineBlank = $this->isCurrentLineBlank();
}
}
} elseif ($notEOF) {
$blockLines[] = '';
}
if ($notEOF) {
$blockLines[] = '';
$this->moveToPreviousLine();
} elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
$blockLines[] = '';
}
// folded style
if ('>' === $style) {
$text = '';
$previousLineIndented = false;
$previousLineBlank = false;
for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
if ('' === $blockLines[$i]) {
$text .= "\n";
$previousLineIndented = false;
$previousLineBlank = true;
} elseif (' ' === $blockLines[$i][0]) {
$text .= "\n".$blockLines[$i];
$previousLineIndented = true;
$previousLineBlank = false;
} elseif ($previousLineIndented) {
$text .= "\n".$blockLines[$i];
$previousLineIndented = false;
$previousLineBlank = false;
} elseif ($previousLineBlank || 0 === $i) {
$text .= $blockLines[$i];
$previousLineIndented = false;
$previousLineBlank = false;
} else {
$text .= ' '.$blockLines[$i];
$previousLineIndented = false;
$previousLineBlank = false;
}
}
} else {
$text = implode("\n", $blockLines);
}
// deal with trailing newlines
if ('' === $chomping) {
$text = preg_replace('/\n+$/', "\n", $text);
} elseif ('-' === $chomping) {
$text = preg_replace('/\n+$/', '', $text);
}
return $text;
}
/**
* Returns true if the next line is indented.
*
* @return bool Returns true if the next line is indented, false otherwise
*/
private function isNextLineIndented(): bool
{
$currentIndentation = $this->getCurrentLineIndentation();
$movements = 0;
do {
$EOF = !$this->moveToNextLine();
if (!$EOF) {
++$movements;
}
} while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
if ($EOF) {
return false;
}
$ret = $this->getCurrentLineIndentation() > $currentIndentation;
for ($i = 0; $i < $movements; ++$i) {
$this->moveToPreviousLine();
}
return $ret;
}
/**
* Returns true if the current line is blank or if it is a comment line.
*
* @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
*/
private function isCurrentLineEmpty(): bool
{
return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
}
/**
* Returns true if the current line is blank.
*
* @return bool Returns true if the current line is blank, false otherwise
*/
private function isCurrentLineBlank(): bool
{
return '' === $this->currentLine || '' === trim($this->currentLine, ' ');
}
/**
* Returns true if the current line is a comment line.
*
* @return bool Returns true if the current line is a comment line, false otherwise
*/
private function isCurrentLineComment(): bool
{
//checking explicitly the first char of the trim is faster than loops or strpos
$ltrimmedLine = '' !== $this->currentLine && ' ' === $this->currentLine[0] ? ltrim($this->currentLine, ' ') : $this->currentLine;
return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
}
private function isCurrentLineLastLineInDocument(): bool
{
return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
}
/**
* Cleanups a YAML string to be parsed.
*
* @param string $value The input YAML string
*
* @return string A cleaned up YAML string
*/
private function cleanup(string $value): string
{
$value = str_replace(["\r\n", "\r"], "\n", $value);
// strip YAML header
$count = 0;
$value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
$this->offset += $count;
// remove leading comments
$trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
if (1 === $count) {
// items have been removed, update the offset
$this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
$value = $trimmedValue;
}
// remove start of the document marker (---)
$trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
if (1 === $count) {
// items have been removed, update the offset
$this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
$value = $trimmedValue;
// remove end of the document marker (...)
$value = preg_replace('#\.\.\.\s*$#', '', $value);
}
return $value;
}
/**
* Returns true if the next line starts unindented collection.
*
* @return bool Returns true if the next line starts unindented collection, false otherwise
*/
private function isNextLineUnIndentedCollection(): bool
{
$currentIndentation = $this->getCurrentLineIndentation();
$movements = 0;
do {
$EOF = !$this->moveToNextLine();
if (!$EOF) {
++$movements;
}
} while (!$EOF && ($this->isCurrentLineEmpty() || $this->isCurrentLineComment()));
if ($EOF) {
return false;
}
$ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
for ($i = 0; $i < $movements; ++$i) {
$this->moveToPreviousLine();
}
return $ret;
}
/**
* Returns true if the string is un-indented collection item.
*
* @return bool Returns true if the string is un-indented collection item, false otherwise
*/
private function isStringUnIndentedCollectionItem(): bool
{
return 0 === strncmp($this->currentLine, '- ', 2) || '-' === rtrim($this->currentLine);
}
/**
* A local wrapper for "preg_match" which will throw a ParseException if there
* is an internal error in the PCRE engine.
*
* This avoids us needing to check for "false" every time PCRE is used
* in the YAML engine
*
* @throws ParseException on a PCRE internal error
*
* @see preg_last_error()
*
* @internal
*/
public static function preg_match(string $pattern, string $subject, array &$matches = null, int $flags = 0, int $offset = 0): int
{
if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
switch (preg_last_error()) {
case \PREG_INTERNAL_ERROR:
$error = 'Internal PCRE error.';
break;
case \PREG_BACKTRACK_LIMIT_ERROR:
$error = 'pcre.backtrack_limit reached.';
break;
case \PREG_RECURSION_LIMIT_ERROR:
$error = 'pcre.recursion_limit reached.';
break;
case \PREG_BAD_UTF8_ERROR:
$error = 'Malformed UTF-8 data.';
break;
case \PREG_BAD_UTF8_OFFSET_ERROR:
$error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
break;
default:
$error = 'Error.';
}
throw new ParseException($error);
}
return $ret;
}
/**
* Trim the tag on top of the value.
*
* Prevent values such as "!foo {quz: bar}" to be considered as
* a mapping block.
*/
private function trimTag(string $value): string
{
if ('!' === $value[0]) {
return ltrim(substr($value, 1, strcspn($value, " \r\n", 1)), ' ');
}
return $value;
}
private function getLineTag(string $value, int $flags, bool $nextLineCheck = true): ?string
{
if ('' === $value || '!' !== $value[0] || 1 !== self::preg_match('/^'.self::TAG_PATTERN.' *( +#.*)?$/', $value, $matches)) {
return null;
}
if ($nextLineCheck && !$this->isNextLineIndented()) {
return null;
}
$tag = substr($matches['tag'], 1);
// Built-in tags
if ($tag && '!' === $tag[0]) {
throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
if (Yaml::PARSE_CUSTOM_TAGS & $flags) {
return $tag;
}
throw new ParseException(sprintf('Tags support is not enabled. You must use the flag "Yaml::PARSE_CUSTOM_TAGS" to use "%s".', $matches['tag']), $this->getRealCurrentLineNb() + 1, $value, $this->filename);
}
private function lexInlineQuotedString(int &$cursor = 0): string
{
$quotation = $this->currentLine[$cursor];
$value = $quotation;
++$cursor;
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
$lineNumber = 0;
do {
if (++$lineNumber > 1) {
$cursor += strspn($this->currentLine, ' ', $cursor);
}
if ($this->isCurrentLineBlank()) {
$value .= "\n";
} elseif (!$previousLineWasNewline && !$previousLineWasTerminatedWithBackslash) {
$value .= ' ';
}
for (; \strlen($this->currentLine) > $cursor; ++$cursor) {
switch ($this->currentLine[$cursor]) {
case '\\':
if ("'" === $quotation) {
$value .= '\\';
} elseif (isset($this->currentLine[++$cursor])) {
$value .= '\\'.$this->currentLine[$cursor];
}
break;
case $quotation:
++$cursor;
if ("'" === $quotation && isset($this->currentLine[$cursor]) && "'" === $this->currentLine[$cursor]) {
$value .= "''";
break;
}
return $value.$quotation;
default:
$value .= $this->currentLine[$cursor];
}
}
if ($this->isCurrentLineBlank()) {
$previousLineWasNewline = true;
$previousLineWasTerminatedWithBackslash = false;
} elseif ('\\' === $this->currentLine[-1]) {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = true;
} else {
$previousLineWasNewline = false;
$previousLineWasTerminatedWithBackslash = false;
}
if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
throw new ParseException('Malformed inline YAML string');
}
private function lexUnquotedString(int &$cursor): string
{
$offset = $cursor;
$cursor += strcspn($this->currentLine, '[]{},: ', $cursor);
return substr($this->currentLine, $offset, $cursor - $offset);
}
private function lexInlineMapping(int &$cursor = 0): string
{
return $this->lexInlineStructure($cursor, '}');
}
private function lexInlineSequence(int &$cursor = 0): string
{
return $this->lexInlineStructure($cursor, ']');
}
private function lexInlineStructure(int &$cursor, string $closingTag): string
{
$value = $this->currentLine[$cursor];
++$cursor;
do {
$this->consumeWhitespaces($cursor);
while (isset($this->currentLine[$cursor])) {
switch ($this->currentLine[$cursor]) {
case '"':
case "'":
$value .= $this->lexInlineQuotedString($cursor);
break;
case ':':
case ',':
$value .= $this->currentLine[$cursor];
++$cursor;
break;
case '{':
$value .= $this->lexInlineMapping($cursor);
break;
case '[':
$value .= $this->lexInlineSequence($cursor);
break;
case $closingTag:
$value .= $this->currentLine[$cursor];
++$cursor;
return $value;
case '#':
break 2;
default:
$value .= $this->lexUnquotedString($cursor);
}
if ($this->consumeWhitespaces($cursor)) {
$value .= ' ';
}
}
if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
throw new ParseException('Malformed inline YAML string');
}
private function consumeWhitespaces(int &$cursor): bool
{
$whitespacesConsumed = 0;
do {
$whitespaceOnlyTokenLength = strspn($this->currentLine, ' ', $cursor);
$whitespacesConsumed += $whitespaceOnlyTokenLength;
$cursor += $whitespaceOnlyTokenLength;
if (isset($this->currentLine[$cursor])) {
return 0 < $whitespacesConsumed;
}
if ($this->hasMoreLines()) {
$cursor = 0;
}
} while ($this->moveToNextLine());
return 0 < $whitespacesConsumed;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
use Symfony\Component\Yaml\Exception\DumpException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Tag\TaggedValue;
/**
* Inline implements a YAML parser/dumper for the YAML inline syntax.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
class Inline
{
public const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
public static $parsedLineNumber = -1;
public static $parsedFilename;
private static $exceptionOnInvalidType = false;
private static $objectSupport = false;
private static $objectForMap = false;
private static $constantSupport = false;
public static function initialize(int $flags, int $parsedLineNumber = null, string $parsedFilename = null)
{
self::$exceptionOnInvalidType = (bool) (Yaml::PARSE_EXCEPTION_ON_INVALID_TYPE & $flags);
self::$objectSupport = (bool) (Yaml::PARSE_OBJECT & $flags);
self::$objectForMap = (bool) (Yaml::PARSE_OBJECT_FOR_MAP & $flags);
self::$constantSupport = (bool) (Yaml::PARSE_CONSTANT & $flags);
self::$parsedFilename = $parsedFilename;
if (null !== $parsedLineNumber) {
self::$parsedLineNumber = $parsedLineNumber;
}
}
/**
* Converts a YAML string to a PHP value.
*
* @param string $value A YAML string
* @param int $flags A bit field of PARSE_* constants to customize the YAML parser behavior
* @param array $references Mapping of variable names to values
*
* @return mixed A PHP value
*
* @throws ParseException
*/
public static function parse(string $value = null, int $flags = 0, array $references = [])
{
self::initialize($flags);
$value = trim($value);
if ('' === $value) {
return '';
}
if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
$mbEncoding = mb_internal_encoding();
mb_internal_encoding('ASCII');
}
try {
$i = 0;
$tag = self::parseTag($value, $i, $flags);
switch ($value[$i]) {
case '[':
$result = self::parseSequence($value, $flags, $i, $references);
++$i;
break;
case '{':
$result = self::parseMapping($value, $flags, $i, $references);
++$i;
break;
default:
$result = self::parseScalar($value, $flags, null, $i, null === $tag, $references);
}
// some comments are allowed at the end
if (preg_replace('/\s*#.*$/A', '', substr($value, $i))) {
throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if (null !== $tag && '' !== $tag) {
return new TaggedValue($tag, $result);
}
return $result;
} finally {
if (isset($mbEncoding)) {
mb_internal_encoding($mbEncoding);
}
}
}
/**
* Dumps a given PHP variable to a YAML string.
*
* @param mixed $value The PHP variable to convert
* @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
*
* @return string The YAML string representing the PHP value
*
* @throws DumpException When trying to dump PHP resource
*/
public static function dump($value, int $flags = 0): string
{
switch (true) {
case \is_resource($value):
if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
}
return self::dumpNull($flags);
case $value instanceof \DateTimeInterface:
return $value->format('c');
case \is_object($value):
if ($value instanceof TaggedValue) {
return '!'.$value->getTag().' '.self::dump($value->getValue(), $flags);
}
if (Yaml::DUMP_OBJECT & $flags) {
return '!php/object '.self::dump(serialize($value));
}
if (Yaml::DUMP_OBJECT_AS_MAP & $flags && ($value instanceof \stdClass || $value instanceof \ArrayObject)) {
$output = [];
foreach ($value as $key => $val) {
$output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
}
return sprintf('{ %s }', implode(', ', $output));
}
if (Yaml::DUMP_EXCEPTION_ON_INVALID_TYPE & $flags) {
throw new DumpException('Object support when dumping a YAML file has been disabled.');
}
return self::dumpNull($flags);
case \is_array($value):
return self::dumpArray($value, $flags);
case null === $value:
return self::dumpNull($flags);
case true === $value:
return 'true';
case false === $value:
return 'false';
case ctype_digit($value):
return \is_string($value) ? "'$value'" : (int) $value;
case is_numeric($value) && false === strpos($value, "\f") && false === strpos($value, "\n") && false === strpos($value, "\r") && false === strpos($value, "\t") && false === strpos($value, "\v"):
$locale = setlocale(\LC_NUMERIC, 0);
if (false !== $locale) {
setlocale(\LC_NUMERIC, 'C');
}
if (\is_float($value)) {
$repr = (string) $value;
if (is_infinite($value)) {
$repr = str_ireplace('INF', '.Inf', $repr);
} elseif (floor($value) == $value && $repr == $value) {
// Preserve float data type since storing a whole number will result in integer value.
$repr = '!!float '.$repr;
}
} else {
$repr = \is_string($value) ? "'$value'" : (string) $value;
}
if (false !== $locale) {
setlocale(\LC_NUMERIC, $locale);
}
return $repr;
case '' == $value:
return "''";
case self::isBinaryString($value):
return '!!binary '.base64_encode($value);
case Escaper::requiresDoubleQuoting($value):
return Escaper::escapeWithDoubleQuotes($value);
case Escaper::requiresSingleQuoting($value):
case Parser::preg_match('{^[0-9]+[_0-9]*$}', $value):
case Parser::preg_match(self::getHexRegex(), $value):
case Parser::preg_match(self::getTimestampRegex(), $value):
return Escaper::escapeWithSingleQuotes($value);
default:
return $value;
}
}
/**
* Check if given array is hash or just normal indexed array.
*
* @param array|\ArrayObject|\stdClass $value The PHP array or array-like object to check
*
* @return bool true if value is hash array, false otherwise
*/
public static function isHash($value): bool
{
if ($value instanceof \stdClass || $value instanceof \ArrayObject) {
return true;
}
$expectedKey = 0;
foreach ($value as $key => $val) {
if ($key !== $expectedKey++) {
return true;
}
}
return false;
}
/**
* Dumps a PHP array to a YAML string.
*
* @param array $value The PHP array to dump
* @param int $flags A bit field of Yaml::DUMP_* constants to customize the dumped YAML string
*
* @return string The YAML string representing the PHP array
*/
private static function dumpArray(array $value, int $flags): string
{
// array
if (($value || Yaml::DUMP_EMPTY_ARRAY_AS_SEQUENCE & $flags) && !self::isHash($value)) {
$output = [];
foreach ($value as $val) {
$output[] = self::dump($val, $flags);
}
return sprintf('[%s]', implode(', ', $output));
}
// hash
$output = [];
foreach ($value as $key => $val) {
$output[] = sprintf('%s: %s', self::dump($key, $flags), self::dump($val, $flags));
}
return sprintf('{ %s }', implode(', ', $output));
}
private static function dumpNull(int $flags): string
{
if (Yaml::DUMP_NULL_AS_TILDE & $flags) {
return '~';
}
return 'null';
}
/**
* Parses a YAML scalar.
*
* @return mixed
*
* @throws ParseException When malformed inline YAML string is parsed
*/
public static function parseScalar(string $scalar, int $flags = 0, array $delimiters = null, int &$i = 0, bool $evaluate = true, array $references = [])
{
if (\in_array($scalar[$i], ['"', "'"], true)) {
// quoted scalar
$output = self::parseQuotedScalar($scalar, $i);
if (null !== $delimiters) {
$tmp = ltrim(substr($scalar, $i), " \n");
if ('' === $tmp) {
throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (!\in_array($tmp[0], $delimiters)) {
throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
}
} else {
// "normal" string
if (!$delimiters) {
$output = substr($scalar, $i);
$i += \strlen($output);
// remove comments
if (Parser::preg_match('/[ \t]+#/', $output, $match, \PREG_OFFSET_CAPTURE)) {
$output = substr($output, 0, $match[0][1]);
}
} elseif (Parser::preg_match('/^(.*?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
$output = $match[1];
$i += \strlen($output);
$output = trim($output);
} else {
throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $scalar), self::$parsedLineNumber + 1, null, self::$parsedFilename);
}
// a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0] || '%' === $output[0])) {
throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]), self::$parsedLineNumber + 1, $output, self::$parsedFilename);
}
if ($evaluate) {
$output = self::evaluateScalar($output, $flags, $references);
}
}
return $output;
}
/**
* Parses a YAML quoted scalar.
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseQuotedScalar(string $scalar, int &$i): string
{
if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
throw new ParseException(sprintf('Malformed inline YAML string: "%s".', substr($scalar, $i)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
$output = substr($match[0], 1, -1);
$unescaper = new Unescaper();
if ('"' == $scalar[$i]) {
$output = $unescaper->unescapeDoubleQuotedString($output);
} else {
$output = $unescaper->unescapeSingleQuotedString($output);
}
$i += \strlen($match[0]);
return $output;
}
/**
* Parses a YAML sequence.
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseSequence(string $sequence, int $flags, int &$i = 0, array $references = []): array
{
$output = [];
$len = \strlen($sequence);
++$i;
// [foo, bar, ...]
while ($i < $len) {
if (']' === $sequence[$i]) {
return $output;
}
if (',' === $sequence[$i] || ' ' === $sequence[$i]) {
++$i;
continue;
}
$tag = self::parseTag($sequence, $i, $flags);
switch ($sequence[$i]) {
case '[':
// nested sequence
$value = self::parseSequence($sequence, $flags, $i, $references);
break;
case '{':
// nested mapping
$value = self::parseMapping($sequence, $flags, $i, $references);
break;
default:
$isQuoted = \in_array($sequence[$i], ['"', "'"], true);
$value = self::parseScalar($sequence, $flags, [',', ']'], $i, null === $tag, $references);
// the value can be an array if a reference has been resolved to an array var
if (\is_string($value) && !$isQuoted && false !== strpos($value, ': ')) {
// embedded mapping?
try {
$pos = 0;
$value = self::parseMapping('{'.$value.'}', $flags, $pos, $references);
} catch (\InvalidArgumentException $e) {
// no, it's not
}
}
--$i;
}
if (null !== $tag && '' !== $tag) {
$value = new TaggedValue($tag, $value);
}
$output[] = $value;
++$i;
}
throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $sequence), self::$parsedLineNumber + 1, null, self::$parsedFilename);
}
/**
* Parses a YAML mapping.
*
* @return array|\stdClass
*
* @throws ParseException When malformed inline YAML string is parsed
*/
private static function parseMapping(string $mapping, int $flags, int &$i = 0, array $references = [])
{
$output = [];
$len = \strlen($mapping);
++$i;
$allowOverwrite = false;
// {foo: bar, bar:foo, ...}
while ($i < $len) {
switch ($mapping[$i]) {
case ' ':
case ',':
case "\n":
++$i;
continue 2;
case '}':
if (self::$objectForMap) {
return (object) $output;
}
return $output;
}
// key
$offsetBeforeKeyParsing = $i;
$isKeyQuoted = \in_array($mapping[$i], ['"', "'"], true);
$key = self::parseScalar($mapping, $flags, [':', ' '], $i, false, []);
if ($offsetBeforeKeyParsing === $i) {
throw new ParseException('Missing mapping key.', self::$parsedLineNumber + 1, $mapping);
}
if ('!php/const' === $key) {
$key .= ' '.self::parseScalar($mapping, $flags, [':'], $i, false, []);
$key = self::evaluateScalar($key, $flags);
}
if (false === $i = strpos($mapping, ':', $i)) {
break;
}
if (!$isKeyQuoted) {
$evaluatedKey = self::evaluateScalar($key, $flags, $references);
if ('' !== $key && $evaluatedKey !== $key && !\is_string($evaluatedKey) && !\is_int($evaluatedKey)) {
throw new ParseException('Implicit casting of incompatible mapping keys to strings is not supported. Quote your evaluable mapping keys instead.', self::$parsedLineNumber + 1, $mapping);
}
}
if (!$isKeyQuoted && (!isset($mapping[$i + 1]) || !\in_array($mapping[$i + 1], [' ', ',', '[', ']', '{', '}', "\n"], true))) {
throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping);
}
if ('<<' === $key) {
$allowOverwrite = true;
}
while ($i < $len) {
if (':' === $mapping[$i] || ' ' === $mapping[$i] || "\n" === $mapping[$i]) {
++$i;
continue;
}
$tag = self::parseTag($mapping, $i, $flags);
switch ($mapping[$i]) {
case '[':
// nested sequence
$value = self::parseSequence($mapping, $flags, $i, $references);
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
// But overwriting is allowed when a merge node is used in current block.
if ('<<' === $key) {
foreach ($value as $parsedValue) {
$output += $parsedValue;
}
} elseif ($allowOverwrite || !isset($output[$key])) {
if (null !== $tag) {
$output[$key] = new TaggedValue($tag, $value);
} else {
$output[$key] = $value;
}
} elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
break;
case '{':
// nested mapping
$value = self::parseMapping($mapping, $flags, $i, $references);
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
// But overwriting is allowed when a merge node is used in current block.
if ('<<' === $key) {
$output += $value;
} elseif ($allowOverwrite || !isset($output[$key])) {
if (null !== $tag) {
$output[$key] = new TaggedValue($tag, $value);
} else {
$output[$key] = $value;
}
} elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
break;
default:
$value = self::parseScalar($mapping, $flags, [',', '}', "\n"], $i, null === $tag, $references);
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
// But overwriting is allowed when a merge node is used in current block.
if ('<<' === $key) {
$output += $value;
} elseif ($allowOverwrite || !isset($output[$key])) {
if (null !== $tag) {
$output[$key] = new TaggedValue($tag, $value);
} else {
$output[$key] = $value;
}
} elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
--$i;
}
++$i;
continue 2;
}
}
throw new ParseException(sprintf('Malformed inline YAML string: "%s".', $mapping), self::$parsedLineNumber + 1, null, self::$parsedFilename);
}
/**
* Evaluates scalars and replaces magic values.
*
* @return mixed The evaluated YAML string
*
* @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
*/
private static function evaluateScalar(string $scalar, int $flags, array $references = [])
{
$scalar = trim($scalar);
if ('*' === ($scalar[0] ?? '')) {
if (false !== $pos = strpos($scalar, '#')) {
$value = substr($scalar, 1, $pos - 2);
} else {
$value = substr($scalar, 1);
}
// an unquoted *
if (false === $value || '' === $value) {
throw new ParseException('A reference must contain at least one character.', self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if (!\array_key_exists($value, $references)) {
throw new ParseException(sprintf('Reference "%s" does not exist.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
return $references[$value];
}
$scalarLower = strtolower($scalar);
switch (true) {
case 'null' === $scalarLower:
case '' === $scalar:
case '~' === $scalar:
return null;
case 'true' === $scalarLower:
return true;
case 'false' === $scalarLower:
return false;
case '!' === $scalar[0]:
switch (true) {
case 0 === strncmp($scalar, '!!str ', 6):
return (string) substr($scalar, 6);
case 0 === strncmp($scalar, '! ', 2):
return substr($scalar, 2);
case 0 === strncmp($scalar, '!php/object', 11):
if (self::$objectSupport) {
if (!isset($scalar[12])) {
trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/object tag without a value is deprecated.');
return false;
}
return unserialize(self::parseScalar(substr($scalar, 12)));
}
if (self::$exceptionOnInvalidType) {
throw new ParseException('Object support when parsing a YAML file has been disabled.', self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return null;
case 0 === strncmp($scalar, '!php/const', 10):
if (self::$constantSupport) {
if (!isset($scalar[11])) {
trigger_deprecation('symfony/yaml', '5.1', 'Using the !php/const tag without a value is deprecated.');
return '';
}
$i = 0;
if (\defined($const = self::parseScalar(substr($scalar, 11), 0, null, $i, false))) {
return \constant($const);
}
throw new ParseException(sprintf('The constant "%s" is not defined.', $const), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (self::$exceptionOnInvalidType) {
throw new ParseException(sprintf('The string "%s" could not be parsed as a constant. Did you forget to pass the "Yaml::PARSE_CONSTANT" flag to the parser?', $scalar), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return null;
case 0 === strncmp($scalar, '!!float ', 8):
return (float) substr($scalar, 8);
case 0 === strncmp($scalar, '!!binary ', 9):
return self::evaluateBinaryScalar(substr($scalar, 9));
default:
throw new ParseException(sprintf('The string "%s" could not be parsed as it uses an unsupported built-in tag.', $scalar), self::$parsedLineNumber, $scalar, self::$parsedFilename);
}
// no break
case preg_match('/^(?:\+|-)?0o(?P<value>[0-7_]++)$/', $scalar, $matches):
$value = str_replace('_', '', $matches['value']);
if ('-' === $scalar[0]) {
return -octdec($value);
} else {
return octdec($value);
}
// Optimize for returning strings.
// no break
case \in_array($scalar[0], ['+', '-', '.'], true) || is_numeric($scalar[0]):
if (Parser::preg_match('{^[+-]?[0-9][0-9_]*$}', $scalar)) {
$scalar = str_replace('_', '', (string) $scalar);
}
switch (true) {
case ctype_digit($scalar):
if (preg_match('/^0[0-7]+$/', $scalar)) {
trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0.');
return octdec($scalar);
}
$cast = (int) $scalar;
return ($scalar === (string) $cast) ? $cast : $scalar;
case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
if (preg_match('/^-0[0-7]+$/', $scalar)) {
trigger_deprecation('symfony/yaml', '5.1', 'Support for parsing numbers prefixed with 0 as octal numbers. They will be parsed as strings as of 6.0.');
return -octdec(substr($scalar, 1));
}
$cast = (int) $scalar;
return ($scalar === (string) $cast) ? $cast : $scalar;
case is_numeric($scalar):
case Parser::preg_match(self::getHexRegex(), $scalar):
$scalar = str_replace('_', '', $scalar);
return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
case '.inf' === $scalarLower:
case '.nan' === $scalarLower:
return -log(0);
case '-.inf' === $scalarLower:
return log(0);
case Parser::preg_match('/^(-|\+)?[0-9][0-9_]*(\.[0-9_]+)?$/', $scalar):
return (float) str_replace('_', '', $scalar);
case Parser::preg_match(self::getTimestampRegex(), $scalar):
if (Yaml::PARSE_DATETIME & $flags) {
// When no timezone is provided in the parsed date, YAML spec says we must assume UTC.
return new \DateTime($scalar, new \DateTimeZone('UTC'));
}
$timeZone = date_default_timezone_get();
date_default_timezone_set('UTC');
$time = strtotime($scalar);
date_default_timezone_set($timeZone);
return $time;
}
}
return (string) $scalar;
}
private static function parseTag(string $value, int &$i, int $flags): ?string
{
if ('!' !== $value[$i]) {
return null;
}
$tagLength = strcspn($value, " \t\n[]{},", $i + 1);
$tag = substr($value, $i + 1, $tagLength);
$nextOffset = $i + $tagLength + 1;
$nextOffset += strspn($value, ' ', $nextOffset);
if ('' === $tag && (!isset($value[$nextOffset]) || \in_array($value[$nextOffset], [']', '}', ','], true))) {
throw new ParseException(sprintf('Using the unquoted scalar value "!" is not supported. You must quote it.', $value), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
// Is followed by a scalar and is a built-in tag
if ('' !== $tag && (!isset($value[$nextOffset]) || !\in_array($value[$nextOffset], ['[', '{'], true)) && ('!' === $tag[0] || 'str' === $tag || 'php/const' === $tag || 'php/object' === $tag)) {
// Manage in {@link self::evaluateScalar()}
return null;
}
$i = $nextOffset;
// Built-in tags
if ('' !== $tag && '!' === $tag[0]) {
throw new ParseException(sprintf('The built-in tag "!%s" is not implemented.', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if ('' !== $tag && !isset($value[$i])) {
throw new ParseException(sprintf('Missing value for tag "%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
if ('' === $tag || Yaml::PARSE_CUSTOM_TAGS & $flags) {
return $tag;
}
throw new ParseException(sprintf('Tags support is not enabled. Enable the "Yaml::PARSE_CUSTOM_TAGS" flag to use "!%s".', $tag), self::$parsedLineNumber + 1, $value, self::$parsedFilename);
}
public static function evaluateBinaryScalar(string $scalar): string
{
$parsedBinaryData = self::parseScalar(preg_replace('/\s/', '', $scalar));
if (0 !== (\strlen($parsedBinaryData) % 4)) {
throw new ParseException(sprintf('The normalized base64 encoded data (data without whitespace characters) length must be a multiple of four (%d bytes given).', \strlen($parsedBinaryData)), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
if (!Parser::preg_match('#^[A-Z0-9+/]+={0,2}$#i', $parsedBinaryData)) {
throw new ParseException(sprintf('The base64 encoded data (%s) contains invalid characters.', $parsedBinaryData), self::$parsedLineNumber + 1, $scalar, self::$parsedFilename);
}
return base64_decode($parsedBinaryData, true);
}
private static function isBinaryString(string $value): bool
{
return !preg_match('//u', $value) || preg_match('/[^\x00\x07-\x0d\x1B\x20-\xff]/', $value);
}
/**
* Gets a regex that matches a YAML date.
*
* @return string The regular expression
*
* @see http://www.yaml.org/spec/1.2/spec.html#id2761573
*/
private static function getTimestampRegex(): string
{
return <<<EOF
~^
(?P<year>[0-9][0-9][0-9][0-9])
-(?P<month>[0-9][0-9]?)
-(?P<day>[0-9][0-9]?)
(?:(?:[Tt]|[ \t]+)
(?P<hour>[0-9][0-9]?)
:(?P<minute>[0-9][0-9])
:(?P<second>[0-9][0-9])
(?:\.(?P<fraction>[0-9]*))?
(?:[ \t]*(?P<tz>Z|(?P<tz_sign>[-+])(?P<tz_hour>[0-9][0-9]?)
(?::(?P<tz_minute>[0-9][0-9]))?))?)?
$~x
EOF;
}
/**
* Gets a regex that matches a YAML number in hexadecimal notation.
*/
private static function getHexRegex(): string
{
return '~^0x[0-9a-f_]++$~i';
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during dumping.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DumpException extends RuntimeException
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during parsing.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParseException extends RuntimeException
{
private $parsedFile;
private $parsedLine;
private $snippet;
private $rawMessage;
/**
* @param string $message The error message
* @param int $parsedLine The line where the error occurred
* @param string|null $snippet The snippet of code near the problem
* @param string|null $parsedFile The file name where the error occurred
*/
public function __construct(string $message, int $parsedLine = -1, string $snippet = null, string $parsedFile = null, \Throwable $previous = null)
{
$this->parsedFile = $parsedFile;
$this->parsedLine = $parsedLine;
$this->snippet = $snippet;
$this->rawMessage = $message;
$this->updateRepr();
parent::__construct($this->message, 0, $previous);
}
/**
* Gets the snippet of code near the error.
*
* @return string The snippet of code
*/
public function getSnippet()
{
return $this->snippet;
}
/**
* Sets the snippet of code near the error.
*/
public function setSnippet(string $snippet)
{
$this->snippet = $snippet;
$this->updateRepr();
}
/**
* Gets the filename where the error occurred.
*
* This method returns null if a string is parsed.
*
* @return string The filename
*/
public function getParsedFile()
{
return $this->parsedFile;
}
/**
* Sets the filename where the error occurred.
*/
public function setParsedFile(string $parsedFile)
{
$this->parsedFile = $parsedFile;
$this->updateRepr();
}
/**
* Gets the line where the error occurred.
*
* @return int The file line
*/
public function getParsedLine()
{
return $this->parsedLine;
}
/**
* Sets the line where the error occurred.
*/
public function setParsedLine(int $parsedLine)
{
$this->parsedLine = $parsedLine;
$this->updateRepr();
}
private function updateRepr()
{
$this->message = $this->rawMessage;
$dot = false;
if ('.' === substr($this->message, -1)) {
$this->message = substr($this->message, 0, -1);
$dot = true;
}
if (null !== $this->parsedFile) {
$this->message .= sprintf(' in %s', json_encode($this->parsedFile, \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE));
}
if ($this->parsedLine >= 0) {
$this->message .= sprintf(' at line %d', $this->parsedLine);
}
if ($this->snippet) {
$this->message .= sprintf(' (near "%s")', $this->snippet);
}
if ($dot) {
$this->message .= '.';
}
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml\Exception;
/**
* Exception class thrown when an error occurs during parsing.
*
* @author Romain Neutron <imprec@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
Copyright (c) 2004-2021 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Yaml;
/**
* Escaper encapsulates escaping rules for single and double-quoted
* YAML strings.
*
* @author Matthew Lewinski <matthew@lewinski.org>
*
* @internal
*/
class Escaper
{
// Characters that would cause a dumped string to require double quoting.
public const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\x7f|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9";
// Mapping arrays for escaping a double quoted string. The backslash is
// first to ensure proper escaping because str_replace operates iteratively
// on the input arrays. This ordering of the characters avoids the use of strtr,
// which performs more slowly.
private const ESCAPEES = ['\\', '\\\\', '\\"', '"',
"\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
"\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f",
"\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
"\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
"\x7f",
"\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",
];
private const ESCAPED = ['\\\\', '\\"', '\\\\', '\\"',
'\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a',
'\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f',
'\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17',
'\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f',
'\\x7f',
'\\N', '\\_', '\\L', '\\P',
];
/**
* Determines if a PHP value would require double quoting in YAML.
*
* @param string $value A PHP value
*
* @return bool True if the value would require double quotes
*/
public static function requiresDoubleQuoting(string $value): bool
{
return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value);
}
/**
* Escapes and surrounds a PHP value with double quotes.
*
* @param string $value A PHP value
*
* @return string The quoted, escaped string
*/
public static function escapeWithDoubleQuotes(string $value): string
{
return sprintf('"%s"', str_replace(self::ESCAPEES, self::ESCAPED, $value));
}
/**
* Determines if a PHP value would require single quoting in YAML.
*
* @param string $value A PHP value
*
* @return bool True if the value would require single quotes
*/
public static function requiresSingleQuoting(string $value): bool
{
// Determines if a PHP value is entirely composed of a value that would
// require single quoting in YAML.
if (\in_array(strtolower($value), ['null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'])) {
return true;
}
// Determines if the PHP value contains any single characters that would
// cause it to require single quoting in YAML.
return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` \p{Zs}]/xu', $value);
}
/**
* Escapes and surrounds a PHP value with single quotes.
*
* @param string $value A PHP value
*
* @return string The quoted, escaped string
*/
public static function escapeWithSingleQuotes(string $value): string
{
return sprintf("'%s'", str_replace('\'', '\'\'', $value));
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem;
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
use Symfony\Component\Filesystem\Exception\InvalidArgumentException;
use Symfony\Component\Filesystem\Exception\IOException;
/**
* Provides basic utility to manipulate the file system.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Filesystem
{
private static $lastError;
/**
* Copies a file.
*
* If the target file is older than the origin file, it's always overwritten.
* If the target file is newer, it is overwritten only when the
* $overwriteNewerFiles option is set to true.
*
* @throws FileNotFoundException When originFile doesn't exist
* @throws IOException When copy fails
*/
public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false)
{
$originIsLocal = stream_is_local($originFile) || 0 === stripos($originFile, 'file://');
if ($originIsLocal && !is_file($originFile)) {
throw new FileNotFoundException(sprintf('Failed to copy "%s" because file does not exist.', $originFile), 0, null, $originFile);
}
$this->mkdir(\dirname($targetFile));
$doCopy = true;
if (!$overwriteNewerFiles && null === parse_url($originFile, \PHP_URL_HOST) && is_file($targetFile)) {
$doCopy = filemtime($originFile) > filemtime($targetFile);
}
if ($doCopy) {
// https://bugs.php.net/64634
if (false === $source = @fopen($originFile, 'r')) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because source file could not be opened for reading.', $originFile, $targetFile), 0, null, $originFile);
}
// Stream context created to allow files overwrite when using FTP stream wrapper - disabled by default
if (false === $target = @fopen($targetFile, 'w', null, stream_context_create(['ftp' => ['overwrite' => true]]))) {
throw new IOException(sprintf('Failed to copy "%s" to "%s" because target file could not be opened for writing.', $originFile, $targetFile), 0, null, $originFile);
}
$bytesCopied = stream_copy_to_stream($source, $target);
fclose($source);
fclose($target);
unset($source, $target);
if (!is_file($targetFile)) {
throw new IOException(sprintf('Failed to copy "%s" to "%s".', $originFile, $targetFile), 0, null, $originFile);
}
if ($originIsLocal) {
// Like `cp`, preserve executable permission bits
@chmod($targetFile, fileperms($targetFile) | (fileperms($originFile) & 0111));
if ($bytesCopied !== $bytesOrigin = filesize($originFile)) {
throw new IOException(sprintf('Failed to copy the whole content of "%s" to "%s" (%g of %g bytes copied).', $originFile, $targetFile, $bytesCopied, $bytesOrigin), 0, null, $originFile);
}
}
}
}
/**
* Creates a directory recursively.
*
* @param string|iterable $dirs The directory path
*
* @throws IOException On any directory creation failure
*/
public function mkdir($dirs, int $mode = 0777)
{
foreach ($this->toIterable($dirs) as $dir) {
if (is_dir($dir)) {
continue;
}
if (!self::box('mkdir', $dir, $mode, true)) {
if (!is_dir($dir)) {
// The directory was not created by a concurrent process. Let's throw an exception with a developer friendly error message if we have one
if (self::$lastError) {
throw new IOException(sprintf('Failed to create "%s": ', $dir).self::$lastError, 0, null, $dir);
}
throw new IOException(sprintf('Failed to create "%s".', $dir), 0, null, $dir);
}
}
}
}
/**
* Checks the existence of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to check
*
* @return bool true if the file exists, false otherwise
*/
public function exists($files)
{
$maxPathLength = \PHP_MAXPATHLEN - 2;
foreach ($this->toIterable($files) as $file) {
if (\strlen($file) > $maxPathLength) {
throw new IOException(sprintf('Could not check if file exist because path length exceeds %d characters.', $maxPathLength), 0, null, $file);
}
if (!file_exists($file)) {
return false;
}
}
return true;
}
/**
* Sets access and modification time of file.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to create
* @param int|null $time The touch time as a Unix timestamp, if not supplied the current system time is used
* @param int|null $atime The access time as a Unix timestamp, if not supplied the current system time is used
*
* @throws IOException When touch fails
*/
public function touch($files, int $time = null, int $atime = null)
{
foreach ($this->toIterable($files) as $file) {
$touch = $time ? @touch($file, $time, $atime) : @touch($file);
if (true !== $touch) {
throw new IOException(sprintf('Failed to touch "%s".', $file), 0, null, $file);
}
}
}
/**
* Removes files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to remove
*
* @throws IOException When removal fails
*/
public function remove($files)
{
if ($files instanceof \Traversable) {
$files = iterator_to_array($files, false);
} elseif (!\is_array($files)) {
$files = [$files];
}
$files = array_reverse($files);
foreach ($files as $file) {
if (is_link($file)) {
// See https://bugs.php.net/52176
if (!(self::box('unlink', $file) || '\\' !== \DIRECTORY_SEPARATOR || self::box('rmdir', $file)) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove symlink "%s": ', $file).self::$lastError);
}
} elseif (is_dir($file)) {
$this->remove(new \FilesystemIterator($file, \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::SKIP_DOTS));
if (!self::box('rmdir', $file) && file_exists($file)) {
throw new IOException(sprintf('Failed to remove directory "%s": ', $file).self::$lastError);
}
} elseif (!self::box('unlink', $file) && (false !== strpos(self::$lastError, 'Permission denied') || file_exists($file))) {
throw new IOException(sprintf('Failed to remove file "%s": ', $file).self::$lastError);
}
}
}
/**
* Change mode for an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change mode
* @param int $mode The new mode (octal)
* @param int $umask The mode mask (octal)
* @param bool $recursive Whether change the mod recursively or not
*
* @throws IOException When the change fails
*/
public function chmod($files, int $mode, int $umask = 0000, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ((\PHP_VERSION_ID < 80000 || \is_int($mode)) && true !== @chmod($file, $mode & ~$umask)) {
throw new IOException(sprintf('Failed to chmod file "%s".', $file), 0, null, $file);
}
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chmod(new \FilesystemIterator($file), $mode, $umask, true);
}
}
}
/**
* Change the owner of an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change owner
* @param string|int $user A user name or number
* @param bool $recursive Whether change the owner recursively or not
*
* @throws IOException When the change fails
*/
public function chown($files, $user, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chown(new \FilesystemIterator($file), $user, true);
}
if (is_link($file) && \function_exists('lchown')) {
if (true !== @lchown($file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
}
} else {
if (true !== @chown($file, $user)) {
throw new IOException(sprintf('Failed to chown file "%s".', $file), 0, null, $file);
}
}
}
}
/**
* Change the group of an array of files or directories.
*
* @param string|iterable $files A filename, an array of files, or a \Traversable instance to change group
* @param string|int $group A group name or number
* @param bool $recursive Whether change the group recursively or not
*
* @throws IOException When the change fails
*/
public function chgrp($files, $group, bool $recursive = false)
{
foreach ($this->toIterable($files) as $file) {
if ($recursive && is_dir($file) && !is_link($file)) {
$this->chgrp(new \FilesystemIterator($file), $group, true);
}
if (is_link($file) && \function_exists('lchgrp')) {
if (true !== @lchgrp($file, $group)) {
throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
}
} else {
if (true !== @chgrp($file, $group)) {
throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
}
}
}
}
/**
* Renames a file or a directory.
*
* @throws IOException When target file or directory already exists
* @throws IOException When origin cannot be renamed
*/
public function rename(string $origin, string $target, bool $overwrite = false)
{
// we check that target does not exist
if (!$overwrite && $this->isReadable($target)) {
throw new IOException(sprintf('Cannot rename because the target "%s" already exists.', $target), 0, null, $target);
}
if (true !== @rename($origin, $target)) {
if (is_dir($origin)) {
// See https://bugs.php.net/54097 & https://php.net/rename#113943
$this->mirror($origin, $target, null, ['override' => $overwrite, 'delete' => $overwrite]);
$this->remove($origin);
return;
}
throw new IOException(sprintf('Cannot rename "%s" to "%s".', $origin, $target), 0, null, $target);
}
}
/**
* Tells whether a file exists and is readable.
*
* @throws IOException When windows path is longer than 258 characters
*/
private function isReadable(string $filename): bool
{
$maxPathLength = \PHP_MAXPATHLEN - 2;
if (\strlen($filename) > $maxPathLength) {
throw new IOException(sprintf('Could not check if file is readable because path length exceeds %d characters.', $maxPathLength), 0, null, $filename);
}
return is_readable($filename);
}
/**
* Creates a symbolic link or copy a directory.
*
* @throws IOException When symlink fails
*/
public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false)
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$originDir = strtr($originDir, '/', '\\');
$targetDir = strtr($targetDir, '/', '\\');
if ($copyOnWindows) {
$this->mirror($originDir, $targetDir);
return;
}
}
$this->mkdir(\dirname($targetDir));
if (is_link($targetDir)) {
if (readlink($targetDir) === $originDir) {
return;
}
$this->remove($targetDir);
}
if (!self::box('symlink', $originDir, $targetDir)) {
$this->linkException($originDir, $targetDir, 'symbolic');
}
}
/**
* Creates a hard link, or several hard links to a file.
*
* @param string|string[] $targetFiles The target file(s)
*
* @throws FileNotFoundException When original file is missing or not a file
* @throws IOException When link fails, including if link already exists
*/
public function hardlink(string $originFile, $targetFiles)
{
if (!$this->exists($originFile)) {
throw new FileNotFoundException(null, 0, null, $originFile);
}
if (!is_file($originFile)) {
throw new FileNotFoundException(sprintf('Origin file "%s" is not a file.', $originFile));
}
foreach ($this->toIterable($targetFiles) as $targetFile) {
if (is_file($targetFile)) {
if (fileinode($originFile) === fileinode($targetFile)) {
continue;
}
$this->remove($targetFile);
}
if (!self::box('link', $originFile, $targetFile)) {
$this->linkException($originFile, $targetFile, 'hard');
}
}
}
/**
* @param string $linkType Name of the link type, typically 'symbolic' or 'hard'
*/
private function linkException(string $origin, string $target, string $linkType)
{
if (self::$lastError) {
if ('\\' === \DIRECTORY_SEPARATOR && false !== strpos(self::$lastError, 'error code(1314)')) {
throw new IOException(sprintf('Unable to create "%s" link due to error code 1314: \'A required privilege is not held by the client\'. Do you have the required Administrator-rights?', $linkType), 0, null, $target);
}
}
throw new IOException(sprintf('Failed to create "%s" link from "%s" to "%s".', $linkType, $origin, $target), 0, null, $target);
}
/**
* Resolves links in paths.
*
* With $canonicalize = false (default)
* - if $path does not exist or is not a link, returns null
* - if $path is a link, returns the next direct target of the link without considering the existence of the target
*
* With $canonicalize = true
* - if $path does not exist, returns null
* - if $path exists, returns its absolute fully resolved final version
*
* @return string|null
*/
public function readlink(string $path, bool $canonicalize = false)
{
if (!$canonicalize && !is_link($path)) {
return null;
}
if ($canonicalize) {
if (!$this->exists($path)) {
return null;
}
if ('\\' === \DIRECTORY_SEPARATOR) {
$path = readlink($path);
}
return realpath($path);
}
if ('\\' === \DIRECTORY_SEPARATOR) {
return realpath($path);
}
return readlink($path);
}
/**
* Given an existing path, convert it to a path relative to a given starting path.
*
* @return string Path of target relative to starting path
*/
public function makePathRelative(string $endPath, string $startPath)
{
if (!$this->isAbsolutePath($startPath)) {
throw new InvalidArgumentException(sprintf('The start path "%s" is not absolute.', $startPath));
}
if (!$this->isAbsolutePath($endPath)) {
throw new InvalidArgumentException(sprintf('The end path "%s" is not absolute.', $endPath));
}
// Normalize separators on Windows
if ('\\' === \DIRECTORY_SEPARATOR) {
$endPath = str_replace('\\', '/', $endPath);
$startPath = str_replace('\\', '/', $startPath);
}
$splitDriveLetter = function ($path) {
return (\strlen($path) > 2 && ':' === $path[1] && '/' === $path[2] && ctype_alpha($path[0]))
? [substr($path, 2), strtoupper($path[0])]
: [$path, null];
};
$splitPath = function ($path) {
$result = [];
foreach (explode('/', trim($path, '/')) as $segment) {
if ('..' === $segment) {
array_pop($result);
} elseif ('.' !== $segment && '' !== $segment) {
$result[] = $segment;
}
}
return $result;
};
[$endPath, $endDriveLetter] = $splitDriveLetter($endPath);
[$startPath, $startDriveLetter] = $splitDriveLetter($startPath);
$startPathArr = $splitPath($startPath);
$endPathArr = $splitPath($endPath);
if ($endDriveLetter && $startDriveLetter && $endDriveLetter != $startDriveLetter) {
// End path is on another drive, so no relative path exists
return $endDriveLetter.':/'.($endPathArr ? implode('/', $endPathArr).'/' : '');
}
// Find for which directory the common path stops
$index = 0;
while (isset($startPathArr[$index]) && isset($endPathArr[$index]) && $startPathArr[$index] === $endPathArr[$index]) {
++$index;
}
// Determine how deep the start path is relative to the common path (ie, "web/bundles" = 2 levels)
if (1 === \count($startPathArr) && '' === $startPathArr[0]) {
$depth = 0;
} else {
$depth = \count($startPathArr) - $index;
}
// Repeated "../" for each level need to reach the common path
$traverser = str_repeat('../', $depth);
$endPathRemainder = implode('/', \array_slice($endPathArr, $index));
// Construct $endPath from traversing to the common path, then to the remaining $endPath
$relativePath = $traverser.('' !== $endPathRemainder ? $endPathRemainder.'/' : '');
return '' === $relativePath ? './' : $relativePath;
}
/**
* Mirrors a directory to another.
*
* Copies files and directories from the origin directory into the target directory. By default:
*
* - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
* - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
*
* @param \Traversable|null $iterator Iterator that filters which files and directories to copy, if null a recursive iterator is created
* @param array $options An array of boolean options
* Valid options are:
* - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown
*/
public function mirror(string $originDir, string $targetDir, \Traversable $iterator = null, array $options = [])
{
$targetDir = rtrim($targetDir, '/\\');
$originDir = rtrim($originDir, '/\\');
$originDirLen = \strlen($originDir);
if (!$this->exists($originDir)) {
throw new IOException(sprintf('The origin directory specified "%s" was not found.', $originDir), 0, null, $originDir);
}
// Iterate in destination folder to remove obsolete entries
if ($this->exists($targetDir) && isset($options['delete']) && $options['delete']) {
$deleteIterator = $iterator;
if (null === $deleteIterator) {
$flags = \FilesystemIterator::SKIP_DOTS;
$deleteIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($targetDir, $flags), \RecursiveIteratorIterator::CHILD_FIRST);
}
$targetDirLen = \strlen($targetDir);
foreach ($deleteIterator as $file) {
$origin = $originDir.substr($file->getPathname(), $targetDirLen);
if (!$this->exists($origin)) {
$this->remove($file);
}
}
}
$copyOnWindows = $options['copy_on_windows'] ?? false;
if (null === $iterator) {
$flags = $copyOnWindows ? \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS : \FilesystemIterator::SKIP_DOTS;
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($originDir, $flags), \RecursiveIteratorIterator::SELF_FIRST);
}
$this->mkdir($targetDir);
$filesCreatedWhileMirroring = [];
foreach ($iterator as $file) {
if ($file->getPathname() === $targetDir || $file->getRealPath() === $targetDir || isset($filesCreatedWhileMirroring[$file->getRealPath()])) {
continue;
}
$target = $targetDir.substr($file->getPathname(), $originDirLen);
$filesCreatedWhileMirroring[$target] = true;
if (!$copyOnWindows && is_link($file)) {
$this->symlink($file->getLinkTarget(), $target);
} elseif (is_dir($file)) {
$this->mkdir($target);
} elseif (is_file($file)) {
$this->copy($file, $target, $options['override'] ?? false);
} else {
throw new IOException(sprintf('Unable to guess "%s" file type.', $file), 0, null, $file);
}
}
}
/**
* Returns whether the file path is an absolute path.
*
* @return bool
*/
public function isAbsolutePath(string $file)
{
return '' !== $file && (strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, \PHP_URL_SCHEME)
);
}
/**
* Creates a temporary file with support for custom stream wrappers.
*
* @param string $prefix The prefix of the generated temporary filename
* Note: Windows uses only the first three characters of prefix
* @param string $suffix The suffix of the generated temporary filename
*
* @return string The new temporary filename (with path), or throw an exception on failure
*/
public function tempnam(string $dir, string $prefix/*, string $suffix = ''*/)
{
$suffix = \func_num_args() > 2 ? func_get_arg(2) : '';
[$scheme, $hierarchy] = $this->getSchemeAndHierarchy($dir);
// If no scheme or scheme is "file" or "gs" (Google Cloud) create temp file in local filesystem
if ((null === $scheme || 'file' === $scheme || 'gs' === $scheme) && '' === $suffix) {
$tmpFile = @tempnam($hierarchy, $prefix);
// If tempnam failed or no scheme return the filename otherwise prepend the scheme
if (false !== $tmpFile) {
if (null !== $scheme && 'gs' !== $scheme) {
return $scheme.'://'.$tmpFile;
}
return $tmpFile;
}
throw new IOException('A temporary file could not be created.');
}
// Loop until we create a valid temp file or have reached 10 attempts
for ($i = 0; $i < 10; ++$i) {
// Create a unique filename
$tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true).$suffix;
// Use fopen instead of file_exists as some streams do not support stat
// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
$handle = @fopen($tmpFile, 'x+');
// If unsuccessful restart the loop
if (false === $handle) {
continue;
}
// Close the file if it was successfully opened
@fclose($handle);
return $tmpFile;
}
throw new IOException('A temporary file could not be created.');
}
/**
* Atomically dumps content into a file.
*
* @param string|resource $content The data to write into the file
*
* @throws IOException if the file cannot be written to
*/
public function dumpFile(string $filename, $content)
{
if (\is_array($content)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
}
$dir = \dirname($filename);
if (!is_dir($dir)) {
$this->mkdir($dir);
}
if (!is_writable($dir)) {
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
}
// Will create a temp file with 0600 access rights
// when the filesystem supports chmod.
$tmpFile = $this->tempnam($dir, basename($filename));
try {
if (false === @file_put_contents($tmpFile, $content)) {
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
@chmod($tmpFile, file_exists($filename) ? fileperms($filename) : 0666 & ~umask());
$this->rename($tmpFile, $filename, true);
} finally {
if (file_exists($tmpFile)) {
@unlink($tmpFile);
}
}
}
/**
* Appends content to an existing file.
*
* @param string|resource $content The content to append
*
* @throws IOException If the file is not writable
*/
public function appendToFile(string $filename, $content)
{
if (\is_array($content)) {
throw new \TypeError(sprintf('Argument 2 passed to "%s()" must be string or resource, array given.', __METHOD__));
}
$dir = \dirname($filename);
if (!is_dir($dir)) {
$this->mkdir($dir);
}
if (!is_writable($dir)) {
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
}
if (false === @file_put_contents($filename, $content, \FILE_APPEND)) {
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
}
}
private function toIterable($files): iterable
{
return \is_array($files) || $files instanceof \Traversable ? $files : [$files];
}
/**
* Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> [file, tmp]).
*/
private function getSchemeAndHierarchy(string $filename): array
{
$components = explode('://', $filename, 2);
return 2 === \count($components) ? [$components[0], $components[1]] : [null, $components[0]];
}
/**
* @param mixed ...$args
*
* @return mixed
*/
private static function box(callable $func, ...$args)
{
self::$lastError = null;
set_error_handler(__CLASS__.'::handleError');
try {
$result = $func(...$args);
restore_error_handler();
return $result;
} catch (\Throwable $e) {
}
restore_error_handler();
throw $e;
}
/**
* @internal
*/
public static function handleError($type, $msg)
{
self::$lastError = $msg;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a filesystem operation failure happens.
*
* @author Romain Neutron <imprec@gmail.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class IOException extends \RuntimeException implements IOExceptionInterface
{
private $path;
public function __construct(string $message, int $code = 0, \Throwable $previous = null, string $path = null)
{
$this->path = $path;
parent::__construct($message, $code, $previous);
}
/**
* {@inheritdoc}
*/
public function getPath()
{
return $this->path;
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Romain Neutron <imprec@gmail.com>
*/
interface ExceptionInterface extends \Throwable
{
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* Exception class thrown when a file couldn't be found.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
class FileNotFoundException extends IOException
{
public function __construct(string $message = null, int $code = 0, \Throwable $previous = null, string $path = null)
{
if (null === $message) {
if (null === $path) {
$message = 'File could not be found.';
} else {
$message = sprintf('File "%s" could not be found.', $path);
}
}
parent::__construct($message, $code, $previous, $path);
}
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* IOException interface for file and input/output stream related exceptions thrown by the component.
*
* @author Christian Gärtner <christiangaertner.film@googlemail.com>
*/
interface IOExceptionInterface extends ExceptionInterface
{
/**
* Returns the associated path for the exception.
*
* @return string|null The path
*/
public function getPath();
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Filesystem\Exception;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
Copyright (c) 2004-2021 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Intl\Normalizer as p;
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
if (!function_exists('normalizer_is_normalized')) {
function normalizer_is_normalized($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::isNormalized($string, $form); }
}
if (!function_exists('normalizer_normalize')) {
function normalizer_normalize($string, $form = p\Normalizer::FORM_C) { return p\Normalizer::normalize($string, $form); }
}
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use Symfony\Polyfill\Intl\Normalizer as p;
if (!function_exists('normalizer_is_normalized')) {
function normalizer_is_normalized(?string $string, ?int $form = p\Normalizer::FORM_C): bool { return p\Normalizer::isNormalized((string) $string, (int) $form); }
}
if (!function_exists('normalizer_normalize')) {
function normalizer_normalize(?string $string, ?int $form = p\Normalizer::FORM_C): string|false { return p\Normalizer::normalize((string) $string, (int) $form); }
}
Copyright (c) 2015-2019 Fabien Potencier
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.
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Polyfill\Intl\Normalizer;
/**
* Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension.
*
* It has been validated with Unicode 6.3 Normalization Conformance Test.
* See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @internal
*/
class Normalizer
{
public const FORM_D = \Normalizer::FORM_D;
public const FORM_KD = \Normalizer::FORM_KD;
public const FORM_C = \Normalizer::FORM_C;
public const FORM_KC = \Normalizer::FORM_KC;
public const NFD = \Normalizer::NFD;
public const NFKD = \Normalizer::NFKD;
public const NFC = \Normalizer::NFC;
public const NFKC = \Normalizer::NFKC;
private static $C;
private static $D;
private static $KD;
private static $cC;
private static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
private static $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
public static function isNormalized(string $s, int $form = self::FORM_C)
{
if (!\in_array($form, [self::NFD, self::NFKD, self::NFC, self::NFKC])) {
return false;
}
if (!isset($s[strspn($s, self::$ASCII)])) {
return true;
}
if (self::NFC == $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) {
return true;
}
return self::normalize($s, $form) === $s;
}
public static function normalize(string $s, int $form = self::FORM_C)
{
if (!preg_match('//u', $s)) {
return false;
}
switch ($form) {
case self::NFC: $C = true; $K = false; break;
case self::NFD: $C = false; $K = false; break;
case self::NFKC: $C = true; $K = true; break;
case self::NFKD: $C = false; $K = true; break;
default:
if (\defined('Normalizer::NONE') && \Normalizer::NONE == $form) {
return $s;
}
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('normalizer_normalize(): Argument #2 ($form) must be a a valid normalization form');
}
if ('' === $s) {
return '';
}
if ($K && null === self::$KD) {
self::$KD = self::getData('compatibilityDecomposition');
}
if (null === self::$D) {
self::$D = self::getData('canonicalDecomposition');
self::$cC = self::getData('combiningClass');
}
if (null !== $mbEncoding = (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) ? mb_internal_encoding() : null) {
mb_internal_encoding('8bit');
}
$r = self::decompose($s, $K);
if ($C) {
if (null === self::$C) {
self::$C = self::getData('canonicalComposition');
}
$r = self::recompose($r);
}
if (null !== $mbEncoding) {
mb_internal_encoding($mbEncoding);
}
return $r;
}
private static function recompose($s)
{
$ASCII = self::$ASCII;
$compMap = self::$C;
$combClass = self::$cC;
$ulenMask = self::$ulenMask;
$result = $tail = '';
$i = $s[0] < "\x80" ? 1 : $ulenMask[$s[0] & "\xF0"];
$len = \strlen($s);
$lastUchr = substr($s, 0, $i);
$lastUcls = isset($combClass[$lastUchr]) ? 256 : 0;
while ($i < $len) {
if ($s[$i] < "\x80") {
// ASCII chars
if ($tail) {
$lastUchr .= $tail;
$tail = '';
}
if ($j = strspn($s, $ASCII, $i + 1)) {
$lastUchr .= substr($s, $i, $j);
$i += $j;
}
$result .= $lastUchr;
$lastUchr = $s[$i];
$lastUcls = 0;
++$i;
continue;
}
$ulen = $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
if ($lastUchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $lastUchr
|| $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr
|| $lastUcls) {
// Table lookup and combining chars composition
$ucls = $combClass[$uchr] ?? 0;
if (isset($compMap[$lastUchr.$uchr]) && (!$lastUcls || $lastUcls < $ucls)) {
$lastUchr = $compMap[$lastUchr.$uchr];
} elseif ($lastUcls = $ucls) {
$tail .= $uchr;
} else {
if ($tail) {
$lastUchr .= $tail;
$tail = '';
}
$result .= $lastUchr;
$lastUchr = $uchr;
}
} else {
// Hangul chars
$L = \ord($lastUchr[2]) - 0x80;
$V = \ord($uchr[2]) - 0xA1;
$T = 0;
$uchr = substr($s, $i + $ulen, 3);
if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82") {
$T = \ord($uchr[2]) - 0xA7;
0 > $T && $T += 0x40;
$ulen += 3;
}
$L = 0xAC00 + ($L * 21 + $V) * 28 + $T;
$lastUchr = \chr(0xE0 | $L >> 12).\chr(0x80 | $L >> 6 & 0x3F).\chr(0x80 | $L & 0x3F);
}
$i += $ulen;
}
return $result.$lastUchr.$tail;
}
private static function decompose($s, $c)
{
$result = '';
$ASCII = self::$ASCII;
$decompMap = self::$D;
$combClass = self::$cC;
$ulenMask = self::$ulenMask;
if ($c) {
$compatMap = self::$KD;
}
$c = [];
$i = 0;
$len = \strlen($s);
while ($i < $len) {
if ($s[$i] < "\x80") {
// ASCII chars
if ($c) {
ksort($c);
$result .= implode('', $c);
$c = [];
}
$j = 1 + strspn($s, $ASCII, $i + 1);
$result .= substr($s, $i, $j);
$i += $j;
continue;
}
$ulen = $ulenMask[$s[$i] & "\xF0"];
$uchr = substr($s, $i, $ulen);
$i += $ulen;
if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr) {
// Table lookup
if ($uchr !== $j = $compatMap[$uchr] ?? ($decompMap[$uchr] ?? $uchr)) {
$uchr = $j;
$j = \strlen($uchr);
$ulen = $uchr[0] < "\x80" ? 1 : $ulenMask[$uchr[0] & "\xF0"];
if ($ulen != $j) {
// Put trailing chars in $s
$j -= $ulen;
$i -= $j;
if (0 > $i) {
$s = str_repeat(' ', -$i).$s;
$len -= $i;
$i = 0;
}
while ($j--) {
$s[$i + $j] = $uchr[$ulen + $j];
}
$uchr = substr($uchr, 0, $ulen);
}
}
if (isset($combClass[$uchr])) {
// Combining chars, for sorting
if (!isset($c[$combClass[$uchr]])) {
$c[$combClass[$uchr]] = '';
}
$c[$combClass[$uchr]] .= $uchr;
continue;
}
} else {
// Hangul chars
$uchr = unpack('C*', $uchr);
$j = (($uchr[1] - 224) << 12) + (($uchr[2] - 128) << 6) + $uchr[3] - 0xAC80;
$uchr = "\xE1\x84".\chr(0x80 + (int) ($j / 588))
."\xE1\x85".\chr(0xA1 + (int) (($j % 588) / 28));
if ($j %= 28) {
$uchr .= $j < 25
? ("\xE1\x86".\chr(0xA7 + $j))
: ("\xE1\x87".\chr(0x67 + $j));
}
}
if ($c) {
ksort($c);
$result .= implode('', $c);
$c = [];
}
$result .= $uchr;
}
if ($c) {
ksort($c);
$result .= implode('', $c);
}
return $result;
}
private static function getData($file)
{
if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) {
return require $file;
}
return false;
}
}
<?php
class Normalizer extends Symfony\Polyfill\Intl\Normalizer\Normalizer
{
/**
* @deprecated since ICU 56 and removed in PHP 8
*/
public const NONE = 2;
public const FORM_D = 4;
public const FORM_KD = 8;
public const FORM_C = 16;
public const FORM_KC = 32;
public const NFD = 4;
public const NFKD = 8;
public const NFC = 16;
public const NFKC = 32;
}
<?php
return array (
'À' => 'À',
<>' => 'AÌ<41>',
'Â' => 'AÌ',
'Ã' => 'Ã',
'Ä' => 'Ä',
'Ã…' => 'AÌŠ',
'Ç' => 'Ç',
'È' => 'È',
'É' => 'EÌ<45>',
'Ê' => 'EÌ',
'Ë' => 'Ë',
'Ì' => 'Ì',
<>' => 'IÌ<49>',
'ÃŽ' => 'IÌ',
<>' => 'Ï',
'Ñ' => 'Ñ',
'Ã’' => 'OÌ€',
'Ó' => 'OÌ<4F>',
'Ô' => 'OÌ',
'Õ' => 'Õ',
'Ö' => 'Ö',
'Ù' => 'Ù',
'Ú' => 'UÌ<55>',
'Û' => 'UÌ',
'Ü' => 'Ü',
<>' => 'YÌ<59>',
'à' => 'à',
'á' => 'aÌ<61>',
'â' => 'aÌ',
'ã' => 'ã',
'ä' => 'ä',
'Ã¥' => 'aÌŠ',
'ç' => 'ç',
'è' => 'è',
'é' => 'eÌ<65>',
'ê' => 'eÌ',
'ë' => 'ë',
'ì' => 'ì',
'í' => 'iÌ<69>',
'î' => 'iÌ',
'ï' => 'ï',
'ñ' => 'ñ',
'ò' => 'ò',
'ó' => 'oÌ<6F>',
'ô' => 'oÌ',
'õ' => 'õ',
'ö' => 'ö',
'ù' => 'ù',
'ú' => 'uÌ<75>',
'û' => 'uÌ',
'ü' => 'ü',
'ý' => 'yÌ<79>',
'ÿ' => 'ÿ',
'Ä€' => 'AÌ„',
<>' => 'ā',
'Ă' => 'Ă',
'ă' => 'ă',
'Ą' => 'Ą',
'ą' => 'ą',
'Ć' => 'CÌ<43>',
'ć' => 'cÌ<63>',
'Ĉ' => 'CÌ',
'ĉ' => 'cÌ',
'Ċ' => 'Ċ',
'ċ' => 'ċ',
'Č' => 'Č',
<>' => 'č',
'Ď' => 'Ď',
<>' => 'ď',
'Ä’' => 'EÌ„',
'ē' => 'ē',
'Ĕ' => 'Ĕ',
'ĕ' => 'ĕ',
'Ė' => 'Ė',
'ė' => 'ė',
'Ę' => 'Ę',
'ę' => 'ę',
'Ě' => 'Ě',
'ě' => 'ě',
'Äœ' => 'GÌ',
<>' => 'gÌ',
'Ğ' => 'Ğ',
'ğ' => 'ğ',
'Ġ' => 'Ġ',
'ġ' => 'ġ',
'Ģ' => 'Ģ',
'ģ' => 'ģ',
'Ĥ' => 'HÌ',
'Ä¥' => 'hÌ',
'Ĩ' => 'Ĩ',
'ĩ' => 'ĩ',
'Ī' => 'Ī',
'ī' => 'ī',
'Ĭ' => 'Ĭ',
'ĭ' => 'ĭ',
'Į' => 'Į',
'į' => 'į',
'İ' => 'İ',
'Ä´' => 'JÌ',
'ĵ' => 'jÌ',
'Ķ' => 'Ķ',
'ķ' => 'ķ',
'Ĺ' => 'LÌ<4C>',
'ĺ' => 'lÌ<6C>',
'Ļ' => 'Ļ',
'ļ' => 'ļ',
'Ľ' => 'Ľ',
'ľ' => 'ľ',
'Ń' => 'NÌ<4E>',
'Å„' => 'nÌ<6E>',
'Ņ' => 'Ņ',
'ņ' => 'ņ',
'Ň' => 'Ň',
'ň' => 'ň',
'Ō' => 'Ō',
<>' => 'ō',
'Ŏ' => 'Ŏ',
<>' => 'ŏ',
<>' => 'OÌ',
'Å‘' => 'oÌ',
'Å”' => 'RÌ<52>',
'Å•' => 'rÌ<72>',
'Ŗ' => 'Ŗ',
'ŗ' => 'ŗ',
'Ř' => 'Ř',
'ř' => 'ř',
'Åš' => 'SÌ<53>',
'Å›' => 'sÌ<73>',
'Åœ' => 'SÌ',
<>' => 'sÌ',
'Ş' => 'Ş',
'ş' => 'ş',
'Š' => 'Š',
'š' => 'š',
'Ţ' => 'Ţ',
'ţ' => 'ţ',
'Ť' => 'Ť',
'ť' => 'ť',
'Ũ' => 'Ũ',
'ũ' => 'ũ',
'Ū' => 'Ū',
'ū' => 'ū',
'Ŭ' => 'Ŭ',
'ŭ' => 'ŭ',
'Å®' => 'UÌŠ',
'ů' => 'ů',
'Å°' => 'UÌ',
'ű' => 'uÌ',
'Ų' => 'Ų',
'ų' => 'ų',
'Å´' => 'WÌ',
'ŵ' => 'wÌ',
'Ŷ' => 'YÌ',
'Å·' => 'yÌ',
'Ÿ' => 'Ÿ',
'Ź' => 'ZÌ<5A>',
'ź' => 'zÌ<7A>',
'Ż' => 'Ż',
'ż' => 'ż',
'Ž' => 'Ž',
'ž' => 'ž',
'Æ ' => 'OÌ',
'Æ¡' => 'oÌ',
'Ư' => 'UÌ',
'Æ°' => 'uÌ',
<>' => 'Ǎ',
'ǎ' => 'ǎ',
<>' => 'Ǐ',
<>' => 'ǐ',
'Ǒ' => 'Ǒ',
'ǒ' => 'ǒ',
'Ǔ' => 'Ǔ',
'ǔ' => 'ǔ',
'Ǖ' => 'Ǖ',
'ǖ' => 'ǖ',
'Ç—' => 'ÜÌ<CB86>',
'ǘ' => 'üÌ<CB86>',
'Ǚ' => 'Ǚ',
'ǚ' => 'ǚ',
'Ǜ' => 'Ǜ',
'ǜ' => 'ǜ',
'Ǟ' => 'Ǟ',
'ǟ' => 'ǟ',
'Ǡ' => 'Ǡ',
'ǡ' => 'ǡ',
'Ǣ' => 'Ǣ',
'ǣ' => 'ǣ',
'Ǧ' => 'Ǧ',
'ǧ' => 'ǧ',
'Ǩ' => 'Ǩ',
'ǩ' => 'ǩ',
'Ǫ' => 'Ǫ',
'ǫ' => 'ǫ',
'Ǭ' => 'Ǭ',
'ǭ' => 'ǭ',
'Ǯ' => 'Ǯ',
'ǯ' => 'ǯ',
'ǰ' => 'ǰ',
'Ç´' => 'GÌ<47>',
'ǵ' => 'gÌ<67>',
'Ǹ' => 'Ǹ',
'ǹ' => 'ǹ',
'Ǻ' => 'AÌŠÌ<C5A0>',
'Ç»' => 'aÌŠÌ<C5A0>',
'Ǽ' => 'ÆÌ<E280A0>',
'ǽ' => 'æÌ<C2A6>',
'Ǿ' => 'ØÌ<CB9C>',
'Ç¿' => 'øÌ<C2B8>',
'È€' => 'AÌ<41>',
<>' => 'aÌ<61>',
'È‚' => 'AÌ',
'ȃ' => 'aÌ',
'È„' => 'EÌ<45>',
'È…' => 'eÌ<65>',
'Ȇ' => 'EÌ',
'ȇ' => 'eÌ',
'Ȉ' => 'IÌ<49>',
'ȉ' => 'iÌ<69>',
'ÈŠ' => 'IÌ',
'È‹' => 'iÌ',
'ÈŒ' => 'OÌ<4F>',
<>' => 'oÌ<6F>',
'ÈŽ' => 'OÌ',
<>' => 'oÌ',
<>' => 'RÌ<52>',
'È‘' => 'rÌ<72>',
'È’' => 'RÌ',
'È“' => 'rÌ',
'È”' => 'UÌ<55>',
'È•' => 'uÌ<75>',
'È–' => 'UÌ',
'È—' => 'uÌ',
'Ș' => 'Ș',
'ș' => 'ș',
'Ț' => 'Ț',
'ț' => 'ț',
'Ȟ' => 'Ȟ',
'ȟ' => 'ȟ',
'Ȧ' => 'Ȧ',
'ȧ' => 'ȧ',
'Ȩ' => 'Ȩ',
'ȩ' => 'ȩ',
'Ȫ' => 'Ȫ',
'ȫ' => 'ȫ',
'Ȭ' => 'Ȭ',
'ȭ' => 'ȭ',
'Ȯ' => 'Ȯ',
'ȯ' => 'ȯ',
'Ȱ' => 'Ȱ',
'ȱ' => 'ȱ',
'Ȳ' => 'Ȳ',
'ȳ' => 'ȳ',
'Í€' => 'Ì€',
<>' => 'Ì<>',
'̓' => '̓',
'Í„' => '̈Ì<CB86>',
'ʹ' => 'ʹ',
';' => ';',
'Î…' => '¨Ì<C2A8>',
'Ά' => 'ÎÌ<E28098>',
'·' => '·',
'Έ' => 'ΕÌ<E280A2>',
'Ή' => 'ΗÌ<E28094>',
'Ί' => 'ΙÌ<E284A2>',
'ÎŒ' => 'ΟÌ<C5B8>',
'ÎŽ' => 'Î¥Ì<C2A5>',
<>' => 'ΩÌ<C2A9>',
<>' => 'ϊÌ<CB86>',
'Ϊ' => 'Ϊ',
'Ϋ' => 'Ϋ',
'ά' => 'αÌ<C2B1>',
'έ' => 'εÌ<C2B5>',
'ή' => 'ηÌ<C2B7>',
'ί' => 'ιÌ<C2B9>',
'ΰ' => 'ϋÌ<CB86>',
'ϊ' => 'ϊ',
'ϋ' => 'ϋ',
'ÏŒ' => 'οÌ<C2BF>',
<>' => 'Ï…Ì<E280A6>',
'ÏŽ' => 'ωÌ<E280B0>',
'Ï“' => 'ÏÌ<E28099>',
'ϔ' => 'ϔ',
'Ѐ' => 'Ѐ',
<>' => 'Ё',
'Ѓ' => 'ГÌ<E2809C>',
'Ї' => 'Ї',
'ÐŒ' => 'КÌ<C5A1>',
<>' => 'Ѝ',
'Ў' => 'Ў',
'Й' => 'Й',
'й' => 'й',
<>' => 'ѐ',
'ё' => 'ё',
'Ñ“' => 'гÌ<C2B3>',
'ї' => 'ї',
'Ñœ' => 'кÌ<C2BA>',
<>' => 'ѝ',
'ў' => 'ў',
'Ѷ' => 'Ñ´Ì<C2B4>',
'Ñ·' => 'ѵÌ<C2B5>',
<>' => 'Ӂ',
'ӂ' => 'ӂ',
<>' => 'Ð<>̆',
'ӑ' => 'ӑ',
'Ó’' => 'Ð<>̈',
'ӓ' => 'ӓ',
'Ӗ' => 'Ӗ',
'ӗ' => 'ӗ',
'Ӛ' => 'Ӛ',
'ӛ' => 'ӛ',
'Ӝ' => 'Ӝ',
<>' => 'ӝ',
'Ӟ' => 'Ӟ',
'ӟ' => 'ӟ',
'Ӣ' => 'Ӣ',
'ӣ' => 'ӣ',
'Ӥ' => 'Ӥ',
'ӥ' => 'ӥ',
'Ӧ' => 'Ӧ',
'ӧ' => 'ӧ',
'Ӫ' => 'Ӫ',
'ӫ' => 'ӫ',
'Ӭ' => 'Ӭ',
'Ó­' => 'Ñ<>̈',
'Ӯ' => 'Ӯ',
'ӯ' => 'ӯ',
'Ӱ' => 'Ӱ',
'ӱ' => 'ӱ',
'Ӳ' => 'Ӳ',
'ӳ' => 'ӳ',
'Ӵ' => 'Ӵ',
'ӵ' => 'ӵ',
'Ӹ' => 'Ӹ',
'ӹ' => 'ӹ',
'آ' => 'آ',
'أ' => 'أ',
'ؤ' => 'ؤ',
'إ' => 'إ',
'ئ' => 'ئ',
'Û€' => 'Û•Ù”',
'Û‚' => 'Û<>Ù”',
'Û“' => 'Û’Ù”',
'ऩ' => 'ऩ',
'ऱ' => 'ऱ',
'ऴ' => 'ऴ',
'क़' => 'क़',
'ख़' => 'ख़',
'ग़' => 'ग़',
'ज़' => 'ज़',
'ड़' => 'ड़',
'à¥<C3A0>' => 'ढ़',
'फ़' => 'फ़',
'य़' => 'य़',
'ো' => 'ো',
'ৌ' => 'ৌ',
'ড়' => 'ড়',
'à§<C3A0>' => 'ঢ়',
'য়' => 'য়',
'ਲ਼' => 'ਲ਼',
'ਸ਼' => 'ਸ਼',
'ਖ਼' => 'ਖ਼',
'ਗ਼' => 'ਗ਼',
'ਜ਼' => 'ਜ਼',
'ਫ਼' => 'ਫ਼',
'ୈ' => 'ୈ',
'ୋ' => 'ୋ',
'ୌ' => 'ୌ',
'ଡ଼' => 'ଡ଼',
­<C3A0>' => 'ଢ଼',
'ஔ' => 'ஔ',
'ொ' => 'ொ',
'ோ' => 'ோ',
'ௌ' => 'ௌ',
'ై' => 'ై',
'ೀ' => 'ೀ',
'ೇ' => 'ೇ',
'ೈ' => 'ೈ',
'ೊ' => 'ೊ',
'ೋ' => 'ೋ',
'ൊ' => 'ൊ',
'ോ' => 'ോ',
'ൌ' => 'ൌ',
'ේ' => 'ේ',
'à·œ' => 'à·™à·<C3A0>',
'à·<C3A0>' => 'à·™à·<C3A0>à·Š',
'ෞ' => 'ෞ',
'གྷ' => 'གྷ',
'à½<C3A0>' => 'ཌྷ',
'དྷ' => 'དྷ',
'བྷ' => 'བྷ',
'ཛྷ' => 'ཛྷ',
'ཀྵ' => 'ཀྵ',
'ཱི' => 'ཱི',
'ཱུ' => 'ཱུ',
'ྲྀ' => 'ྲྀ',
'ླྀ' => 'ླྀ',
'à¾<C3A0>' => 'ཱྀ',
'ྒྷ' => 'ྒྷ',
'à¾<C3A0>' => 'ྜྷ',
'ྡྷ' => 'ྡྷ',
'ྦྷ' => 'ྦྷ',
'ྫྷ' => 'ྫྷ',
'ྐྵ' => 'à¾<C3A0>ྵ',
'ဦ' => 'ဦ',
'ᬆ' => 'ᬆ',
'ᬈ' => 'ᬈ',
'ᬊ' => 'ᬊ',
'ᬌ' => 'ᬌ',
'ᬎ' => 'á¬<C3A1>ᬵ',
'ᬒ' => 'ᬒ',
'ᬻ' => 'ᬻ',
'ᬽ' => 'ᬽ',
'ᭀ' => 'ᭀ',
­<C3A1>' => 'ᭁ',
'ᭃ' => 'ᭃ',
'Ḁ' => 'Ḁ',
¸<C3A1>' => 'aÌ¥',
'Ḃ' => 'Ḃ',
'ḃ' => 'ḃ',
'Ḅ' => 'Ḅ',
'ḅ' => 'ḅ',
'Ḇ' => 'Ḇ',
'ḇ' => 'ḇ',
'Ḉ' => 'ÇÌ<C2A7>',
'ḉ' => 'çÌ<C2A7>',
'Ḋ' => 'Ḋ',
'ḋ' => 'ḋ',
'Ḍ' => 'Ḍ',
¸<C3A1>' => 'dÌ£',
'Ḏ' => 'Ḏ',
¸<C3A1>' => 'ḏ',
¸<C3A1>' => 'Ḑ',
'ḑ' => 'ḑ',
'Ḓ' => 'Ḓ',
'ḓ' => 'ḓ',
'Ḕ' => 'Ḕ',
'ḕ' => 'ḕ',
'Ḗ' => 'EÌ„Ì<E2809E>',
'ḗ' => 'eÌ„Ì<E2809E>',
'Ḙ' => 'Ḙ',
'ḙ' => 'ḙ',
'Ḛ' => 'Ḛ',
'ḛ' => 'ḛ',
'Ḝ' => 'Ḝ',
¸<C3A1>' => 'ḝ',
'Ḟ' => 'Ḟ',
'ḟ' => 'ḟ',
'Ḡ' => 'Ḡ',
'ḡ' => 'ḡ',
'Ḣ' => 'Ḣ',
'ḣ' => 'ḣ',
'Ḥ' => 'Ḥ',
'ḥ' => 'ḥ',
'Ḧ' => 'Ḧ',
'ḧ' => 'ḧ',
'Ḩ' => 'Ḩ',
'ḩ' => 'ḩ',
'Ḫ' => 'Ḫ',
'ḫ' => 'ḫ',
'Ḭ' => 'Ḭ',
'ḭ' => 'ḭ',
'Ḯ' => 'ÏÌ<CB86>',
'ḯ' => 'ïÌ<CB86>',
'Ḱ' => 'KÌ<4B>',
'ḱ' => 'kÌ<6B>',
'Ḳ' => 'Ḳ',
'ḳ' => 'ḳ',
'Ḵ' => 'Ḵ',
'ḵ' => 'ḵ',
'Ḷ' => 'Ḷ',
'ḷ' => 'ḷ',
'Ḹ' => 'Ḹ',
'ḹ' => 'ḹ',
'Ḻ' => 'Ḻ',
'ḻ' => 'ḻ',
'Ḽ' => 'Ḽ',
'ḽ' => 'ḽ',
'Ḿ' => 'MÌ<4D>',
'ḿ' => 'mÌ<6D>',
'Ṁ' => 'Ṁ',
'á¹<C3A1>' => 'ṁ',
'Ṃ' => 'Ṃ',
'ṃ' => 'ṃ',
'Ṅ' => 'Ṅ',
'ṅ' => 'ṅ',
'Ṇ' => 'Ṇ',
'ṇ' => 'ṇ',
'Ṉ' => 'Ṉ',
'ṉ' => 'ṉ',
'Ṋ' => 'Ṋ',
'ṋ' => 'ṋ',
'Ṍ' => 'ÕÌ<C692>',
'á¹<C3A1>' => 'õÌ<C692>',
'Ṏ' => 'Ṏ',
'á¹<C3A1>' => 'ṏ',
'á¹<C3A1>' => 'OÌ„Ì€',
'ṑ' => 'ṑ',
'á¹’' => 'OÌ„Ì<E2809E>',
'ṓ' => 'oÌ„Ì<E2809E>',
'á¹”' => 'PÌ<50>',
'ṕ' => 'pÌ<70>',
'Ṗ' => 'Ṗ',
'ṗ' => 'ṗ',
'Ṙ' => 'Ṙ',
'ṙ' => 'ṙ',
'Ṛ' => 'Ṛ',
'ṛ' => 'ṛ',
'Ṝ' => 'Ṝ',
'á¹<C3A1>' => 'ṝ',
'Ṟ' => 'Ṟ',
'ṟ' => 'ṟ',
'Ṡ' => 'Ṡ',
'ṡ' => 'ṡ',
'á¹¢' => 'SÌ£',
'ṣ' => 'ṣ',
'Ṥ' => 'SÌ<53>̇',
'á¹¥' => 'sÌ<73>̇',
'Ṧ' => 'Ṧ',
'ṧ' => 'ṧ',
'Ṩ' => 'Ṩ',
'ṩ' => 'ṩ',
'Ṫ' => 'Ṫ',
'ṫ' => 'ṫ',
'Ṭ' => 'Ṭ',
'ṭ' => 'ṭ',
'Ṯ' => 'Ṯ',
'ṯ' => 'ṯ',
'á¹°' => 'TÌ­',
'á¹±' => 'tÌ­',
'Ṳ' => 'Ṳ',
'ṳ' => 'ṳ',
'á¹´' => 'UÌ°',
'á¹µ' => 'uÌ°',
'Ṷ' => 'Ṷ',
'á¹·' => 'uÌ­',
'Ṹ' => 'ŨÌ<C692>',
'á¹¹' => 'ũÌ<C692>',
'Ṻ' => 'Ṻ',
'ṻ' => 'ṻ',
'Ṽ' => 'Ṽ',
'ṽ' => 'ṽ',
'á¹¾' => 'VÌ£',
'ṿ' => 'ṿ',
'Ẁ' => 'Ẁ',
'áº<C3A1>' => 'wÌ€',
'Ẃ' => 'WÌ<57>',
'ẃ' => 'wÌ<77>',
'Ẅ' => 'Ẅ',
'ẅ' => 'ẅ',
'Ẇ' => 'Ẇ',
'ẇ' => 'ẇ',
'Ẉ' => 'Ẉ',
'ẉ' => 'ẉ',
'Ẋ' => 'Ẋ',
'ẋ' => 'ẋ',
'Ẍ' => 'Ẍ',
'áº<C3A1>' => 'ẍ',
'Ẏ' => 'Ẏ',
'áº<C3A1>' => 'ẏ',
'áº<C3A1>' => 'ZÌ',
'ẑ' => 'zÌ',
'Ẓ' => 'Ẓ',
'ẓ' => 'ẓ',
'Ẕ' => 'Ẕ',
'ẕ' => 'ẕ',
'ẖ' => 'ẖ',
'ẗ' => 'ẗ',
'ẘ' => 'ẘ',
'ẙ' => 'ẙ',
'ẛ' => 'ẛ',
'Ạ' => 'Ạ',
'ạ' => 'ạ',
'Ả' => 'Ả',
'ả' => 'ả',
'Ấ' => 'AÌÌ<E2809A>',
'ấ' => 'aÌÌ<E2809A>',
'Ầ' => 'AÌÌ€',
'ầ' => 'aÌÌ€',
'Ẩ' => 'AÌ̉',
'ẩ' => 'aÌ̉',
'Ẫ' => 'AÌ̃',
'ẫ' => 'aÌ̃',
'Ậ' => 'AÌ£Ì',
'ậ' => 'aÌ£Ì',
'Ắ' => 'ĂÌ<E280A0>',
'ắ' => 'ăÌ<E280A0>',
'Ằ' => 'Ằ',
'ằ' => 'ằ',
'Ẳ' => 'Ẳ',
'ẳ' => 'ẳ',
'Ẵ' => 'Ẵ',
'ẵ' => 'ẵ',
'Ặ' => 'Ặ',
'ặ' => 'ặ',
'Ẹ' => 'Ẹ',
'ẹ' => 'ẹ',
'Ẻ' => 'Ẻ',
'ẻ' => 'ẻ',
'Ẽ' => 'Ẽ',
'ẽ' => 'ẽ',
'Ế' => 'EÌÌ<E2809A>',
'ế' => 'eÌÌ<E2809A>',
'Ề' => 'EÌÌ€',
'á»<C3A1>' => 'eÌÌ€',
'Ể' => 'EÌ̉',
'ể' => 'eÌ̉',
'Ễ' => 'EÌ̃',
'á»…' => 'eÌ̃',
'Ệ' => 'EÌ£Ì',
'ệ' => 'eÌ£Ì',
'Ỉ' => 'Ỉ',
'ỉ' => 'ỉ',
'Ị' => 'Ị',
'ị' => 'ị',
'Ọ' => 'Ọ',
'á»<C3A1>' => 'oÌ£',
'Ỏ' => 'Ỏ',
'á»<C3A1>' => 'ỏ',
'á»<C3A1>' => 'OÌÌ<E2809A>',
'ố' => 'oÌÌ<E2809A>',
'á»’' => 'OÌÌ€',
'ồ' => 'oÌÌ€',
'á»”' => 'OÌ̉',
'ổ' => 'oÌ̉',
'á»–' => 'OÌ̃',
'á»—' => 'oÌ̃',
'Ộ' => 'OÌ£Ì',
'á»™' => 'oÌ£Ì',
'Ớ' => 'OÌÌ<E280BA>',
'á»›' => 'oÌÌ<E280BA>',
'Ờ' => 'OÌÌ€',
'á»<C3A1>' => 'oÌÌ€',
'Ở' => 'OÌ̉',
'ở' => 'oÌ̉',
'á» ' => 'OÌ̃',
'ỡ' => 'oÌ̃',
'Ợ' => 'OÌÌ£',
'ợ' => 'oÌÌ£',
'Ụ' => 'Ụ',
'ụ' => 'ụ',
'Ủ' => 'Ủ',
'ủ' => 'ủ',
'Ứ' => 'UÌÌ<E280BA>',
'ứ' => 'uÌÌ<E280BA>',
'Ừ' => 'UÌÌ€',
'ừ' => 'uÌÌ€',
'Ử' => 'UÌ̉',
'á»­' => 'uÌ̉',
'á»®' => 'UÌ̃',
'ữ' => 'uÌ̃',
'á»°' => 'UÌÌ£',
'á»±' => 'uÌÌ£',
'Ỳ' => 'Ỳ',
'ỳ' => 'ỳ',
'á»´' => 'YÌ£',
'ỵ' => 'ỵ',
'Ỷ' => 'Ỷ',
'ỷ' => 'ỷ',
'Ỹ' => 'Ỹ',
'ỹ' => 'ỹ',
'ἀ' => 'ἀ',
'á¼<C3A1>' => 'ἁ',
'ἂ' => 'ἂ',
'ἃ' => 'ἃ',
'ἄ' => 'ἀÌ<E2809C>',
'á¼…' => 'ἁÌ<E2809D>',
'ἆ' => 'ἆ',
'ἇ' => 'ἇ',
'Ἀ' => 'Ἀ',
'Ἁ' => 'Ἁ',
'Ἂ' => 'Ἂ',
'Ἃ' => 'Ἃ',
'Ἄ' => 'ÎÌ“Ì<E2809C>',
'á¼<C3A1>' => 'ÎÌ”Ì<E2809D>',
'Ἆ' => 'Ἆ',
'á¼<C3A1>' => 'Ἇ',
'á¼<C3A1>' => 'ἐ',
'ἑ' => 'ἑ',
'ἒ' => 'ἒ',
'ἓ' => 'ἓ',
'á¼”' => 'ἐÌ<E2809C>',
'ἕ' => 'ἑÌ<E2809D>',
'Ἐ' => 'Ἐ',
'Ἑ' => 'Ἑ',
'Ἒ' => 'Ἒ',
'Ἓ' => 'Ἓ',
'Ἔ' => 'ἘÌ<E2809C>',
'á¼<C3A1>' => 'ἙÌ<E2809D>',
'ἠ' => 'ἠ',
'ἡ' => 'ἡ',
'ἢ' => 'ἢ',
'ἣ' => 'ἣ',
'ἤ' => 'ἠÌ<E2809C>',
'á¼¥' => 'ἡÌ<E2809D>',
'ἦ' => 'ἦ',
'ἧ' => 'ἧ',
'Ἠ' => 'Ἠ',
'Ἡ' => 'Ἡ',
'Ἢ' => 'Ἢ',
'Ἣ' => 'Ἣ',
'Ἤ' => 'ἨÌ<E2809C>',
'á¼­' => 'ἩÌ<E2809D>',
'Ἦ' => 'Ἦ',
'Ἧ' => 'Ἧ',
'ἰ' => 'ἰ',
'ἱ' => 'ἱ',
'ἲ' => 'ἲ',
'ἳ' => 'ἳ',
'á¼´' => 'ἰÌ<E2809C>',
'á¼µ' => 'ἱÌ<E2809D>',
'ἶ' => 'ἶ',
'ἷ' => 'ἷ',
'Ἰ' => 'Ἰ',
'Ἱ' => 'Ἱ',
'Ἲ' => 'Ἲ',
'Ἳ' => 'Ἳ',
'á¼¼' => 'ἸÌ<E2809C>',
'á¼½' => 'ἹÌ<E2809D>',
'Ἶ' => 'Ἶ',
'Ἷ' => 'Ἷ',
'ὀ' => 'ὀ',
'á½<C3A1>' => 'ὁ',
'ὂ' => 'ὂ',
'ὃ' => 'ὃ',
'ὄ' => 'ὀÌ<E2809C>',
'á½…' => 'ὁÌ<E2809D>',
'Ὀ' => 'Ὀ',
'Ὁ' => 'Ὁ',
'Ὂ' => 'Ὂ',
'Ὃ' => 'Ὃ',
'Ὄ' => 'ὈÌ<E2809C>',
'á½<C3A1>' => 'ὉÌ<E2809D>',
'á½<C3A1>' => 'Ï…Ì“',
'ὑ' => 'ὑ',
'á½’' => 'Ï…Ì“Ì€',
'ὓ' => 'ὓ',
'á½”' => 'Ï…Ì“Ì<E2809C>',
'ὕ' => 'Ï…Ì”Ì<E2809D>',
'á½–' => 'Ï…Ì“Í‚',
'ὗ' => 'ὗ',
'Ὑ' => 'Ὑ',
'Ὓ' => 'Ὓ',
'á½<C3A1>' => 'ὙÌ<E2809D>',
'Ὗ' => 'Ὗ',
'ὠ' => 'ὠ',
'ὡ' => 'ὡ',
'ὢ' => 'ὢ',
'ὣ' => 'ὣ',
'ὤ' => 'ὠÌ<E2809C>',
'á½¥' => 'ὡÌ<E2809D>',
'ὦ' => 'ὦ',
'ὧ' => 'ὧ',
'Ὠ' => 'Ὠ',
'Ὡ' => 'Ὡ',
'Ὢ' => 'Ὢ',
'Ὣ' => 'Ὣ',
'Ὤ' => 'ὨÌ<E2809C>',
'á½­' => 'ὩÌ<E2809D>',
'Ὦ' => 'Ὦ',
'Ὧ' => 'Ὧ',
'ὰ' => 'ὰ',
'á½±' => 'αÌ<C2B1>',
'ὲ' => 'ὲ',
'á½³' => 'εÌ<C2B5>',
'ὴ' => 'ὴ',
'á½µ' => 'ηÌ<C2B7>',
'ὶ' => 'ὶ',
'á½·' => 'ιÌ<C2B9>',
'ὸ' => 'ὸ',
'á½¹' => 'οÌ<C2BF>',
'ὺ' => 'ὺ',
'á½»' => 'Ï…Ì<E280A6>',
'ὼ' => 'ὼ',
'á½½' => 'ωÌ<E280B0>',
'ᾀ' => 'ᾀ',
'á¾<C3A1>' => 'ᾁ',
'ᾂ' => 'ᾂ',
'ᾃ' => 'ᾃ',
'ᾄ' => 'ἀÌ<E2809C>Í…',
'á¾…' => 'ἁÌ<E2809D>Í…',
'ᾆ' => 'ᾆ',
'ᾇ' => 'ᾇ',
'ᾈ' => 'ᾈ',
'ᾉ' => 'ᾉ',
'ᾊ' => 'ᾊ',
'ᾋ' => 'ᾋ',
'ᾌ' => 'ÎÌ“Ì<E2809C>Í…',
'á¾<C3A1>' => 'ÎÌ”Ì<E2809D>Í…',
'ᾎ' => 'ᾎ',
'á¾<C3A1>' => 'ᾏ',
'á¾<C3A1>' => 'ᾐ',
'ᾑ' => 'ᾑ',
'ᾒ' => 'ᾒ',
'ᾓ' => 'ᾓ',
'á¾”' => 'ἠÌ<E2809C>Í…',
'ᾕ' => 'ἡÌ<E2809D>Í…',
'ᾖ' => 'ᾖ',
'ᾗ' => 'ᾗ',
'ᾘ' => 'ᾘ',
'ᾙ' => 'ᾙ',
'ᾚ' => 'ᾚ',
'ᾛ' => 'ᾛ',
'ᾜ' => 'ἨÌ<E2809C>Í…',
'á¾<C3A1>' => 'ἩÌ<E2809D>Í…',
'ᾞ' => 'ᾞ',
'ᾟ' => 'ᾟ',
'ᾠ' => 'ᾠ',
'ᾡ' => 'ᾡ',
'ᾢ' => 'ᾢ',
'ᾣ' => 'ᾣ',
'ᾤ' => 'ὠÌ<E2809C>Í…',
'á¾¥' => 'ὡÌ<E2809D>Í…',
'ᾦ' => 'ᾦ',
'ᾧ' => 'ᾧ',
'ᾨ' => 'ᾨ',
'ᾩ' => 'ᾩ',
'ᾪ' => 'ᾪ',
'ᾫ' => 'ᾫ',
'ᾬ' => 'ὨÌ<E2809C>Í…',
'á¾­' => 'ὩÌ<E2809D>Í…',
'ᾮ' => 'ᾮ',
'ᾯ' => 'ᾯ',
'ᾰ' => 'ᾰ',
'ᾱ' => 'ᾱ',
'ᾲ' => 'ᾲ',
'ᾳ' => 'ᾳ',
'á¾´' => 'αÌ<C2B1>Í…',
'ᾶ' => 'ᾶ',
'ᾷ' => 'ᾷ',
'Ᾰ' => 'Ᾰ',
'Ᾱ' => 'Ᾱ',
'Ὰ' => 'Ὰ',
'á¾»' => 'ÎÌ<E28098>',
'ᾼ' => 'ᾼ',
'ι' => 'ι',
'á¿<C3A1>' => '῁',
'ῂ' => 'ῂ',
'ῃ' => 'ῃ',
'á¿„' => 'ηÌ<C2B7>Í…',
'ῆ' => 'ῆ',
'ῇ' => 'ῇ',
'Ὲ' => 'Ὲ',
'Έ' => 'ΕÌ<E280A2>',
'Ὴ' => 'Ὴ',
'á¿‹' => 'ΗÌ<E28094>',
'ῌ' => 'ῌ',
'á¿<C3A1>' => '῍',
'á¿Ž' => '᾿Ì<C2BF>',
'á¿<C3A1>' => '῏',
'á¿<C3A1>' => 'ῐ',
'ῑ' => 'ῑ',
'ῒ' => 'ῒ',
'á¿“' => 'ϊÌ<CB86>',
'ῖ' => 'ῖ',
'ῗ' => 'ῗ',
'Ῐ' => 'Ῐ',
'Ῑ' => 'Ῑ',
'Ὶ' => 'Ὶ',
'á¿›' => 'ΙÌ<E284A2>',
'á¿<C3A1>' => '῝',
'á¿ž' => '῾Ì<C2BE>',
'῟' => '῟',
'ῠ' => 'ῠ',
'á¿¡' => 'Ï…Ì„',
'ῢ' => 'ῢ',
'á¿£' => 'ϋÌ<CB86>',
'ῤ' => 'Ï<>Ì“',
'á¿¥' => 'Ï<>Ì”',
'ῦ' => 'ῦ',
'ῧ' => 'ῧ',
'Ῠ' => 'Ῠ',
'Ῡ' => 'Ῡ',
'Ὺ' => 'Ὺ',
'á¿«' => 'Î¥Ì<C2A5>',
'Ῥ' => 'Ῥ',
'῭' => '῭',
'á¿®' => '¨Ì<C2A8>',
'`' => '`',
'ῲ' => 'ῲ',
'ῳ' => 'ῳ',
'á¿´' => 'ωÌ<E280B0>Í…',
'ῶ' => 'ῶ',
'ῷ' => 'ῷ',
'Ὸ' => 'Ὸ',
'Ό' => 'ΟÌ<C5B8>',
'Ὼ' => 'Ὼ',
'á¿»' => 'ΩÌ<C2A9>',
'ῼ' => 'ῼ',
'´' => '´',
' ' => ' ',
'â€<C3A2>' => ' ',
'Ω' => 'Ω',
'K' => 'K',
'â„«' => 'AÌŠ',
'↚' => 'â†<C3A2≯',
'↛' => '↛',
'↮' => '↮',
'â‡<C3A2>' => 'â‡<C3A2≯',
'⇎' => '⇎',
'â‡<C3A2>' => '⇏',
'∄' => '∄',
'∉' => '∉',
'∌' => '∌',
'∤' => '∤',
'∦' => '∦',
'â‰<C3A2>' => '≁',
'≄' => '≄',
'≇' => '≇',
'≉' => '≉',
'≠' => '≠',
'≢' => '≢',
'≭' => 'â‰<C3A2≯',
'≮' => '≮',
'≯' => '≯',
'≰' => '≰',
'≱' => '≱',
'≴' => '≴',
'≵' => '≵',
'≸' => '≸',
'≹' => '≹',
'⊀' => '⊀',
'âŠ<C3A2>' => '⊁',
'⊄' => '⊄',
'⊅' => '⊅',
'⊈' => '⊈',
'⊉' => '⊉',
'⊬' => '⊬',
'⊭' => '⊭',
'⊮' => '⊮',
'⊯' => '⊯',
'⋠' => '⋠',
'⋡' => '⋡',
'⋢' => '⋢',
'⋣' => '⋣',
'⋪' => '⋪',
'⋫' => '⋫',
'⋬' => '⋬',
'⋭' => '⋭',
'〈' => '〈',
'〉' => '〉',
'â«œ' => 'â«<C3A2≯',
<>Œ' => 'ã<>ã™',
<>Ž' => 'ã<><C3A3>ã™',
<><C3A3>' => 'ã<><C3A3>ã™',
<>' => 'ã<>ã™',
<>”' => 'ã<>“ã™',
<>' => 'ã<>•ã™',
<>˜' => 'ã<>—ã™',
<>š' => 'ã<>™ã™',
<>œ' => 'ã<>ã™',
<>ž' => 'ã<><C3A3>ã™',
<> ' => 'ã<>Ÿã™',
<>¢' => 'ã<>¡ã™',
<>¥' => 'ã<>¤ã™',
<>§' => 'ã<>¦ã™',
<>©' => 'ã<>¨ã™',
<>°' => 'ã<>¯ã™',
<>±' => 'ã<>¯ãš',
<>³' => 'ã<>²ã™',
<>´' => 'ã<>²ãš',
<>¶' => 'ã<>µã™',
<>·' => 'ã<>µãš',
<>¹' => 'ã<>¸ã™',
<>º' => 'ã<>¸ãš',
<>¼' => 'ã<>»ã™',
<>½' => 'ã<>»ãš',
'ã‚”' => 'ã<>†ã™',
'ã‚ž' => 'ã<C3A3>ã™',
'ガ' => 'ガ',
'ã‚®' => 'ã‚­ã‚™',
'グ' => 'グ',
'ゲ' => 'ゲ',
'ゴ' => 'ゴ',
'ザ' => 'ザ',
'ジ' => 'ジ',
'ズ' => 'ズ',
'ゼ' => 'ゼ',
'ゾ' => 'ゾ',
'ダ' => 'ダ',
'ヂ' => 'ãƒ<C3A3>ã™',
'ヅ' => 'ヅ',
'デ' => 'デ',
'ド' => 'ド',
'ãƒ<C3A3>' => 'ãƒ<C3A3>ã™',
'パ' => 'ãƒ<C3A3>ãš',
'ビ' => 'ビ',
'ピ' => 'ピ',
'ブ' => 'ブ',
'プ' => 'プ',
'ベ' => 'ベ',
'ペ' => 'ペ',
'ボ' => 'ボ',
'ãƒ<C3A3>' => 'ポ',
'ヴ' => 'ヴ',
'ヷ' => 'ヷ',
'ヸ' => 'ヸ',
'ヹ' => 'ヹ',
'ヺ' => 'ヺ',
'ヾ' => 'ヾ',
'豈' => '豈',
'ï¤<C3AF>' => 'æ›´',
'車' => '車',
'賈' => '賈',
'滑' => '滑',
'串' => '串',
'句' => 'å<>¥',
'龜' => '龜',
'龜' => '龜',
'契' => '契',
'金' => '金',
'喇' => '喇',
'奈' => '奈',
'ï¤<C3AF>' => '懶',
'癩' => '癩',
'ï¤<C3AF>' => 'ç¾…',
'ï¤<C3AF>' => '蘿',
'螺' => '螺',
'裸' => '裸',
'邏' => 'é<C3A9>',
'樂' => '樂',
'洛' => '洛',
'烙' => '烙',
'珞' => 'ç<>ž',
'落' => 'è<>½',
'酪' => '酪',
'駱' => '駱',
'亂' => '亂',
'卵' => 'å<>µ',
'ï¤<C3AF>' => '欄',
'爛' => '爛',
'蘭' => '蘭',
'鸞' => '鸞',
'嵐' => 'åµ<C3A5>',
'濫' => '濫',
'藍' => 'è—<C3A8>',
'襤' => '襤',
'拉' => '拉',
'臘' => '臘',
'蠟' => '蠟',
'廊' => '廊',
'朗' => '朗',
'浪' => '浪',
'狼' => '狼',
'郎' => '郎',
'來' => '來',
'冷' => '冷',
'勞' => '勞',
'擄' => '擄',
'櫓' => '櫓',
'爐' => 'çˆ<C3A7>',
'盧' => '盧',
'老' => 'è€<C3A8>',
'蘆' => '蘆',
'虜' => '虜',
'路' => '路',
'露' => '露',
'魯' => '魯',
'鷺' => '鷺',
'碌' => '碌',
'祿' => '祿',
'綠' => '綠',
'菉' => 'è<>‰',
'錄' => '錄',
'鹿' => '鹿',
'ï¥<C3AF>' => 'è«–',
'壟' => '壟',
'弄' => '弄',
'籠' => '籠',
'聾' => 'è<>¾',
'牢' => '牢',
'磊' => '磊',
'賂' => '賂',
'雷' => '雷',
'壘' => '壘',
'屢' => '屢',
'樓' => '樓',
'ï¥<C3AF>' => 'æ·š',
'漏' => 'æ¼<C3A6>',
'ï¥<C3AF>' => 'ç´¯',
'ï¥<C3AF>' => '縷',
'陋' => '陋',
'勒' => '勒',
'肋' => '肋',
'凜' => '凜',
'凌' => '凌',
'稜' => '稜',
'綾' => '綾',
'菱' => 'è<>±',
'陵' => '陵',
'讀' => '讀',
'拏' => 'æ<C3A6>',
'樂' => '樂',
'ï¥<C3AF>' => '諾',
'丹' => '丹',
'寧' => '寧',
'怒' => '怒',
'率' => '率',
'異' => '異',
'北' => '北',
'磻' => '磻',
'便' => '便',
'復' => '復',
'不' => 'ä¸<C3A4>',
'泌' => '泌',
'數' => '數',
'索' => '索',
'參' => 'å<>ƒ',
'塞' => '塞',
'省' => 'çœ<C3A7>',
'葉' => '葉',
'說' => '說',
'殺' => '殺',
'辰' => '辰',
'沈' => '沈',
'拾' => '拾',
'若' => '若',
'掠' => '掠',
'略' => '略',
'亮' => '亮',
'兩' => '兩',
'凉' => '凉',
'梁' => 'æ¢<C3A6>',
'糧' => '糧',
'良' => '良',
'諒' => '諒',
'量' => 'é‡<C3A9>',
'勵' => '勵',
'呂' => '呂',
'ï¦<C3AF>' => '女',
'廬' => '廬',
'旅' => '旅',
'濾' => '濾',
'礪' => '礪',
'閭' => '閭',
'驪' => '驪',
'麗' => '麗',
'黎' => '黎',
'力' => '力',
'曆' => '曆',
'歷' => '歷',
'ï¦<C3AF>' => 'è½¢',
'年' => '年',
'ï¦<C3AF>' => 'æ†<C3A6>',
'ï¦<C3AF>' => '戀',
'撚' => '撚',
'漣' => '漣',
'煉' => '煉',
'璉' => '璉',
'秊' => '秊',
'練' => '練',
'聯' => 'è<>¯',
'輦' => '輦',
'蓮' => '蓮',
'連' => '連',
'鍊' => 'é<>Š',
'列' => '列',
'ï¦<C3AF>' => '劣',
'咽' => '咽',
'烈' => '烈',
'裂' => '裂',
'說' => '說',
'廉' => '廉',
'念' => '念',
'捻' => 'æ<>»',
'殮' => '殮',
'簾' => '簾',
'獵' => 'ç<>µ',
'令' => '令',
'囹' => '囹',
'寧' => '寧',
'嶺' => '嶺',
'怜' => '怜',
'玲' => '玲',
'瑩' => '瑩',
'羚' => '羚',
'聆' => 'è<>†',
'鈴' => '鈴',
'零' => '零',
'靈' => 'é<>ˆ',
'領' => '領',
'例' => '例',
'禮' => '禮',
'醴' => '醴',
'隸' => '隸',
'惡' => '惡',
'了' => '了',
'僚' => '僚',
'寮' => '寮',
'尿' => '尿',
'料' => '料',
'樂' => '樂',
'燎' => '燎',
'ï§<C3AF>' => '療',
'蓼' => '蓼',
'遼' => 'é<>¼',
'龍' => 'é¾<C3A9>',
'暈' => '暈',
'阮' => '阮',
'劉' => '劉',
'杻' => 'æ<>»',
'柳' => '柳',
'流' => 'æµ<C3A6>',
'溜' => '溜',
'琉' => 'ç<>‰',
'ï§<C3AF>' => 'ç•™',
'硫' => '硫',
'ï§<C3AF>' => 'ç´<C3A7>',
'ï§<C3AF>' => 'é¡ž',
'六' => '六',
'戮' => '戮',
'陸' => '陸',
'倫' => '倫',
'崙' => '崙',
'淪' => '淪',
'輪' => '輪',
'律' => '律',
'慄' => '慄',
'栗' => '栗',
'率' => '率',
'隆' => '隆',
'ï§<C3AF>' => '利',
'吏' => 'å<><C3A5>',
'履' => '履',
'易' => '易',
'李' => 'æ<>Ž',
'梨' => '梨',
'泥' => '泥',
'理' => 'ç<>†',
'痢' => '痢',
'罹' => '罹',
'裏' => 'è£<C3A8>',
'裡' => '裡',
'里' => '里',
'離' => '離',
'匿' => '匿',
'溺' => '溺',
'吝' => 'å<><C3A5>',
'燐' => 'ç‡<C3A7>',
'璘' => '璘',
'藺' => '藺',
'隣' => '隣',
'鱗' => '鱗',
'麟' => '麟',
'林' => '林',
'淋' => '淋',
'臨' => '臨',
'立' => '立',
'笠' => '笠',
'粒' => '粒',
'狀' => '狀',
'炙' => '炙',
'識' => '識',
'什' => '什',
'茶' => '茶',
'刺' => '刺',
'切' => '切',
'ï¨<C3AF>' => '度',
'拓' => '拓',
'糖' => '糖',
'宅' => '宅',
'洞' => '洞',
'暴' => '暴',
'輻' => '輻',
'行' => '行',
'降' => 'é™<C3A9>',
'見' => '見',
'廓' => '廓',
'兀' => '兀',
'ï¨<C3AF>' => 'å—€',
'ï¨<C3AF>' => 'å¡š',
'晴' => '晴',
'凞' => '凞',
'猪' => '猪',
'益' => '益',
'礼' => '礼',
'神' => '神',
'祥' => '祥',
'福' => 'ç¦<C3A7>',
'靖' => 'é<>',
'ï¨<C3AF>' => 'ç²¾',
'羽' => '羽',
'蘒' => '蘒',
'諸' => '諸',
'逸' => '逸',
'都' => '都',
'飯' => '飯',
'飼' => '飼',
'館' => '館',
'鶴' => '鶴',
'郞' => '郞',
'隷' => '隷',
'侮' => '侮',
'僧' => '僧',
'免' => 'å…<C3A5>',
'勉' => '勉',
'勤' => '勤',
'卑' => 'å<>',
'喝' => 'å<C3A5>',
'嘆' => '嘆',
'器' => '器',
'塀' => '塀',
'墨' => '墨',
'層' => '層',
'屮' => '屮',
'悔' => '悔',
'慨' => '慨',
'憎' => '憎',
'懲' => '懲',
'ï©<C3AF>' => 'æ•<C3A6>',
'ï©‚' => 'æ—¢',
'暑' => '暑',
'梅' => '梅',
'ï©…' => 'æµ·',
'渚' => '渚',
'漢' => '漢',
'煮' => '煮',
'爫' => '爫',
'ï©Š' => 'ç<>¢',
'碑' => '碑',
'社' => '社',
'ï©<C3AF>' => '祉',
'祈' => '祈',
'ï©<C3AF>' => 'ç¥<C3A7>',
'ï©<C3AF>' => '祖',
'ï©‘' => 'ç¥<C3A7>',
'ï©’' => 'ç¦<C3A7>',
'禎' => '禎',
'ï©”' => 'ç©€',
'ï©•' => 'çª<C3A7>',
'節' => '節',
'ï©—' => 'ç·´',
'縉' => '縉',
'ï©™' => 'ç¹<C3A7>',
'ï©š' => 'ç½²',
'者' => '者',
'臭' => '臭',
'ï©<C3AF>' => '艹',
'艹' => '艹',
'ï©Ÿ' => 'è‘—',
'ï© ' => 'è¤<C3A8>',
'視' => '視',
'ï©¢' => 'è¬<C3A8>',
'謹' => '謹',
'賓' => '賓',
'ï©¥' => 'è´ˆ',
'辶' => '辶',
'逸' => '逸',
'難' => '難',
'響' => '響',
'頻' => '頻',
'ï©«' => 'æ<>µ',
'𤋮' => '𤋮',
'舘' => '舘',
'並' => '並',
'况' => '况',
'全' => '全',
'侀' => '侀',
'ï©´' => 'å……',
'冀' => '冀',
'勇' => '勇',
'勺' => '勺',
'喝' => 'å<C3A5>',
'啕' => '啕',
'喙' => '喙',
'ï©»' => 'å—¢',
'塚' => '塚',
'墳' => '墳',
'奄' => '奄',
'奔' => '奔',
'婢' => '婢',
'ïª<C3AF>' => '嬨',
'廒' => '廒',
'廙' => '廙',
'彩' => '彩',
'徭' => '徭',
'惘' => '惘',
'慎' => '慎',
'愈' => '愈',
'憎' => '憎',
'慠' => '慠',
'懲' => '懲',
'戴' => '戴',
'ïª<C3AF>' => 'æ<>„',
'搜' => 'æ<>œ',
'ïª<C3AF>' => 'æ‘’',
'ïª<C3AF>' => 'æ•–',
'晴' => '晴',
'朗' => '朗',
'望' => '望',
'杖' => 'æ<>',
'歹' => '歹',
'殺' => '殺',
'流' => 'æµ<C3A6>',
'滛' => '滛',
'滋' => '滋',
'漢' => '漢',
'瀞' => '瀞',
'煮' => '煮',
'ïª<C3AF>' => '瞧',
'爵' => '爵',
'犯' => '犯',
'猪' => '猪',
'瑱' => '瑱',
'甆' => '甆',
'画' => '画',
'瘝' => 'ç˜<C3A7>',
'瘟' => '瘟',
'益' => '益',
'盛' => '盛',
'直' => '直',
'睊' => 'ç<>Š',
'着' => 'ç<>€',
'磌' => '磌',
'窱' => '窱',
'節' => '節',
'类' => '类',
'絛' => '絛',
'練' => '練',
'缾' => '缾',
'者' => '者',
'荒' => 'è<>',
'華' => 'è<>¯',
'蝹' => 'è<>¹',
'襁' => 'è¥<C3A8>',
'覆' => '覆',
'視' => '視',
'調' => '調',
'諸' => '諸',
'請' => '請',
'謁' => 'è¬<C3A8>',
'諾' => '諾',
'諭' => '諭',
'謹' => '謹',
'變' => '變',
'ï«<C3AF>' => 'è´ˆ',
'輸' => '輸',
'遲' => 'é<>²',
'醙' => '醙',
'鉶' => '鉶',
'陼' => '陼',
'難' => '難',
'靖' => 'é<>',
'韛' => '韛',
'響' => '響',
'ï«‹' => 'é ‹',
'頻' => '頻',
'ï«<C3AF>' => '鬒',
'龜' => '龜',
'ï«<C3AF>' => '𢡊',
'ï«<C3AF>' => '𢡄',
'ï«‘' => 'ð£<C3B0>•',
'ï«’' => 'ã®<C3A3>',
'䀘' => '䀘',
'䀹' => '䀹',
'𥉉' => '𥉉',
'ï«–' => 'ð¥³<C2A5>',
'𧻓' => '𧻓',
'齃' => '齃',
'龎' => '龎',
'ï¬<C3AF>' => '×™Ö´',
'ײַ' => 'ײַ',
'שׁ' => 'ש×<C2A9>',
'שׂ' => 'שׂ',
'שּׁ' => 'שּ×<C2BC>',
'שּׂ' => 'שּׂ',
'אַ' => '×<>Ö·',
'אָ' => '×<>Ö¸',
'אּ' => '×<>Ö¼',
'בּ' => 'בּ',
'גּ' => 'גּ',
'דּ' => 'דּ',
'הּ' => 'הּ',
'וּ' => 'וּ',
'זּ' => 'זּ',
'טּ' => 'טּ',
'יּ' => 'יּ',
'ךּ' => 'ךּ',
'כּ' => 'כּ',
'לּ' => 'לּ',
'מּ' => 'מּ',
'ï­€' => '× Ö¼',
­<C3AF>' => 'סּ',
'ï­ƒ' => '×£Ö¼',
'פּ' => 'פּ',
'צּ' => 'צּ',
'קּ' => 'קּ',
'רּ' => 'רּ',
'שּ' => 'שּ',
'תּ' => 'תּ',
'וֹ' => 'וֹ',
'בֿ' => 'בֿ',
­<C3AF>' => '×›Ö¿',
'פֿ' => 'פֿ',
'𑂚' => '𑂚',
'𑂜' => '𑂜',
'𑂫' => '𑂫',
'𑄮' => '𑄮',
'𑄯' => '𑄯',
<C3B0>' => 'ð<C3B0>‡ðŒ¾',
<C3B0>Œ' => 'ð<C3B0>‡ð<C3B0>—',
'𑒻' => '𑒻',
'𑒼' => '𑒼',
'𑒾' => '𑒾',
'𑖺' => '𑖺',
'𑖻' => '𑖻',
'𑤸' => '𑤸',
<>…ž' => 'ð<>…—ð<E28094>…¥',
<>…Ÿ' => 'ð<>˜ð<CB9C>…¥',
<> ' => 'ð<>˜ð<CB9C>…¥ð<C2A5>…®',
<>…¡' => 'ð<>˜ð<CB9C>…¥ð<C2A5>…¯',
<>…¢' => 'ð<>˜ð<CB9C>…¥ð<C2A5>…°',
<>…£' => 'ð<>˜ð<CB9C>…¥ð<C2A5>…±',
<>…¤' => 'ð<>˜ð<CB9C>…¥ð<C2A5>…²',
<>†»' => 'ð<>†¹ð<C2B9>…¥',
<>†¼' => 'ð<>†ºð<C2BA>…¥',
<>†½' => 'ð<>†¹ð<C2B9>…¥ð<C2A5>…®',
<>†¾' => 'ð<>†ºð<C2BA>…¥ð<C2A5>…®',
<>†¿' => 'ð<>†¹ð<C2B9>…¥ð<C2A5>…¯',
<>‡€' => 'ð<>†ºð<C2BA>…¥ð<C2A5>…¯',
'丽' => '丽',
'ð¯ <C2AF>' => '丸',
'乁' => 'ä¹<C3A4>',
'𠄢' => '𠄢',
'你' => '你',
'侮' => '侮',
'侻' => '侻',
'倂' => '倂',
'偺' => 'å<>º',
'備' => '備',
'僧' => '僧',
'像' => 'åƒ<C3A5>',
'㒞' => '㒞',
'ð¯ <C2AF>' => '𠘺',
'免' => 'å…<C3A5>',
'ð¯ <C2AF>' => 'å…”',
'ð¯ <C2AF>' => 'å…¤',
'具' => '具',
'𠔜' => '𠔜',
'㒹' => '㒹',
'內' => '內',
'再' => 'å†<C3A5>',
'𠕋' => '𠕋',
'冗' => '冗',
'冤' => '冤',
'仌' => '仌',
'冬' => '冬',
'况' => '况',
'𩇟' => '𩇟',
'ð¯ <C2AF>' => '凵',
'刃' => '刃',
'㓟' => '㓟',
'刻' => '刻',
'剆' => '剆',
'割' => '割',
'剷' => '剷',
'㔕' => '㔕',
'勇' => '勇',
'勉' => '勉',
'勤' => '勤',
'勺' => '勺',
'包' => '包',
'匆' => '匆',
'北' => '北',
'卉' => 'å<>‰',
'卑' => 'å<>',
'博' => 'å<>š',
'即' => 'å<>³',
'卽' => 'å<>½',
'卿' => 'å<>¿',
'卿' => 'å<>¿',
'卿' => 'å<>¿',
'𠨬' => '𠨬',
'灰' => 'ç<>°',
'及' => 'å<>Š',
'叟' => 'å<>Ÿ',
'𠭣' => '𠭣',
'叫' => 'å<>«',
'叱' => 'å<>±',
'吆' => 'å<>†',
'咞' => '咞',
'吸' => 'å<>¸',
'呈' => '呈',
'周' => '周',
'咢' => '咢',
'ð¯¡<C2AF>' => '哶',
'唐' => 'å”<C3A5>',
'啓' => '啓',
'啣' => '啣',
'善' => '善',
'善' => '善',
'喙' => '喙',
'喫' => '喫',
'喳' => '喳',
'嗂' => '嗂',
'圖' => '圖',
'嘆' => '嘆',
'ð¯¡<C2AF>' => '圗',
'噑' => '噑',
'ð¯¡<C2AF>' => 'å™´',
'ð¯¡<C2AF>' => '切',
'壮' => '壮',
'城' => '城',
'埴' => '埴',
'堍' => 'å <C3A5>',
'型' => '型',
'堲' => '堲',
'報' => '報',
'墬' => '墬',
'𡓤' => '𡓤',
'売' => '売',
'壷' => '壷',
'夆' => '夆',
'ð¯¡<C2AF>' => '多',
'夢' => '夢',
'奢' => '奢',
'𡚨' => '𡚨',
'𡛪' => '𡛪',
'姬' => '姬',
'娛' => '娛',
'娧' => '娧',
'姘' => '姘',
'婦' => '婦',
'㛮' => '㛮',
'㛼' => '㛼',
'嬈' => '嬈',
'嬾' => '嬾',
'嬾' => '嬾',
'𡧈' => '𡧈',
'寃' => '寃',
'寘' => '寘',
'寧' => '寧',
'寳' => '寳',
'𡬘' => '𡬘',
'寿' => '寿',
'将' => '将',
'当' => '当',
'尢' => '尢',
'㞁' => 'ãž<C3A3>',
'屠' => '屠',
'屮' => '屮',
'峀' => '峀',
'岍' => 'å²<C3A5>',
'𡷤' => '𡷤',
'嵃' => '嵃',
'𡷦' => '𡷦',
'嵮' => '嵮',
'嵫' => '嵫',
'嵼' => '嵼',
'ð¯¢<C2AF>' => 'å·¡',
'巢' => '巢',
'㠯' => '㠯',
'巽' => '巽',
'帨' => '帨',
'帽' => '帽',
'幩' => '幩',
'㡢' => '㡢',
'𢆃' => '𢆃',
'㡼' => '㡼',
'庰' => '庰',
'庳' => '庳',
'ð¯¢<C2AF>' => '庶',
'廊' => '廊',
'ð¯¢<C2AF>' => '𪎒',
'ð¯¢<C2AF>' => '廾',
'𢌱' => '𢌱',
'𢌱' => '𢌱',
'舁' => 'èˆ<C3A8>',
'弢' => '弢',
'弢' => '弢',
'㣇' => '㣇',
'𣊸' => '𣊸',
'𦇚' => '𦇚',
'形' => '形',
'彫' => '彫',
'㣣' => '㣣',
'徚' => '徚',
'ð¯¢<C2AF>' => 'å¿<C3A5>',
'志' => '志',
'忹' => '忹',
'悁' => 'æ<C3A6>',
'㤺' => '㤺',
'㤜' => '㤜',
'悔' => '悔',
'𢛔' => '𢛔',
'惇' => '惇',
'慈' => '慈',
'慌' => '慌',
'慎' => '慎',
'慌' => '慌',
'慺' => '慺',
'憎' => '憎',
'憲' => '憲',
'憤' => '憤',
'憯' => '憯',
'懞' => '懞',
'懲' => '懲',
'懶' => '懶',
'成' => 'æˆ<C3A6>',
'戛' => '戛',
'扝' => 'æ‰<C3A6>',
'抱' => '抱',
'拔' => '拔',
'捐' => 'æ<><C3A6>',
'𢬌' => '𢬌',
'挽' => '挽',
'拼' => '拼',
'捨' => 'æ<>¨',
'掃' => '掃',
'揤' => 'æ<>¤',
'𢯱' => '𢯱',
'搢' => 'æ<>¢',
'揅' => 'æ<>…',
'ð¯£<C2AF>' => '掩',
'㨮' => '㨮',
'摩' => '摩',
'摾' => '摾',
'撝' => 'æ<C3A6>',
'摷' => '摷',
'㩬' => '㩬',
'敏' => 'æ•<C3A6>',
'敬' => '敬',
'𣀊' => '𣀊',
'旣' => '旣',
'書' => '書',
'ð¯£<C2AF>' => '晉',
'㬙' => '㬙',
'ð¯£<C2AF>' => 'æš‘',
'ð¯£<C2AF>' => '㬈',
'㫤' => '㫤',
'冒' => '冒',
'冕' => '冕',
'最' => '最',
'暜' => '暜',
'肭' => '肭',
'䏙' => 'ä<>™',
'朗' => '朗',
'望' => '望',
'朡' => '朡',
'杞' => 'æ<>ž',
'杓' => 'æ<>“',
'ð¯£<C2AF>' => 'ð£<C3B0>ƒ',
'㭉' => '㭉',
'柺' => '柺',
'枅' => '枅',
'桒' => '桒',
'梅' => '梅',
'𣑭' => '𣑭',
'梎' => '梎',
'栟' => '栟',
'椔' => '椔',
'㮝' => 'ã®<C3A3>',
'楂' => '楂',
'榣' => '榣',
'槪' => '槪',
'檨' => '檨',
'𣚣' => '𣚣',
'櫛' => '櫛',
'㰘' => '㰘',
'次' => '次',
'𣢧' => '𣢧',
'歔' => '歔',
'㱎' => '㱎',
'歲' => '歲',
'殟' => '殟',
'殺' => '殺',
'殻' => '殻',
'𣪍' => 'ð£ª<C2A3>',
'𡴋' => '𡴋',
'𣫺' => '𣫺',
'汎' => '汎',
'𣲼' => '𣲼',
'沿' => '沿',
'泍' => 'æ³<C3A6>',
'汧' => '汧',
'洖' => '洖',
'派' => '派',
'ð¯¤<C2AF>' => 'æµ·',
'流' => 'æµ<C3A6>',
'浩' => '浩',
'浸' => '浸',
'涅' => '涅',
'𣴞' => '𣴞',
'洴' => '洴',
'港' => '港',
'湮' => '湮',
'㴳' => '㴳',
'滋' => '滋',
'滇' => '滇',
'ð¯¤<C2AF>' => '𣻑',
'淹' => '淹',
'ð¯¤<C2AF>' => 'æ½®',
'ð¯¤<C2AF>' => '𣽞',
'𣾎' => '𣾎',
'濆' => '濆',
'瀹' => '瀹',
'瀞' => '瀞',
'瀛' => '瀛',
'㶖' => '㶖',
'灊' => 'ç<>Š',
'災' => 'ç<>½',
'灷' => 'ç<>·',
'炭' => '炭',
'𠔥' => '𠔥',
'煅' => '煅',
'ð¯¤<C2AF>' => '𤉣',
'熜' => '熜',
'𤎫' => '𤎫',
'爨' => '爨',
'爵' => '爵',
'牐' => 'ç‰<C3A7>',
'𤘈' => '𤘈',
'犀' => '犀',
'犕' => '犕',
'𤜵' => '𤜵',
'𤠔' => '𤠔',
'獺' => 'ç<>º',
'王' => '王',
'㺬' => '㺬',
'玥' => '玥',
'㺸' => '㺸',
'㺸' => '㺸',
'瑇' => '瑇',
'瑜' => '瑜',
'瑱' => '瑱',
'璅' => '璅',
'瓊' => '瓊',
'㼛' => '㼛',
'甤' => '甤',
'𤰶' => '𤰶',
'甾' => '甾',
'𤲒' => '𤲒',
'異' => '異',
'𢆟' => '𢆟',
'瘐' => 'ç˜<C3A7>',
'𤾡' => '𤾡',
'𤾸' => '𤾸',
'𥁄' => 'ð¥<C3B0>„',
'㿼' => '㿼',
'䀈' => '䀈',
'直' => '直',
'ð¯¥<C2AF>' => '𥃳',
'𥃲' => '𥃲',
'𥄙' => '𥄙',
'𥄳' => '𥄳',
'眞' => '眞',
'真' => '真',
'真' => '真',
'睊' => 'ç<>Š',
'䀹' => '䀹',
'瞋' => '瞋',
'䁆' => 'ä<>†',
'䂖' => '䂖',
'ð¯¥<C2AF>' => 'ð¥<C3B0><C2A5>',
'硎' => '硎',
'ð¯¥<C2AF>' => '碌',
'ð¯¥<C2AF>' => '磌',
'䃣' => '䃣',
'𥘦' => '𥘦',
'祖' => '祖',
'𥚚' => '𥚚',
'𥛅' => '𥛅',
'福' => 'ç¦<C3A7>',
'秫' => '秫',
'䄯' => '䄯',
'穀' => '穀',
'穊' => '穊',
'穏' => 'ç©<C3A7>',
'𥥼' => '𥥼',
'ð¯¥<C2AF>' => '𥪧',
'𥪧' => '𥪧',
'竮' => '竮',
'䈂' => '䈂',
'𥮫' => '𥮫',
'篆' => '篆',
'築' => '築',
'䈧' => '䈧',
'𥲀' => '𥲀',
'糒' => '糒',
'䊠' => '䊠',
'糨' => '糨',
'糣' => '糣',
'紀' => '紀',
'𥾆' => '𥾆',
'絣' => '絣',
'䌁' => 'äŒ<C3A4>',
'緇' => '緇',
'縂' => '縂',
'繅' => '繅',
'䌴' => '䌴',
'𦈨' => '𦈨',
'𦉇' => '𦉇',
'䍙' => 'ä<>™',
'𦋙' => '𦋙',
'罺' => '罺',
'𦌾' => '𦌾',
'羕' => '羕',
'翺' => '翺',
'者' => '者',
'𦓚' => '𦓚',
'𦔣' => '𦔣',
'聠' => 'è<> ',
'𦖨' => '𦖨',
'聰' => 'è<>°',
'𣍟' => 'ð£<C3B0>Ÿ',
'ð¯¦<C2AF>' => 'ä<>•',
'育' => '育',
'脃' => '脃',
'䐋' => 'ä<>',
'脾' => '脾',
'媵' => '媵',
'𦞧' => '𦞧',
'𦞵' => '𦞵',
'𣎓' => '𣎓',
'𣎜' => '𣎜',
'舁' => 'èˆ<C3A8>',
'舄' => '舄',
'ð¯¦<C2AF>' => '辞',
'䑫' => '䑫',
'ð¯¦<C2AF>' => '芑',
'ð¯¦<C2AF>' => '芋',
'芝' => 'èŠ<C3A8>',
'劳' => '劳',
'花' => '花',
'芳' => '芳',
'芽' => '芽',
'苦' => '苦',
'𦬼' => '𦬼',
'若' => '若',
'茝' => 'èŒ<C3A8>',
'荣' => 'è<>£',
'莭' => '莭',
'茣' => '茣',
'ð¯¦<C2AF>' => '莽',
'菧' => 'è<>§',
'著' => '著',
'荓' => 'è<>“',
'菊' => 'è<>Š',
'菌' => 'è<>Œ',
'菜' => 'è<>œ',
'𦰶' => '𦰶',
'𦵫' => '𦵫',
'𦳕' => '𦳕',
'䔫' => '䔫',
'蓱' => '蓱',
'蓳' => '蓳',
'蔖' => '蔖',
'𧏊' => 'ð§<C3B0>Š',
'蕤' => '蕤',
'𦼬' => '𦼬',
'䕝' => 'ä•<C3A4>',
'䕡' => '䕡',
'𦾱' => '𦾱',
'𧃒' => '𧃒',
'䕫' => '䕫',
'虐' => 'è™<C3A8>',
'虜' => '虜',
'虧' => '虧',
'虩' => '虩',
'蚩' => '蚩',
'蚈' => '蚈',
'蜎' => '蜎',
'蛢' => '蛢',
'蝹' => 'è<>¹',
'蜨' => '蜨',
'蝫' => 'è<>«',
'螆' => '螆',
'䗗' => '䗗',
'蟡' => '蟡',
'ð¯§<C2AF>' => 'è <C3A8>',
'䗹' => '䗹',
'衠' => '衠',
'衣' => '衣',
'𧙧' => '𧙧',
'裗' => '裗',
'裞' => '裞',
'䘵' => '䘵',
'裺' => '裺',
'㒻' => '㒻',
'𧢮' => '𧢮',
'𧥦' => '𧥦',
'ð¯§<C2AF>' => 'äš¾',
'䛇' => '䛇',
'ð¯§<C2AF>' => '誠',
'ð¯§<C2AF>' => 'è«­',
'變' => '變',
'豕' => '豕',
'𧲨' => '𧲨',
'貫' => '貫',
'賁' => 'è³<C3A8>',
'贛' => '贛',
'起' => '起',
'𧼯' => '𧼯',
'𠠄' => '𠠄',
'跋' => '跋',
'趼' => '趼',
'跰' => '跰',
'ð¯§<C2AF>' => '𠣞',
'軔' => '軔',
'輸' => '輸',
'𨗒' => '𨗒',
'𨗭' => '𨗭',
'邔' => '邔',
'郱' => '郱',
'鄑' => '鄑',
'𨜮' => '𨜮',
'鄛' => '鄛',
'鈸' => '鈸',
'鋗' => '鋗',
'鋘' => '鋘',
'鉼' => '鉼',
'鏹' => 'é<>¹',
'鐕' => 'é<>•',
'𨯺' => '𨯺',
'開' => '開',
'䦕' => '䦕',
'閷' => '閷',
'𨵷' => '𨵷',
'䧦' => '䧦',
'雃' => '雃',
'嶲' => '嶲',
'霣' => '霣',
'𩅅' => '𩅅',
'𩈚' => '𩈚',
'䩮' => '䩮',
'䩶' => '䩶',
'韠' => '韠',
'𩐊' => 'ð©<C3B0>Š',
'䪲' => '䪲',
'𩒖' => '𩒖',
'頋' => '頋',
'頋' => '頋',
'頩' => '頩',
'ð¯¨<C2AF>' => 'ð©–¶',
'飢' => '飢',
'䬳' => '䬳',
'餩' => '餩',
'馧' => '馧',
'駂' => '駂',
'駾' => '駾',
'䯎' => '䯎',
'𩬰' => '𩬰',
'鬒' => '鬒',
'鱀' => '鱀',
'鳽' => '鳽',
'ð¯¨<C2AF>' => '䳎',
'䳭' => '䳭',
'ð¯¨<C2AF>' => '鵧',
'ð¯¨<C2AF>' => '𪃎',
'䳸' => '䳸',
'𪄅' => '𪄅',
'𪈎' => '𪈎',
'𪊑' => '𪊑',
'麻' => '麻',
'䵖' => '䵖',
'黹' => '黹',
'黾' => '黾',
'鼅' => '鼅',
'鼏' => 'é¼<C3A9>',
'鼖' => '鼖',
'鼻' => '鼻',
'ð¯¨<C2AF>' => '𪘀',
);
<?php
return array (
' ' => ' ',
'¨' => ' ̈',
'ª' => 'a',
'¯' => ' ̄',
'²' => '2',
'³' => '3',
'´' => ' Ì<>',
'µ' => 'μ',
'¸' => ' ̧',
'¹' => '1',
'º' => 'o',
'¼' => '1â<31>„4',
'½' => '1â<31>„2',
'¾' => '3â<33>„4',
'IJ' => 'IJ',
'ij' => 'ij',
'Ŀ' => 'L·',
'ŀ' => 'l·',
'ʼn' => 'ʼn',
'Å¿' => 's',
'DŽ' => 'DŽ',
'Dž' => 'Dž',
'dž' => 'dž',
'LJ' => 'LJ',
'Lj' => 'Lj',
'lj' => 'lj',
'ÇŠ' => 'NJ',
'Ç‹' => 'Nj',
'nj' => 'nj',
'DZ' => 'DZ',
'Dz' => 'Dz',
'dz' => 'dz',
'Ê°' => 'h',
'ʱ' => 'ɦ',
'ʲ' => 'j',
'ʳ' => 'r',
'ʴ' => 'ɹ',
'ʵ' => 'ɻ',
'ʶ' => 'Ê<>',
'Ê·' => 'w',
'ʸ' => 'y',
'˘' => ' ̆',
'˙' => ' ̇',
'Ëš' => ' ÌŠ',
'˛' => ' ̨',
'˜' => ' ̃',
<>' => ' Ì‹',
'Ë ' => 'É£',
'Ë¡' => 'l',
'Ë¢' => 's',
'Ë£' => 'x',
'ˤ' => 'ʕ',
'ͺ' => ' ͅ',
'΄' => ' Ì<>',
'Î…' => ' ̈Ì<CB86>',
<>' => 'β',
'ϑ' => 'θ',
'Ï’' => 'Î¥',
'Ï“' => 'Î¥Ì<C2A5>',
'ϔ' => 'Ϋ',
'ϕ' => 'φ',
'Ï–' => 'Ï€',
'ϰ' => 'κ',
'ϱ' => 'Ï<>',
'ϲ' => 'ς',
'ϴ' => 'Θ',
'ϵ' => 'ε',
'Ϲ' => 'Σ',
'Ö‡' => 'Õ¥Ö‚',
'ٵ' => 'اٴ',
'ٶ' => 'وٴ',
'Ù·' => 'Û‡Ù´',
'ٸ' => 'يٴ',
'ำ' => 'à¹<C3A0>า',
'ຳ' => 'à»<C3A0>າ',
'ໜ' => 'ຫນ',
'à»<C3A0>' => 'ຫມ',
'༌' => '་',
'ཷ' => 'ྲཱྀ',
'ཹ' => 'ླཱྀ',
'ჼ' => 'ნ',
'á´¬' => 'A',
'ᴭ' => 'Æ',
'á´®' => 'B',
'á´°' => 'D',
'á´±' => 'E',
'á´²' => 'ÆŽ',
'á´³' => 'G',
'á´´' => 'H',
'á´µ' => 'I',
'á´¶' => 'J',
'á´·' => 'K',
'á´¸' => 'L',
'á´¹' => 'M',
'á´º' => 'N',
'á´¼' => 'O',
'á´½' => 'È¢',
'á´¾' => 'P',
'á´¿' => 'R',
'áµ€' => 'T',
'áµ<C3A1>' => 'U',
'ᵂ' => 'W',
'ᵃ' => 'a',
'ᵄ' => 'É<>',
'áµ…' => 'É‘',
'ᵆ' => 'ᴂ',
'ᵇ' => 'b',
'ᵈ' => 'd',
'ᵉ' => 'e',
'ᵊ' => 'ə',
'ᵋ' => 'ɛ',
'ᵌ' => 'ɜ',
'áµ<C3A1>' => 'g',
'áµ<C3A1>' => 'k',
'áµ<C3A1>' => 'm',
'ᵑ' => 'ŋ',
'áµ’' => 'o',
'ᵓ' => 'ɔ',
'áµ”' => 'á´–',
'ᵕ' => 'ᴗ',
'áµ–' => 'p',
'áµ—' => 't',
'ᵘ' => 'u',
'áµ™' => 'á´<C3A1>',
'ᵚ' => 'ɯ',
'áµ›' => 'v',
'ᵜ' => 'ᴥ',
'áµ<C3A1>' => 'β',
'ᵞ' => 'γ',
'ᵟ' => 'δ',
'ᵠ' => 'φ',
'ᵡ' => 'χ',
'áµ¢' => 'i',
'áµ£' => 'r',
'ᵤ' => 'u',
'áµ¥' => 'v',
'ᵦ' => 'β',
'ᵧ' => 'γ',
'ᵨ' => 'Ï<>',
'ᵩ' => 'φ',
'ᵪ' => 'χ',
'ᵸ' => 'н',
'ᶛ' => 'ɒ',
'ᶜ' => 'c',
'á¶<C3A1>' => 'É•',
'ᶞ' => 'ð',
'ᶟ' => 'ɜ',
'ᶠ' => 'f',
'ᶡ' => 'ɟ',
'ᶢ' => 'ɡ',
'ᶣ' => 'ɥ',
'ᶤ' => 'ɨ',
'ᶥ' => 'ɩ',
'ᶦ' => 'ɪ',
'ᶧ' => 'ᵻ',
'ᶨ' => 'Ê<>',
'ᶩ' => 'ɭ',
'ᶪ' => 'ᶅ',
'ᶫ' => 'ʟ',
'ᶬ' => 'ɱ',
'ᶭ' => 'ɰ',
'ᶮ' => 'ɲ',
'ᶯ' => 'ɳ',
'ᶰ' => 'ɴ',
'ᶱ' => 'ɵ',
'ᶲ' => 'ɸ',
'ᶳ' => 'ʂ',
'ᶴ' => 'ʃ',
'ᶵ' => 'ƫ',
'ᶶ' => 'ʉ',
'ᶷ' => 'ʊ',
'ᶸ' => 'ᴜ',
'ᶹ' => 'ʋ',
'ᶺ' => 'ʌ',
'ᶻ' => 'z',
'ᶼ' => 'Ê<>',
'ᶽ' => 'ʑ',
'ᶾ' => 'ʒ',
'ᶿ' => 'θ',
'ẚ' => 'aʾ',
'ẛ' => 'ṡ',
'á¾½' => ' Ì“',
'᾿' => ' ̓',
'á¿€' => ' Í‚',
'á¿<C3A1>' => ' ̈͂',
'á¿<C3A1>' => ' Ì“Ì€',
'á¿Ž' => ' Ì“Ì<E2809C>',
'á¿<C3A1>' => ' Ì“Í‚',
'á¿<C3A1>' => ' ̔̀',
'á¿ž' => ' Ì”Ì<E2809D>',
'῟' => ' ̔͂',
'῭' => ' ̈̀',
'á¿®' => ' ̈Ì<CB86>',
'´' => ' Ì<>',
'῾' => ' ̔',
' ' => ' ',
'â€<C3A2>' => ' ',
' ' => ' ',
' ' => ' ',
' ' => ' ',
' ' => ' ',
' ' => ' ',
' ' => ' ',
' ' => ' ',
' ' => ' ',
' ' => ' ',
'‑' => 'â€<C3A2>',
'‗' => ' ̳',
'․' => '.',
'‥' => '..',
'…' => '...',
' ' => ' ',
'″' => '′′',
'‴' => '′′′',
'‶' => '‵‵',
'‷' => '‵‵‵',
'‼' => '!!',
'‾' => ' ̅',
<>‡' => '??',
<>ˆ' => '?!',
<>‰' => '!?',
<>—' => '′′′′',
<>Ÿ' => ' ',
<>°' => '0',
<>±' => 'i',
<>´' => '4',
<>µ' => '5',
<>¶' => '6',
<>·' => '7',
<>¸' => '8',
<>¹' => '9',
<>º' => '+',
<>»' => '−',
<>¼' => '=',
<>½' => '(',
<>¾' => ')',
<>¿' => 'n',
'â‚€' => '0',
<C3A2>' => '1',
'â‚‚' => '2',
'₃' => '3',
'â‚„' => '4',
'â‚…' => '5',
'₆' => '6',
'₇' => '7',
'₈' => '8',
'₉' => '9',
'â‚Š' => '+',
'₋' => '−',
'₌' => '=',
<C3A2>' => '(',
'â‚Ž' => ')',
<C3A2>' => 'a',
'â‚‘' => 'e',
'â‚’' => 'o',
'â‚“' => 'x',
'â‚”' => 'É™',
'â‚•' => 'h',
'â‚–' => 'k',
'â‚—' => 'l',
'ₘ' => 'm',
'â‚™' => 'n',
'â‚š' => 'p',
'â‚›' => 's',
'ₜ' => 't',
'₨' => 'Rs',
'â„€' => 'a/c',
'â„<C3A2>' => 'a/s',
'â„‚' => 'C',
'℃' => '°C',
'â„…' => 'c/o',
'℆' => 'c/u',
'ℇ' => 'Æ<>',
'℉' => '°F',
'â„Š' => 'g',
'â„‹' => 'H',
'ℌ' => 'H',
'â„<C3A2>' => 'H',
'â„Ž' => 'h',
'â„<C3A2>' => 'ħ',
'â„<C3A2>' => 'I',
'â„‘' => 'I',
'â„’' => 'L',
'â„“' => 'l',
'â„•' => 'N',
'â„–' => 'No',
'â„™' => 'P',
'â„š' => 'Q',
'â„›' => 'R',
'ℜ' => 'R',
'â„<C3A2>' => 'R',
'â„ ' => 'SM',
'â„¡' => 'TEL',
'â„¢' => 'TM',
'ℤ' => 'Z',
'ℨ' => 'Z',
'ℬ' => 'B',
'â„­' => 'C',
'ℯ' => 'e',
'â„°' => 'E',
'ℱ' => 'F',
'ℳ' => 'M',
'â„´' => 'o',
'ℵ' => '×<>',
'ℶ' => '×',
'â„·' => '×',
'ℸ' => 'ד',
'ℹ' => 'i',
'â„»' => 'FAX',
'ℼ' => 'π',
'ℽ' => 'γ',
'ℾ' => 'Γ',
'ℿ' => 'Π',
'⅀' => '∑',
'â……' => 'D',
'â…†' => 'd',
'â…‡' => 'e',
'â…ˆ' => 'i',
'â…‰' => 'j',
'â…<C3A2>' => '1â<31>„7',
'â…‘' => '1â<31>„9',
'â…’' => '1â<31>„10',
'â…“' => '1â<31>„3',
'â…”' => '2â<32>„3',
'â…•' => '1â<31>„5',
'â…–' => '2â<32>„5',
'â…—' => '3â<33>„5',
'â…˜' => '4â<34>„5',
'â…™' => '1â<31>„6',
'â…š' => '5â<35>„6',
'â…›' => '1â<31>„8',
'â…œ' => '3â<33>„8',
'â…<C3A2>' => '5â<35>„8',
'â…ž' => '7â<37>„8',
'â…Ÿ' => '1â<31>„',
'â… ' => 'I',
'â…¡' => 'II',
'â…¢' => 'III',
'â…£' => 'IV',
'â…¤' => 'V',
'â…¥' => 'VI',
'â…¦' => 'VII',
'â…§' => 'VIII',
'â…¨' => 'IX',
'â…©' => 'X',
'â…ª' => 'XI',
'â…«' => 'XII',
'â…¬' => 'L',
'â…­' => 'C',
'â…®' => 'D',
'â…¯' => 'M',
'â…°' => 'i',
'â…±' => 'ii',
'â…²' => 'iii',
'â…³' => 'iv',
'â…´' => 'v',
'â…µ' => 'vi',
'â…¶' => 'vii',
'â…·' => 'viii',
'â…¸' => 'ix',
'â…¹' => 'x',
'â…º' => 'xi',
'â…»' => 'xii',
'â…¼' => 'l',
'â…½' => 'c',
'â…¾' => 'd',
'â…¿' => 'm',
'↉' => '0â<30>„3',
'∬' => '∫∫',
'∭' => '∫∫∫',
'∯' => '∮∮',
'∰' => '∮∮∮',
'â‘ ' => '1',
'â‘¡' => '2',
'â‘¢' => '3',
'â‘£' => '4',
'⑤' => '5',
'â‘¥' => '6',
'⑦' => '7',
'⑧' => '8',
'⑨' => '9',
'â‘©' => '10',
'⑪' => '11',
'â‘«' => '12',
'⑬' => '13',
'â‘­' => '14',
'â‘®' => '15',
'⑯' => '16',
'â‘°' => '17',
'⑱' => '18',
'⑲' => '19',
'⑳' => '20',
'â‘´' => '(1)',
'⑵' => '(2)',
'⑶' => '(3)',
'â‘·' => '(4)',
'⑸' => '(5)',
'⑹' => '(6)',
'⑺' => '(7)',
'â‘»' => '(8)',
'⑼' => '(9)',
'⑽' => '(10)',
'⑾' => '(11)',
'â‘¿' => '(12)',
'â’€' => '(13)',
<C3A2>' => '(14)',
'â’‚' => '(15)',
'â’ƒ' => '(16)',
'â’„' => '(17)',
'â’…' => '(18)',
'â’†' => '(19)',
'â’‡' => '(20)',
'â’ˆ' => '1.',
'â’‰' => '2.',
'â’Š' => '3.',
'â’‹' => '4.',
'⒌' => '5.',
<C3A2>' => '6.',
'â’Ž' => '7.',
<C3A2>' => '8.',
<C3A2>' => '9.',
'â’‘' => '10.',
'â’’' => '11.',
'â’“' => '12.',
'â’”' => '13.',
'â’•' => '14.',
'â’–' => '15.',
'â’—' => '16.',
'â’˜' => '17.',
'â’™' => '18.',
'â’š' => '19.',
'â’›' => '20.',
'⒜' => '(a)',
<C3A2>' => '(b)',
'â’ž' => '(c)',
'â’Ÿ' => '(d)',
'â’ ' => '(e)',
'â’¡' => '(f)',
'â’¢' => '(g)',
'â’£' => '(h)',
'â’¤' => '(i)',
'â’¥' => '(j)',
'â’¦' => '(k)',
'â’§' => '(l)',
'â’¨' => '(m)',
'â’©' => '(n)',
'â’ª' => '(o)',
'â’«' => '(p)',
'â’¬' => '(q)',
'â’­' => '(r)',
'â’®' => '(s)',
'â’¯' => '(t)',
'â’°' => '(u)',
'â’±' => '(v)',
'â’²' => '(w)',
'â’³' => '(x)',
'â’´' => '(y)',
'â’µ' => '(z)',
'â’¶' => 'A',
'â’·' => 'B',
'â’¸' => 'C',
'â’¹' => 'D',
'â’º' => 'E',
'â’»' => 'F',
'â’¼' => 'G',
'â’½' => 'H',
'â’¾' => 'I',
'â’¿' => 'J',
'â“€' => 'K',
'â“<C3A2>' => 'L',
'â“‚' => 'M',
'Ⓝ' => 'N',
'â“„' => 'O',
'â“…' => 'P',
'Ⓠ' => 'Q',
'Ⓡ' => 'R',
'Ⓢ' => 'S',
'Ⓣ' => 'T',
'â“Š' => 'U',
'â“‹' => 'V',
'Ⓦ' => 'W',
'â“<C3A2>' => 'X',
'â“Ž' => 'Y',
'â“<C3A2>' => 'Z',
'â“<C3A2>' => 'a',
'â“‘' => 'b',
'â“’' => 'c',
'â““' => 'd',
'â“”' => 'e',
'â“•' => 'f',
'â“–' => 'g',
'â“—' => 'h',
'ⓘ' => 'i',
'â“™' => 'j',
'â“š' => 'k',
'â“›' => 'l',
'ⓜ' => 'm',
'â“<C3A2>' => 'n',
'â“ž' => 'o',
'â“Ÿ' => 'p',
'â“ ' => 'q',
'â“¡' => 'r',
'â“¢' => 's',
'â“£' => 't',
'ⓤ' => 'u',
'â“¥' => 'v',
'ⓦ' => 'w',
'ⓧ' => 'x',
'ⓨ' => 'y',
'â“©' => 'z',
'⓪' => '0',
'⨌' => '∫∫∫∫',
'â©´' => '::=',
'⩵' => '==',
'⩶' => '===',
'â±¼' => 'j',
'â±½' => 'V',
'ⵯ' => 'ⵡ',
'⺟' => 'æ¯<C3A6>',
'⻳' => '龟',
'⼀' => '一',
'â¼<C3A2>' => '丨',
'⼂' => '丶',
'⼃' => '丿',
'⼄' => '乙',
'⼅' => '亅',
'⼆' => '二',
'⼇' => '亠',
'⼈' => '人',
'⼉' => '儿',
'⼊' => '入',
'⼋' => '八',
'⼌' => '冂',
'â¼<C3A2>' => '冖',
'⼎' => '冫',
'â¼<C3A2>' => '几',
'â¼<C3A2>' => '凵',
'⼑' => '刀',
'⼒' => '力',
'⼓' => '勹',
'⼔' => '匕',
'⼕' => '匚',
'⼖' => '匸',
'â¼—' => 'å<><C3A5>',
'⼘' => 'å<>œ',
'â¼™' => 'å<>©',
'⼚' => '厂',
'⼛' => '厶',
'⼜' => 'å<>ˆ',
'â¼<C3A2>' => 'å<>£',
'⼞' => '囗',
'⼟' => '土',
'⼠' => '士',
'⼡' => '夂',
'⼢' => '夊',
'⼣' => '夕',
'⼤' => '大',
'⼥' => '女',
'⼦' => 'å­<C3A5>',
'⼧' => '宀',
'⼨' => '寸',
'⼩' => 'å°<C3A5>',
'⼪' => '尢',
'⼫' => '尸',
'⼬' => '屮',
'â¼­' => 'å±±',
'â¼®' => 'å·›',
'⼯' => '工',
'â¼°' => 'å·±',
'â¼±' => 'å·¾',
'â¼²' => 'å¹²',
'⼳' => '幺',
'⼴' => '广',
'â¼µ' => 'å»´',
'⼶' => '廾',
'⼷' => '弋',
'⼸' => '弓',
'â¼¹' => 'å½<C3A5>',
'⼺' => '彡',
'â¼»' => 'å½³',
'⼼' => '心',
'⼽' => '戈',
'⼾' => '戶',
'⼿' => '手',
'⽀' => '支',
'â½<C3A2>' => 'æ”´',
'⽂' => '文',
'⽃' => '斗',
'⽄' => '斤',
'â½…' => 'æ–¹',
'⽆' => '无',
'⽇' => '日',
'⽈' => '曰',
'⽉' => '月',
'⽊' => '木',
'⽋' => '欠',
'⽌' => '止',
'â½<C3A2>' => 'æ­¹',
'⽎' => '殳',
'â½<C3A2>' => '毋',
'â½<C3A2>' => '比',
'⽑' => '毛',
'â½’' => 'æ°<C3A6>',
'⽓' => '气',
'â½”' => 'æ°´',
'⽕' => 'ç<>«',
'⽖' => '爪',
'⽗' => '父',
'⽘' => '爻',
'⽙' => '爿',
'⽚' => '片',
'⽛' => '牙',
'⽜' => '牛',
'â½<C3A2>' => '犬',
'⽞' => '玄',
'⽟' => '玉',
'⽠' => '瓜',
'⽡' => '瓦',
'⽢' => '甘',
'⽣' => '生',
'⽤' => '用',
'â½¥' => 'ç”°',
'⽦' => '疋',
'⽧' => '疒',
'⽨' => '癶',
'⽩' => '白',
'⽪' => '皮',
'⽫' => '皿',
'⽬' => '目',
'⽭' => '矛',
'⽮' => '矢',
'⽯' => '石',
'⽰' => '示',
'⽱' => '禸',
'⽲' => '禾',
'â½³' => 'ç©´',
'â½´' => 'ç«‹',
'⽵' => '竹',
'⽶' => '米',
'⽷' => '糸',
'⽸' => '缶',
'⽹' => '网',
'⽺' => '羊',
'â½»' => 'ç¾½',
'â½¼' => 'è€<C3A8>',
'⽽' => '而',
'⽾' => '耒',
'⽿' => '耳',
'â¾€' => 'è<>¿',
'â¾<C3A2>' => '肉',
'⾂' => '臣',
'⾃' => '自',
'⾄' => '至',
'⾅' => '臼',
'⾆' => '舌',
'⾇' => '舛',
'⾈' => '舟',
'⾉' => '艮',
'⾊' => '色',
'⾋' => '艸',
'⾌' => 'è™<C3A8>',
'â¾<C3A2>' => '虫',
'⾎' => '血',
'â¾<C3A2>' => 'è¡Œ',
'â¾<C3A2>' => 'è¡£',
'⾑' => '襾',
'⾒' => '見',
'⾓' => '角',
'⾔' => '言',
'⾕' => '谷',
'⾖' => '豆',
'⾗' => '豕',
'⾘' => '豸',
'â¾™' => 'è²<C3A8>',
'⾚' => '赤',
'â¾›' => 'èµ°',
'⾜' => '足',
'â¾<C3A2>' => '身',
'⾞' => '車',
'⾟' => '辛',
'â¾ ' => 'è¾°',
'⾡' => '辵',
'â¾¢' => 'é‚‘',
'â¾£' => 'é…‰',
'⾤' => '釆',
'⾥' => '里',
'⾦' => '金',
'⾧' => '長',
'⾨' => '門',
'⾩' => '阜',
'⾪' => '隶',
'⾫' => '隹',
'⾬' => '雨',
'â¾­' => 'é<>',
'â¾®' => 'é<>ž',
'⾯' => 'é<>¢',
'â¾°' => 'é<>©',
'⾱' => '韋',
'⾲' => '韭',
'⾳' => '音',
'â¾´' => 'é <C3A9>',
'⾵' => '風',
'⾶' => '飛',
'⾷' => '食',
'⾸' => '首',
'⾹' => '香',
'⾺' => '馬',
'⾻' => '骨',
'⾼' => '高',
'â¾½' => 'é«Ÿ',
'⾾' => '鬥',
'⾿' => '鬯',
'⿀' => '鬲',
'â¿<C3A2>' => '鬼',
'â¿‚' => 'é­š',
'⿃' => '鳥',
'â¿„' => 'é¹µ',
'⿅' => '鹿',
'⿆' => '麥',
'⿇' => '麻',
'⿈' => '黃',
'⿉' => 'é»<C3A9>',
'⿊' => '黑',
'⿋' => '黹',
'⿌' => '黽',
'â¿<C3A2>' => '鼎',
'⿎' => '鼓',
'â¿<C3A2>' => 'é¼ ',
'â¿<C3A2>' => 'é¼»',
'⿑' => '齊',
'â¿’' => 'é½’',
'â¿“' => 'é¾<C3A9>',
'⿔' => '龜',
'â¿•' => 'é¾ ',
' ' => ' ',
'〶' => '〒',
'〸' => 'å<><C3A5>',
'〹' => 'å<>„',
'〺' => 'å<>…',
'ã‚›' => ' ã‚™',
'゜' => ' ゚',
'ゟ' => 'より',
'ヿ' => 'コト',
'ㄱ' => 'ᄀ',
'ㄲ' => 'á„<C3A1>',
'ㄳ' => 'ᆪ',
'ã„´' => 'á„‚',
'ㄵ' => 'ᆬ',
'ㄶ' => 'ᆭ',
'ㄷ' => 'ᄃ',
'ㄸ' => 'ᄄ',
'ㄹ' => 'ᄅ',
'ㄺ' => 'ᆰ',
'ㄻ' => 'ᆱ',
'ㄼ' => 'ᆲ',
'ㄽ' => 'ᆳ',
'ㄾ' => 'ᆴ',
'ㄿ' => 'ᆵ',
'ã…€' => 'á„š',
'ã…<C3A3>' => 'ᄆ',
'ㅂ' => 'ᄇ',
'ㅃ' => 'ᄈ',
'ã…„' => 'á„¡',
'ㅅ' => 'ᄉ',
'ã…†' => 'á„Š',
'ã…‡' => 'á„‹',
'ㅈ' => 'ᄌ',
'ã…‰' => 'á„<C3A1>',
'ã…Š' => 'á„Ž',
'ã…‹' => 'á„<C3A1>',
'ã…Œ' => 'á„<C3A1>',
'ã…<C3A3>' => 'á„‘',
'ã…Ž' => 'á„’',
'ã…<C3A3>' => 'á…¡',
'ã…<C3A3>' => 'á…¢',
'ã…‘' => 'á…£',
'ã…’' => 'á…¤',
'ã…“' => 'á…¥',
'ã…”' => 'á…¦',
'ã…•' => 'á…§',
'ã…–' => 'á…¨',
'ã…—' => 'á…©',
'ã…˜' => 'á…ª',
'ã…™' => 'á…«',
'ã…š' => 'á…¬',
'ã…›' => 'á…­',
'ㅜ' => 'ᅮ',
'ã…<C3A3>' => 'á…¯',
'ã…ž' => 'á…°',
'ã…Ÿ' => 'á…±',
'ã… ' => 'á…²',
'ã…¡' => 'á…³',
'ã…¢' => 'á…´',
'ã…£' => 'á…µ',
'ã…¤' => 'á… ',
'ã…¥' => 'á„”',
'ã…¦' => 'á„•',
'ㅧ' => 'ᇇ',
'ㅨ' => 'ᇈ',
'ㅩ' => 'ᇌ',
'ㅪ' => 'ᇎ',
'ㅫ' => 'ᇓ',
'ㅬ' => 'ᇗ',
'ㅭ' => 'ᇙ',
'ㅮ' => 'ᄜ',
'ã…¯' => 'á‡<C3A1>',
'ㅰ' => 'ᇟ',
'ã…±' => 'á„<C3A1>',
'ã…²' => 'á„ž',
'ã…³' => 'á„ ',
'ã…´' => 'á„¢',
'ã…µ' => 'á„£',
'ㅶ' => 'ᄧ',
'ã…·' => 'á„©',
'ã…¸' => 'á„«',
'ㅹ' => 'ᄬ',
'ã…º' => 'á„­',
'ã…»' => 'á„®',
'ㅼ' => 'ᄯ',
'ㅽ' => 'ᄲ',
'ㅾ' => 'ᄶ',
'ã…¿' => 'á…€',
'ㆀ' => 'ᅇ',
'ã†<C3A3>' => 'á…Œ',
'ㆂ' => 'ᇱ',
'ㆃ' => 'ᇲ',
'ㆄ' => 'ᅗ',
'ㆅ' => 'ᅘ',
'ㆆ' => 'ᅙ',
'ㆇ' => 'ᆄ',
'ㆈ' => 'ᆅ',
'ㆉ' => 'ᆈ',
'ㆊ' => 'ᆑ',
'ㆋ' => 'ᆒ',
'ㆌ' => 'ᆔ',
'ã†<C3A3>' => 'ᆞ',
'ㆎ' => 'ᆡ',
'㆒' => '一',
'㆓' => '二',
'㆔' => '三',
'㆕' => '四',
'㆖' => '上',
'㆗' => '中',
'㆘' => '下',
'㆙' => '甲',
'㆚' => '乙',
'㆛' => '丙',
'㆜' => 'ä¸<C3A4>',
'ã†<C3A3>' => '天',
'㆞' => '地',
'㆟' => '人',
'㈀' => '(ᄀ)',
ˆ<C3A3>' => '(á„‚)',
'㈂' => '(ᄃ)',
'㈃' => '(ᄅ)',
'㈄' => '(ᄆ)',
'㈅' => '(ᄇ)',
'㈆' => '(ᄉ)',
'㈇' => '(ᄋ)',
'㈈' => '(ᄌ)',
'㈉' => '(ᄎ)',
'㈊' => '(á„<C3A1>)',
'㈋' => '(á„<C3A1>)',
'㈌' => '(ᄑ)',
ˆ<C3A3>' => '(á„’)',
'㈎' => '(가)',
ˆ<C3A3>' => '(á„‚á…¡)',
ˆ<C3A3>' => '(다)',
'㈑' => '(라)',
'㈒' => '(마)',
'㈓' => '(바)',
'㈔' => '(사)',
'㈕' => '(아)',
'㈖' => '(자)',
'㈗' => '(차)',
'㈘' => '(á„<C3A1>á…¡)',
'㈙' => '(á„<C3A1>á…¡)',
'㈚' => '(파)',
'㈛' => '(하)',
'㈜' => '(주)',
ˆ<C3A3>' => '(오전)',
'㈞' => '(오후)',
'㈠' => '(一)',
'㈡' => '(二)',
'㈢' => '(三)',
'㈣' => '(四)',
'㈤' => '(五)',
'㈥' => '(六)',
'㈦' => '(七)',
'㈧' => '(八)',
'㈨' => '(ä¹<C3A4>)',
'㈩' => '(å<><C3A5>)',
'㈪' => '(月)',
'㈫' => '(ç<>«)',
'㈬' => '(水)',
'㈭' => '(木)',
'㈮' => '(金)',
'㈯' => '(土)',
'㈰' => '(日)',
'㈱' => '(株)',
'㈲' => '(有)',
'㈳' => '(社)',
'㈴' => '(å<><C3A5>)',
'㈵' => '(特)',
'㈶' => '(財)',
'㈷' => '(ç¥<C3A7>)',
'㈸' => '(労)',
'㈹' => '(代)',
'㈺' => '(呼)',
'㈻' => '(学)',
'㈼' => '(監)',
'㈽' => '(ä¼<C3A4>)',
'㈾' => '(資)',
'㈿' => '(å<>”)',
'㉀' => '(祭)',
'ã‰<C3A3>' => '(休)',
'㉂' => '(自)',
'㉃' => '(至)',
'㉄' => 'å•<C3A5>',
'㉅' => '幼',
'㉆' => '文',
'㉇' => 'ç®<C3A7>',
'ã‰<C3A3>' => 'PTE',
'㉑' => '21',
'㉒' => '22',
'㉓' => '23',
'㉔' => '24',
'㉕' => '25',
'㉖' => '26',
'㉗' => '27',
'㉘' => '28',
'㉙' => '29',
'㉚' => '30',
'㉛' => '31',
'㉜' => '32',
'ã‰<C3A3>' => '33',
'㉞' => '34',
'㉟' => '35',
'㉠' => 'ᄀ',
'㉡' => 'ᄂ',
'㉢' => 'ᄃ',
'㉣' => 'ᄅ',
'㉤' => 'ᄆ',
'㉥' => 'ᄇ',
'㉦' => 'ᄉ',
'㉧' => 'ᄋ',
'㉨' => 'ᄌ',
'㉩' => 'ᄎ',
'㉪' => 'á„<C3A1>',
'㉫' => 'á„<C3A1>',
'㉬' => 'ᄑ',
'㉭' => 'ᄒ',
'㉮' => '가',
'㉯' => '나',
'㉰' => '다',
'㉱' => '라',
'㉲' => '마',
'㉳' => '바',
'㉴' => '사',
'㉵' => '아',
'㉶' => '자',
'㉷' => '차',
'㉸' => 'á„<C3A1>á…¡',
'㉹' => 'á„<C3A1>á…¡',
'㉺' => '파',
'㉻' => '하',
'㉼' => '참고',
'㉽' => '주의',
'㉾' => '우',
'㊀' => '一',
'ãŠ<C3A3>' => '二',
'㊂' => '三',
'㊃' => '四',
'㊄' => '五',
'㊅' => '六',
'㊆' => '七',
'㊇' => '八',
'㊈' => 'ä¹<C3A4>',
'㊉' => 'å<><C3A5>',
'㊊' => '月',
'㊋' => 'ç<>«',
'㊌' => '水',
'ãŠ<C3A3>' => '木',
'㊎' => '金',
'ãŠ<C3A3>' => '土',
'ãŠ<C3A3>' => 'æ—¥',
'㊑' => '株',
'㊒' => '有',
'㊓' => '社',
'㊔' => 'å<><C3A5>',
'㊕' => '特',
'㊖' => '財',
'㊗' => 'ç¥<C3A7>',
'㊘' => '労',
'㊙' => '秘',
'㊚' => '男',
'㊛' => '女',
'㊜' => 'é<>©',
'ãŠ<C3A3>' => '優',
'㊞' => 'å<>°',
'㊟' => '注',
'㊠' => '項',
'㊡' => '休',
'㊢' => '写',
'㊣' => '正',
'㊤' => '上',
'㊥' => '中',
'㊦' => '下',
'㊧' => '左',
'㊨' => 'å<>³',
'㊩' => '医',
'㊪' => '宗',
'㊫' => '学',
'㊬' => '監',
'㊭' => 'ä¼<C3A4>',
'㊮' => '資',
'㊯' => 'å<>”',
'㊰' => '夜',
'㊱' => '36',
'㊲' => '37',
'㊳' => '38',
'㊴' => '39',
'㊵' => '40',
'㊶' => '41',
'㊷' => '42',
'㊸' => '43',
'㊹' => '44',
'㊺' => '45',
'㊻' => '46',
'㊼' => '47',
'㊽' => '48',
'㊾' => '49',
'㊿' => '50',
'㋀' => '1月',
<C3A3>' => '2月',
'㋂' => '3月',
'㋃' => '4月',
'㋄' => '5月',
'㋅' => '6月',
'㋆' => '7月',
'㋇' => '8月',
'㋈' => '9月',
'㋉' => '10月',
'㋊' => '11月',
'㋋' => '12月',
'㋌' => 'Hg',
<C3A3>' => 'erg',
'ã‹Ž' => 'eV',
<C3A3>' => 'LTD',
<C3A3>' => 'ã‚¢',
'㋑' => 'イ',
'㋒' => 'ウ',
'㋓' => 'エ',
'㋔' => 'オ',
'ã‹•' => 'ã‚«',
'ã‹–' => 'ã‚­',
'㋗' => 'ク',
'㋘' => 'ケ',
'㋙' => 'コ',
'㋚' => 'サ',
'ã‹›' => 'ã‚·',
'㋜' => 'ス',
<C3A3>' => 'ã‚»',
'㋞' => 'ソ',
'ã‹Ÿ' => 'ã‚¿',
'ã‹ ' => 'ãƒ<C3A3>',
'㋡' => 'ツ',
'㋢' => 'テ',
'㋣' => 'ト',
'㋤' => 'ナ',
'㋥' => 'ニ',
'㋦' => 'ヌ',
'㋧' => 'ãƒ<C3A3>',
'㋨' => 'ノ',
'ã‹©' => 'ãƒ<C3A3>',
'㋪' => 'ヒ',
'㋫' => 'フ',
'㋬' => 'ヘ',
'㋭' => 'ホ',
'㋮' => 'マ',
'㋯' => 'ミ',
'㋰' => 'ム',
'㋱' => 'メ',
'㋲' => 'モ',
'㋳' => 'ヤ',
'㋴' => 'ユ',
'㋵' => 'ヨ',
'㋶' => 'ラ',
'㋷' => 'リ',
'㋸' => 'ル',
'㋹' => 'レ',
'㋺' => 'ロ',
'㋻' => 'ワ',
'㋼' => 'ヰ',
'㋽' => 'ヱ',
'㋾' => 'ヲ',
'㋿' => '令和',
'㌀' => 'ã¢ãƒ<C3A3>ãšãƒ¼ãƒˆ',
'ãŒ<C3A3>' => 'アルファ',
'㌂' => 'アンペア',
'㌃' => 'アール',
'㌄' => 'イニング',
'㌅' => 'ã¤ãƒ³ãƒ<C3A3>',
'㌆' => 'ウォン',
'㌇' => 'エスクード',
'㌈' => 'エーカー',
'㌉' => 'オンス',
'㌊' => 'オーム',
'㌋' => 'カイリ',
'㌌' => 'カラット',
'ãŒ<C3A3>' => 'カロリー',
'㌎' => 'ガロン',
'ãŒ<C3A3>' => 'ガンマ',
'ãŒ<C3A3>' => 'ギガ',
'㌑' => 'ギニー',
'㌒' => 'キュリー',
'㌓' => 'ギルダー',
'㌔' => 'キロ',
'㌕' => 'キログラム',
'㌖' => 'キロメートル',
'㌗' => 'キロワット',
'㌘' => 'グラム',
'㌙' => 'グラムトン',
'㌚' => 'クルゼイロ',
'㌛' => 'ã¯ãƒ­ãƒ¼ãƒ<C3A3>',
'㌜' => 'ケース',
'ãŒ<C3A3>' => 'コルナ',
'㌞' => 'コーポ',
'㌟' => 'サイクル',
'㌠' => 'ãµãƒ³ãƒ<C3A3>ーム',
'㌡' => 'シリング',
'㌢' => 'ã»ãƒ³ãƒ<C3A3>',
'㌣' => 'セント',
'㌤' => 'ダース',
'㌥' => 'デシ',
'㌦' => 'ドル',
'㌧' => 'トン',
'㌨' => 'ナノ',
'㌩' => 'ノット',
'㌪' => 'ãƒ<C3A3>ã¤ãƒ„',
'㌫' => 'ãƒ<C3A3>ãšãƒ¼ã»ãƒ³ãƒˆ',
'㌬' => 'ãƒ<C3A3>ãšãƒ¼ãƒ„',
'㌭' => 'ãƒ<C3A3>ã™ãƒ¼ãƒ¬ãƒ«',
'㌮' => 'ピアストル',
'㌯' => 'ピクル',
'㌰' => 'ピコ',
'㌱' => 'ビル',
'㌲' => 'ファラッド',
'㌳' => 'フィート',
'㌴' => 'ブッシェル',
'㌵' => 'フラン',
'㌶' => 'ヘクタール',
'㌷' => 'ペソ',
'㌸' => 'ペニヒ',
'㌹' => 'ヘルツ',
'㌺' => 'ペンス',
'㌻' => 'ページ',
'㌼' => 'ベータ',
'㌽' => 'ポイント',
'㌾' => 'ボルト',
'㌿' => 'ホン',
<>€' => 'ポンド',
<><C3A3>' => 'ホール',
<>' => 'ホーン',
<>ƒ' => 'マイクロ',
<>„' => 'マイル',
<>…' => 'マッãƒ<C3A3>',
<>†' => 'マルク',
<>‡' => 'マンション',
<>ˆ' => 'ミクロン',
<>‰' => 'ミリ',
<>Š' => 'ミリãƒ<C3A3>ã™ãƒ¼ãƒ«',
<>' => 'メガ',
<>Œ' => 'メガトン',
<><C3A3>' => 'メートル',
<>Ž' => 'ヤード',
<><C3A3>' => 'ヤール',
<><C3A3>' => 'ユアン',
<>' => 'リットル',
<>' => 'リラ',
<>“' => 'ルピー',
<>”' => 'ルーブル',
<>•' => 'レム',
<>' => 'レントゲン',
<>—' => 'ワット',
<>˜' => '0ç¹',
<>™' => '1ç¹',
<>š' => '2ç¹',
<>' => '3ç¹',
<>œ' => '4ç¹',
<><C3A3>' => '5ç¹',
<>ž' => '6ç¹',
<>Ÿ' => '7ç¹',
<> ' => '8ç¹',
<>¡' => '9ç¹',
<>¢' => '10ç¹',
<>£' => '11ç¹',
<>¤' => '12ç¹',
<>¥' => '13ç¹',
<>¦' => '14ç¹',
<>§' => '15ç¹',
<>¨' => '16ç¹',
<>©' => '17ç¹',
<>ª' => '18ç¹',
<>«' => '19ç¹',
<>¬' => '20ç¹',
<>­' => '21ç¹',
<>®' => '22ç¹',
<>¯' => '23ç¹',
<>°' => '24ç¹',
<>±' => 'hPa',
<>²' => 'da',
<>³' => 'AU',
<>´' => 'bar',
<>µ' => 'oV',
<>¶' => 'pc',
<>·' => 'dm',
<>¸' => 'dm2',
<>¹' => 'dm3',
<>º' => 'IU',
<>»' => 'å¹³æˆ<C3A6>',
<>¼' => '昭和',
<>½' => '大正',
<>¾' => '明治',
<>¿' => 'æ ªå¼<C3A5>会社',
'㎀' => 'pA',
'ãŽ<C3A3>' => 'nA',
'㎂' => 'μA',
'㎃' => 'mA',
'㎄' => 'kA',
'㎅' => 'KB',
'㎆' => 'MB',
'㎇' => 'GB',
'㎈' => 'cal',
'㎉' => 'kcal',
'㎊' => 'pF',
'㎋' => 'nF',
'㎌' => 'μF',
'ãŽ<C3A3>' => 'μg',
'㎎' => 'mg',
'ãŽ<C3A3>' => 'kg',
'ãŽ<C3A3>' => 'Hz',
'㎑' => 'kHz',
'㎒' => 'MHz',
'㎓' => 'GHz',
'㎔' => 'THz',
'㎕' => 'μl',
'㎖' => 'ml',
'㎗' => 'dl',
'㎘' => 'kl',
'㎙' => 'fm',
'㎚' => 'nm',
'㎛' => 'μm',
'㎜' => 'mm',
'ãŽ<C3A3>' => 'cm',
'㎞' => 'km',
'㎟' => 'mm2',
'㎠' => 'cm2',
'㎡' => 'm2',
'㎢' => 'km2',
'㎣' => 'mm3',
'㎤' => 'cm3',
'㎥' => 'm3',
'㎦' => 'km3',
'㎧' => 'm∕s',
'㎨' => 'm∕s2',
'㎩' => 'Pa',
'㎪' => 'kPa',
'㎫' => 'MPa',
'㎬' => 'GPa',
'㎭' => 'rad',
'㎮' => 'rad∕s',
'㎯' => 'rad∕s2',
'㎰' => 'ps',
'㎱' => 'ns',
'㎲' => 'μs',
'㎳' => 'ms',
'㎴' => 'pV',
'㎵' => 'nV',
'㎶' => 'μV',
'㎷' => 'mV',
'㎸' => 'kV',
'㎹' => 'MV',
'㎺' => 'pW',
'㎻' => 'nW',
'㎼' => 'μW',
'㎽' => 'mW',
'㎾' => 'kW',
'㎿' => 'MW',
<>€' => 'kΩ',
<><C3A3>' => 'MΩ',
<>' => 'a.m.',
<>ƒ' => 'Bq',
<>„' => 'cc',
<>…' => 'cd',
<>†' => 'C∕kg',
<>‡' => 'Co.',
<>ˆ' => 'dB',
<>‰' => 'Gy',
<>Š' => 'ha',
<>' => 'HP',
<>Œ' => 'in',
<><C3A3>' => 'KK',
<>Ž' => 'KM',
<><C3A3>' => 'kt',
<><C3A3>' => 'lm',
<>' => 'ln',
<>' => 'log',
<>“' => 'lx',
<>”' => 'mb',
<>•' => 'mil',
<>' => 'mol',
<>—' => 'PH',
<>˜' => 'p.m.',
<>™' => 'PPM',
<>š' => 'PR',
<>' => 'sr',
<>œ' => 'Sv',
<><C3A3>' => 'Wb',
<>ž' => 'V∕m',
<>Ÿ' => 'A∕m',
<> ' => '1æ—¥',
<>¡' => '2æ—¥',
<>¢' => '3æ—¥',
<>£' => '4æ—¥',
<>¤' => '5æ—¥',
<>¥' => '6æ—¥',
<>¦' => '7æ—¥',
<>§' => '8æ—¥',
<>¨' => '9æ—¥',
<>©' => '10æ—¥',
<>ª' => '11æ—¥',
<>«' => '12æ—¥',
<>¬' => '13æ—¥',
<>­' => '14æ—¥',
<>®' => '15æ—¥',
<>¯' => '16æ—¥',
<>°' => '17æ—¥',
<>±' => '18æ—¥',
<>²' => '19æ—¥',
<>³' => '20æ—¥',
<>´' => '21æ—¥',
<>µ' => '22æ—¥',
<>¶' => '23æ—¥',
<>·' => '24æ—¥',
<>¸' => '25æ—¥',
<>¹' => '26æ—¥',
<>º' => '27æ—¥',
<>»' => '28æ—¥',
<>¼' => '29æ—¥',
<>½' => '30æ—¥',
<>¾' => '31æ—¥',
<>¿' => 'gal',
'ꚜ' => 'ъ',
'êš<C3AA>' => 'ÑŒ',
<>°' => 'ê<>¯',
'ꟸ' => 'Ħ',
'ꟹ' => 'œ',
'ꭜ' => 'ꜧ',
­<C3AA>' => 'ꬷ',
'ê­ž' => 'É«',
'ê­Ÿ' => 'ê­’',
'ê­©' => 'Ê<>',
'ff' => 'ff',
'ï¬<C3AF>' => 'fi',
'fl' => 'fl',
'ffi' => 'ffi',
'ffl' => 'ffl',
'ſt' => 'st',
'st' => 'st',
'ﬓ' => 'մն',
'ﬔ' => 'մե',
'ﬕ' => 'մի',
'ﬖ' => 'վն',
'ﬗ' => 'մխ',
'ﬠ' => 'ע',
'ﬡ' => '×<>',
'ﬢ' => 'ד',
'ﬣ' => 'ה',
'ﬤ' => '×',
'ﬥ' => 'ל',
'ﬦ' => '×<>',
'ﬧ' => 'ר',
'ﬨ' => 'ת',
'﬩' => '+',
­<C3AF>' => '×<>ל',
­<C3AF>' => 'Ù±',
'ï­‘' => 'Ù±',
'ï­’' => 'Ù»',
'ï­“' => 'Ù»',
'ï­”' => 'Ù»',
'ï­•' => 'Ù»',
'ï­–' => 'Ù¾',
'ï­—' => 'Ù¾',
'ï­˜' => 'Ù¾',
'ï­™' => 'Ù¾',
'ï­š' => 'Ú€',
'ï­›' => 'Ú€',
'ﭜ' => 'ڀ',
­<C3AF>' => 'Ú€',
'ï­ž' => 'Ùº',
'ï­Ÿ' => 'Ùº',
'ï­ ' => 'Ùº',
'ï­¡' => 'Ùº',
'ï­¢' => 'Ù¿',
'ï­£' => 'Ù¿',
'ï­¤' => 'Ù¿',
'ï­¥' => 'Ù¿',
'ï­¦' => 'Ù¹',
'ï­§' => 'Ù¹',
'ï­¨' => 'Ù¹',
'ï­©' => 'Ù¹',
'ï­ª' => 'Ú¤',
'ï­«' => 'Ú¤',
'ï­¬' => 'Ú¤',
'ï­­' => 'Ú¤',
'ï­®' => 'Ú¦',
'ï­¯' => 'Ú¦',
'ï­°' => 'Ú¦',
'ï­±' => 'Ú¦',
'ï­²' => 'Ú„',
'ï­³' => 'Ú„',
'ï­´' => 'Ú„',
'ï­µ' => 'Ú„',
'ï­¶' => 'Úƒ',
'ï­·' => 'Úƒ',
'ï­¸' => 'Úƒ',
'ï­¹' => 'Úƒ',
'ï­º' => 'Ú†',
'ï­»' => 'Ú†',
'ï­¼' => 'Ú†',
'ï­½' => 'Ú†',
'ï­¾' => 'Ú‡',
'ï­¿' => 'Ú‡',
'ﮀ' => 'ڇ',
'ï®<C3AF>' => 'Ú‡',
'ﮂ' => 'Ú<>',
'ﮃ' => 'Ú<>',
'ﮄ' => 'ڌ',
'ﮅ' => 'ڌ',
'ﮆ' => 'ڎ',
'ﮇ' => 'ڎ',
'ﮈ' => 'ڈ',
'ﮉ' => 'ڈ',
'ﮊ' => 'ژ',
'ﮋ' => 'ژ',
'ﮌ' => 'ڑ',
'ï®<C3AF>' => 'Ú‘',
'ﮎ' => 'ک',
'ï®<C3AF>' => 'Ú©',
'ï®<C3AF>' => 'Ú©',
'ﮑ' => 'ک',
'ï®’' => 'Ú¯',
'ﮓ' => 'گ',
'ï®”' => 'Ú¯',
'ﮕ' => 'گ',
'ï®–' => 'Ú³',
'ï®—' => 'Ú³',
'ﮘ' => 'ڳ',
'ï®™' => 'Ú³',
'ﮚ' => 'ڱ',
'ï®›' => 'Ú±',
'ﮜ' => 'ڱ',
'ï®<C3AF>' => 'Ú±',
'ﮞ' => 'ں',
'ﮟ' => 'ں',
'ï® ' => 'Ú»',
'ﮡ' => 'ڻ',
'ﮢ' => 'ڻ',
'ﮣ' => 'ڻ',
'ﮤ' => 'ۀ',
'ﮥ' => 'ۀ',
'ﮦ' => 'Û<>',
'ﮧ' => 'Û<>',
'ﮨ' => 'Û<>',
'ﮩ' => 'Û<>',
'ﮪ' => 'ھ',
'ﮫ' => 'ھ',
'ﮬ' => 'ھ',
'ï®­' => 'Ú¾',
'ï®®' => 'Û’',
'ﮯ' => 'ے',
'ï®°' => 'Û’Ù”',
'ï®±' => 'Û’Ù”',
'ﯓ' => 'ڭ',
'ﯔ' => 'ڭ',
'ﯕ' => 'ڭ',
'ﯖ' => 'ڭ',
'ﯗ' => 'ۇ',
'ﯘ' => 'ۇ',
'ﯙ' => 'ۆ',
'ﯚ' => 'ۆ',
'ﯛ' => 'ۈ',
'ﯜ' => 'ۈ',
'ï¯<C3AF>' => 'Û‡Ù´',
'ﯞ' => 'ۋ',
'ﯟ' => 'ۋ',
'ﯠ' => 'ۅ',
'ﯡ' => 'ۅ',
'ﯢ' => 'ۉ',
'ﯣ' => 'ۉ',
'ﯤ' => 'Û<>',
'ﯥ' => 'Û<>',
'ﯦ' => 'Û<>',
'ﯧ' => 'Û<>',
'ﯨ' => 'ى',
'ﯩ' => 'ى',
'ﯪ' => 'ئا',
'ﯫ' => 'ئا',
'ﯬ' => 'ئە',
'ﯭ' => 'ئە',
'ﯮ' => 'ئو',
'ﯯ' => 'ئو',
'ﯰ' => 'ئۇ',
'ﯱ' => 'ئۇ',
'ﯲ' => 'ئۆ',
'ﯳ' => 'ئۆ',
'ﯴ' => 'ئۈ',
'ﯵ' => 'ئۈ',
'ﯶ' => 'ÙŠÙ”Û<E2809D>',
'ﯷ' => 'ÙŠÙ”Û<E2809D>',
'ﯸ' => 'ÙŠÙ”Û<E2809D>',
'ﯹ' => 'ئى',
'ﯺ' => 'ئى',
'ﯻ' => 'ئى',
'ﯼ' => 'ی',
'ﯽ' => 'ی',
'ﯾ' => 'ی',
'ﯿ' => 'ی',
'ﰀ' => 'ئج',
'ï°<C3AF>' => 'ئح',
'ï°‚' => 'ÙŠÙ”Ù…',
'ï°ƒ' => 'ÙŠÙ”Ù‰',
'ï°„' => 'ÙŠÙ”ÙŠ',
'ﰅ' => 'بج',
'ﰆ' => 'بح',
'ﰇ' => 'بخ',
'ﰈ' => 'بم',
'ﰉ' => 'بى',
'ﰊ' => 'بي',
'ﰋ' => 'تج',
'ﰌ' => 'تح',
'ï°<C3AF>' => 'تخ',
'ﰎ' => 'تم',
'ï°<C3AF>' => 'تى',
'ï°<C3AF>' => 'تي',
'ﰑ' => 'ثج',
'ﰒ' => 'ثم',
'ﰓ' => 'ثى',
'ﰔ' => 'ثي',
'ﰕ' => 'جح',
'ﰖ' => 'جم',
'ﰗ' => 'حج',
'ﰘ' => 'حم',
'ﰙ' => 'خج',
'ﰚ' => 'خح',
'ﰛ' => 'خم',
'ﰜ' => 'سج',
'ï°<C3AF>' => 'سح',
'ﰞ' => 'سخ',
'ﰟ' => 'سم',
'ﰠ' => 'صح',
'ﰡ' => 'صم',
'ﰢ' => 'ضج',
'ﰣ' => 'ضح',
'ﰤ' => 'ضخ',
'ﰥ' => 'ضم',
'ﰦ' => 'طح',
'ﰧ' => 'طم',
'ﰨ' => 'ظم',
'ﰩ' => 'عج',
'ﰪ' => 'عم',
'ﰫ' => 'غج',
'ﰬ' => 'غم',
'ï°­' => 'Ù<>ج',
'ï°®' => 'Ù<>Ø­',
'ï°¯' => 'Ù<>Ø®',
'ï°°' => 'Ù<>Ù…',
'ï°±' => 'Ù<>Ù‰',
'ï°²' => 'Ù<>ÙŠ',
'ﰳ' => 'قح',
'ﰴ' => 'قم',
'ﰵ' => 'قى',
'ﰶ' => 'قي',
'ﰷ' => 'كا',
'ﰸ' => 'كج',
'ﰹ' => 'كح',
'ﰺ' => 'كخ',
'ﰻ' => 'كل',
'ﰼ' => 'كم',
'ﰽ' => 'كى',
'ﰾ' => 'كي',
'ﰿ' => 'لج',
'ﱀ' => 'لح',
'ï±<C3AF>' => 'لخ',
'ﱂ' => 'لم',
'ﱃ' => 'لى',
'ﱄ' => 'لي',
'ﱅ' => 'مج',
'ﱆ' => 'مح',
'ﱇ' => 'مخ',
'ﱈ' => 'مم',
'ﱉ' => 'مى',
'ﱊ' => 'مي',
'ﱋ' => 'نج',
'ﱌ' => 'نح',
'ï±<C3AF>' => 'نخ',
'ﱎ' => 'نم',
'ï±<C3AF>' => 'نى',
'ï±<C3AF>' => 'ني',
'ﱑ' => 'هج',
'ﱒ' => 'هم',
'ﱓ' => 'هى',
'ﱔ' => 'هي',
'ﱕ' => 'يج',
'ﱖ' => 'يح',
'ﱗ' => 'يخ',
'ﱘ' => 'يم',
'ﱙ' => 'يى',
'ﱚ' => 'يي',
'ﱛ' => 'ذٰ',
'ﱜ' => 'رٰ',
'ï±<C3AF>' => 'ىٰ',
'ﱞ' => ' ٌّ',
'ﱟ' => ' Ù<>Ù',
'ï± ' => ' ÙŽÙ‘',
'ﱡ' => ' Ù<>Ù',
'ï±¢' => ' Ù<>Ù',
'ﱣ' => ' ّٰ',
'ﱤ' => 'ئر',
'ﱥ' => 'ئز',
'ﱦ' => 'ئم',
'ﱧ' => 'ئن',
'ﱨ' => 'ئى',
'ﱩ' => 'ئي',
'ﱪ' => 'بر',
'ﱫ' => 'بز',
'ﱬ' => 'بم',
'ﱭ' => 'بن',
'ﱮ' => 'بى',
'ﱯ' => 'بي',
'ﱰ' => 'تر',
'ﱱ' => 'تز',
'ﱲ' => 'تم',
'ﱳ' => 'تن',
'ﱴ' => 'تى',
'ﱵ' => 'تي',
'ﱶ' => 'ثر',
'ﱷ' => 'ثز',
'ﱸ' => 'ثم',
'ﱹ' => 'ثن',
'ﱺ' => 'ثى',
'ﱻ' => 'ثي',
'ï±¼' => 'Ù<>Ù‰',
'ï±½' => 'Ù<>ÙŠ',
'ﱾ' => 'قى',
'ﱿ' => 'قي',
'ﲀ' => 'كا',
'ï²<C3AF>' => 'كل',
'ﲂ' => 'كم',
'ﲃ' => 'كى',
'ﲄ' => 'كي',
'ﲅ' => 'لم',
'ﲆ' => 'لى',
'ﲇ' => 'لي',
'ﲈ' => 'ما',
'ﲉ' => 'مم',
'ﲊ' => 'نر',
'ﲋ' => 'نز',
'ﲌ' => 'نم',
'ï²<C3AF>' => 'نن',
'ﲎ' => 'نى',
'ï²<C3AF>' => 'ني',
'ï²<C3AF>' => 'ىٰ',
'ﲑ' => 'ير',
'ﲒ' => 'يز',
'ﲓ' => 'يم',
'ﲔ' => 'ين',
'ﲕ' => 'يى',
'ﲖ' => 'يي',
'ﲗ' => 'ئج',
'ﲘ' => 'ئح',
'ﲙ' => 'ئخ',
'ﲚ' => 'ئم',
'ï²›' => 'ÙŠÙ”Ù‡',
'ﲜ' => 'بج',
'ï²<C3AF>' => 'بح',
'ﲞ' => 'بخ',
'ﲟ' => 'بم',
'ﲠ' => 'به',
'ﲡ' => 'تج',
'ﲢ' => 'تح',
'ﲣ' => 'تخ',
'ﲤ' => 'تم',
'ﲥ' => 'ته',
'ﲦ' => 'ثم',
'ﲧ' => 'جح',
'ﲨ' => 'جم',
'ﲩ' => 'حج',
'ﲪ' => 'حم',
'ﲫ' => 'خج',
'ﲬ' => 'خم',
'ﲭ' => 'سج',
'ﲮ' => 'سح',
'ﲯ' => 'سخ',
'ﲰ' => 'سم',
'ﲱ' => 'صح',
'ﲲ' => 'صخ',
'ﲳ' => 'صم',
'ﲴ' => 'ضج',
'ﲵ' => 'ضح',
'ﲶ' => 'ضخ',
'ﲷ' => 'ضم',
'ﲸ' => 'طح',
'ﲹ' => 'ظم',
'ﲺ' => 'عج',
'ﲻ' => 'عم',
'ﲼ' => 'غج',
'ﲽ' => 'غم',
'ï²¾' => 'Ù<>ج',
'ﲿ' => 'Ù<>Ø­',
'ï³€' => 'Ù<>Ø®',
'ï³<C3AF>' => 'Ù<>Ù…',
'ﳂ' => 'قح',
'ﳃ' => 'قم',
'ﳄ' => 'كج',
'ﳅ' => 'كح',
'ﳆ' => 'كخ',
'ﳇ' => 'كل',
'ﳈ' => 'كم',
'ﳉ' => 'لج',
'ﳊ' => 'لح',
'ﳋ' => 'لخ',
'ﳌ' => 'لم',
'ï³<C3AF>' => 'له',
'ﳎ' => 'مج',
'ï³<C3AF>' => 'مح',
'ï³<C3AF>' => 'مخ',
'ﳑ' => 'مم',
'ﳒ' => 'نج',
'ﳓ' => 'نح',
'ﳔ' => 'نخ',
'ﳕ' => 'نم',
'ﳖ' => 'نه',
'ﳗ' => 'هج',
'ﳘ' => 'هم',
'ﳙ' => 'هٰ',
'ﳚ' => 'يج',
'ﳛ' => 'يح',
'ﳜ' => 'يخ',
'ï³<C3AF>' => 'يم',
'ﳞ' => 'يه',
'ﳟ' => 'ئم',
'ï³ ' => 'ÙŠÙ”Ù‡',
'ﳡ' => 'بم',
'ﳢ' => 'به',
'ﳣ' => 'تم',
'ﳤ' => 'ته',
'ﳥ' => 'ثم',
'ﳦ' => 'ثه',
'ﳧ' => 'سم',
'ﳨ' => 'سه',
'ﳩ' => 'شم',
'ﳪ' => 'شه',
'ﳫ' => 'كل',
'ﳬ' => 'كم',
'ﳭ' => 'لم',
'ﳮ' => 'نم',
'ﳯ' => 'نه',
'ﳰ' => 'يم',
'ﳱ' => 'يه',
'ï³²' => 'Ù€ÙŽÙ‘',
'ï³³' => 'Ù€Ù<E282AC>Ù',
'ï³´' => 'Ù€Ù<E282AC>Ù',
'ﳵ' => 'طى',
'ﳶ' => 'طي',
'ﳷ' => 'عى',
'ﳸ' => 'عي',
'ﳹ' => 'غى',
'ﳺ' => 'غي',
'ﳻ' => 'سى',
'ﳼ' => 'سي',
'ﳽ' => 'شى',
'ﳾ' => 'شي',
'ﳿ' => 'حى',
'ﴀ' => 'حي',
´<C3AF>' => 'جى',
'ﴂ' => 'جي',
'ﴃ' => 'خى',
'ﴄ' => 'خي',
'ﴅ' => 'صى',
'ﴆ' => 'صي',
'ﴇ' => 'ضى',
'ﴈ' => 'ضي',
'ﴉ' => 'شج',
'ﴊ' => 'شح',
'ﴋ' => 'شخ',
'ﴌ' => 'شم',
´<C3AF>' => 'شر',
'ﴎ' => 'سر',
´<C3AF>' => 'صر',
´<C3AF>' => 'ضر',
'ﴑ' => 'طى',
'ﴒ' => 'طي',
'ﴓ' => 'عى',
'ﴔ' => 'عي',
'ﴕ' => 'غى',
'ﴖ' => 'غي',
'ﴗ' => 'سى',
'ﴘ' => 'سي',
'ﴙ' => 'شى',
'ﴚ' => 'شي',
'ﴛ' => 'حى',
'ﴜ' => 'حي',
´<C3AF>' => 'جى',
'ﴞ' => 'جي',
'ﴟ' => 'خى',
'ﴠ' => 'خي',
'ﴡ' => 'صى',
'ﴢ' => 'صي',
'ﴣ' => 'ضى',
'ﴤ' => 'ضي',
'ﴥ' => 'شج',
'ﴦ' => 'شح',
'ﴧ' => 'شخ',
'ﴨ' => 'شم',
'ﴩ' => 'شر',
'ﴪ' => 'سر',
'ﴫ' => 'صر',
'ﴬ' => 'ضر',
'ﴭ' => 'شج',
'ﴮ' => 'شح',
'ﴯ' => 'شخ',
'ﴰ' => 'شم',
'ﴱ' => 'سه',
'ﴲ' => 'شه',
'ﴳ' => 'طم',
'ﴴ' => 'سج',
'ﴵ' => 'سح',
'ﴶ' => 'سخ',
'ﴷ' => 'شج',
'ﴸ' => 'شح',
'ﴹ' => 'شخ',
'ﴺ' => 'طم',
'ﴻ' => 'ظم',
'ﴼ' => 'اً',
'ﴽ' => 'اً',
'ïµ<C3AF>' => 'تجم',
'ﵑ' => 'تحج',
'ﵒ' => 'تحج',
'ﵓ' => 'تحم',
'ﵔ' => 'تخم',
'ﵕ' => 'تمج',
'ﵖ' => 'تمح',
'ﵗ' => 'تمخ',
'ﵘ' => 'جمح',
'ﵙ' => 'جمح',
'ﵚ' => 'حمي',
'ﵛ' => 'حمى',
'ﵜ' => 'سحج',
'ïµ<C3AF>' => 'سجح',
'ﵞ' => 'سجى',
'ﵟ' => 'سمح',
'ﵠ' => 'سمح',
'ﵡ' => 'سمج',
'ﵢ' => 'سمم',
'ﵣ' => 'سمم',
'ﵤ' => 'صحح',
'ﵥ' => 'صحح',
'ﵦ' => 'صمم',
'ﵧ' => 'شحم',
'ﵨ' => 'شحم',
'ﵩ' => 'شجي',
'ﵪ' => 'شمخ',
'ﵫ' => 'شمخ',
'ﵬ' => 'شمم',
'ﵭ' => 'شمم',
'ﵮ' => 'ضحى',
'ﵯ' => 'ضخم',
'ﵰ' => 'ضخم',
'ﵱ' => 'طمح',
'ﵲ' => 'طمح',
'ﵳ' => 'طمم',
'ﵴ' => 'طمي',
'ﵵ' => 'عجم',
'ﵶ' => 'عمم',
'ﵷ' => 'عمم',
'ﵸ' => 'عمى',
'ﵹ' => 'غمم',
'ﵺ' => 'غمي',
'ﵻ' => 'غمى',
'ïµ¼' => 'Ù<>خم',
'ïµ½' => 'Ù<>خم',
'ﵾ' => 'قمح',
'ﵿ' => 'قمم',
'ﶀ' => 'لحم',
'ï¶<C3AF>' => 'لحي',
'ﶂ' => 'لحى',
'ﶃ' => 'لجج',
'ﶄ' => 'لجج',
'ﶅ' => 'لخم',
'ﶆ' => 'لخم',
'ﶇ' => 'لمح',
'ﶈ' => 'لمح',
'ﶉ' => 'محج',
'ﶊ' => 'محم',
'ﶋ' => 'محي',
'ﶌ' => 'مجح',
'ï¶<C3AF>' => 'مجم',
'ﶎ' => 'مخج',
'ï¶<C3AF>' => 'مخم',
'ﶒ' => 'مجخ',
'ﶓ' => 'همج',
'ﶔ' => 'همم',
'ﶕ' => 'نحم',
'ﶖ' => 'نحى',
'ﶗ' => 'نجم',
'ﶘ' => 'نجم',
'ﶙ' => 'نجى',
'ﶚ' => 'نمي',
'ﶛ' => 'نمى',
'ﶜ' => 'يمم',
'ï¶<C3AF>' => 'يمم',
'ﶞ' => 'بخي',
'ﶟ' => 'تجي',
'ﶠ' => 'تجى',
'ﶡ' => 'تخي',
'ﶢ' => 'تخى',
'ﶣ' => 'تمي',
'ﶤ' => 'تمى',
'ﶥ' => 'جمي',
'ﶦ' => 'جحى',
'ﶧ' => 'جمى',
'ﶨ' => 'سخى',
'ﶩ' => 'صحي',
'ﶪ' => 'شحي',
'ﶫ' => 'ضحي',
'ﶬ' => 'لجي',
'ﶭ' => 'لمي',
'ﶮ' => 'يحي',
'ﶯ' => 'يجي',
'ﶰ' => 'يمي',
'ﶱ' => 'ممي',
'ﶲ' => 'قمي',
'ﶳ' => 'نحي',
'ﶴ' => 'قمح',
'ﶵ' => 'لحم',
'ﶶ' => 'عمي',
'ﶷ' => 'كمي',
'ﶸ' => 'نجح',
'ﶹ' => 'مخي',
'ﶺ' => 'لجم',
'ﶻ' => 'كمم',
'ﶼ' => 'لجم',
'ﶽ' => 'نجح',
'ﶾ' => 'جحي',
'ﶿ' => 'حجي',
'ﷀ' => 'مجي',
'ï·<C3AF>' => 'Ù<>مي',
'ﷂ' => 'بحي',
'ﷃ' => 'كمم',
'ﷄ' => 'عجم',
'ﷅ' => 'صمم',
'ﷆ' => 'سخي',
'ﷇ' => 'نجي',
'ﷰ' => 'صلے',
'ﷱ' => 'قلے',
'ﷲ' => 'الله',
'ﷳ' => 'اكبر',
'ﷴ' => 'محمد',
'ﷵ' => 'صلعم',
'ﷶ' => 'رسول',
'ﷷ' => 'عليه',
'ﷸ' => 'وسلم',
'ﷹ' => 'صلى',
'ﷺ' => 'صلى الله عليه وسلم',
'ﷻ' => 'جل جلاله',
'﷼' => 'ریال',
¸<C3AF>' => ',',
'︑' => 'ã€<C3A3>',
'︒' => '。',
'︓' => ':',
'︔' => ';',
'︕' => '!',
'︖' => '?',
'︗' => '〖',
'︘' => '〗',
'︙' => '...',
'︰' => '..',
'︱' => '—',
'︲' => '–',
'︳' => '_',
'︴' => '_',
'︵' => '(',
'︶' => ')',
'︷' => '{',
'︸' => '}',
'︹' => '〔',
'︺' => '〕',
'︻' => 'ã€<C3A3>',
'︼' => '】',
'︽' => '《',
'︾' => '》',
'︿' => '〈',
'﹀' => '〉',
'ï¹<C3AF>' => '「',
'﹂' => 'ã€<C3A3>',
'﹃' => '『',
'﹄' => 'ã€<C3A3>',
'﹇' => '[',
'﹈' => ']',
'﹉' => ' ̅',
'﹊' => ' ̅',
'﹋' => ' ̅',
'﹌' => ' ̅',
'ï¹<C3AF>' => '_',
'﹎' => '_',
'ï¹<C3AF>' => '_',
'ï¹<C3AF>' => ',',
'﹑' => 'ã€<C3A3>',
'ï¹’' => '.',
'ï¹”' => ';',
'﹕' => ':',
'ï¹–' => '?',
'ï¹—' => '!',
'﹘' => '—',
'ï¹™' => '(',
'﹚' => ')',
'ï¹›' => '{',
'﹜' => '}',
'ï¹<C3AF>' => '〔',
'﹞' => '〕',
'﹟' => '#',
'ï¹ ' => '&',
'﹡' => '*',
'ï¹¢' => '+',
'ï¹£' => '-',
'﹤' => '<',
'ï¹¥' => '>',
'﹦' => '=',
'﹨' => '\\',
'﹩' => '$',
'﹪' => '%',
'﹫' => '@',
'ï¹°' => ' Ù‹',
'ﹱ' => 'ـً',
'ﹲ' => ' ٌ',
'ï¹´' => ' Ù<>',
'ﹶ' => ' َ',
'ï¹·' => 'Ù€ÙŽ',
'ﹸ' => ' Ù<>',
'ï¹¹' => 'Ù€Ù<E282AC>',
'ﹺ' => ' Ù<>',
'ï¹»' => 'Ù€Ù<E282AC>',
'ï¹¼' => ' Ù‘',
'ﹽ' => 'ـّ',
'ï¹¾' => ' Ù’',
'ﹿ' => 'ـْ',
'ﺀ' => 'ء',
'ïº<C3AF>' => 'آ',
'ﺂ' => 'آ',
'ﺃ' => 'أ',
'ﺄ' => 'أ',
'ﺅ' => 'ؤ',
'ﺆ' => 'ؤ',
'ﺇ' => 'إ',
'ﺈ' => 'إ',
'ﺉ' => 'ئ',
'ﺊ' => 'ئ',
'ﺋ' => 'ئ',
'ﺌ' => 'ئ',
'ïº<C3AF>' => 'ا',
'ﺎ' => 'ا',
'ïº<C3AF>' => 'ب',
'ïº<C3AF>' => 'ب',
'ﺑ' => 'ب',
'ﺒ' => 'ب',
'ﺓ' => 'ة',
'ﺔ' => 'ة',
'ﺕ' => 'ت',
'ﺖ' => 'ت',
'ﺗ' => 'ت',
'ﺘ' => 'ت',
'ﺙ' => 'ث',
'ﺚ' => 'ث',
'ﺛ' => 'ث',
'ﺜ' => 'ث',
'ïº<C3AF>' => 'ج',
'ﺞ' => 'ج',
'ﺟ' => 'ج',
'ﺠ' => 'ج',
'ﺡ' => 'ح',
'ﺢ' => 'ح',
'ﺣ' => 'ح',
'ﺤ' => 'ح',
'ﺥ' => 'خ',
'ﺦ' => 'خ',
'ﺧ' => 'خ',
'ﺨ' => 'خ',
'ﺩ' => 'د',
'ﺪ' => 'د',
'ﺫ' => 'ذ',
'ﺬ' => 'ذ',
'ﺭ' => 'ر',
'ﺮ' => 'ر',
'ﺯ' => 'ز',
'ﺰ' => 'ز',
'ﺱ' => 'س',
'ﺲ' => 'س',
'ﺳ' => 'س',
'ﺴ' => 'س',
'ﺵ' => 'ش',
'ﺶ' => 'ش',
'ﺷ' => 'ش',
'ﺸ' => 'ش',
'ﺹ' => 'ص',
'ﺺ' => 'ص',
'ﺻ' => 'ص',
'ﺼ' => 'ص',
'ﺽ' => 'ض',
'ﺾ' => 'ض',
'ﺿ' => 'ض',
'ﻀ' => 'ض',
'ï»<C3AF>' => 'Ø·',
'ﻂ' => 'ط',
'ﻃ' => 'ط',
'ﻄ' => 'ط',
'ﻅ' => 'ظ',
'ﻆ' => 'ظ',
'ﻇ' => 'ظ',
'ﻈ' => 'ظ',
'ﻉ' => 'ع',
'ﻊ' => 'ع',
'ﻋ' => 'ع',
'ﻌ' => 'ع',
'ï»<C3AF>' => 'غ',
'ﻎ' => 'غ',
'ï»<C3AF>' => 'غ',
'ï»<C3AF>' => 'غ',
'ﻑ' => 'Ù<>',
'ï»’' => 'Ù<>',
'ﻓ' => 'Ù<>',
'ï»”' => 'Ù<>',
'ﻕ' => 'ق',
'ï»–' => 'Ù‚',
'ï»—' => 'Ù‚',
'ﻘ' => 'ق',
'ï»™' => 'Ùƒ',
'ﻚ' => 'ك',
'ï»›' => 'Ùƒ',
'ﻜ' => 'ك',
'ï»<C3AF>' => 'Ù„',
'ﻞ' => 'ل',
'ﻟ' => 'ل',
'ï» ' => 'Ù„',
'ﻡ' => 'م',
'ﻢ' => 'م',
'ﻣ' => 'م',
'ﻤ' => 'م',
'ﻥ' => 'ن',
'ﻦ' => 'ن',
'ﻧ' => 'ن',
'ﻨ' => 'ن',
'ﻩ' => 'ه',
'ﻪ' => 'ه',
'ﻫ' => 'ه',
'ﻬ' => 'ه',
'ï»­' => 'Ùˆ',
'ï»®' => 'Ùˆ',
'ﻯ' => 'ى',
'ï»°' => 'Ù‰',
'ï»±' => 'ÙŠ',
'ﻲ' => 'ي',
'ﻳ' => 'ي',
'ï»´' => 'ÙŠ',
'ﻵ' => 'لآ',
'ﻶ' => 'لآ',
'ﻷ' => 'لأ',
'ﻸ' => 'لأ',
'ﻹ' => 'لإ',
'ﻺ' => 'لإ',
'ﻻ' => 'لا',
'ﻼ' => 'لا',
'ï¼<C3AF>' => '!',
'"' => '"',
'#' => '#',
'$' => '$',
'ï¼…' => '%',
'&' => '&',
''' => '\'',
'(' => '(',
')' => ')',
'*' => '*',
'+' => '+',
',' => ',',
'ï¼<C3AF>' => '-',
'.' => '.',
'ï¼<C3AF>' => '/',
'ï¼<C3AF>' => '0',
'1' => '1',
'ï¼’' => '2',
'3' => '3',
'ï¼”' => '4',
'5' => '5',
'ï¼–' => '6',
'ï¼—' => '7',
'8' => '8',
'ï¼™' => '9',
':' => ':',
'ï¼›' => ';',
'<' => '<',
'ï¼<C3AF>' => '=',
'>' => '>',
'?' => '?',
'ï¼ ' => '@',
'A' => 'A',
'ï¼¢' => 'B',
'ï¼£' => 'C',
'D' => 'D',
'ï¼¥' => 'E',
'F' => 'F',
'G' => 'G',
'H' => 'H',
'I' => 'I',
'J' => 'J',
'K' => 'K',
'L' => 'L',
'ï¼­' => 'M',
'ï¼®' => 'N',
'O' => 'O',
'ï¼°' => 'P',
'ï¼±' => 'Q',
'ï¼²' => 'R',
'ï¼³' => 'S',
'ï¼´' => 'T',
'ï¼µ' => 'U',
'V' => 'V',
'ï¼·' => 'W',
'X' => 'X',
'ï¼¹' => 'Y',
'Z' => 'Z',
'ï¼»' => '[',
'ï¼¼' => '\\',
'ï¼½' => ']',
'ï¼¾' => '^',
'_' => '_',
'ï½€' => '`',
'ï½<C3AF>' => 'a',
'b' => 'b',
'c' => 'c',
'd' => 'd',
'ï½…' => 'e',
'f' => 'f',
'g' => 'g',
'h' => 'h',
'i' => 'i',
'j' => 'j',
'k' => 'k',
'l' => 'l',
'ï½<C3AF>' => 'm',
'n' => 'n',
'ï½<C3AF>' => 'o',
'ï½<C3AF>' => 'p',
'q' => 'q',
'ï½’' => 'r',
's' => 's',
'ï½”' => 't',
'u' => 'u',
'ï½–' => 'v',
'ï½—' => 'w',
'x' => 'x',
'ï½™' => 'y',
'z' => 'z',
'ï½›' => '{',
'|' => '|',
'ï½<C3AF>' => '}',
'~' => '~',
'⦅' => '⦅',
'⦆' => '⦆',
'。' => '。',
'「' => '「',
'ï½£' => 'ã€<C3A3>',
'、' => 'ã€<C3A3>',
'・' => '・',
'ヲ' => 'ヲ',
'ァ' => 'ァ',
'ィ' => 'ィ',
'ゥ' => 'ゥ',
'ェ' => 'ェ',
'ォ' => 'ォ',
'ャ' => 'ャ',
'ュ' => 'ュ',
'ョ' => 'ョ',
'ッ' => 'ッ',
'ー' => 'ー',
'ï½±' => 'ã‚¢',
'イ' => 'イ',
'ウ' => 'ウ',
'エ' => 'エ',
'オ' => 'オ',
'カ' => 'カ',
'ï½·' => 'ã‚­',
'ク' => 'ク',
'ケ' => 'ケ',
'コ' => 'コ',
'サ' => 'サ',
'ï½¼' => 'ã‚·',
'ス' => 'ス',
'ï½¾' => 'ã‚»',
'ソ' => 'ソ',
'ï¾€' => 'ã‚¿',
'ï¾<C3AF>' => 'ãƒ<C3A3>',
'ツ' => 'ツ',
'テ' => 'テ',
'ト' => 'ト',
'ナ' => 'ナ',
'ニ' => 'ニ',
'ヌ' => 'ヌ',
'ネ' => 'ãƒ<C3A3>',
'ノ' => 'ノ',
'ハ' => 'ãƒ<C3A3>',
'ヒ' => 'ヒ',
'フ' => 'フ',
'ï¾<C3AF>' => 'ヘ',
'ホ' => 'ホ',
'ï¾<C3AF>' => 'マ',
'ï¾<C3AF>' => 'ミ',
'ム' => 'ム',
'メ' => 'メ',
'モ' => 'モ',
'ヤ' => 'ヤ',
'ユ' => 'ユ',
'ヨ' => 'ヨ',
'ラ' => 'ラ',
'リ' => 'リ',
'ル' => 'ル',
'レ' => 'レ',
'ロ' => 'ロ',
'ワ' => 'ワ',
'ï¾<C3AF>' => 'ン',
'゙' => '゙',
'゚' => '゚',
'ï¾ ' => 'á… ',
'ᄀ' => 'ᄀ',
'ï¾¢' => 'á„<C3A1>',
'ᆪ' => 'ᆪ',
'ᄂ' => 'ᄂ',
'ᆬ' => 'ᆬ',
'ᆭ' => 'ᆭ',
'ᄃ' => 'ᄃ',
'ᄄ' => 'ᄄ',
'ᄅ' => 'ᄅ',
'ᆰ' => 'ᆰ',
'ᆱ' => 'ᆱ',
'ᆲ' => 'ᆲ',
'ᆳ' => 'ᆳ',
'ᆴ' => 'ᆴ',
'ᆵ' => 'ᆵ',
'ï¾°' => 'á„š',
'ᄆ' => 'ᄆ',
'ᄇ' => 'ᄇ',
'ᄈ' => 'ᄈ',
'ï¾´' => 'á„¡',
'ᄉ' => 'ᄉ',
'ᄊ' => 'ᄊ',
'ï¾·' => 'á„‹',
'ᄌ' => 'ᄌ',
'ï¾¹' => 'á„<C3A1>',
'ᄎ' => 'ᄎ',
'ï¾»' => 'á„<C3A1>',
'ï¾¼' => 'á„<C3A1>',
'ï¾½' => 'á„‘',
'ï¾¾' => 'á„’',
'ï¿‚' => 'á…¡',
'ᅢ' => 'ᅢ',
'ï¿„' => 'á…£',
'ï¿…' => 'á…¤',
'ᅥ' => 'ᅥ',
'ᅦ' => 'ᅦ',
'ï¿Š' => 'á…§',
'ï¿‹' => 'á…¨',
'ᅩ' => 'ᅩ',
'ï¿<C3AF>' => 'á…ª',
'ï¿Ž' => 'á…«',
'ï¿<C3AF>' => 'á…¬',
'ï¿’' => 'á…­',
'ï¿“' => 'á…®',
'ï¿”' => 'á…¯',
'ï¿•' => 'á…°',
'ï¿–' => 'á…±',
'ï¿—' => 'á…²',
'ï¿š' => 'á…³',
'ï¿›' => 'á…´',
'ᅵ' => 'ᅵ',
'¢' => '¢',
'£' => '£',
'¬' => '¬',
'ï¿£' => ' Ì„',
'¦' => '¦',
'ï¿¥' => 'Â¥',
'₩' => '₩',
'│' => '│',
'ï¿©' => 'â†<C3A2>',
'↑' => '↑',
'→' => '→',
'↓' => '↓',
'ï¿­' => 'â– ',
'ï¿®' => 'â—‹',
<><C3B0>€' => 'A',
<><C3B0><EFBFBD>' => 'B',
<><C3B0>' => 'C',
<><C3B0>ƒ' => 'D',
<><C3B0>„' => 'E',
<><C3B0>…' => 'F',
<><C3B0>†' => 'G',
<><C3B0>‡' => 'H',
<><C3B0>ˆ' => 'I',
<><C3B0>‰' => 'J',
<><C3B0>Š' => 'K',
<><C3B0>' => 'L',
<><C3B0>Œ' => 'M',
<><C3B0><EFBFBD>' => 'N',
<><C3B0>Ž' => 'O',
<><C3B0><EFBFBD>' => 'P',
<><C3B0><EFBFBD>' => 'Q',
<><C3B0>' => 'R',
<><C3B0>' => 'S',
<><C3B0>“' => 'T',
<><C3B0>”' => 'U',
<><C3B0>•' => 'V',
<><C3B0>' => 'W',
<><C3B0>—' => 'X',
<><C3B0>˜' => 'Y',
<><C3B0>™' => 'Z',
<><C3B0>š' => 'a',
<><C3B0>' => 'b',
<><C3B0>œ' => 'c',
<><C3B0><EFBFBD>' => 'd',
<><C3B0>ž' => 'e',
<><C3B0>Ÿ' => 'f',
<><C3B0> ' => 'g',
<><C3B0>¡' => 'h',
<><C3B0>¢' => 'i',
<><C3B0>£' => 'j',
<><C3B0>¤' => 'k',
<><C3B0>¥' => 'l',
<><C3B0>¦' => 'm',
<><C3B0>§' => 'n',
<><C3B0>¨' => 'o',
<><C3B0>©' => 'p',
<><C3B0>ª' => 'q',
<><C3B0>«' => 'r',
<><C3B0>¬' => 's',
<><C3B0>­' => 't',
<><C3B0>®' => 'u',
<><C3B0>¯' => 'v',
<><C3B0>°' => 'w',
<><C3B0>±' => 'x',
<><C3B0>²' => 'y',
<><C3B0>³' => 'z',
<><C3B0>´' => 'A',
<><C3B0>µ' => 'B',
<><C3B0>¶' => 'C',
<><C3B0>·' => 'D',
<><C3B0>¸' => 'E',
<><C3B0>¹' => 'F',
<><C3B0>º' => 'G',
<><C3B0>»' => 'H',
<><C3B0>¼' => 'I',
<><C3B0>½' => 'J',
<><C3B0>¾' => 'K',
<><C3B0>¿' => 'L',
<>€' => 'M',
<><EFBFBD>' => 'N',
<>' => 'O',
<>ƒ' => 'P',
<>„' => 'Q',
<>…' => 'R',
<>†' => 'S',
<>‡' => 'T',
<>ˆ' => 'U',
<>‰' => 'V',
<>Š' => 'W',
<>' => 'X',
<>Œ' => 'Y',
<><EFBFBD>' => 'Z',
<>Ž' => 'a',
<><EFBFBD>' => 'b',
<><EFBFBD>' => 'c',
<>' => 'd',
<>' => 'e',
<>“' => 'f',
<>”' => 'g',
<>' => 'i',
<>—' => 'j',
<>˜' => 'k',
<>™' => 'l',
<>š' => 'm',
<>' => 'n',
<>œ' => 'o',
<><EFBFBD>' => 'p',
<>ž' => 'q',
<>Ÿ' => 'r',
<> ' => 's',
<>¡' => 't',
<>¢' => 'u',
<>£' => 'v',
<>¤' => 'w',
<>¥' => 'x',
<>¦' => 'y',
<>§' => 'z',
<>¨' => 'A',
<>©' => 'B',
<>ª' => 'C',
<>«' => 'D',
<>¬' => 'E',
<>­' => 'F',
<>®' => 'G',
<>¯' => 'H',
<>°' => 'I',
<>±' => 'J',
<>²' => 'K',
<>³' => 'L',
<>´' => 'M',
<>µ' => 'N',
<>¶' => 'O',
<>·' => 'P',
<>¸' => 'Q',
<>¹' => 'R',
<>º' => 'S',
<>»' => 'T',
<>¼' => 'U',
<>½' => 'V',
<>¾' => 'W',
<>¿' => 'X',
<>€' => 'Y',
<><EFBFBD>' => 'Z',
<>' => 'a',
<>ƒ' => 'b',
<>„' => 'c',
<>…' => 'd',
<>†' => 'e',
<>‡' => 'f',
<>ˆ' => 'g',
<>‰' => 'h',
<>Š' => 'i',
<>' => 'j',
<>Œ' => 'k',
<><EFBFBD>' => 'l',
<>Ž' => 'm',
<><EFBFBD>' => 'n',
<><EFBFBD>' => 'o',
<>' => 'p',
<>' => 'q',
<>“' => 'r',
<>”' => 's',
<>•' => 't',
<>' => 'u',
<>—' => 'v',
<>˜' => 'w',
<>™' => 'x',
<>š' => 'y',
<>' => 'z',
<>œ' => 'A',
<>ž' => 'C',
<>Ÿ' => 'D',
<>¢' => 'G',
<>¥' => 'J',
<>¦' => 'K',
<>©' => 'N',
<>ª' => 'O',
<>«' => 'P',
<>¬' => 'Q',
<>®' => 'S',
<>¯' => 'T',
<>°' => 'U',
<>±' => 'V',
<>²' => 'W',
<>³' => 'X',
<>´' => 'Y',
<>µ' => 'Z',
<>¶' => 'a',
<>·' => 'b',
<>¸' => 'c',
<>¹' => 'd',
<>»' => 'f',
<>½' => 'h',
<>¾' => 'i',
<>¿' => 'j',
<>“€' => 'k',
<><EFBFBD>' => 'l',
<>' => 'm',
<>“ƒ' => 'n',
<>“…' => 'p',
<>Ҡ' => 'q',
<>“‡' => 'r',
<>ˆ' => 's',
<>“‰' => 't',
<>“Š' => 'u',
<>' => 'v',
<>“Œ' => 'w',
<><EFBFBD>' => 'x',
<>“Ž' => 'y',
<><EFBFBD>' => 'z',
<><EFBFBD>' => 'A',
<>' => 'B',
<>' => 'C',
<>““' => 'D',
<>“”' => 'E',
<>“•' => 'F',
<>' => 'G',
<>“—' => 'H',
<>˜' => 'I',
<>“™' => 'J',
<>“š' => 'K',
<>' => 'L',
<>“œ' => 'M',
<><EFBFBD>' => 'N',
<>“ž' => 'O',
<>“Ÿ' => 'P',
<> ' => 'Q',
<>“¡' => 'R',
<>“¢' => 'S',
<>“£' => 'T',
<>“¤' => 'U',
<>“¥' => 'V',
<>“¦' => 'W',
<>Ҥ' => 'X',
<>Ҭ' => 'Y',
<>“©' => 'Z',
<>һ' => 'a',
<>“«' => 'b',
<>“¬' => 'c',
<>­' => 'd',
<>“®' => 'e',
<>“¯' => 'f',
<>“°' => 'g',
<>“±' => 'h',
<>“²' => 'i',
<>“³' => 'j',
<>´' => 'k',
<>“µ' => 'l',
<>Ҧ' => 'm',
<>“·' => 'n',
<>¸' => 'o',
<>“¹' => 'p',
<>Ҽ' => 'q',
<>“»' => 'r',
<>“¼' => 's',
<>“½' => 't',
<>“¾' => 'u',
<>“¿' => 'v',
<>”€' => 'w',
<><EFBFBD>' => 'x',
<>' => 'y',
<>”ƒ' => 'z',
<>”„' => 'A',
<>”…' => 'B',
<>”‡' => 'D',
<>ˆ' => 'E',
<>”‰' => 'F',
<>”Š' => 'G',
<><EFBFBD>' => 'J',
<>”Ž' => 'K',
<><EFBFBD>' => 'L',
<><EFBFBD>' => 'M',
<>' => 'N',
<>' => 'O',
<>”“' => 'P',
<>””' => 'Q',
<>' => 'S',
<>”—' => 'T',
<>˜' => 'U',
<>”™' => 'V',
<>”š' => 'W',
<>' => 'X',
<>”œ' => 'Y',
<>”ž' => 'a',
<>”Ÿ' => 'b',
<> ' => 'c',
<>”¡' => 'd',
<>Ӣ' => 'e',
<>ӣ' => 'f',
<>”¤' => 'g',
<>Ӵ' => 'h',
<>”¦' => 'i',
<>Ӥ' => 'j',
<>Ӭ' => 'k',
<>”©' => 'l',
<>ӻ' => 'm',
<>”«' => 'n',
<>”¬' => 'o',
<>­' => 'p',
<>”®' => 'q',
<>”¯' => 'r',
<>”°' => 's',
<>”±' => 't',
<>”²' => 'u',
<>”³' => 'v',
<>´' => 'w',
<>”µ' => 'x',
<>Ӧ' => 'y',
<>”·' => 'z',
<>¸' => 'A',
<>”¹' => 'B',
<>”»' => 'D',
<>”¼' => 'E',
<>”½' => 'F',
<>”¾' => 'G',
<>•€' => 'I',
<><EFBFBD>' => 'J',
<>' => 'K',
<>•ƒ' => 'L',
<>•„' => 'M',
<>•†' => 'O',
<>•Š' => 'S',
<>' => 'T',
<>•Œ' => 'U',
<><EFBFBD>' => 'V',
<>•Ž' => 'W',
<><EFBFBD>' => 'X',
<><EFBFBD>' => 'Y',
<>' => 'a',
<>•“' => 'b',
<>•”' => 'c',
<>••' => 'd',
<>' => 'e',
<>•—' => 'f',
<>˜' => 'g',
<>•™' => 'h',
<>•š' => 'i',
<>' => 'j',
<>•œ' => 'k',
<><EFBFBD>' => 'l',
<>•ž' => 'm',
<>•Ÿ' => 'n',
<> ' => 'o',
<>•¡' => 'p',
<>•¢' => 'q',
<>•£' => 'r',
<>•¤' => 's',
<>•¥' => 't',
<>•¦' => 'u',
<>•§' => 'v',
<>•¨' => 'w',
<>•©' => 'x',
<>•ª' => 'y',
<>•«' => 'z',
<>•¬' => 'A',
<>­' => 'B',
<>•®' => 'C',
<>•¯' => 'D',
<>•°' => 'E',
<>•±' => 'F',
<>•²' => 'G',
<>•³' => 'H',
<>´' => 'I',
<>•µ' => 'J',
<>•¶' => 'K',
<>•·' => 'L',
<>¸' => 'M',
<>•¹' => 'N',
<>•º' => 'O',
<>•»' => 'P',
<>•¼' => 'Q',
<>•½' => 'R',
<>•¾' => 'S',
<>•¿' => 'T',
<>€' => 'U',
<><EFBFBD>' => 'V',
<>' => 'W',
<>ƒ' => 'X',
<>„' => 'Y',
<>…' => 'Z',
<>†' => 'a',
<>‡' => 'b',
<>ˆ' => 'c',
<>‰' => 'd',
<>Š' => 'e',
<>' => 'f',
<>Œ' => 'g',
<><EFBFBD>' => 'h',
<>Ž' => 'i',
<><EFBFBD>' => 'j',
<><EFBFBD>' => 'k',
<>' => 'l',
<>' => 'm',
<>“' => 'n',
<>”' => 'o',
<>•' => 'p',
<>' => 'q',
<>—' => 'r',
<>˜' => 's',
<>™' => 't',
<>š' => 'u',
<>' => 'v',
<>œ' => 'w',
<><EFBFBD>' => 'x',
<>ž' => 'y',
<>Ÿ' => 'z',
<> ' => 'A',
<>¡' => 'B',
<>¢' => 'C',
<>£' => 'D',
<>¤' => 'E',
<>¥' => 'F',
<>¦' => 'G',
<>§' => 'H',
<>¨' => 'I',
<>©' => 'J',
<>ª' => 'K',
<>«' => 'L',
<>¬' => 'M',
<>­' => 'N',
<>®' => 'O',
<>¯' => 'P',
<>°' => 'Q',
<>±' => 'R',
<>²' => 'S',
<>³' => 'T',
<>´' => 'U',
<>µ' => 'V',
<>¶' => 'W',
<>·' => 'X',
<>¸' => 'Y',
<>¹' => 'Z',
<>º' => 'a',
<>»' => 'b',
<>¼' => 'c',
<>½' => 'd',
<>¾' => 'e',
<>¿' => 'f',
<>—€' => 'g',
<><EFBFBD>' => 'h',
<>' => 'i',
<>—ƒ' => 'j',
<>—„' => 'k',
<>—…' => 'l',
<>—†' => 'm',
<>—‡' => 'n',
<>ˆ' => 'o',
<>—‰' => 'p',
<>—Š' => 'q',
<>' => 'r',
<>—Œ' => 's',
<><EFBFBD>' => 't',
<>—Ž' => 'u',
<><EFBFBD>' => 'v',
<><EFBFBD>' => 'w',
<>' => 'x',
<>' => 'y',
<>—“' => 'z',
<>—”' => 'A',
<>—•' => 'B',
<>' => 'C',
<>——' => 'D',
<>˜' => 'E',
<>—™' => 'F',
<>—š' => 'G',
<>' => 'H',
<>—œ' => 'I',
<><EFBFBD>' => 'J',
<>—ž' => 'K',
<>—Ÿ' => 'L',
<> ' => 'M',
<>—¡' => 'N',
<>—¢' => 'O',
<>—£' => 'P',
<>—¤' => 'Q',
<>—¥' => 'R',
<>—¦' => 'S',
<>—§' => 'T',
<>—¨' => 'U',
<>—©' => 'V',
<>—ª' => 'W',
<>—«' => 'X',
<>—¬' => 'Y',
<>­' => 'Z',
<>—®' => 'a',
<>—¯' => 'b',
<>—°' => 'c',
<>—±' => 'd',
<>—²' => 'e',
<>—³' => 'f',
<>´' => 'g',
<>—µ' => 'h',
<>—¶' => 'i',
<>—·' => 'j',
<>¸' => 'k',
<>—¹' => 'l',
<>—º' => 'm',
<>—»' => 'n',
<>—¼' => 'o',
<>—½' => 'p',
<>—¾' => 'q',
<>—¿' => 'r',
<>˜€' => 's',
<>˜<EFBFBD>' => 't',
<>˜' => 'u',
<>˜ƒ' => 'v',
<>˜„' => 'w',
<>˜…' => 'x',
<>˜†' => 'y',
<>˜‡' => 'z',
<>˜ˆ' => 'A',
<>˜‰' => 'B',
<>˜Š' => 'C',
<>˜' => 'D',
<>˜Œ' => 'E',
<>˜<EFBFBD>' => 'F',
<>˜Ž' => 'G',
<>˜<EFBFBD>' => 'H',
<>˜<EFBFBD>' => 'I',
<>˜' => 'J',
<>˜' => 'K',
<>˜“' => 'L',
<>˜”' => 'M',
<>˜•' => 'N',
<>˜' => 'O',
<>˜—' => 'P',
<>˜˜' => 'Q',
<>˜™' => 'R',
<>˜š' => 'S',
<>˜' => 'T',
<>˜œ' => 'U',
<>˜<EFBFBD>' => 'V',
<>˜ž' => 'W',
<>˜Ÿ' => 'X',
<>˜ ' => 'Y',
<>˜¡' => 'Z',
<>˜¢' => 'a',
<>˜£' => 'b',
<>˜¤' => 'c',
<>˜¥' => 'd',
<>˜¦' => 'e',
<>˜§' => 'f',
<>˜¨' => 'g',
<>˜©' => 'h',
<>˜ª' => 'i',
<>˜«' => 'j',
<>˜¬' => 'k',
<>˜­' => 'l',
<>˜®' => 'm',
<>˜¯' => 'n',
<>˜°' => 'o',
<>˜±' => 'p',
<>˜²' => 'q',
<>˜³' => 'r',
<>˜´' => 's',
<>˜µ' => 't',
<>˜¶' => 'u',
<>˜·' => 'v',
<>˜¸' => 'w',
<>˜¹' => 'x',
<>˜º' => 'y',
<>˜»' => 'z',
<>˜¼' => 'A',
<>˜½' => 'B',
<>˜¾' => 'C',
<>˜¿' => 'D',
<>™€' => 'E',
<><EFBFBD>' => 'F',
<>' => 'G',
<>™ƒ' => 'H',
<>™„' => 'I',
<>™…' => 'J',
<>™†' => 'K',
<>™‡' => 'L',
<>ˆ' => 'M',
<>™‰' => 'N',
<>™Š' => 'O',
<>' => 'P',
<>™Œ' => 'Q',
<><EFBFBD>' => 'R',
<>™Ž' => 'S',
<><EFBFBD>' => 'T',
<><EFBFBD>' => 'U',
<>' => 'V',
<>' => 'W',
<>™“' => 'X',
<>™”' => 'Y',
<>™•' => 'Z',
<>' => 'a',
<>™—' => 'b',
<>˜' => 'c',
<>™™' => 'd',
<>™š' => 'e',
<>' => 'f',
<>™œ' => 'g',
<><EFBFBD>' => 'h',
<>™ž' => 'i',
<>™Ÿ' => 'j',
<> ' => 'k',
<>™¡' => 'l',
<>™¢' => 'm',
<>™£' => 'n',
<>™¤' => 'o',
<>™¥' => 'p',
<>™¦' => 'q',
<>™§' => 'r',
<>™¨' => 's',
<>™©' => 't',
<>™ª' => 'u',
<>™«' => 'v',
<>™¬' => 'w',
<>­' => 'x',
<>™®' => 'y',
<>™¯' => 'z',
<>™°' => 'A',
<>™±' => 'B',
<>™²' => 'C',
<>™³' => 'D',
<>´' => 'E',
<>™µ' => 'F',
<>™¶' => 'G',
<>™·' => 'H',
<>¸' => 'I',
<>™¹' => 'J',
<>™º' => 'K',
<>™»' => 'L',
<>™¼' => 'M',
<>™½' => 'N',
<>™¾' => 'O',
<>™¿' => 'P',
<>š€' => 'Q',
<>š<EFBFBD>' => 'R',
<>š' => 'S',
<>šƒ' => 'T',
<>š„' => 'U',
<>š…' => 'V',
<>š†' => 'W',
<>š‡' => 'X',
<>šˆ' => 'Y',
<>š‰' => 'Z',
<>šŠ' => 'a',
<>š' => 'b',
<>šŒ' => 'c',
<>š<EFBFBD>' => 'd',
<>šŽ' => 'e',
<>š<EFBFBD>' => 'f',
<>š<EFBFBD>' => 'g',
<>š' => 'h',
<>š' => 'i',
<>š“' => 'j',
<>š”' => 'k',
<>š•' => 'l',
<>š' => 'm',
<>š—' => 'n',
<>š˜' => 'o',
<>š™' => 'p',
<>šš' => 'q',
<>š' => 'r',
<>šœ' => 's',
<>š<EFBFBD>' => 't',
<>šž' => 'u',
<>šŸ' => 'v',
<>š ' => 'w',
<>š¡' => 'x',
<>š¢' => 'y',
<>š£' => 'z',
<>š¤' => 'ı',
<>š¥' => 'È·',
<>š¨' => 'Α',
<>š©' => 'Î’',
<>šª' => 'Γ',
<>š«' => 'Δ',
<>š¬' => 'Ε',
<>š­' => 'Ζ',
<>š®' => 'Η',
<>š¯' => 'Θ',
<>š°' => 'Ι',
<>š±' => 'Κ',
<>š²' => 'Λ',
<>š³' => 'Îœ',
<>š´' => 'Î<>',
<>šµ' => 'Ξ',
<>š¶' => 'Ο',
<>š·' => 'Π',
<>š¸' => 'Ρ',
<>š¹' => 'Θ',
<>šº' => 'Σ',
<>š»' => 'Τ',
<>š¼' => 'Î¥',
<>š½' => 'Φ',
<>š¾' => 'Χ',
<>š¿' => 'Ψ',
<>€' => 'Ω',
<><EFBFBD>' => '∇',
<>' => 'α',
<>ƒ' => 'β',
<>„' => 'γ',
<>…' => 'δ',
<>†' => 'ε',
<>‡' => 'ζ',
<>ˆ' => 'η',
<>‰' => 'θ',
<>Š' => 'ι',
<>' => 'κ',
<>Œ' => 'λ',
<><EFBFBD>' => 'μ',
<>Ž' => 'ν',
<><EFBFBD>' => 'ξ',
<><EFBFBD>' => 'ο',
<>' => 'Ï€',
<>' => 'Ï<>',
<>“' => 'Ï‚',
<>”' => 'σ',
<>•' => 'Ï„',
<>' => 'Ï…',
<>—' => 'φ',
<>˜' => 'χ',
<>™' => 'ψ',
<>š' => 'ω',
<>' => '∂',
<>œ' => 'ε',
<><EFBFBD>' => 'θ',
<>ž' => 'κ',
<>Ÿ' => 'φ',
<> ' => 'Ï<>',
<>¡' => 'Ï€',
<>¢' => 'Α',
<>£' => 'Î’',
<>¤' => 'Γ',
<>¥' => 'Δ',
<>¦' => 'Ε',
<>§' => 'Ζ',
<>¨' => 'Η',
<>©' => 'Θ',
<>ª' => 'Ι',
<>«' => 'Κ',
<>¬' => 'Λ',
<>­' => 'Îœ',
<>®' => 'Î<>',
<>¯' => 'Ξ',
<>°' => 'Ο',
<>±' => 'Π',
<>²' => 'Ρ',
<>³' => 'Θ',
<>´' => 'Σ',
<>µ' => 'Τ',
<>¶' => 'Î¥',
<>·' => 'Φ',
<>¸' => 'Χ',
<>¹' => 'Ψ',
<>º' => 'Ω',
<>»' => '∇',
<>¼' => 'α',
<>½' => 'β',
<>¾' => 'γ',
<>¿' => 'δ',
<>œ€' => 'ε',
<>œ<EFBFBD>' => 'ζ',
<>œ' => 'η',
<>œƒ' => 'θ',
<>œ„' => 'ι',
<>œ…' => 'κ',
<>œ†' => 'λ',
<>œ‡' => 'μ',
<>œˆ' => 'ν',
<>œ‰' => 'ξ',
<>œŠ' => 'ο',
<>œ' => 'Ï€',
<>œŒ' => 'Ï<>',
<>œ<EFBFBD>' => 'Ï‚',
<>œŽ' => 'σ',
<>œ<EFBFBD>' => 'Ï„',
<>œ<EFBFBD>' => 'Ï…',
<>œ' => 'φ',
<>œ' => 'χ',
<>œ“' => 'ψ',
<>œ”' => 'ω',
<>œ•' => '∂',
<>œ' => 'ε',
<>œ—' => 'θ',
<>œ˜' => 'κ',
<>œ™' => 'φ',
<>œš' => 'Ï<>',
<>œ' => 'Ï€',
<>œœ' => 'Α',
<>œ<EFBFBD>' => 'Î’',
<>œž' => 'Γ',
<>œŸ' => 'Δ',
<>œ ' => 'Ε',
<>œ¡' => 'Ζ',
<>œ¢' => 'Η',
<>œ£' => 'Θ',
<>œ¤' => 'Ι',
<>œ¥' => 'Κ',
<>œ¦' => 'Λ',
<>œ§' => 'Îœ',
<>œ¨' => 'Î<>',
<>œ©' => 'Ξ',
<>œª' => 'Ο',
<>œ«' => 'Π',
<>œ¬' => 'Ρ',
<>œ­' => 'Θ',
<>œ®' => 'Σ',
<>œ¯' => 'Τ',
<>œ°' => 'Î¥',
<>œ±' => 'Φ',
<>œ²' => 'Χ',
<>œ³' => 'Ψ',
<>œ´' => 'Ω',
<>œµ' => '∇',
<>œ¶' => 'α',
<>œ·' => 'β',
<>œ¸' => 'γ',
<>œ¹' => 'δ',
<>œº' => 'ε',
<>œ»' => 'ζ',
<>œ¼' => 'η',
<>œ½' => 'θ',
<>œ¾' => 'ι',
<>œ¿' => 'κ',
<><C3B0>€' => 'λ',
<><C3B0><EFBFBD>' => 'μ',
<><C3B0>' => 'ν',
<><C3B0>ƒ' => 'ξ',
<><C3B0>„' => 'ο',
<><C3B0>…' => 'Ï€',
<><C3B0>†' => 'Ï<>',
<><C3B0>‡' => 'Ï‚',
<><C3B0>ˆ' => 'σ',
<><C3B0>‰' => 'Ï„',
<><C3B0>Š' => 'Ï…',
<><C3B0>' => 'φ',
<><C3B0>Œ' => 'χ',
<><C3B0><EFBFBD>' => 'ψ',
<><C3B0>Ž' => 'ω',
<><C3B0><EFBFBD>' => '∂',
<><C3B0><EFBFBD>' => 'ε',
<><C3B0>' => 'θ',
<><C3B0>' => 'κ',
<><C3B0>“' => 'φ',
<><C3B0>”' => 'Ï<>',
<><C3B0>•' => 'Ï€',
<><C3B0>' => 'Α',
<><C3B0>—' => 'Î’',
<><C3B0>˜' => 'Γ',
<><C3B0>™' => 'Δ',
<><C3B0>š' => 'Ε',
<><C3B0>' => 'Ζ',
<><C3B0>œ' => 'Η',
<><C3B0><EFBFBD>' => 'Θ',
<><C3B0>ž' => 'Ι',
<><C3B0>Ÿ' => 'Κ',
<><C3B0> ' => 'Λ',
<><C3B0>¡' => 'Îœ',
<><C3B0>¢' => 'Î<>',
<><C3B0>£' => 'Ξ',
<><C3B0>¤' => 'Ο',
<><C3B0>¥' => 'Π',
<><C3B0>¦' => 'Ρ',
<><C3B0>§' => 'Θ',
<><C3B0>¨' => 'Σ',
<><C3B0>©' => 'Τ',
<><C3B0>ª' => 'Î¥',
<><C3B0>«' => 'Φ',
<><C3B0>¬' => 'Χ',
<><C3B0>­' => 'Ψ',
<><C3B0>®' => 'Ω',
<><C3B0>¯' => '∇',
<><C3B0>°' => 'α',
<><C3B0>±' => 'β',
<><C3B0>²' => 'γ',
<><C3B0>³' => 'δ',
<><C3B0>´' => 'ε',
<><C3B0>µ' => 'ζ',
<><C3B0>¶' => 'η',
<><C3B0>·' => 'θ',
<><C3B0>¸' => 'ι',
<><C3B0>¹' => 'κ',
<><C3B0>º' => 'λ',
<><C3B0>»' => 'μ',
<><C3B0>¼' => 'ν',
<><C3B0>½' => 'ξ',
<><C3B0>¾' => 'ο',
<><C3B0>¿' => 'Ï€',
<>ž€' => 'Ï<>',
<>ž<EFBFBD>' => 'Ï‚',
<>ž' => 'σ',
<>žƒ' => 'Ï„',
<>ž„' => 'Ï…',
<>ž…' => 'φ',
<>ž†' => 'χ',
<>ž‡' => 'ψ',
<>žˆ' => 'ω',
<>ž‰' => '∂',
<>žŠ' => 'ε',
<>ž' => 'θ',
<>žŒ' => 'κ',
<>ž<EFBFBD>' => 'φ',
<>žŽ' => 'Ï<>',
<>ž<EFBFBD>' => 'Ï€',
<>ž<EFBFBD>' => 'Α',
<>ž' => 'Î’',
<>ž' => 'Γ',
<>ž“' => 'Δ',
<>ž”' => 'Ε',
<>ž•' => 'Ζ',
<>ž' => 'Η',
<>ž—' => 'Θ',
<>ž˜' => 'Ι',
<>ž™' => 'Κ',
<>žš' => 'Λ',
<>ž' => 'Îœ',
<>žœ' => 'Î<>',
<>ž<EFBFBD>' => 'Ξ',
<>žž' => 'Ο',
<>žŸ' => 'Π',
<>ž ' => 'Ρ',
<>ž¡' => 'Θ',
<>ž¢' => 'Σ',
<>ž£' => 'Τ',
<>ž¤' => 'Î¥',
<>ž¥' => 'Φ',
<>ž¦' => 'Χ',
<>ž§' => 'Ψ',
<>ž¨' => 'Ω',
<>ž©' => '∇',
<>žª' => 'α',
<>ž«' => 'β',
<>ž¬' => 'γ',
<>ž­' => 'δ',
<>ž®' => 'ε',
<>ž¯' => 'ζ',
<>ž°' => 'η',
<>ž±' => 'θ',
<>ž²' => 'ι',
<>ž³' => 'κ',
<>ž´' => 'λ',
<>žµ' => 'μ',
<>ž¶' => 'ν',
<>ž·' => 'ξ',
<>ž¸' => 'ο',
<>ž¹' => 'Ï€',
<>žº' => 'Ï<>',
<>ž»' => 'Ï‚',
<>ž¼' => 'σ',
<>ž½' => 'Ï„',
<>ž¾' => 'Ï…',
<>ž¿' => 'φ',
<>Ÿ€' => 'χ',
<>Ÿ<EFBFBD>' => 'ψ',
<>Ÿ' => 'ω',
<>Ÿƒ' => '∂',
<>Ÿ„' => 'ε',
<>Ÿ…' => 'θ',
<>Ÿ†' => 'κ',
<>Ÿ‡' => 'φ',
<>Ÿˆ' => 'Ï<>',
<>Ÿ‰' => 'Ï€',
<>ŸŠ' => 'Ïœ',
<>Ÿ' => 'Ï<>',
<>ŸŽ' => '0',
<>Ÿ<EFBFBD>' => '1',
<>Ÿ<EFBFBD>' => '2',
<>Ÿ' => '3',
<>Ÿ' => '4',
<>Ÿ“' => '5',
<>Ÿ”' => '6',
<>Ÿ•' => '7',
<>Ÿ' => '8',
<>Ÿ—' => '9',
<>Ÿ˜' => '0',
<>Ÿ™' => '1',
<>Ÿš' => '2',
<>Ÿ' => '3',
<>Ÿœ' => '4',
<>Ÿ<EFBFBD>' => '5',
<>Ÿž' => '6',
<>ŸŸ' => '7',
<>Ÿ ' => '8',
<>Ÿ¡' => '9',
<>Ÿ¢' => '0',
<>Ÿ£' => '1',
<>Ÿ¤' => '2',
<>Ÿ¥' => '3',
<>Ÿ¦' => '4',
<>٤' => '5',
<>٬' => '6',
<>Ÿ©' => '7',
<>ٻ' => '8',
<>Ÿ«' => '9',
<>Ÿ¬' => '0',
<>Ÿ­' => '1',
<>Ÿ®' => '2',
<>Ÿ¯' => '3',
<>Ÿ°' => '4',
<>ٱ' => '5',
<>Ÿ²' => '6',
<>Ÿ³' => '7',
<>Ÿ´' => '8',
<>ٵ' => '9',
<>٦' => '0',
<>Ÿ·' => '1',
<>Ÿ¸' => '2',
<>Ÿ¹' => '3',
<>ټ' => '4',
<>Ÿ»' => '5',
<>Ÿ¼' => '6',
<>Ÿ½' => '7',
<>Ÿ¾' => '8',
<>Ÿ¿' => '9',
'𞸀' => 'ا',
'ðž¸<C5BE>' => 'ب',
'𞸂' => 'ج',
'𞸃' => 'د',
'𞸅' => 'و',
'𞸆' => 'ز',
'𞸇' => 'ح',
'𞸈' => 'ط',
'𞸉' => 'ي',
'𞸊' => 'ك',
'𞸋' => 'ل',
'𞸌' => 'م',
'ðž¸<C5BE>' => 'Ù†',
'𞸎' => 'س',
'ðž¸<C5BE>' => 'ع',
'ðž¸<C5BE>' => 'Ù<>',
'𞸑' => 'ص',
'𞸒' => 'ق',
'𞸓' => 'ر',
'𞸔' => 'ش',
'𞸕' => 'ت',
'𞸖' => 'ث',
'𞸗' => 'خ',
'𞸘' => 'ذ',
'𞸙' => 'ض',
'𞸚' => 'ظ',
'𞸛' => 'غ',
'𞸜' => 'ٮ',
'ðž¸<C5BE>' => 'Úº',
'𞸞' => 'ڡ',
'𞸟' => 'ٯ',
'𞸡' => 'ب',
'𞸢' => 'ج',
'𞸤' => 'ه',
'𞸧' => 'ح',
'𞸩' => 'ي',
'𞸪' => 'ك',
'𞸫' => 'ل',
'𞸬' => 'م',
'𞸭' => 'ن',
'𞸮' => 'س',
'𞸯' => 'ع',
'𞸰' => 'Ù<>',
'𞸱' => 'ص',
'𞸲' => 'ق',
'𞸴' => 'ش',
'𞸵' => 'ت',
'𞸶' => 'ث',
'𞸷' => 'خ',
'𞸹' => 'ض',
'𞸻' => 'غ',
'𞹂' => 'ج',
'𞹇' => 'ح',
'𞹉' => 'ي',
'𞹋' => 'ل',
'ðž¹<C5BE>' => 'Ù†',
'𞹎' => 'س',
'ðž¹<C5BE>' => 'ع',
'𞹑' => 'ص',
'ðž¹’' => 'Ù‚',
'ðž¹”' => 'Ø´',
'ðž¹—' => 'Ø®',
'𞹙' => 'ض',
'𞹛' => 'غ',
'ðž¹<C5BE>' => 'Úº',
'𞹟' => 'ٯ',
'𞹡' => 'ب',
'𞹢' => 'ج',
'𞹤' => 'ه',
'𞹧' => 'ح',
'𞹨' => 'ط',
'𞹩' => 'ي',
'𞹪' => 'ك',
'𞹬' => 'م',
'ðž¹­' => 'Ù†',
'𞹮' => 'س',
'𞹯' => 'ع',
'ðž¹°' => 'Ù<>',
'𞹱' => 'ص',
'ðž¹²' => 'Ù‚',
'ðž¹´' => 'Ø´',
'𞹵' => 'ت',
'𞹶' => 'ث',
'ðž¹·' => 'Ø®',
'𞹹' => 'ض',
'𞹺' => 'ظ',
'𞹻' => 'غ',
'ðž¹¼' => 'Ù®',
'ðž¹¾' => 'Ú¡',
'𞺀' => 'ا',
'ðžº<C5BE>' => 'ب',
'𞺂' => 'ج',
'𞺃' => 'د',
'𞺄' => 'ه',
'𞺅' => 'و',
'𞺆' => 'ز',
'𞺇' => 'ح',
'𞺈' => 'ط',
'𞺉' => 'ي',
'𞺋' => 'ل',
'𞺌' => 'م',
'ðžº<C5BE>' => 'Ù†',
'𞺎' => 'س',
'ðžº<C5BE>' => 'ع',
'ðžº<C5BE>' => 'Ù<>',
'𞺑' => 'ص',
'𞺒' => 'ق',
'𞺓' => 'ر',
'𞺔' => 'ش',
'𞺕' => 'ت',
'𞺖' => 'ث',
'𞺗' => 'خ',
'𞺘' => 'ذ',
'𞺙' => 'ض',
'𞺚' => 'ظ',
'𞺛' => 'غ',
'𞺡' => 'ب',
'𞺢' => 'ج',
'𞺣' => 'د',
'𞺥' => 'و',
'𞺦' => 'ز',
'𞺧' => 'ح',
'𞺨' => 'ط',
'𞺩' => 'ي',
'𞺫' => 'ل',
'𞺬' => 'م',
'𞺭' => 'ن',
'𞺮' => 'س',
'𞺯' => 'ع',
'𞺰' => 'Ù<>',
'𞺱' => 'ص',
'𞺲' => 'ق',
'𞺳' => 'ر',
'𞺴' => 'ش',
'𞺵' => 'ت',
'𞺶' => 'ث',
'𞺷' => 'خ',
'𞺸' => 'ذ',
'𞺹' => 'ض',
'𞺺' => 'ظ',
'𞺻' => 'غ',
'🄀' => '0.',
'ðŸ„<C5B8>' => '0,',
'🄂' => '1,',
'🄃' => '2,',
'🄄' => '3,',
'🄅' => '4,',
'🄆' => '5,',
'🄇' => '6,',
'🄈' => '7,',
'🄉' => '8,',
'🄊' => '9,',
'ðŸ„<C5B8>' => '(A)',
'🄑' => '(B)',
'🄒' => '(C)',
'🄓' => '(D)',
'🄔' => '(E)',
'🄕' => '(F)',
'🄖' => '(G)',
'🄗' => '(H)',
'🄘' => '(I)',
'🄙' => '(J)',
'🄚' => '(K)',
'🄛' => '(L)',
'🄜' => '(M)',
'ðŸ„<C5B8>' => '(N)',
'🄞' => '(O)',
'🄟' => '(P)',
'🄠' => '(Q)',
'🄡' => '(R)',
'🄢' => '(S)',
'🄣' => '(T)',
'🄤' => '(U)',
'🄥' => '(V)',
'🄦' => '(W)',
'🄧' => '(X)',
'🄨' => '(Y)',
'🄩' => '(Z)',
'🄪' => '〔S〕',
'🄫' => 'C',
'🄬' => 'R',
'🄭' => 'CD',
'🄮' => 'WZ',
'🄰' => 'A',
'🄱' => 'B',
'🄲' => 'C',
'🄳' => 'D',
'🄴' => 'E',
'🄵' => 'F',
'🄶' => 'G',
'🄷' => 'H',
'🄸' => 'I',
'🄹' => 'J',
'🄺' => 'K',
'🄻' => 'L',
'🄼' => 'M',
'🄽' => 'N',
'🄾' => 'O',
'🄿' => 'P',
'🅀' => 'Q',
'ðŸ…<C5B8>' => 'R',
'🅂' => 'S',
'🅃' => 'T',
'🅄' => 'U',
'🅅' => 'V',
'🅆' => 'W',
'🅇' => 'X',
'🅈' => 'Y',
'🅉' => 'Z',
'🅊' => 'HV',
'🅋' => 'MV',
'🅌' => 'SD',
'ðŸ…<C5B8>' => 'SS',
'🅎' => 'PPV',
'ðŸ…<C5B8>' => 'WC',
'🅪' => 'MC',
'🅫' => 'MD',
'🅬' => 'MR',
'ðŸ†<C5B8>' => 'DJ',
'🈀' => 'ã<>»ã<C2BB>',
'ðŸˆ<C5B8>' => 'ココ',
'🈂' => 'サ',
'ðŸˆ<C5B8>' => '手',
'🈑' => '字',
'🈒' => 'å<>Œ',
'🈓' => 'デ',
'🈔' => '二',
'🈕' => '多',
'🈖' => '解',
'🈗' => '天',
'🈘' => '交',
'🈙' => '映',
'🈚' => '無',
'🈛' => '料',
'🈜' => 'å‰<C3A5>',
'ðŸˆ<C5B8>' => '後',
'🈞' => 'å†<C3A5>',
'🈟' => '新',
'🈠' => 'åˆ<C3A5>',
'🈡' => '終',
'🈢' => '生',
'🈣' => '販',
'🈤' => '声',
'🈥' => 'å<>¹',
'🈦' => '演',
'🈧' => '投',
'🈨' => 'æ<>•',
'🈩' => '一',
'🈪' => '三',
'🈫' => 'é<>Š',
'🈬' => '左',
'🈭' => '中',
'🈮' => 'å<>³',
'🈯' => '指',
'🈰' => '走',
'🈱' => '打',
'🈲' => 'ç¦<C3A7>',
'🈳' => '空',
'🈴' => 'å<>ˆ',
'🈵' => '満',
'🈶' => '有',
'🈷' => '月',
'🈸' => '申',
'🈹' => '割',
'🈺' => '営',
'🈻' => 'é…<C3A9>',
'🉀' => '〔本〕',
'ðŸ‰<C5B8>' => '〔三〕',
'🉂' => '〔二〕',
'🉃' => '〔安〕',
'🉄' => '〔点〕',
'🉅' => '〔打〕',
'🉆' => '〔盗〕',
'🉇' => '〔å<C3A5>〕',
'🉈' => '〔敗〕',
'ðŸ‰<C5B8>' => 'å¾—',
'🉑' => 'å<>¯',
'🯰' => '0',
'🯱' => '1',
'🯲' => '2',
'🯳' => '3',
'🯴' => '4',
'🯵' => '5',
'🯶' => '6',
'🯷' => '7',
'🯸' => '8',
'🯹' => '9',
);
<?php
return array (
'À' => 'À',
'AÌ<41>' => 'Ã<>',
'AÌ' => 'Â',
'Ã' => 'Ã',
'Ä' => 'Ä',
'AÌŠ' => 'Ã…',
'Ç' => 'Ç',
'È' => 'È',
'EÌ<45>' => 'É',
'EÌ' => 'Ê',
'Ë' => 'Ë',
'Ì' => 'Ì',
'IÌ<49>' => 'Ã<>',
'IÌ' => 'ÃŽ',
'Ï' => 'Ã<>',
'Ñ' => 'Ñ',
'OÌ€' => 'Ã’',
'OÌ<4F>' => 'Ó',
'OÌ' => 'Ô',
'Õ' => 'Õ',
'Ö' => 'Ö',
'Ù' => 'Ù',
'UÌ<55>' => 'Ú',
'UÌ' => 'Û',
'Ü' => 'Ü',
'YÌ<59>' => 'Ã<>',
'à' => 'à',
'aÌ<61>' => 'á',
'aÌ' => 'â',
'ã' => 'ã',
'ä' => 'ä',
'aÌŠ' => 'Ã¥',
'ç' => 'ç',
'è' => 'è',
'eÌ<65>' => 'é',
'eÌ' => 'ê',
'ë' => 'ë',
'ì' => 'ì',
'iÌ<69>' => 'í',
'iÌ' => 'î',
'ï' => 'ï',
'ñ' => 'ñ',
'ò' => 'ò',
'oÌ<6F>' => 'ó',
'oÌ' => 'ô',
'õ' => 'õ',
'ö' => 'ö',
'ù' => 'ù',
'uÌ<75>' => 'ú',
'uÌ' => 'û',
'ü' => 'ü',
'yÌ<79>' => 'ý',
'ÿ' => 'ÿ',
'AÌ„' => 'Ä€',
'aÌ„' => 'Ä<>',
'Ă' => 'Ă',
'ă' => 'ă',
'Ą' => 'Ą',
'ą' => 'ą',
'CÌ<43>' => 'Ć',
'cÌ<63>' => 'ć',
'CÌ' => 'Ĉ',
'cÌ' => 'ĉ',
'Ċ' => 'Ċ',
'ċ' => 'ċ',
'Č' => 'Č',
'cÌŒ' => 'Ä<>',
'Ď' => 'Ď',
'dÌŒ' => 'Ä<>',
'EÌ„' => 'Ä’',
'ē' => 'ē',
'Ĕ' => 'Ĕ',
'ĕ' => 'ĕ',
'Ė' => 'Ė',
'ė' => 'ė',
'Ę' => 'Ę',
'ę' => 'ę',
'Ě' => 'Ě',
'ě' => 'ě',
'GÌ' => 'Äœ',
'gÌ' => 'Ä<>',
'Ğ' => 'Ğ',
'ğ' => 'ğ',
'Ġ' => 'Ġ',
'ġ' => 'ġ',
'Ģ' => 'Ģ',
'ģ' => 'ģ',
'HÌ' => 'Ĥ',
'hÌ' => 'Ä¥',
'Ĩ' => 'Ĩ',
'ĩ' => 'ĩ',
'Ī' => 'Ī',
'ī' => 'ī',
'Ĭ' => 'Ĭ',
'ĭ' => 'ĭ',
'Į' => 'Į',
'į' => 'į',
'İ' => 'İ',
'JÌ' => 'Ä´',
'jÌ' => 'ĵ',
'Ķ' => 'Ķ',
'ķ' => 'ķ',
'LÌ<4C>' => 'Ĺ',
'lÌ<6C>' => 'ĺ',
'Ļ' => 'Ļ',
'ļ' => 'ļ',
'Ľ' => 'Ľ',
'ľ' => 'ľ',
'NÌ<4E>' => 'Ń',
'nÌ<6E>' => 'Å„',
'Ņ' => 'Ņ',
'ņ' => 'ņ',
'Ň' => 'Ň',
'ň' => 'ň',
'Ō' => 'Ō',
'oÌ„' => 'Å<>',
'Ŏ' => 'Ŏ',
'ŏ' => 'Å<>',
'OÌ' => 'Å<>',
'oÌ' => 'Å‘',
'RÌ<52>' => 'Å”',
'rÌ<72>' => 'Å•',
'Ŗ' => 'Ŗ',
'ŗ' => 'ŗ',
'Ř' => 'Ř',
'ř' => 'ř',
'SÌ<53>' => 'Åš',
'sÌ<73>' => 'Å›',
'SÌ' => 'Åœ',
'sÌ' => 'Å<>',
'Ş' => 'Ş',
'ş' => 'ş',
'Š' => 'Š',
'š' => 'š',
'Ţ' => 'Ţ',
'ţ' => 'ţ',
'Ť' => 'Ť',
'ť' => 'ť',
'Ũ' => 'Ũ',
'ũ' => 'ũ',
'Ū' => 'Ū',
'ū' => 'ū',
'Ŭ' => 'Ŭ',
'ŭ' => 'ŭ',
'UÌŠ' => 'Å®',
'ů' => 'ů',
'UÌ' => 'Å°',
'uÌ' => 'ű',
'Ų' => 'Ų',
'ų' => 'ų',
'WÌ' => 'Å´',
'wÌ' => 'ŵ',
'YÌ' => 'Ŷ',
'yÌ' => 'Å·',
'Ÿ' => 'Ÿ',
'ZÌ<5A>' => 'Ź',
'zÌ<7A>' => 'ź',
'Ż' => 'Ż',
'ż' => 'ż',
'Ž' => 'Ž',
'ž' => 'ž',
'OÌ' => 'Æ ',
'oÌ' => 'Æ¡',
'UÌ' => 'Ư',
'uÌ' => 'Æ°',
'AÌŒ' => 'Ç<>',
'ǎ' => 'ǎ',
'IÌŒ' => 'Ç<>',
'iÌŒ' => 'Ç<>',
'Ǒ' => 'Ǒ',
'ǒ' => 'ǒ',
'Ǔ' => 'Ǔ',
'ǔ' => 'ǔ',
'Ǖ' => 'Ǖ',
'ǖ' => 'ǖ',
'ÃœÌ<C593>' => 'Ç—',
'üÌ<C2BC>' => 'ǘ',
'Ǚ' => 'Ǚ',
'ǚ' => 'ǚ',
'Ǜ' => 'Ǜ',
'ǜ' => 'ǜ',
'Ǟ' => 'Ǟ',
'ǟ' => 'ǟ',
'Ǡ' => 'Ǡ',
'ǡ' => 'ǡ',
'Ǣ' => 'Ǣ',
'ǣ' => 'ǣ',
'Ǧ' => 'Ǧ',
'ǧ' => 'ǧ',
'Ǩ' => 'Ǩ',
'ǩ' => 'ǩ',
'Ǫ' => 'Ǫ',
'ǫ' => 'ǫ',
'Ǭ' => 'Ǭ',
'Ç«Ì„' => 'Ç­',
'Ǯ' => 'Ǯ',
'ǯ' => 'ǯ',
'ǰ' => 'ǰ',
'GÌ<47>' => 'Ç´',
'gÌ<67>' => 'ǵ',
'Ǹ' => 'Ǹ',
'ǹ' => 'ǹ',
'Ã…Ì<E280A6>' => 'Ǻ',
'Ã¥Ì<C2A5>' => 'Ç»',
'ÆÌ<E280A0>' => 'Ǽ',
'æÌ<C2A6>' => 'ǽ',
˜Ì<CB9C>' => 'Ǿ',
¸Ì<C2B8>' => 'Ç¿',
'AÌ<41>' => 'È€',
'aÌ<61>' => 'È<>',
'AÌ' => 'È‚',
'aÌ' => 'ȃ',
'EÌ<45>' => 'È„',
'eÌ<65>' => 'È…',
'EÌ' => 'Ȇ',
'eÌ' => 'ȇ',
'IÌ<49>' => 'Ȉ',
'iÌ<69>' => 'ȉ',
'IÌ' => 'ÈŠ',
'iÌ' => 'È‹',
'OÌ<4F>' => 'ÈŒ',
'oÌ<6F>' => 'È<>',
'OÌ' => 'ÈŽ',
'oÌ' => 'È<>',
'RÌ<52>' => 'È<>',
'rÌ<72>' => 'È‘',
'RÌ' => 'È’',
'rÌ' => 'È“',
'UÌ<55>' => 'È”',
'uÌ<75>' => 'È•',
'UÌ' => 'È–',
'uÌ' => 'È—',
'Ș' => 'Ș',
'ș' => 'ș',
'Ț' => 'Ț',
'ț' => 'ț',
'Ȟ' => 'Ȟ',
'ȟ' => 'ȟ',
'Ȧ' => 'Ȧ',
'ȧ' => 'ȧ',
'Ȩ' => 'Ȩ',
'ȩ' => 'ȩ',
'Ȫ' => 'Ȫ',
'ȫ' => 'ȫ',
'Ȭ' => 'Ȭ',
'ȭ' => 'ȭ',
'Ȯ' => 'Ȯ',
'ȯ' => 'ȯ',
'Ȱ' => 'Ȱ',
'ȱ' => 'ȱ',
'Ȳ' => 'Ȳ',
'ȳ' => 'ȳ',
'¨Ì<C2A8>' => 'Î…',
Ì<E28098>' => 'Ά',
'ΕÌ<E280A2>' => 'Έ',
'ΗÌ<E28094>' => 'Ή',
'ΙÌ<E284A2>' => 'Ί',
'ΟÌ<C5B8>' => 'ÎŒ',
'Î¥Ì<C2A5>' => 'ÎŽ',
'ΩÌ<C2A9>' => 'Î<>',
'ÏŠÌ<C5A0>' => 'Î<>',
'Ϊ' => 'Ϊ',
'Ϋ' => 'Ϋ',
'αÌ<C2B1>' => 'ά',
'εÌ<C2B5>' => 'έ',
'ηÌ<C2B7>' => 'ή',
'ιÌ<C2B9>' => 'ί',
Ì<E280B9>' => 'ΰ',
'ϊ' => 'ϊ',
'ϋ' => 'ϋ',
'οÌ<C2BF>' => 'ÏŒ',
'Ï…Ì<E280A6>' => 'Ï<>',
'ωÌ<E280B0>' => 'ÏŽ',
Ì<E28099>' => 'Ï“',
'ϔ' => 'ϔ',
'Ѐ' => 'Ѐ',
'Ё' => 'Ð<>',
'ГÌ<E2809C>' => 'Ѓ',
'Ї' => 'Ї',
'КÌ<C5A1>' => 'ÐŒ',
'Ѝ' => 'Ð<>',
'Ў' => 'Ў',
'Й' => 'Й',
'й' => 'й',
'ѐ' => 'Ñ<>',
'ё' => 'ё',
'гÌ<C2B3>' => 'Ñ“',
'ї' => 'ї',
'кÌ<C2BA>' => 'Ñœ',
'ѝ' => 'Ñ<>',
'ў' => 'ў',
´Ì<C2B4>' => 'Ѷ',
'ѵÌ<C2B5>' => 'Ñ·',
'Ӂ' => 'Ó<>',
'ӂ' => 'ӂ',
<>̆' => 'Ó<>',
'ӑ' => 'ӑ',
<>̈' => 'Ӓ',
'ӓ' => 'ӓ',
'Ӗ' => 'Ӗ',
'ӗ' => 'ӗ',
'Ӛ' => 'Ӛ',
'ӛ' => 'ӛ',
'Ӝ' => 'Ӝ',
'ӝ' => 'Ó<>',
'Ӟ' => 'Ӟ',
'ӟ' => 'ӟ',
'Ӣ' => 'Ӣ',
'ӣ' => 'ӣ',
'Ӥ' => 'Ӥ',
'ӥ' => 'ӥ',
'Ӧ' => 'Ӧ',
'ӧ' => 'ӧ',
'Ӫ' => 'Ӫ',
'ӫ' => 'ӫ',
'Ӭ' => 'Ӭ',
<>̈' => 'ӭ',
'Ӯ' => 'Ӯ',
'ӯ' => 'ӯ',
'Ӱ' => 'Ӱ',
'ӱ' => 'ӱ',
'Ӳ' => 'Ӳ',
'ӳ' => 'ӳ',
'Ӵ' => 'Ӵ',
'ӵ' => 'ӵ',
'Ӹ' => 'Ӹ',
'ӹ' => 'ӹ',
'آ' => 'آ',
'أ' => 'أ',
'ؤ' => 'ؤ',
'إ' => 'إ',
'ئ' => 'ئ',
'Û•Ù”' => 'Û€',
<>Ù”' => 'Û‚',
'Û’Ù”' => 'Û“',
'ऩ' => 'ऩ',
'ऱ' => 'ऱ',
'ऴ' => 'ऴ',
'ো' => 'ো',
'ৌ' => 'ৌ',
'ୈ' => 'ୈ',
'ୋ' => 'ୋ',
'ୌ' => 'ୌ',
'ஔ' => 'ஔ',
'ொ' => 'ொ',
'ோ' => 'ோ',
'ௌ' => 'ௌ',
'ై' => 'ై',
'ೀ' => 'ೀ',
'ೇ' => 'ೇ',
'ೈ' => 'ೈ',
'ೊ' => 'ೊ',
'ೋ' => 'ೋ',
'ൊ' => 'ൊ',
'ോ' => 'ോ',
'ൌ' => 'ൌ',
'ේ' => 'ේ',
'à·™à·<C3A0>' => 'à·œ',
'ෝ' => 'à·<C3A0>',
'ෞ' => 'ෞ',
'ဦ' => 'ဦ',
'ᬆ' => 'ᬆ',
'ᬈ' => 'ᬈ',
'ᬊ' => 'ᬊ',
'ᬌ' => 'ᬌ',
'á¬<C3A1>ᬵ' => 'ᬎ',
'ᬒ' => 'ᬒ',
'ᬻ' => 'ᬻ',
'ᬽ' => 'ᬽ',
'ᭀ' => 'ᭀ',
'ᭁ' => 'á­<C3A1>',
'ᭃ' => 'ᭃ',
'Ḁ' => 'Ḁ',
'aÌ¥' => 'á¸<C3A1>',
'Ḃ' => 'Ḃ',
'ḃ' => 'ḃ',
'Ḅ' => 'Ḅ',
'ḅ' => 'ḅ',
'Ḇ' => 'Ḇ',
'ḇ' => 'ḇ',
'ÇÌ<E280A1>' => 'Ḉ',
'çÌ<C2A7>' => 'ḉ',
'Ḋ' => 'Ḋ',
'ḋ' => 'ḋ',
'Ḍ' => 'Ḍ',
'dÌ£' => 'á¸<C3A1>',
'Ḏ' => 'Ḏ',
'ḏ' => 'á¸<C3A1>',
'Ḑ' => 'á¸<C3A1>',
'ḑ' => 'ḑ',
'Ḓ' => 'Ḓ',
'ḓ' => 'ḓ',
'Ḕ' => 'Ḕ',
'ḕ' => 'ḕ',
Ì<E28099>' => 'Ḗ',
'Ä“Ì<E2809C>' => 'ḗ',
'Ḙ' => 'Ḙ',
'ḙ' => 'ḙ',
'Ḛ' => 'Ḛ',
'ḛ' => 'ḛ',
'Ḝ' => 'Ḝ',
'ḝ' => 'á¸<C3A1>',
'Ḟ' => 'Ḟ',
'ḟ' => 'ḟ',
'Ḡ' => 'Ḡ',
'ḡ' => 'ḡ',
'Ḣ' => 'Ḣ',
'ḣ' => 'ḣ',
'Ḥ' => 'Ḥ',
'ḥ' => 'ḥ',
'Ḧ' => 'Ḧ',
'ḧ' => 'ḧ',
'Ḩ' => 'Ḩ',
'ḩ' => 'ḩ',
'Ḫ' => 'Ḫ',
'ḫ' => 'ḫ',
'Ḭ' => 'Ḭ',
'ḭ' => 'ḭ',
<>Ì<EFBFBD>' => 'Ḯ',
'ïÌ<C2AF>' => 'ḯ',
'KÌ<4B>' => 'Ḱ',
'kÌ<6B>' => 'ḱ',
'Ḳ' => 'Ḳ',
'ḳ' => 'ḳ',
'Ḵ' => 'Ḵ',
'ḵ' => 'ḵ',
'Ḷ' => 'Ḷ',
'ḷ' => 'ḷ',
'Ḹ' => 'Ḹ',
'ḹ' => 'ḹ',
'Ḻ' => 'Ḻ',
'ḻ' => 'ḻ',
'Ḽ' => 'Ḽ',
'ḽ' => 'ḽ',
'MÌ<4D>' => 'Ḿ',
'mÌ<6D>' => 'ḿ',
'Ṁ' => 'Ṁ',
'ṁ' => 'á¹<C3A1>',
'Ṃ' => 'Ṃ',
'ṃ' => 'ṃ',
'Ṅ' => 'Ṅ',
'ṅ' => 'ṅ',
'Ṇ' => 'Ṇ',
'ṇ' => 'ṇ',
'Ṉ' => 'Ṉ',
'ṉ' => 'ṉ',
'Ṋ' => 'Ṋ',
'ṋ' => 'ṋ',
'ÕÌ<E280A2>' => 'Ṍ',
'õÌ<C2B5>' => 'á¹<C3A1>',
'Ṏ' => 'Ṏ',
'ṏ' => 'á¹<C3A1>',
'Ṑ' => 'á¹<C3A1>',
<>̀' => 'ṑ',
'ÅŒÌ<C592>' => 'á¹’',
<>Ì<EFBFBD>' => 'ṓ',
'PÌ<50>' => 'á¹”',
'pÌ<70>' => 'ṕ',
'Ṗ' => 'Ṗ',
'ṗ' => 'ṗ',
'Ṙ' => 'Ṙ',
'ṙ' => 'ṙ',
'Ṛ' => 'Ṛ',
'ṛ' => 'ṛ',
'Ṝ' => 'Ṝ',
'ṝ' => 'á¹<C3A1>',
'Ṟ' => 'Ṟ',
'ṟ' => 'ṟ',
'Ṡ' => 'Ṡ',
'ṡ' => 'ṡ',
'SÌ£' => 'á¹¢',
'ṣ' => 'ṣ',
'Ṥ' => 'Ṥ',
'ṥ' => 'ṥ',
'Ṧ' => 'Ṧ',
'ṧ' => 'ṧ',
'Ṩ' => 'Ṩ',
'ṩ' => 'ṩ',
'Ṫ' => 'Ṫ',
'ṫ' => 'ṫ',
'Ṭ' => 'Ṭ',
'ṭ' => 'ṭ',
'Ṯ' => 'Ṯ',
'ṯ' => 'ṯ',
'TÌ­' => 'á¹°',
'tÌ­' => 'á¹±',
'Ṳ' => 'Ṳ',
'ṳ' => 'ṳ',
'UÌ°' => 'á¹´',
'uÌ°' => 'á¹µ',
'Ṷ' => 'Ṷ',
'uÌ­' => 'á¹·',
'ŨÌ<C2A8>' => 'Ṹ',
'Å©Ì<C2A9>' => 'á¹¹',
'Ṻ' => 'Ṻ',
'ṻ' => 'ṻ',
'Ṽ' => 'Ṽ',
'ṽ' => 'ṽ',
'VÌ£' => 'á¹¾',
'ṿ' => 'ṿ',
'Ẁ' => 'Ẁ',
'wÌ€' => 'áº<C3A1>',
'WÌ<57>' => 'Ẃ',
'wÌ<77>' => 'ẃ',
'Ẅ' => 'Ẅ',
'ẅ' => 'ẅ',
'Ẇ' => 'Ẇ',
'ẇ' => 'ẇ',
'Ẉ' => 'Ẉ',
'ẉ' => 'ẉ',
'Ẋ' => 'Ẋ',
'ẋ' => 'ẋ',
'Ẍ' => 'Ẍ',
'ẍ' => 'áº<C3A1>',
'Ẏ' => 'Ẏ',
'ẏ' => 'áº<C3A1>',
'ZÌ' => 'áº<C3A1>',
'zÌ' => 'ẑ',
'Ẓ' => 'Ẓ',
'ẓ' => 'ẓ',
'Ẕ' => 'Ẕ',
'ẕ' => 'ẕ',
'ẖ' => 'ẖ',
'ẗ' => 'ẗ',
'ẘ' => 'ẘ',
'ẙ' => 'ẙ',
'ẛ' => 'ẛ',
'Ạ' => 'Ạ',
'ạ' => 'ạ',
'Ả' => 'Ả',
'ả' => 'ả',
Ì<E2809A>' => 'Ấ',
'âÌ<C2A2>' => 'ấ',
'Ầ' => 'Ầ',
'ầ' => 'ầ',
'Ẩ' => 'Ẩ',
'ẩ' => 'ẩ',
'Ẫ' => 'Ẫ',
'ẫ' => 'ẫ',
'Ậ' => 'Ậ',
'ậ' => 'ậ',
Ì<E2809A>' => 'Ắ',
'ăÌ<C692>' => 'ắ',
'Ằ' => 'Ằ',
'ằ' => 'ằ',
'Ẳ' => 'Ẳ',
'ẳ' => 'ẳ',
'Ẵ' => 'Ẵ',
'ẵ' => 'ẵ',
'Ặ' => 'Ặ',
'ặ' => 'ặ',
'Ẹ' => 'Ẹ',
'ẹ' => 'ẹ',
'Ẻ' => 'Ẻ',
'ẻ' => 'ẻ',
'Ẽ' => 'Ẽ',
'ẽ' => 'ẽ',
'ÊÌ<C5A0>' => 'Ế',
'êÌ<C2AA>' => 'ế',
'Ề' => 'Ề',
'ề' => 'á»<C3A1>',
'Ể' => 'Ể',
'ể' => 'ể',
'Ễ' => 'Ễ',
'ễ' => 'ễ',
'Ệ' => 'Ệ',
'ệ' => 'ệ',
'Ỉ' => 'Ỉ',
'ỉ' => 'ỉ',
'Ị' => 'Ị',
'ị' => 'ị',
'Ọ' => 'Ọ',
'oÌ£' => 'á»<C3A1>',
'Ỏ' => 'Ỏ',
'ỏ' => 'á»<C3A1>',
'ÔÌ<E2809D>' => 'á»<C3A1>',
´Ì<C2B4>' => 'ố',
'Ồ' => 'Ồ',
'ồ' => 'ồ',
'Ổ' => 'Ổ',
'ổ' => 'ổ',
'Ỗ' => 'Ỗ',
'ỗ' => 'ỗ',
'Ộ' => 'Ộ',
'á»<C3A1>Ì' => 'á»™',
 Ì<C2A0>' => 'Ớ',
'Æ¡Ì<C2A1>' => 'á»›',
'Ờ' => 'Ờ',
'Æ¡Ì€' => 'á»<C3A1>',
'Ở' => 'Ở',
'ở' => 'ở',
'Ỡ' => 'Ỡ',
'ỡ' => 'ỡ',
'Ợ' => 'Ợ',
'ợ' => 'ợ',
'Ụ' => 'Ụ',
'ụ' => 'ụ',
'Ủ' => 'Ủ',
'ủ' => 'ủ',
'ƯÌ<C2AF>' => 'Ứ',
'Æ°Ì<C2B0>' => 'ứ',
'Ừ' => 'Ừ',
'ừ' => 'ừ',
'Ử' => 'Ử',
'ử' => 'ử',
'Ữ' => 'Ữ',
'ữ' => 'ữ',
'Ự' => 'Ự',
'Æ°Ì£' => 'á»±',
'Ỳ' => 'Ỳ',
'ỳ' => 'ỳ',
'YÌ£' => 'á»´',
'ỵ' => 'ỵ',
'Ỷ' => 'Ỷ',
'ỷ' => 'ỷ',
'Ỹ' => 'Ỹ',
'ỹ' => 'ỹ',
'ἀ' => 'ἀ',
'ἁ' => 'á¼<C3A1>',
'ἂ' => 'ἂ',
'á¼<C3A1>Ì€' => 'ἃ',
'á¼€Ì<E282AC>' => 'ἄ',
'á¼<C3A1>Ì<EFBFBD>' => 'á¼…',
'ἆ' => 'ἆ',
'á¼<C3A1>Í' => 'ἇ',
'Ἀ' => 'Ἀ',
'Ἁ' => 'Ἁ',
'Ἂ' => 'Ἂ',
'Ἃ' => 'Ἃ',
'ἈÌ<CB86>' => 'Ἄ',
'ἉÌ<E280B0>' => 'á¼<C3A1>',
'Ἆ' => 'Ἆ',
'Ἇ' => 'á¼<C3A1>',
'ἐ' => 'á¼<C3A1>',
'ἑ' => 'ἑ',
'á¼<C3A1>Ì€' => 'á¼’',
'ἓ' => 'ἓ',
'á¼<C3A1>Ì<EFBFBD>' => 'á¼”',
'á¼Ì<E28098>' => 'ἕ',
'Ἐ' => 'Ἐ',
'Ἑ' => 'Ἑ',
'Ἒ' => 'Ἒ',
'Ἓ' => 'Ἓ',
'ἘÌ<CB9C>' => 'Ἔ',
'á¼™Ì<E284A2>' => 'á¼<C3A1>',
'ἠ' => 'ἠ',
'ἡ' => 'ἡ',
'ἢ' => 'ἢ',
'ἣ' => 'ἣ',
'á¼ Ì<C2A0>' => 'ἤ',
'ἡÌ<C2A1>' => 'á¼¥',
'ἦ' => 'ἦ',
'ἧ' => 'ἧ',
'Ἠ' => 'Ἠ',
'Ἡ' => 'Ἡ',
'Ἢ' => 'Ἢ',
'Ἣ' => 'Ἣ',
'ἨÌ<C2A8>' => 'Ἤ',
'ἩÌ<C2A9>' => 'á¼­',
'Ἦ' => 'Ἦ',
'Ἧ' => 'Ἧ',
'ἰ' => 'ἰ',
'ἱ' => 'ἱ',
'á¼°Ì€' => 'á¼²',
'ἳ' => 'ἳ',
'á¼°Ì<C2B0>' => 'á¼´',
'á¼±Ì<C2B1>' => 'á¼µ',
'ἶ' => 'ἶ',
'ἷ' => 'ἷ',
'Ἰ' => 'Ἰ',
'Ἱ' => 'Ἱ',
'Ἲ' => 'Ἲ',
'Ἳ' => 'Ἳ',
'ἸÌ<C2B8>' => 'á¼¼',
'á¼¹Ì<C2B9>' => 'á¼½',
'Ἶ' => 'Ἶ',
'Ἷ' => 'Ἷ',
'ὀ' => 'ὀ',
'ὁ' => 'á½<C3A1>',
'ὂ' => 'ὂ',
'á½<C3A1>Ì€' => 'ὃ',
'á½€Ì<E282AC>' => 'ὄ',
'á½<C3A1>Ì<EFBFBD>' => 'á½…',
'Ὀ' => 'Ὀ',
'Ὁ' => 'Ὁ',
'Ὂ' => 'Ὂ',
'Ὃ' => 'Ὃ',
'ὈÌ<CB86>' => 'Ὄ',
'ὉÌ<E280B0>' => 'á½<C3A1>',
'Ï…Ì“' => 'á½<C3A1>',
'ὑ' => 'ὑ',
'á½<C3A1>Ì€' => 'á½’',
'ὓ' => 'ὓ',
'á½<C3A1>Ì<EFBFBD>' => 'á½”',
'á½Ì<E28098>' => 'ὕ',
'á½<C3A1>Í' => 'á½–',
'ὗ' => 'ὗ',
'Ὑ' => 'Ὑ',
'Ὓ' => 'Ὓ',
'á½™Ì<E284A2>' => 'á½<C3A1>',
'Ὗ' => 'Ὗ',
'ὠ' => 'ὠ',
'ὡ' => 'ὡ',
'ὢ' => 'ὢ',
'ὣ' => 'ὣ',
'á½ Ì<C2A0>' => 'ὤ',
'ὡÌ<C2A1>' => 'á½¥',
'ὦ' => 'ὦ',
'ὧ' => 'ὧ',
'Ὠ' => 'Ὠ',
'Ὡ' => 'Ὡ',
'Ὢ' => 'Ὢ',
'Ὣ' => 'Ὣ',
'ὨÌ<C2A8>' => 'Ὤ',
'ὩÌ<C2A9>' => 'á½­',
'Ὦ' => 'Ὦ',
'Ὧ' => 'Ὧ',
'ὰ' => 'ὰ',
'ὲ' => 'ὲ',
'ὴ' => 'ὴ',
'ὶ' => 'ὶ',
'ὸ' => 'ὸ',
'ὺ' => 'ὺ',
'ὼ' => 'ὼ',
'ᾀ' => 'ᾀ',
'á¼<C3A1>Í…' => 'á¾<C3A1>',
'ᾂ' => 'ᾂ',
'ᾃ' => 'ᾃ',
'ᾄ' => 'ᾄ',
'á¼…Í…' => 'á¾…',
'ᾆ' => 'ᾆ',
'ᾇ' => 'ᾇ',
'ᾈ' => 'ᾈ',
'ᾉ' => 'ᾉ',
'ᾊ' => 'ᾊ',
'ᾋ' => 'ᾋ',
'ᾌ' => 'ᾌ',
'á¼<C3A1>Í…' => 'á¾<C3A1>',
'ᾎ' => 'ᾎ',
'á¼<C3A1>Í…' => 'á¾<C3A1>',
'á¼ Í…' => 'á¾<C3A1>',
'ᾑ' => 'ᾑ',
'ᾒ' => 'ᾒ',
'ᾓ' => 'ᾓ',
'ᾔ' => 'ᾔ',
'ᾕ' => 'ᾕ',
'ᾖ' => 'ᾖ',
'ᾗ' => 'ᾗ',
'ᾘ' => 'ᾘ',
'ᾙ' => 'ᾙ',
'ᾚ' => 'ᾚ',
'ᾛ' => 'ᾛ',
'ᾜ' => 'ᾜ',
'á¼­Í…' => 'á¾<C3A1>',
'ᾞ' => 'ᾞ',
'ᾟ' => 'ᾟ',
'á½ Í…' => 'á¾ ',
'ᾡ' => 'ᾡ',
'ᾢ' => 'ᾢ',
'ᾣ' => 'ᾣ',
'ᾤ' => 'ᾤ',
'ᾥ' => 'ᾥ',
'ᾦ' => 'ᾦ',
'ᾧ' => 'ᾧ',
'ᾨ' => 'ᾨ',
'ᾩ' => 'ᾩ',
'ᾪ' => 'ᾪ',
'ᾫ' => 'ᾫ',
'ᾬ' => 'ᾬ',
'á½­Í…' => 'á¾­',
'ᾮ' => 'ᾮ',
'ᾯ' => 'ᾯ',
'ᾰ' => 'ᾰ',
'ᾱ' => 'ᾱ',
'á½°Í…' => 'á¾²',
'ᾳ' => 'ᾳ',
'ᾴ' => 'ᾴ',
'ᾶ' => 'ᾶ',
'ᾷ' => 'ᾷ',
'Ᾰ' => 'Ᾰ',
'Ᾱ' => 'Ᾱ',
'Ὰ' => 'Ὰ',
'ᾼ' => 'ᾼ',
'῁' => 'á¿<C3A1>',
'á½´Í…' => 'á¿‚',
'ῃ' => 'ῃ',
'ῄ' => 'ῄ',
'ῆ' => 'ῆ',
'ῇ' => 'ῇ',
'Ὲ' => 'Ὲ',
'Ὴ' => 'Ὴ',
'ῌ' => 'ῌ',
'῍' => 'á¿<C3A1>',
'᾿Ì<C2BF>' => 'á¿Ž',
'῏' => 'á¿<C3A1>',
'ῐ' => 'á¿<C3A1>',
'ῑ' => 'ῑ',
'ÏŠÌ€' => 'á¿’',
'ῖ' => 'ῖ',
'ÏŠÍ‚' => 'á¿—',
'Ῐ' => 'Ῐ',
'Ῑ' => 'Ῑ',
'Ὶ' => 'Ὶ',
'῝' => 'á¿<C3A1>',
'῾Ì<C2BE>' => 'á¿ž',
'῟' => '῟',
'ῠ' => 'ῠ',
'Ï…Ì„' => 'á¿¡',
'Ï‹Ì€' => 'á¿¢',
<>̓' => 'ῤ',
<>Ì”' => 'á¿¥',
'ῦ' => 'ῦ',
'ῧ' => 'ῧ',
'Ῠ' => 'Ῠ',
'Ῡ' => 'Ῡ',
'Ὺ' => 'Ὺ',
'Ῥ' => 'Ῥ',
'῭' => '῭',
'ῲ' => 'ῲ',
'ῳ' => 'ῳ',
'ÏŽÍ…' => 'á¿´',
'ῶ' => 'ῶ',
'ῷ' => 'ῷ',
'Ὸ' => 'Ὸ',
'Ὼ' => 'Ὼ',
'ῼ' => 'ῼ',
'â†<C3A2≯' => '↚',
'↛' => '↛',
'↮' => '↮',
'â‡<C3A2≯' => 'â‡<C3A2>',
'⇎' => '⇎',
'⇏' => 'â‡<C3A2>',
'∄' => '∄',
'∉' => '∉',
'∌' => '∌',
'∤' => '∤',
'∦' => '∦',
'≁' => 'â‰<C3A2>',
'≄' => '≄',
'≇' => '≇',
'≉' => '≉',
'≠' => '≠',
'≢' => '≢',
'â‰<C3A2≯' => '≭',
'≮' => '≮',
'≯' => '≯',
'≰' => '≰',
'≱' => '≱',
'≴' => '≴',
'≵' => '≵',
'≸' => '≸',
'≹' => '≹',
'⊀' => '⊀',
'⊁' => 'âŠ<C3A2>',
'⊄' => '⊄',
'⊅' => '⊅',
'⊈' => '⊈',
'⊉' => '⊉',
'⊬' => '⊬',
'⊭' => '⊭',
'⊮' => '⊮',
'⊯' => '⊯',
'⋠' => '⋠',
'⋡' => '⋡',
'⋢' => '⋢',
'⋣' => '⋣',
'⋪' => '⋪',
'⋫' => '⋫',
'⋬' => '⋬',
'⋭' => '⋭',
<>ã™' => 'ã<>Œ',
<><C3A3>ã™' => 'ã<>Ž',
<><C3A3>ã™' => 'ã<><C3A3>',
<>ã™' => 'ã<>',
<>“ã™' => 'ã<>”',
<>•ã™' => 'ã<>',
<>—ã™' => 'ã<>˜',
<>™ã™' => 'ã<>š',
<>ã™' => 'ã<>œ',
<><C3A3>ã™' => 'ã<>ž',
<>Ÿã™' => 'ã<> ',
<>¡ã™' => 'ã<>¢',
<>¤ã™' => 'ã<>¥',
<>¦ã™' => 'ã<>§',
<>¨ã™' => 'ã<>©',
<>¯ã™' => 'ã<>°',
<>¯ãš' => 'ã<>±',
<>²ã™' => 'ã<>³',
<>²ãš' => 'ã<>´',
<>µã™' => 'ã<>¶',
<>µãš' => 'ã<>·',
<>¸ã™' => 'ã<>¹',
<>¸ãš' => 'ã<>º',
<>»ã™' => 'ã<>¼',
<>»ãš' => 'ã<>½',
<>†ã™' => 'ã‚”',
<C3A3>ã™' => 'ã‚ž',
'ガ' => 'ガ',
'ã‚­ã‚™' => 'ã‚®',
'グ' => 'グ',
'ゲ' => 'ゲ',
'ゴ' => 'ゴ',
'ザ' => 'ザ',
'ジ' => 'ジ',
'ズ' => 'ズ',
'ゼ' => 'ゼ',
'ゾ' => 'ゾ',
'ダ' => 'ダ',
'ãƒ<C3A3>ã™' => 'ヂ',
'ヅ' => 'ヅ',
'デ' => 'デ',
'ド' => 'ド',
'ãƒ<C3A3>ã™' => 'ãƒ<C3A3>',
'ãƒ<C3A3>ãš' => 'パ',
'ビ' => 'ビ',
'ピ' => 'ピ',
'ブ' => 'ブ',
'プ' => 'プ',
'ベ' => 'ベ',
'ペ' => 'ペ',
'ボ' => 'ボ',
'ポ' => 'ãƒ<C3A3>',
'ヴ' => 'ヴ',
'ヷ' => 'ヷ',
'ヸ' => 'ヸ',
'ヹ' => 'ヹ',
'ヺ' => 'ヺ',
'ヾ' => 'ヾ',
'𑂚' => '𑂚',
'𑂜' => '𑂜',
'𑂫' => '𑂫',
'𑄮' => '𑄮',
'𑄯' => '𑄯',
<C3B0>‡ðŒ¾' => 'ð<C3B0>',
<C3B0>‡ð<C3B0>—' => 'ð<C3B0>Œ',
'𑒻' => '𑒻',
'𑒼' => '𑒼',
'𑒾' => '𑒾',
'𑖺' => '𑖺',
'𑖻' => '𑖻',
'𑤸' => '𑤸',
);
<?php
return array (
'Ì€' => 230,
<>' => 230,
'Ì‚' => 230,
'̃' => 230,
'Ì„' => 230,
'Ì…' => 230,
'̆' => 230,
'̇' => 230,
'̈' => 230,
'̉' => 230,
'ÌŠ' => 230,
'Ì‹' => 230,
'̌' => 230,
<>' => 230,
'ÌŽ' => 230,
<>' => 230,
<>' => 230,
'Ì‘' => 230,
'Ì’' => 230,
'Ì“' => 230,
'Ì”' => 230,
'Ì•' => 232,
'Ì–' => 220,
'Ì—' => 220,
'̘' => 220,
'Ì™' => 220,
'Ìš' => 232,
'Ì›' => 216,
'̜' => 220,
<>' => 220,
'Ìž' => 220,
'ÌŸ' => 220,
'Ì ' => 220,
'Ì¡' => 202,
'Ì¢' => 202,
'Ì£' => 220,
'̤' => 220,
'Ì¥' => 220,
'̦' => 220,
'̧' => 202,
'̨' => 202,
'Ì©' => 220,
'̪' => 220,
'Ì«' => 220,
'̬' => 220,
'Ì­' => 220,
'Ì®' => 220,
'̯' => 220,
'Ì°' => 220,
'̱' => 220,
'̲' => 220,
'̳' => 220,
'Ì´' => 1,
'̵' => 1,
'̶' => 1,
'Ì·' => 1,
'̸' => 1,
'̹' => 220,
'̺' => 220,
'Ì»' => 220,
'̼' => 220,
'̽' => 230,
'̾' => 230,
'Ì¿' => 230,
'Í€' => 230,
<>' => 230,
'Í‚' => 230,
'̓' => 230,
'Í„' => 230,
'Í…' => 240,
'͆' => 230,
'͇' => 220,
'͈' => 220,
'͉' => 220,
'ÍŠ' => 230,
'Í‹' => 230,
'͌' => 230,
<>' => 220,
'ÍŽ' => 220,
<>' => 230,
'Í‘' => 230,
'Í’' => 230,
'Í“' => 220,
'Í”' => 220,
'Í•' => 220,
'Í–' => 220,
'Í—' => 230,
'͘' => 232,
'Í™' => 220,
'Íš' => 220,
'Í›' => 230,
'͜' => 233,
<>' => 234,
'Íž' => 234,
'ÍŸ' => 233,
'Í ' => 234,
'Í¡' => 234,
'Í¢' => 233,
'Í£' => 230,
'ͤ' => 230,
'Í¥' => 230,
'ͦ' => 230,
'ͧ' => 230,
'ͨ' => 230,
'Í©' => 230,
'ͪ' => 230,
'Í«' => 230,
'ͬ' => 230,
'Í­' => 230,
'Í®' => 230,
'ͯ' => 230,
'Òƒ' => 230,
'Ò„' => 230,
'Ò…' => 230,
'Ò†' => 230,
'Ò‡' => 230,
'Ö‘' => 220,
'Ö’' => 230,
'Ö“' => 230,
'Ö”' => 230,
'Ö•' => 230,
'Ö–' => 220,
'Ö—' => 230,
'Ö˜' => 230,
'Ö™' => 230,
'Öš' => 222,
'Ö›' => 220,
'֜' => 230,
<>' => 230,
'Öž' => 230,
'ÖŸ' => 230,
'Ö ' => 230,
'Ö¡' => 230,
'Ö¢' => 220,
'Ö£' => 220,
'Ö¤' => 220,
'Ö¥' => 220,
'Ö¦' => 220,
'Ö§' => 220,
'Ö¨' => 230,
'Ö©' => 230,
'Öª' => 220,
'Ö«' => 230,
'Ö¬' => 230,
'Ö­' => 222,
'Ö®' => 228,
'Ö¯' => 230,
'Ö°' => 10,
'Ö±' => 11,
'Ö²' => 12,
'Ö³' => 13,
'Ö´' => 14,
'Öµ' => 15,
'Ö¶' => 16,
'Ö·' => 17,
'Ö¸' => 18,
'Ö¹' => 19,
'Öº' => 19,
'Ö»' => 20,
'Ö¼' => 21,
'Ö½' => 22,
'Ö¿' => 23,
'×<>' => 24,
'×' => 25,
'ׄ' => 230,
'×…' => 220,
'ׇ' => 18,
<>' => 230,
'Ø‘' => 230,
'Ø’' => 230,
'Ø“' => 230,
'Ø”' => 230,
'Ø•' => 230,
'Ø–' => 230,
'Ø—' => 230,
'ؘ' => 30,
'Ø™' => 31,
'Øš' => 32,
'Ù‹' => 27,
'ٌ' => 28,
<>' => 29,
'ÙŽ' => 30,
<>' => 31,
<>' => 32,
'Ù‘' => 33,
'Ù’' => 34,
'Ù“' => 230,
'Ù”' => 230,
'Ù•' => 220,
'Ù–' => 220,
'Ù—' => 230,
'Ù˜' => 230,
'Ù™' => 230,
'Ùš' => 230,
'Ù›' => 230,
'ٜ' => 220,
<>' => 230,
'Ùž' => 230,
'ÙŸ' => 220,
'Ù°' => 35,
'Û–' => 230,
'Û—' => 230,
'Û˜' => 230,
'Û™' => 230,
'Ûš' => 230,
'Û›' => 230,
'ۜ' => 230,
'ÛŸ' => 230,
'Û ' => 230,
'Û¡' => 230,
'Û¢' => 230,
'Û£' => 220,
'Û¤' => 230,
'Û§' => 230,
'Û¨' => 230,
'Ûª' => 220,
'Û«' => 230,
'Û¬' => 230,
'Û­' => 220,
'Ü‘' => 36,
'Ü°' => 230,
'ܱ' => 220,
'ܲ' => 230,
'ܳ' => 230,
'Ü´' => 220,
'ܵ' => 230,
'ܶ' => 230,
'Ü·' => 220,
'ܸ' => 220,
'ܹ' => 220,
'ܺ' => 230,
'Ü»' => 220,
'ܼ' => 220,
'ܽ' => 230,
'ܾ' => 220,
'Ü¿' => 230,
'Ý€' => 230,
<>' => 230,
'Ý‚' => 220,
'݃' => 230,
'Ý„' => 220,
'Ý…' => 230,
'݆' => 220,
'݇' => 230,
'݈' => 220,
'݉' => 230,
'ÝŠ' => 230,
'ß«' => 230,
'߬' => 230,
'ß­' => 230,
'ß®' => 230,
'߯' => 230,
'ß°' => 230,
'ß±' => 230,
'ß²' => 220,
'ß³' => 230,
'ß½' => 220,
'à –' => 230,
'à —' => 230,
'à ˜' => 230,
'à ™' => 230,
'à ›' => 230,
'ࠜ' => 230,
 <C3A0>' => 230,
'à ž' => 230,
'à Ÿ' => 230,
'à  ' => 230,
'à ¡' => 230,
'à ¢' => 230,
'à £' => 230,
'à ¥' => 230,
'à ¦' => 230,
'à §' => 230,
'à ©' => 230,
'à ª' => 230,
'à «' => 230,
'à ¬' => 230,
'à ­' => 230,
'à¡™' => 220,
'à¡š' => 220,
'à¡›' => 220,
'࣓' => 220,
'ࣔ' => 230,
'ࣕ' => 230,
'ࣖ' => 230,
'ࣗ' => 230,
'ࣘ' => 230,
'ࣙ' => 230,
'ࣚ' => 230,
'ࣛ' => 230,
'ࣜ' => 230,
'à£<C3A0>' => 230,
'ࣞ' => 230,
'ࣟ' => 230,
'࣠' => 230,
'࣡' => 230,
'ࣣ' => 220,
'ࣤ' => 230,
'ࣥ' => 230,
'ࣦ' => 220,
'ࣧ' => 230,
'ࣨ' => 230,
'ࣩ' => 220,
'࣪' => 230,
'࣫' => 230,
'࣬' => 230,
'࣭' => 220,
'࣮' => 220,
'࣯' => 220,
'ࣰ' => 27,
'ࣱ' => 28,
'ࣲ' => 29,
'ࣳ' => 230,
'ࣴ' => 230,
'ࣵ' => 230,
'ࣶ' => 220,
'ࣷ' => 230,
'ࣸ' => 230,
'ࣹ' => 220,
'ࣺ' => 220,
'ࣻ' => 230,
'ࣼ' => 230,
'ࣽ' => 230,
'ࣾ' => 230,
'ࣿ' => 230,
'़' => 7,
'à¥<C3A0>' => 9,
'॑' => 230,
'॒' => 220,
'॓' => 230,
'॔' => 230,
'়' => 7,
'à§<C3A0>' => 9,
'৾' => 230,
'਼' => 7,
'à©<C3A0>' => 9,
'઼' => 7,
'à«<C3A0>' => 9,
'଼' => 7,
­<C3A0>' => 9,
'à¯<C3A0>' => 9,
'à±<C3A0>' => 9,
'ౕ' => 84,
'à±–' => 91,
'಼' => 7,
'à³<C3A0>' => 9,
'à´»' => 9,
'à´¼' => 9,
'àµ<C3A0>' => 9,
'à·Š' => 9,
'ุ' => 103,
'ู' => 103,
'ฺ' => 9,
'่' => 107,
'้' => 107,
'๊' => 107,
'๋' => 107,
'ຸ' => 118,
'ູ' => 118,
'຺' => 9,
'່' => 122,
'້' => 122,
'໊' => 122,
'໋' => 122,
'༘' => 220,
'༙' => 220,
'༵' => 220,
'༷' => 220,
'༹' => 216,
'ཱ' => 129,
'ི' => 130,
'ུ' => 132,
'ེ' => 130,
'ཻ' => 130,
'ོ' => 130,
'ཽ' => 130,
'ྀ' => 130,
'ྂ' => 230,
'ྃ' => 230,
'྄' => 9,
'྆' => 230,
'྇' => 230,
'࿆' => 220,
'့' => 7,
'္' => 9,
'်' => 9,
<C3A1>' => 220,
<><C3A1>' => 230,
<>ž' => 230,
<>Ÿ' => 230,
'᜔' => 9,
'᜴' => 9,
'្' => 9,
'áŸ<C3A1>' => 230,
'ᢩ' => 228,
'᤹' => 222,
'᤺' => 230,
'᤻' => 220,
'ᨗ' => 230,
'ᨘ' => 220,
'á© ' => 9,
'᩵' => 230,
'᩶' => 230,
'á©·' => 230,
'᩸' => 230,
'᩹' => 230,
'᩺' => 230,
'á©»' => 230,
'᩼' => 230,
'á©¿' => 220,
'᪰' => 230,
'᪱' => 230,
'᪲' => 230,
'᪳' => 230,
'᪴' => 230,
'᪵' => 220,
'᪶' => 220,
'᪷' => 220,
'᪸' => 220,
'᪹' => 220,
'᪺' => 220,
'᪻' => 230,
'᪼' => 230,
'᪽' => 220,
'ᪿ' => 220,
'á«€' => 220,
'᬴' => 7,
'á­„' => 9,
'á­«' => 230,
'á­¬' => 220,
'á­­' => 230,
'á­®' => 230,
'á­¯' => 230,
'á­°' => 230,
'á­±' => 230,
'á­²' => 230,
'á­³' => 230,
'᮪' => 9,
'᮫' => 9,
'᯦' => 7,
'᯲' => 9,
'᯳' => 9,
'á°·' => 7,
'á³<C3A1>' => 230,
'᳑' => 230,
'á³’' => 230,
'á³”' => 1,
'᳕' => 220,
'á³–' => 220,
'á³—' => 220,
'᳘' => 220,
'á³™' => 220,
'᳚' => 230,
'á³›' => 230,
'᳜' => 220,
'á³<C3A1>' => 220,
'᳞' => 220,
'᳟' => 220,
'á³ ' => 230,
'á³¢' => 1,
'á³£' => 1,
'᳤' => 1,
'á³¥' => 1,
'᳦' => 1,
'᳧' => 1,
'᳨' => 1,
'á³­' => 220,
'á³´' => 230,
'᳸' => 230,
'á³¹' => 230,
'á·€' => 230,
'á·<C3A1>' => 230,
'á·‚' => 220,
'á·ƒ' => 230,
'á·„' => 230,
'á·…' => 230,
'á·†' => 230,
'á·‡' => 230,
'á·ˆ' => 230,
'á·‰' => 230,
'á·Š' => 220,
'á·‹' => 230,
'᷌' => 230,
'á·<C3A1>' => 234,
'á·Ž' => 214,
'á·<C3A1>' => 220,
'á·<C3A1>' => 202,
'á·‘' => 230,
'á·’' => 230,
'á·“' => 230,
'á·”' => 230,
'á·•' => 230,
'á·–' => 230,
'á·—' => 230,
'á·˜' => 230,
'á·™' => 230,
'á·š' => 230,
'á·›' => 230,
'ᷜ' => 230,
'á·<C3A1>' => 230,
'á·ž' => 230,
'á·Ÿ' => 230,
'á· ' => 230,
'á·¡' => 230,
'á·¢' => 230,
'á·£' => 230,
'á·¤' => 230,
'á·¥' => 230,
'á·¦' => 230,
'á·§' => 230,
'á·¨' => 230,
'á·©' => 230,
'á·ª' => 230,
'á·«' => 230,
'á·¬' => 230,
'á·­' => 230,
'á·®' => 230,
'á·¯' => 230,
'á·°' => 230,
'á·±' => 230,
'á·²' => 230,
'á·³' => 230,
'á·´' => 230,
'á·µ' => 230,
'á·¶' => 232,
'á··' => 228,
'á·¸' => 228,
'á·¹' => 220,
'á·»' => 230,
'á·¼' => 233,
'á·½' => 220,
'á·¾' => 230,
'á·¿' => 220,
'âƒ<C3A2>' => 230,
'⃑' => 230,
'⃒' => 1,
'⃓' => 1,
'⃔' => 230,
'⃕' => 230,
'⃖' => 230,
'⃗' => 230,
'⃘' => 1,
'⃙' => 1,
'⃚' => 1,
'⃛' => 230,
'⃜' => 230,
'⃡' => 230,
'⃥' => 1,
'⃦' => 1,
'⃧' => 230,
'⃨' => 220,
'⃩' => 230,
'⃪' => 1,
'⃫' => 1,
'⃬' => 220,
'⃭' => 220,
'⃮' => 220,
'⃯' => 220,
'⃰' => 230,
'⳯' => 230,
'â³°' => 230,
'â³±' => 230,
'⵿' => 9,
'â· ' => 230,
'â·¡' => 230,
'â·¢' => 230,
'â·£' => 230,
'â·¤' => 230,
'â·¥' => 230,
'â·¦' => 230,
'â·§' => 230,
'â·¨' => 230,
'â·©' => 230,
'â·ª' => 230,
'â·«' => 230,
'â·¬' => 230,
'â·­' => 230,
'â·®' => 230,
'â·¯' => 230,
'â·°' => 230,
'â·±' => 230,
'â·²' => 230,
'â·³' => 230,
'â·´' => 230,
'â·µ' => 230,
'â·¶' => 230,
'â··' => 230,
'â·¸' => 230,
'â·¹' => 230,
'â·º' => 230,
'â·»' => 230,
'â·¼' => 230,
'â·½' => 230,
'â·¾' => 230,
'â·¿' => 230,
'〪' => 218,
'〫' => 228,
'〬' => 232,
'〭' => 222,
'〮' => 224,
'〯' => 224,
'ã‚™' => 8,
'ã‚š' => 8,
'꙯' => 230,
'ê™´' => 230,
'ꙵ' => 230,
'ꙶ' => 230,
'ê™·' => 230,
'ꙸ' => 230,
'ꙹ' => 230,
'ꙺ' => 230,
'ê™»' => 230,
'꙼' => 230,
'꙽' => 230,
'êšž' => 230,
'ꚟ' => 230,
'ê›°' => 230,
'ê›±' => 230,
'ê †' => 9,
'ê ¬' => 9,
'꣄' => 9,
'꣠' => 230,
'꣡' => 230,
'꣢' => 230,
'꣣' => 230,
'꣤' => 230,
'꣥' => 230,
'꣦' => 230,
'꣧' => 230,
'꣨' => 230,
'꣩' => 230,
'꣪' => 230,
'꣫' => 230,
'꣬' => 230,
'꣭' => 230,
'꣮' => 230,
'꣯' => 230,
'꣰' => 230,
'꣱' => 230,
'꤫' => 220,
'꤬' => 220,
'꤭' => 220,
'꥓' => 9,
'꦳' => 7,
'꧀' => 9,
'ꪰ' => 230,
'ꪲ' => 230,
'ꪳ' => 230,
'ꪴ' => 220,
'ꪷ' => 230,
'ꪸ' => 230,
'ꪾ' => 230,
'꪿' => 230,
'ê«<C3AA>' => 230,
'꫶' => 9,
'꯭' => 9,
'ﬞ' => 26,
'︠' => 230,
'︡' => 230,
'︢' => 230,
'︣' => 230,
'︤' => 230,
'︥' => 230,
'︦' => 230,
'︧' => 220,
'︨' => 220,
'︩' => 220,
'︪' => 220,
'︫' => 220,
'︬' => 220,
'︭' => 220,
'︮' => 230,
'︯' => 230,
<>‡½' => 220,
<> ' => 220,
<><C3B0>¶' => 230,
<><C3B0>·' => 230,
<><C3B0>¸' => 230,
<><C3B0>¹' => 230,
<><C3B0>º' => 230,
<>¨<EFBFBD>' => 220,
<>¨<EFBFBD>' => 230,
<>¨¸' => 230,
<>¨¹' => 1,
<>¨º' => 220,
<>¨¿' => 9,
<>«¥' => 230,
<>«¦' => 220,
<>´¤' => 230,
<>´¥' => 230,
<>´¦' => 230,
<>´§' => 230,
<>º«' => 230,
<>º¬' => 230,
<>½†' => 220,
<>½‡' => 220,
<>½ˆ' => 230,
<>½‰' => 230,
<>½Š' => 230,
<>½' => 220,
<>½Œ' => 230,
<>½<EFBFBD>' => 220,
<>½Ž' => 220,
<>½<EFBFBD>' => 220,
<>½<EFBFBD>' => 220,
<C3B0>†' => 9,
<C3B0>¿' => 9,
'ð‘‚¹' => 9,
'𑂺' => 7,
'ð‘„€' => 230,
<E28098>' => 230,
'ð‘„‚' => 230,
'ð‘„³' => 9,
'ð‘„´' => 9,
'ð‘…³' => 7,
'𑇀' => 9,
'𑇊' => 7,
'𑈵' => 9,
'𑈶' => 7,
'ð‘‹©' => 7,
'𑋪' => 9,
'𑌻' => 7,
'𑌼' => 7,
<C3B0><E28098>' => 9,
<C3B0>¦' => 230,
<C3B0>§' => 230,
<C3B0>¨' => 230,
<C3B0>©' => 230,
<C3B0>ª' => 230,
<C3B0>«' => 230,
<C3B0>¬' => 230,
<C3B0>°' => 230,
<C3B0>±' => 230,
<C3B0>²' => 230,
<C3B0>³' => 230,
<C3B0>´' => 230,
'ð‘‘‚' => 9,
'𑑆' => 7,
'ð‘‘ž' => 230,
'ð‘“‚' => 9,
'𑓃' => 7,
'ð‘–¿' => 9,
'ð‘—€' => 7,
'𑘿' => 9,
'𑚶' => 9,
'ð‘š·' => 7,
'𑜫' => 9,
'ð‘ ¹' => 9,
'ð‘ º' => 7,
'𑤽' => 9,
'𑤾' => 9,
'𑥃' => 7,
'𑧠' => 9,
'𑨴' => 9,
'𑩇' => 9,
'𑪙' => 9,
'ð‘°¿' => 9,
'𑵂' => 7,
'𑵄' => 9,
'𑵅' => 9,
'𑶗' => 9,
'ð–«°' => 1,
'ð–«±' => 1,
'ð–«²' => 1,
'ð–«³' => 1,
'ð–«´' => 1,
'ð–¬°' => 230,
'𖬱' => 230,
'𖬲' => 230,
'𖬳' => 230,
'ð–¬´' => 230,
'𖬵' => 230,
'𖬶' => 230,
'ð–¿°' => 6,
'ð–¿±' => 6,
'𛲞' => 1,
<>…¥' => 216,
<>…¦' => 216,
<>…§' => 1,
<>…¨' => 1,
<>…©' => 1,
<>­' => 226,
<>…®' => 216,
<>…¯' => 216,
<>…°' => 216,
<>…±' => 216,
<>…²' => 216,
<>…»' => 220,
<>…¼' => 220,
<>…½' => 220,
<>…¾' => 220,
<>…¿' => 220,
<>†€' => 220,
<><EFBFBD>' => 220,
<>' => 220,
<>†…' => 230,
<>††' => 230,
<>†‡' => 230,
<>ˆ' => 230,
<>†‰' => 230,
<>†Š' => 220,
<>' => 220,
<>†ª' => 230,
<>†«' => 230,
<>†¬' => 230,
<>­' => 230,
<>' => 230,
<>‰ƒ' => 230,
<>‰„' => 230,
'𞀀' => 230,
'ðž€<C5BE>' => 230,
'𞀂' => 230,
'𞀃' => 230,
'𞀄' => 230,
'𞀅' => 230,
'𞀆' => 230,
'𞀈' => 230,
'𞀉' => 230,
'𞀊' => 230,
'𞀋' => 230,
'𞀌' => 230,
'ðž€<C5BE>' => 230,
'𞀎' => 230,
'ðž€<C5BE>' => 230,
'ðž€<C5BE>' => 230,
'𞀑' => 230,
'𞀒' => 230,
'𞀓' => 230,
'𞀔' => 230,
'𞀕' => 230,
'𞀖' => 230,
'𞀗' => 230,
'𞀘' => 230,
'𞀛' => 230,
'𞀜' => 230,
'ðž€<C5BE>' => 230,
'𞀞' => 230,
'𞀟' => 230,
'𞀠' => 230,
'𞀡' => 230,
'𞀣' => 230,
'𞀤' => 230,
'𞀦' => 230,
'𞀧' => 230,
'𞀨' => 230,
'𞀩' => 230,
'𞀪' => 230,
'ðž„°' => 230,
'𞄱' => 230,
'𞄲' => 230,
'𞄳' => 230,
'ðž„´' => 230,
'𞄵' => 230,
'𞄶' => 230,
'𞋬' => 230,
'ðž‹­' => 230,
'ðž‹®' => 230,
'𞋯' => 230,
'ðž£<C5BE>' => 220,
'𞣑' => 220,
'𞣒' => 220,
'𞣓' => 220,
'𞣔' => 220,
'𞣕' => 220,
'𞣖' => 220,
'𞥄' => 230,
'𞥅' => 230,
'𞥆' => 230,
'𞥇' => 230,
'𞥈' => 230,
'𞥉' => 230,
'𞥊' => 7,
);
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitf2e3a964c11223b024edc03e013baa7e::getLoader();
<?php
declare(strict_types = 1);
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace Wdes\phpI18nL10n\plugins;
/**
* Plugin interface
* @author William Desportes <williamdes@wdes.fr>
* @license MPL-2.0
*/
abstract class BasePlugin
{
/**
* Build the plugin instance
*
* @param array $options The options for the plugin
*/
abstract public function __construct(array $options = []);
/**
* Get translation for a message id
*
* @param string $msgId Message id
* @return string
*/
abstract public function gettext(string $msgId): string;
/**
* Get translation for a message id
*
* @param string $msgId Message id
* @return string
*/
abstract public function __(string $msgId): string;
/**
* Get translation
*
* @param string $domain Domain
* @param string $msgctxt msgctxt
* @param string $msgId Message id
* @param string $msgIdPlural Plural message id
* @param int $number Number
* @return string
*/
abstract public function dnpgettext(string $domain, string $msgctxt, string $msgId, string $msgIdPlural, int $number): string;
/**
* Get translation
*
* @param string $domain Domain
* @param string $msgId Message id
* @param string $msgIdPlural Plural message id
* @param int $number Number
* @return string
*/
abstract public function dngettext(string $domain, string $msgId, string $msgIdPlural, int $number): string;
/**
* Get translation
*
* @param string $msgctxt msgctxt
* @param string $msgId Message id
* @param string $msgIdPlural Plural message id
* @param int $number Number
* @return string
*/
abstract public function npgettext(string $msgctxt, string $msgId, string $msgIdPlural, int $number): string;
/**
* Plural version of gettext
*
* @param string $msgId Message id
* @param string $msgIdPlural Plural message id
* @param int $number Number
* @return string
*/
abstract public function ngettext(string $msgId, string $msgIdPlural, int $number);
/**
* Get translation
*
* @param string $domain Domain
* @param string $msgctxt msgctxt
* @param string $msgId Message id
* @return string
*/
abstract public function dpgettext(string $domain, string $msgctxt, string $msgId): string;
/**
* Get translation
*
* @param string $domain Domain
* @param string $msgId Message id
* @return string
*/
abstract public function dgettext(string $domain, string $msgId): string;
/**
* Get translation
*
* @param string $msgctxt msgctxt
* @param string $msgId Message id
* @return string
*/
abstract public function pgettext(string $msgctxt, string $msgId): string;
}
<?php
declare(strict_types = 1);
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace Wdes\phpI18nL10n\plugins;
use Exception;
use stdClass;
/**
* Plugin for reading .mo files
* @see https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
* @author William Desportes <williamdes@wdes.fr>
* @license MPL-2.0
*/
class MoReader extends BasePlugin
{
public const ENDIAN_BIG = 1;
public const ENDIAN_LITTLE = 2;
/**
* Translations
*
* @var array<string,string>
*/
protected $translations = [];
/**
* Build a MoReader instance
*
* @param array $options The options
*/
public function __construct(array $options = [])
{
$options = null;// Unused param detection
}
/**
* Set the translations
* @param array<string,string> $translations
*/
public function setTranslations(array $translations): void
{
$this->translations = $translations;
}
/**
* Get the translations
* @return array<string,string> $translations
*/
public function getTranslations(): array
{
return $this->translations;
}
/**
* Read the .mo file
*
* @param string $file The complete path to the file
* @return stdClass
*/
public function readFile(string $file): stdClass
{
$data = new stdClass();
if (! is_file($file)) {
throw new Exception($file . ' does not exist.');
}
$res = fopen($file, 'rb');
if (is_resource($res)) {
$fileRes = $res;
} else {
throw new Exception($file . ' could not be open.');
}
$data->endian = self::determineByteOrder($fileRes);
if ($data->endian === -1) {
fclose($fileRes);
throw new Exception($file . ' is not a valid gettext file.');
}
fseek($fileRes, 4);
$data->fileFormatRevision = self::readInteger($data->endian, $fileRes);
if ($data->fileFormatRevision !== 0 && $data->fileFormatRevision !== 1) {
fclose($fileRes);
throw new Exception($file . ' has an unknown major revision.');
}
$data->nbrOfStrings = self::readInteger($data->endian, $fileRes);
$data->tableOffsetOriginal = self::readInteger($data->endian, $fileRes);//offset of table with original strings
$data->tableOffsetTranslation = self::readInteger($data->endian, $fileRes);//offset of table with translation strings
// see: https://gist.github.com/timwhitlock/8255619
// Hash table needs to be implemented if not useless
//$data->tblHashSize = self::readInteger($data->endian, $fileRes);//size of hashing table
//$data->hashTableOffsetStart = self::readInteger($data->endian, $fileRes);//offset of hashing table
//$data->hashTableOffsetEnd = $data->hashTableOffsetStart + $data->tblHashSize * 4;//offset of hashing table
fseek($fileRes, $data->tableOffsetOriginal);
$data->msgIdTable = self::readIntegerList($data->endian, $fileRes, 2 * $data->nbrOfStrings);
fseek($fileRes, $data->tableOffsetTranslation);
$data->msgStrTable = self::readIntegerList($data->endian, $fileRes, 2 * $data->nbrOfStrings);
$this->translations = $this->readTranslations($fileRes, $data);
$data->translations = $this->translations;
// Clean memory
unset($data->tableOffsetOriginal);
unset($data->tableOffsetTranslation);
unset($data->fileFormatRevision);
unset($data->endian);
unset($data->msgIdTable);
unset($data->msgStrTable);
// Close resource
fclose($fileRes);
return $data;
}
/**
* Read translations
*
* @copyright 2015 Max Grigorian
* @license MIT
*
* @param resource $fileRes The mo file
* @param stdClass $data The data
* @return array<string,string>
*/
public function readTranslations($fileRes, stdClass $data): array
{
$dataOut = [];
for ($counter = 0; $counter < $data->nbrOfStrings; $counter++) {
$msgId = null;
$msgStr = null;
try {
$msgId = $this->readStringFromTable($fileRes, $counter, $data->msgIdTable);
} catch (Exception $e) {
$msgId = [''];
}
try {
$msgStr = $this->readStringFromTable($fileRes, $counter, $data->msgStrTable);
} catch (Exception $e) {
$msgStr = [];
}
$this->processRecord($dataOut, $msgId, $msgStr);
}
return $dataOut;
}
/**
* Process a record
*
* @copyright 2015 Max Grigorian
* @license MIT
* @param array $data The record
* @param array $msgIds Message ids
* @param array $msgStrs Message strings
* @return void
*/
public function processRecord(array &$data, array $msgIds, array $msgStrs): void
{
// note: Contexts are stored by storing the concatenation of the context, a EOT byte, and the original string, instead of the original string.
foreach ($msgIds as $key => $msgId) {
$data[$msgId] = $msgStrs[$key];
}
}
/**
* Read a string from table
*
* @copyright 2015 Max Grigorian
* @license MIT
*
* @param resource $fileRes The mo file
* @param int $index Position
* @param array $table Table
*
* @return array
*/
public function readStringFromTable($fileRes, int $index, array $table): array
{
$sizeKey = ($index * 2 ) + 1;
$size = $table[$sizeKey];
if ($size > 0) {
$offset = $table[$sizeKey + 1];
fseek($fileRes, $offset);
$read = fread($fileRes, $size);
if (is_string($read)) {
return explode("\0", $read);
} else {
throw new Exception('read error !');
}
}
throw new Exception('size error !');
}
/**
* Determines byte order
*
* @copyright 2015 Max Grigorian
* @license MIT
* @see github.com/MAXakaWIZARD/MoReader:src/Reader.php
* @param resource $res The file resource
* @return int ENDIAN_BIG/ENDIAN_LITTLE/-1
*/
public static function determineByteOrder($res): int
{
$orderBytes = fread($res, 4);
if ($orderBytes === "\x95\x04\x12\xde") {
return self::ENDIAN_BIG;
} elseif ($orderBytes === "\xde\x12\x04\x95") {
return self::ENDIAN_LITTLE;
} else {
return -1;
}
}
/**
* Read an integer from the current file.
*
* @copyright 2015 Max Grigorian
* @license MIT
* @see github.com/MAXakaWIZARD/MoReader:src/Reader.php
* @param int $endian ENDIAN_BIG/ENDIAN_LITTLE
* @param resource $res The file resource
* @param int $nbr Number of strings
* @return array
*/
public static function readIntegerList(int $endian, $res, int $nbr): array
{
$read = fread($res, 4 * $nbr);
if (is_string($read)) {
if ($endian === self::ENDIAN_LITTLE) {
$unPack = unpack('V' . $nbr, $read);
if ($unPack === false) {
return [];
}
return $unPack;
}
$unPack = unpack('N' . $nbr, $read);
if ($unPack === false) {
return [];
}
return $unPack;
} else {
throw new Exception('read error !');
}
}
/**
* Read a single integer from the current file.
*
* @copyright 2015 Max Grigorian
* @license MIT
* @param int $endian ENDIAN_BIG/ENDIAN_LITTLE
* @param resource $res The file resource
* @return int
*/
public static function readInteger(int $endian, $res)
{
$read = fread($res, 4);
if (is_string($read)) {
if ($endian === self::ENDIAN_LITTLE) {
$result = unpack('Vint', $read);
} else {
$result = unpack('Nint', $read);
}
return $result === false ? PHP_INT_MAX : $result['int'];
} else {
throw new Exception('read error !');
}
}
/**
* Return string id or translation
*
* @param string $msgId The message id
* @return string
*/
public function idOrFind(string $msgId): string
{
if (array_key_exists($msgId, $this->translations)) {
return $this->translations[$msgId];
} else {
return $msgId;
}
}
/**
* Get translation for a message id
*
* @param string $msgId Message id
* @return string
*/
public function __(string $msgId): string
{
return self::gettext($msgId);
}
/**
* Get translation for a message id
*
* @param string $msgId Message id
* @return string
*/
public function gettext(string $msgId): string
{
return self::idOrFind($msgId);
}
/**
* Get translation
*
* @param string $domain Domain
* @param string $msgctxt msgctxt
* @param string $msgId Message id
* @param string $msgIdPlural Plural message id
* @param int $number Number
* @return string
*/
public function dnpgettext(string $domain, string $msgctxt, string $msgId, string $msgIdPlural, int $number): string
{
return self::idOrFind($msgId);
}
/**
* Get translation
*
* @param string $domain Domain
* @param string $msgId Message id
* @param string $msgIdPlural Plural message id
* @param int $number Number
* @return string
*/
public function dngettext(string $domain, string $msgId, string $msgIdPlural, int $number): string
{
return self::idOrFind($msgId);
}
/**
* Get translation
*
* @param string $msgctxt msgctxt
* @param string $msgId Message id
* @param string $msgIdPlural Plural message id
* @param int $number Number
* @return string
*/
public function npgettext(string $msgctxt, string $msgId, string $msgIdPlural, int $number): string
{
if ($number > 1) {
return self::idOrFind($msgIdPlural);
} else {
return self::idOrFind($msgId);
}
}
/**
* Plural version of gettext
*
* @param string $msgId Message id
* @param string $msgIdPlural Plural message id
* @param int $number Number
* @return string
*/
public function ngettext(string $msgId, string $msgIdPlural, int $number): string
{
if ($number > 1) {
return self::idOrFind($msgIdPlural);
} else {
return self::idOrFind($msgId);
}
}
/**
* Get translation
*
* @param string $domain Domain
* @param string $msgctxt msgctxt
* @param string $msgId Message id
* @return string
*/
public function dpgettext(string $domain, string $msgctxt, string $msgId): string
{
return self::idOrFind($msgId);
}
/**
* Get translation
*
* @param string $domain Domain
* @param string $msgId Message id
* @return string
*/
public function dgettext(string $domain, string $msgId): string
{
return self::idOrFind($msgId);
}
/**
* Get translation
*
* @param string $msgctxt msgctxt
* @param string $msgId Message id
* @return string
*/
public function pgettext(string $msgctxt, string $msgId): string
{
return self::idOrFind($msgId);
}
}
<?php
declare(strict_types = 1);
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace Wdes\phpI18nL10n;
use Wdes\phpI18nL10n\plugins\BasePlugin;
/**
* Plugin for reading .mo files
* @see https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
* @author William Desportes <williamdes@wdes.fr>
* @license MPL-2.0
*/
class Launcher
{
/**
* Plugin to use for translation
*
* @var BasePlugin
*/
private static $plugin;
/**
* Return registered plugin
*
* @return BasePlugin
*/
public static function getPlugin(): BasePlugin
{
return Launcher::$plugin;
}
/**
* Set the translation plugin
* @param BasePlugin $plugin The translation plugin to provide
* @return void
*/
public static function setPlugin(BasePlugin $plugin): void
{
Launcher::$plugin = $plugin;
}
/**
* Access gettext directly, not recommended
* Reserved to twig filter
*
* @param string $msgId Message id
* @return string
*/
public static function gettext(string $msgId): string
{
return Launcher::$plugin->gettext($msgId);
}
/**
* Access dgettext directly, not recommended
* Reserved to twig filter
*
* @param string $domain The domain
* @param string $msgId Message id
* @return string
*/
public static function dgettext(string $domain, string $msgId): string
{
return Launcher::$plugin->dgettext($domain, $msgId);
}
}
<?php
declare(strict_types = 1);
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace Wdes\phpI18nL10n\Twig\Extension;
use PhpMyAdmin\Twig\Extensions\I18nExtension;
use Twig\TwigFilter;
use Wdes\phpI18nL10n\Twig\TokenParser;
/**
* I18n extension for Twig
* @license MPL-2.0
*/
class I18n extends I18nExtension
{
/**
* @return array
*/
public function getTokenParsers()
{
return [new TokenParser()];
}
/**
* @return array
*/
public function getFilters()
{
return [
new TwigFilter('trans', '\Wdes\phpI18nL10n\Launcher::gettext'), /* Note, the filter does not handle plurals */
];
}
}
<?php
declare(strict_types = 1);
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace Wdes\phpI18nL10n\Twig;
use Twig\Cache\CacheInterface;
use Twig\TemplateWrapper;
/**
* Token parser for Twig
* @license MPL-2.0
*/
class MemoryCache implements CacheInterface
{
/**
* Contains all templates
*
* @var array<string, string>
*/
private $memory = [];
/**
* Generate a cache key
*
* @param string $name The name
* @param string $className The class name
* @return string
*/
public function generateKey($name, $className): string
{
return 'memcache_' . $name;
}
/**
* Write to cache
*
* @param string $key The cache key
* @param string $content The content to write
* @return void
*/
public function write($key, $content): void
{
$this->memory[$key] = $content;
}
/**
* Load from cache
*
* @param string $key The cache key
* @return void
*/
public function load(string $key): void
{
//load(string $key): void for twig 3.x
}
/**
* Get the timestamp
*
* @param string $key The cache key
* @return int
*/
public function getTimestamp($key): int
{
return 0;
}
/**
* Get the cached string for key
*
* @param TemplateWrapper $template The template
* @return string
*/
public function getFromCache(TemplateWrapper $template): string
{
return $this->memory['memcache_' . $template->getTemplateName()];
}
/**
* Extract source code from memory cache
*
* @param TemplateWrapper $template The template
* @return string
*/
public function extractDoDisplayFromCache(TemplateWrapper $template): string
{
$content = self::getFromCache($template);
// "/function ([a-z_]+)\("
preg_match_all(
'/protected function doDisplay\(([a-z\s,\$=\[\]]+)\)([\s]+){([=%\sa-z\/0-9-\>:\(\)\\\";.,\$\[\]?_-]+)/msi',
$content,
$output_array
);
return $output_array[3][0];
}
}
<?php
declare(strict_types = 1);
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
namespace Wdes\phpI18nL10n\Twig;
use PhpMyAdmin\Twig\Extensions\Node\TransNode;
/**
* Translation node for Twig
* @license MPL-2.0
*/
class TranslationNode extends TransNode
{
/**
* The label for gettext notes to be exported
*
* @var string
*/
public static $notesLabel = '// l10n: ';
/**
* Enables context functions usage
*
* @var bool
*/
public static $hasContextFunctions = true;
/**
* @param bool $hasPlural use plural
* @param bool $hasContext use context
* @param bool $hasDomain use domain
*
* @return string The function
*/
protected function getTransFunction(bool $hasPlural, bool $hasContext, bool $hasDomain): string
{
$functionPrefix = '\Wdes\phpI18nL10n\Launcher::getPlugin()->';
if ($hasDomain) {
if ($hasPlural) {
// dnpgettext($domain, $msgctxt, $msgid, $msgidPlural, $number);
// dngettext($domain, $msgid, $msgidPlural, $number);
return $functionPrefix . ($hasContext ? 'dnpgettext' : 'dngettext');
}
// dpgettext($domain, $msgctxt, $msgid);
// dgettext($domain, $msgid);
return $functionPrefix . ($hasContext ? 'dpgettext' : 'dgettext');
}
if ($hasPlural) {
// npgettext($msgctxt, $msgid, $msgidPlural, $number);
// ngettext($msgid, $msgidPlural, $number);
return $functionPrefix . ($hasContext ? 'npgettext' : 'ngettext');
}
// pgettext($msgctxt, $msgid);
// gettext($msgid);
return $functionPrefix . ($hasContext ? 'pgettext' : 'gettext');
}
}
<?php
declare(strict_types = 1);
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
namespace Wdes\phpI18nL10n\Twig;
use PhpMyAdmin\Twig\Extensions\TokenParser\TransTokenParser;
use Twig\Token;
/**
* Token parser for Twig
* @license MPL-2.0
*/
class TokenParser extends TransTokenParser
{
/**
* Parses a token and returns a node.
*
* @param Token $token The token
*
* @return TranslationNode
*/
public function parse(Token $token)
{
[
$body,
$plural,
$count,
$context,
$notes,
$domain,
$lineno,
$tag,
] = $this->preParse($token);
return new TranslationNode($body, $plural, $count, $context, $notes, $domain, $lineno, $tag);
}
}
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link https://phpdoc.org
*/
namespace phpDocumentor\Reflection\PseudoTypes;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Boolean;
use function class_alias;
/**
* Value Object representing the PseudoType 'False', which is a Boolean type.
*
* @psalm-immutable
*/
final class True_ extends Boolean implements PseudoType
{
public function underlyingType() : Type
{
return new Boolean();
}
public function __toString() : string
{
return 'true';
}
}
class_alias('\phpDocumentor\Reflection\PseudoTypes\True_', 'phpDocumentor\Reflection\Types\True_', false);
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link https://phpdoc.org
*/
namespace phpDocumentor\Reflection\PseudoTypes;
use phpDocumentor\Reflection\PseudoType;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\Types\Boolean;
use function class_alias;
/**
* Value Object representing the PseudoType 'False', which is a Boolean type.
*
* @psalm-immutable
*/
final class False_ extends Boolean implements PseudoType
{
public function underlyingType() : Type
{
return new Boolean();
}
public function __toString() : string
{
return 'false';
}
}
class_alias('\phpDocumentor\Reflection\PseudoTypes\False_', 'phpDocumentor\Reflection\Types\False_', false);
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* @psalm-immutable
*/
interface Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string;
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
interface PseudoType extends Type
{
public function underlyingType() : Type;
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use InvalidArgumentException;
use phpDocumentor\Reflection\Types\Context;
use function explode;
use function implode;
use function strpos;
/**
* Resolver for Fqsen using Context information
*
* @psalm-immutable
*/
class FqsenResolver
{
/** @var string Definition of the NAMESPACE operator in PHP */
private const OPERATOR_NAMESPACE = '\\';
public function resolve(string $fqsen, ?Context $context = null) : Fqsen
{
if ($context === null) {
$context = new Context('');
}
if ($this->isFqsen($fqsen)) {
return new Fqsen($fqsen);
}
return $this->resolvePartialStructuralElementName($fqsen, $context);
}
/**
* Tests whether the given type is a Fully Qualified Structural Element Name.
*/
private function isFqsen(string $type) : bool
{
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
}
/**
* Resolves a partial Structural Element Name (i.e. `Reflection\DocBlock`) to its FQSEN representation
* (i.e. `\phpDocumentor\Reflection\DocBlock`) based on the Namespace and aliases mentioned in the Context.
*
* @throws InvalidArgumentException When type is not a valid FQSEN.
*/
private function resolvePartialStructuralElementName(string $type, Context $context) : Fqsen
{
$typeParts = explode(self::OPERATOR_NAMESPACE, $type, 2);
$namespaceAliases = $context->getNamespaceAliases();
// if the first segment is not an alias; prepend namespace name and return
if (!isset($namespaceAliases[$typeParts[0]])) {
$namespace = $context->getNamespace();
if ($namespace !== '') {
$namespace .= self::OPERATOR_NAMESPACE;
}
return new Fqsen(self::OPERATOR_NAMESPACE . $namespace . $type);
}
$typeParts[0] = $namespaceAliases[$typeParts[0]];
return new Fqsen(self::OPERATOR_NAMESPACE . implode(self::OPERATOR_NAMESPACE, $typeParts));
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value object representing Integer type
*
* @psalm-immutable
*/
final class Integer implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'int';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the '$this' pseudo-type.
*
* $this, as a Type, represents the instance of the class associated with the element as it was called. $this is
* commonly used when documenting fluent interfaces since it represents that the same object is returned.
*
* @psalm-immutable
*/
final class This implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return '$this';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;
/**
* Represents a collection type as described in the PSR-5, the PHPDoc Standard.
*
* A collection can be represented in two forms:
*
* 1. `ACollectionObject<aValueType>`
* 2. `ACollectionObject<aValueType,aKeyType>`
*
* - ACollectionObject can be 'array' or an object that can act as an array
* - aValueType and aKeyType can be any type expression
*
* @psalm-immutable
*/
final class Collection extends AbstractList
{
/** @var Fqsen|null */
private $fqsen;
/**
* Initializes this representation of an array with the given Type or Fqsen.
*/
public function __construct(?Fqsen $fqsen, Type $valueType, ?Type $keyType = null)
{
parent::__construct($valueType, $keyType);
$this->fqsen = $fqsen;
}
/**
* Returns the FQSEN associated with this object.
*/
public function getFqsen() : ?Fqsen
{
return $this->fqsen;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
$objectType = (string) ($this->fqsen ?? 'object');
if ($this->keyType === null) {
return $objectType . '<' . $this->valueType . '>';
}
return $objectType . '<' . $this->keyType . ',' . $this->valueType . '>';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Represents an expression type as described in the PSR-5, the PHPDoc Standard.
*
* @psalm-immutable
*/
final class Expression implements Type
{
/** @var Type */
protected $valueType;
/**
* Initializes this representation of an array with the given Type.
*/
public function __construct(Type $valueType)
{
$this->valueType = $valueType;
}
/**
* Returns the value for the keys of this array.
*/
public function getValueType() : Type
{
return $this->valueType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return '(' . $this->valueType . ')';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'resource' Type.
*
* @psalm-immutable
*/
final class Resource_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'resource';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Represents a list of values. This is an abstract class for Array_ and Collection.
*
* @psalm-immutable
*/
abstract class AbstractList implements Type
{
/** @var Type */
protected $valueType;
/** @var Type|null */
protected $keyType;
/** @var Type */
protected $defaultKeyType;
/**
* Initializes this representation of an array with the given Type.
*/
public function __construct(?Type $valueType = null, ?Type $keyType = null)
{
if ($valueType === null) {
$valueType = new Mixed_();
}
$this->valueType = $valueType;
$this->defaultKeyType = new Compound([new String_(), new Integer()]);
$this->keyType = $keyType;
}
/**
* Returns the type for the keys of this array.
*/
public function getKeyType() : Type
{
return $this->keyType ?? $this->defaultKeyType;
}
/**
* Returns the value for the keys of this array.
*/
public function getValueType() : Type
{
return $this->valueType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
if ($this->keyType) {
return 'array<' . $this->keyType . ',' . $this->valueType . '>';
}
if ($this->valueType instanceof Mixed_) {
return 'array';
}
if ($this->valueType instanceof Compound) {
return '(' . $this->valueType . ')[]';
}
return $this->valueType . '[]';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'scalar' pseudo-type, which is either a string, integer, float or boolean.
*
* @psalm-immutable
*/
final class Scalar implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'scalar';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
/**
* Value Object representing iterable type
*
* @psalm-immutable
*/
final class Iterable_ extends AbstractList
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
if ($this->keyType) {
return 'iterable<' . $this->keyType . ',' . $this->valueType . '>';
}
if ($this->valueType instanceof Mixed_) {
return 'iterable';
}
return 'iterable<' . $this->valueType . '>';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use function strlen;
use function substr;
use function trim;
/**
* Provides information about the Context in which the DocBlock occurs that receives this context.
*
* A DocBlock does not know of its own accord in which namespace it occurs and which namespace aliases are applicable
* for the block of code in which it is in. This information is however necessary to resolve Class names in tags since
* you can provide a short form or make use of namespace aliases.
*
* The phpDocumentor Reflection component knows how to create this class but if you use the DocBlock parser from your
* own application it is possible to generate a Context class using the ContextFactory; this will analyze the file in
* which an associated class resides for its namespace and imports.
*
* @see ContextFactory::createFromClassReflector()
* @see ContextFactory::createForNamespace()
*
* @psalm-immutable
*/
final class Context
{
/** @var string The current namespace. */
private $namespace;
/**
* @var string[] List of namespace aliases => Fully Qualified Namespace.
* @psalm-var array<string, string>
*/
private $namespaceAliases;
/**
* Initializes the new context and normalizes all passed namespaces to be in Qualified Namespace Name (QNN)
* format (without a preceding `\`).
*
* @param string $namespace The namespace where this DocBlock resides in.
* @param string[] $namespaceAliases List of namespace aliases => Fully Qualified Namespace.
*
* @psalm-param array<string, string> $namespaceAliases
*/
public function __construct(string $namespace, array $namespaceAliases = [])
{
$this->namespace = $namespace !== 'global' && $namespace !== 'default'
? trim($namespace, '\\')
: '';
foreach ($namespaceAliases as $alias => $fqnn) {
if ($fqnn[0] === '\\') {
$fqnn = substr($fqnn, 1);
}
if ($fqnn[strlen($fqnn) - 1] === '\\') {
$fqnn = substr($fqnn, 0, -1);
}
$namespaceAliases[$alias] = $fqnn;
}
$this->namespaceAliases = $namespaceAliases;
}
/**
* Returns the Qualified Namespace Name (thus without `\` in front) where the associated element is in.
*/
public function getNamespace() : string
{
return $this->namespace;
}
/**
* Returns a list of Qualified Namespace Names (thus without `\` in front) that are imported, the keys represent
* the alias for the imported Namespace.
*
* @return string[]
*
* @psalm-return array<string, string>
*/
public function getNamespaceAliases() : array
{
return $this->namespaceAliases;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Boolean type.
*
* @psalm-immutable
*/
class Boolean implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'bool';
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
declare(strict_types=1);
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Compound Type.
*
* A Intersection Type is not so much a special keyword or object reference but is a series of Types that are separated
* using an AND operator (`&`). This combination of types signifies that whatever is associated with this Intersection
* type may contain a value with any of the given types.
*
* @psalm-immutable
*/
final class Intersection extends AggregatedType
{
/**
* Initializes a intersection type (i.e. `\A&\B`) and tests if the provided types all implement the Type interface.
*
* @param array<Type> $types
*/
public function __construct(array $types)
{
parent::__construct($types, '&');
}
}
<?php
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
declare(strict_types=1);
namespace phpDocumentor\Reflection\Types;
use ArrayIterator;
use IteratorAggregate;
use phpDocumentor\Reflection\Type;
use function array_key_exists;
use function implode;
/**
* Base class for aggregated types like Compound and Intersection
*
* A Aggregated Type is not so much a special keyword or object reference but is a series of Types that are separated
* using separator.
*
* @psalm-immutable
* @template-implements IteratorAggregate<int, Type>
*/
abstract class AggregatedType implements Type, IteratorAggregate
{
/**
* @psalm-allow-private-mutation
* @var array<int, Type>
*/
private $types = [];
/** @var string */
private $token;
/**
* @param array<Type> $types
*/
public function __construct(array $types, string $token)
{
foreach ($types as $type) {
$this->add($type);
}
$this->token = $token;
}
/**
* Returns the type at the given index.
*/
public function get(int $index) : ?Type
{
if (!$this->has($index)) {
return null;
}
return $this->types[$index];
}
/**
* Tests if this compound type has a type with the given index.
*/
public function has(int $index) : bool
{
return array_key_exists($index, $this->types);
}
/**
* Tests if this compound type contains the given type.
*/
public function contains(Type $type) : bool
{
foreach ($this->types as $typePart) {
// if the type is duplicate; do not add it
if ((string) $typePart === (string) $type) {
return true;
}
}
return false;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return implode($this->token, $this->types);
}
/**
* @return ArrayIterator<int, Type>
*/
public function getIterator() : ArrayIterator
{
return new ArrayIterator($this->types);
}
/**
* @psalm-suppress ImpureMethodCall
*/
private function add(Type $type) : void
{
if ($type instanceof self) {
foreach ($type->getIterator() as $subType) {
$this->add($subType);
}
return;
}
// if the type is duplicate; do not add it
if ($this->contains($type)) {
return;
}
$this->types[] = $type;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'static' type.
*
* Self, as a Type, represents the class in which the associated element was called. This differs from self as self does
* not take inheritance into account but static means that the return type is always that of the class of the called
* element.
*
* See the documentation on late static binding in the PHP Documentation for more information on the difference between
* static and self.
*
* @psalm-immutable
*/
final class Static_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'static';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Float.
*
* @psalm-immutable
*/
final class Float_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'float';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the type 'string'.
*
* @psalm-immutable
*/
final class ClassString implements Type
{
/** @var Fqsen|null */
private $fqsen;
/**
* Initializes this representation of a class string with the given Fqsen.
*/
public function __construct(?Fqsen $fqsen = null)
{
$this->fqsen = $fqsen;
}
/**
* Returns the FQSEN associated with this object.
*/
public function getFqsen() : ?Fqsen
{
return $this->fqsen;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
if ($this->fqsen === null) {
return 'class-string';
}
return 'class-string<' . (string) $this->fqsen . '>';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use InvalidArgumentException;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\Type;
use function strpos;
/**
* Value Object representing an object.
*
* An object can be either typed or untyped. When an object is typed it means that it has an identifier, the FQSEN,
* pointing to an element in PHP. Object types that are untyped do not refer to a specific class but represent objects
* in general.
*
* @psalm-immutable
*/
final class Object_ implements Type
{
/** @var Fqsen|null */
private $fqsen;
/**
* Initializes this object with an optional FQSEN, if not provided this object is considered 'untyped'.
*
* @throws InvalidArgumentException When provided $fqsen is not a valid type.
*/
public function __construct(?Fqsen $fqsen = null)
{
if (strpos((string) $fqsen, '::') !== false || strpos((string) $fqsen, '()') !== false) {
throw new InvalidArgumentException(
'Object types can only refer to a class, interface or trait but a method, function, constant or '
. 'property was received: ' . (string) $fqsen
);
}
$this->fqsen = $fqsen;
}
/**
* Returns the FQSEN associated with this object.
*/
public function getFqsen() : ?Fqsen
{
return $this->fqsen;
}
public function __toString() : string
{
if ($this->fqsen) {
return (string) $this->fqsen;
}
return 'object';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
/**
* Represents an array type as described in the PSR-5, the PHPDoc Standard.
*
* An array can be represented in two forms:
*
* 1. Untyped (`array`), where the key and value type is unknown and hence classified as 'Mixed_'.
* 2. Types (`string[]`), where the value type is provided by preceding an opening and closing square bracket with a
* type name.
*
* @psalm-immutable
*/
final class Array_ extends AbstractList
{
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use ArrayIterator;
use InvalidArgumentException;
use ReflectionClass;
use ReflectionClassConstant;
use ReflectionMethod;
use ReflectionParameter;
use ReflectionProperty;
use Reflector;
use RuntimeException;
use UnexpectedValueException;
use function define;
use function defined;
use function file_exists;
use function file_get_contents;
use function get_class;
use function in_array;
use function is_string;
use function strrpos;
use function substr;
use function token_get_all;
use function trim;
use const T_AS;
use const T_CLASS;
use const T_CURLY_OPEN;
use const T_DOLLAR_OPEN_CURLY_BRACES;
use const T_NAMESPACE;
use const T_NS_SEPARATOR;
use const T_STRING;
use const T_USE;
if (!defined('T_NAME_QUALIFIED')) {
define('T_NAME_QUALIFIED', 'T_NAME_QUALIFIED');
}
if (!defined('T_NAME_FULLY_QUALIFIED')) {
define('T_NAME_FULLY_QUALIFIED', 'T_NAME_FULLY_QUALIFIED');
}
/**
* Convenience class to create a Context for DocBlocks when not using the Reflection Component of phpDocumentor.
*
* For a DocBlock to be able to resolve types that use partial namespace names or rely on namespace imports we need to
* provide a bit of context so that the DocBlock can read that and based on it decide how to resolve the types to
* Fully Qualified names.
*
* @see Context for more information.
*/
final class ContextFactory
{
/** The literal used at the end of a use statement. */
private const T_LITERAL_END_OF_USE = ';';
/** The literal used between sets of use statements */
private const T_LITERAL_USE_SEPARATOR = ',';
/**
* Build a Context given a Class Reflection.
*
* @see Context for more information on Contexts.
*/
public function createFromReflector(Reflector $reflector) : Context
{
if ($reflector instanceof ReflectionClass) {
//phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
/** @var ReflectionClass<object> $reflector */
return $this->createFromReflectionClass($reflector);
}
if ($reflector instanceof ReflectionParameter) {
return $this->createFromReflectionParameter($reflector);
}
if ($reflector instanceof ReflectionMethod) {
return $this->createFromReflectionMethod($reflector);
}
if ($reflector instanceof ReflectionProperty) {
return $this->createFromReflectionProperty($reflector);
}
if ($reflector instanceof ReflectionClassConstant) {
return $this->createFromReflectionClassConstant($reflector);
}
throw new UnexpectedValueException('Unhandled \Reflector instance given: ' . get_class($reflector));
}
private function createFromReflectionParameter(ReflectionParameter $parameter) : Context
{
$class = $parameter->getDeclaringClass();
if (!$class) {
throw new InvalidArgumentException('Unable to get class of ' . $parameter->getName());
}
//phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
/** @var ReflectionClass<object> $class */
return $this->createFromReflectionClass($class);
}
private function createFromReflectionMethod(ReflectionMethod $method) : Context
{
//phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
/** @var ReflectionClass<object> $class */
$class = $method->getDeclaringClass();
return $this->createFromReflectionClass($class);
}
private function createFromReflectionProperty(ReflectionProperty $property) : Context
{
//phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
/** @var ReflectionClass<object> $class */
$class = $property->getDeclaringClass();
return $this->createFromReflectionClass($class);
}
private function createFromReflectionClassConstant(ReflectionClassConstant $constant) : Context
{
//phpcs:ignore SlevomatCodingStandard.Commenting.InlineDocCommentDeclaration.MissingVariable
/** @var ReflectionClass<object> $class */
$class = $constant->getDeclaringClass();
return $this->createFromReflectionClass($class);
}
/**
* @param ReflectionClass<object> $class
*/
private function createFromReflectionClass(ReflectionClass $class) : Context
{
$fileName = $class->getFileName();
$namespace = $class->getNamespaceName();
if (is_string($fileName) && file_exists($fileName)) {
$contents = file_get_contents($fileName);
if ($contents === false) {
throw new RuntimeException('Unable to read file "' . $fileName . '"');
}
return $this->createForNamespace($namespace, $contents);
}
return new Context($namespace, []);
}
/**
* Build a Context for a namespace in the provided file contents.
*
* @see Context for more information on Contexts.
*
* @param string $namespace It does not matter if a `\` precedes the namespace name,
* this method first normalizes.
* @param string $fileContents The file's contents to retrieve the aliases from with the given namespace.
*/
public function createForNamespace(string $namespace, string $fileContents) : Context
{
$namespace = trim($namespace, '\\');
$useStatements = [];
$currentNamespace = '';
$tokens = new ArrayIterator(token_get_all($fileContents));
while ($tokens->valid()) {
$currentToken = $tokens->current();
switch ($currentToken[0]) {
case T_NAMESPACE:
$currentNamespace = $this->parseNamespace($tokens);
break;
case T_CLASS:
// Fast-forward the iterator through the class so that any
// T_USE tokens found within are skipped - these are not
// valid namespace use statements so should be ignored.
$braceLevel = 0;
$firstBraceFound = false;
while ($tokens->valid() && ($braceLevel > 0 || !$firstBraceFound)) {
$currentToken = $tokens->current();
if ($currentToken === '{'
|| in_array($currentToken[0], [T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES], true)) {
if (!$firstBraceFound) {
$firstBraceFound = true;
}
++$braceLevel;
}
if ($currentToken === '}') {
--$braceLevel;
}
$tokens->next();
}
break;
case T_USE:
if ($currentNamespace === $namespace) {
$useStatements += $this->parseUseStatement($tokens);
}
break;
}
$tokens->next();
}
return new Context($namespace, $useStatements);
}
/**
* Deduce the name from tokens when we are at the T_NAMESPACE token.
*
* @param ArrayIterator<int, string|array{0:int,1:string,2:int}> $tokens
*/
private function parseNamespace(ArrayIterator $tokens) : string
{
// skip to the first string or namespace separator
$this->skipToNextStringOrNamespaceSeparator($tokens);
$name = '';
$acceptedTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED];
while ($tokens->valid() && in_array($tokens->current()[0], $acceptedTokens, true)) {
$name .= $tokens->current()[1];
$tokens->next();
}
return $name;
}
/**
* Deduce the names of all imports when we are at the T_USE token.
*
* @param ArrayIterator<int, string|array{0:int,1:string,2:int}> $tokens
*
* @return string[]
*
* @psalm-return array<string, string>
*/
private function parseUseStatement(ArrayIterator $tokens) : array
{
$uses = [];
while ($tokens->valid()) {
$this->skipToNextStringOrNamespaceSeparator($tokens);
$uses += $this->extractUseStatements($tokens);
$currentToken = $tokens->current();
if ($currentToken[0] === self::T_LITERAL_END_OF_USE) {
return $uses;
}
}
return $uses;
}
/**
* Fast-forwards the iterator as longs as we don't encounter a T_STRING or T_NS_SEPARATOR token.
*
* @param ArrayIterator<int, string|array{0:int,1:string,2:int}> $tokens
*/
private function skipToNextStringOrNamespaceSeparator(ArrayIterator $tokens) : void
{
while ($tokens->valid()) {
$currentToken = $tokens->current();
if (in_array($currentToken[0], [T_STRING, T_NS_SEPARATOR], true)) {
break;
}
if ($currentToken[0] === T_NAME_QUALIFIED) {
break;
}
if (defined('T_NAME_FULLY_QUALIFIED') && $currentToken[0] === T_NAME_FULLY_QUALIFIED) {
break;
}
$tokens->next();
}
}
/**
* Deduce the namespace name and alias of an import when we are at the T_USE token or have not reached the end of
* a USE statement yet. This will return a key/value array of the alias => namespace.
*
* @param ArrayIterator<int, string|array{0:int,1:string,2:int}> $tokens
*
* @return string[]
*
* @psalm-suppress TypeDoesNotContainType
*
* @psalm-return array<string, string>
*/
private function extractUseStatements(ArrayIterator $tokens) : array
{
$extractedUseStatements = [];
$groupedNs = '';
$currentNs = '';
$currentAlias = '';
$state = 'start';
while ($tokens->valid()) {
$currentToken = $tokens->current();
$tokenId = is_string($currentToken) ? $currentToken : $currentToken[0];
$tokenValue = is_string($currentToken) ? null : $currentToken[1];
switch ($state) {
case 'start':
switch ($tokenId) {
case T_STRING:
case T_NS_SEPARATOR:
$currentNs .= (string) $tokenValue;
$currentAlias = $tokenValue;
break;
case T_NAME_QUALIFIED:
case T_NAME_FULLY_QUALIFIED:
$currentNs .= (string) $tokenValue;
$currentAlias = substr(
(string) $tokenValue,
(int) (strrpos((string) $tokenValue, '\\')) + 1
);
break;
case T_CURLY_OPEN:
case '{':
$state = 'grouped';
$groupedNs = $currentNs;
break;
case T_AS:
$state = 'start-alias';
break;
case self::T_LITERAL_USE_SEPARATOR:
case self::T_LITERAL_END_OF_USE:
$state = 'end';
break;
default:
break;
}
break;
case 'start-alias':
switch ($tokenId) {
case T_STRING:
$currentAlias = $tokenValue;
break;
case self::T_LITERAL_USE_SEPARATOR:
case self::T_LITERAL_END_OF_USE:
$state = 'end';
break;
default:
break;
}
break;
case 'grouped':
switch ($tokenId) {
case T_STRING:
case T_NS_SEPARATOR:
$currentNs .= (string) $tokenValue;
$currentAlias = $tokenValue;
break;
case T_AS:
$state = 'grouped-alias';
break;
case self::T_LITERAL_USE_SEPARATOR:
$state = 'grouped';
$extractedUseStatements[(string) $currentAlias] = $currentNs;
$currentNs = $groupedNs;
$currentAlias = '';
break;
case self::T_LITERAL_END_OF_USE:
$state = 'end';
break;
default:
break;
}
break;
case 'grouped-alias':
switch ($tokenId) {
case T_STRING:
$currentAlias = $tokenValue;
break;
case self::T_LITERAL_USE_SEPARATOR:
$state = 'grouped';
$extractedUseStatements[(string) $currentAlias] = $currentNs;
$currentNs = $groupedNs;
$currentAlias = '';
break;
case self::T_LITERAL_END_OF_USE:
$state = 'end';
break;
default:
break;
}
}
if ($state === 'end') {
break;
}
$tokens->next();
}
if ($groupedNs !== $currentNs) {
$extractedUseStatements[(string) $currentAlias] = $currentNs;
}
return $extractedUseStatements;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the type 'string'.
*
* @psalm-immutable
*/
final class String_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'string';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'self' type.
*
* Self, as a Type, represents the class in which the associated element was defined.
*
* @psalm-immutable
*/
final class Self_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'self';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the 'parent' type.
*
* Parent, as a Type, represents the parent class of class in which the associated element was defined.
*
* @psalm-immutable
*/
final class Parent_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'parent';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Compound Type.
*
* A Compound Type is not so much a special keyword or object reference but is a series of Types that are separated
* using an OR operator (`|`). This combination of types signifies that whatever is associated with this compound type
* may contain a value with any of the given types.
*
* @psalm-immutable
*/
final class Compound extends AggregatedType
{
/**
* Initializes a compound type (i.e. `string|int`) and tests if the provided types all implement the Type interface.
*
* @param array<Type> $types
*/
public function __construct(array $types)
{
parent::__construct($types, '|');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a nullable type. The real type is wrapped.
*
* @psalm-immutable
*/
final class Nullable implements Type
{
/** @var Type The actual type that is wrapped */
private $realType;
/**
* Initialises this nullable type using the real type embedded
*/
public function __construct(Type $realType)
{
$this->realType = $realType;
}
/**
* Provide access to the actual type directly, if needed.
*/
public function getActualType() : Type
{
return $this->realType;
}
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return '?' . $this->realType->__toString();
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a Callable type.
*
* @psalm-immutable
*/
final class Callable_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'callable';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing an unknown, or mixed, type.
*
* @psalm-immutable
*/
final class Mixed_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'mixed';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing a null value or type.
*
* @psalm-immutable
*/
final class Null_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'null';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\Types;
use phpDocumentor\Reflection\Type;
/**
* Value Object representing the pseudo-type 'void'.
*
* Void is generally only used when working with return types as it signifies that the method intentionally does not
* return any value.
*
* @psalm-immutable
*/
final class Void_ implements Type
{
/**
* Returns a rendered output of the Type as it would be used in a DocBlock.
*/
public function __toString() : string
{
return 'void';
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use ArrayIterator;
use InvalidArgumentException;
use phpDocumentor\Reflection\Types\Array_;
use phpDocumentor\Reflection\Types\ClassString;
use phpDocumentor\Reflection\Types\Collection;
use phpDocumentor\Reflection\Types\Compound;
use phpDocumentor\Reflection\Types\Context;
use phpDocumentor\Reflection\Types\Expression;
use phpDocumentor\Reflection\Types\Integer;
use phpDocumentor\Reflection\Types\Intersection;
use phpDocumentor\Reflection\Types\Iterable_;
use phpDocumentor\Reflection\Types\Nullable;
use phpDocumentor\Reflection\Types\Object_;
use phpDocumentor\Reflection\Types\String_;
use RuntimeException;
use function array_key_exists;
use function array_pop;
use function array_values;
use function class_exists;
use function class_implements;
use function count;
use function end;
use function in_array;
use function key;
use function preg_split;
use function strpos;
use function strtolower;
use function trim;
use const PREG_SPLIT_DELIM_CAPTURE;
use const PREG_SPLIT_NO_EMPTY;
final class TypeResolver
{
/** @var string Definition of the ARRAY operator for types */
private const OPERATOR_ARRAY = '[]';
/** @var string Definition of the NAMESPACE operator in PHP */
private const OPERATOR_NAMESPACE = '\\';
/** @var int the iterator parser is inside a compound context */
private const PARSER_IN_COMPOUND = 0;
/** @var int the iterator parser is inside a nullable expression context */
private const PARSER_IN_NULLABLE = 1;
/** @var int the iterator parser is inside an array expression context */
private const PARSER_IN_ARRAY_EXPRESSION = 2;
/** @var int the iterator parser is inside a collection expression context */
private const PARSER_IN_COLLECTION_EXPRESSION = 3;
/**
* @var array<string, string> List of recognized keywords and unto which Value Object they map
* @psalm-var array<string, class-string<Type>>
*/
private $keywords = [
'string' => Types\String_::class,
'class-string' => Types\ClassString::class,
'int' => Types\Integer::class,
'integer' => Types\Integer::class,
'bool' => Types\Boolean::class,
'boolean' => Types\Boolean::class,
'real' => Types\Float_::class,
'float' => Types\Float_::class,
'double' => Types\Float_::class,
'object' => Object_::class,
'mixed' => Types\Mixed_::class,
'array' => Array_::class,
'resource' => Types\Resource_::class,
'void' => Types\Void_::class,
'null' => Types\Null_::class,
'scalar' => Types\Scalar::class,
'callback' => Types\Callable_::class,
'callable' => Types\Callable_::class,
'false' => PseudoTypes\False_::class,
'true' => PseudoTypes\True_::class,
'self' => Types\Self_::class,
'$this' => Types\This::class,
'static' => Types\Static_::class,
'parent' => Types\Parent_::class,
'iterable' => Iterable_::class,
];
/**
* @var FqsenResolver
* @psalm-readonly
*/
private $fqsenResolver;
/**
* Initializes this TypeResolver with the means to create and resolve Fqsen objects.
*/
public function __construct(?FqsenResolver $fqsenResolver = null)
{
$this->fqsenResolver = $fqsenResolver ?: new FqsenResolver();
}
/**
* Analyzes the given type and returns the FQCN variant.
*
* When a type is provided this method checks whether it is not a keyword or
* Fully Qualified Class Name. If so it will use the given namespace and
* aliases to expand the type to a FQCN representation.
*
* This method only works as expected if the namespace and aliases are set;
* no dynamic reflection is being performed here.
*
* @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
* replaced with another namespace.
* @uses Context::getNamespace() to determine with what to prefix the type name.
*
* @param string $type The relative or absolute type.
*/
public function resolve(string $type, ?Context $context = null) : Type
{
$type = trim($type);
if (!$type) {
throw new InvalidArgumentException('Attempted to resolve "' . $type . '" but it appears to be empty');
}
if ($context === null) {
$context = new Context('');
}
// split the type string into tokens `|`, `?`, `<`, `>`, `,`, `(`, `)`, `[]`, '<', '>' and type names
$tokens = preg_split(
'/(\\||\\?|<|>|&|, ?|\\(|\\)|\\[\\]+)/',
$type,
-1,
PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE
);
if ($tokens === false) {
throw new InvalidArgumentException('Unable to split the type string "' . $type . '" into tokens');
}
/** @var ArrayIterator<int, string|null> $tokenIterator */
$tokenIterator = new ArrayIterator($tokens);
return $this->parseTypes($tokenIterator, $context, self::PARSER_IN_COMPOUND);
}
/**
* Analyse each tokens and creates types
*
* @param ArrayIterator<int, string|null> $tokens the iterator on tokens
* @param int $parserContext on of self::PARSER_* constants, indicating
* the context where we are in the parsing
*/
private function parseTypes(ArrayIterator $tokens, Context $context, int $parserContext) : Type
{
$types = [];
$token = '';
$compoundToken = '|';
while ($tokens->valid()) {
$token = $tokens->current();
if ($token === null) {
throw new RuntimeException(
'Unexpected nullable character'
);
}
if ($token === '|' || $token === '&') {
if (count($types) === 0) {
throw new RuntimeException(
'A type is missing before a type separator'
);
}
if (!in_array($parserContext, [
self::PARSER_IN_COMPOUND,
self::PARSER_IN_ARRAY_EXPRESSION,
self::PARSER_IN_COLLECTION_EXPRESSION,
], true)
) {
throw new RuntimeException(
'Unexpected type separator'
);
}
$compoundToken = $token;
$tokens->next();
} elseif ($token === '?') {
if (!in_array($parserContext, [
self::PARSER_IN_COMPOUND,
self::PARSER_IN_ARRAY_EXPRESSION,
self::PARSER_IN_COLLECTION_EXPRESSION,
], true)
) {
throw new RuntimeException(
'Unexpected nullable character'
);
}
$tokens->next();
$type = $this->parseTypes($tokens, $context, self::PARSER_IN_NULLABLE);
$types[] = new Nullable($type);
} elseif ($token === '(') {
$tokens->next();
$type = $this->parseTypes($tokens, $context, self::PARSER_IN_ARRAY_EXPRESSION);
$token = $tokens->current();
if ($token === null) { // Someone did not properly close their array expression ..
break;
}
$tokens->next();
$resolvedType = new Expression($type);
$types[] = $resolvedType;
} elseif ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION && $token[0] === ')') {
break;
} elseif ($token === '<') {
if (count($types) === 0) {
throw new RuntimeException(
'Unexpected collection operator "<", class name is missing'
);
}
$classType = array_pop($types);
if ($classType !== null) {
if ((string) $classType === 'class-string') {
$types[] = $this->resolveClassString($tokens, $context);
} else {
$types[] = $this->resolveCollection($tokens, $classType, $context);
}
}
$tokens->next();
} elseif ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION
&& ($token === '>' || trim($token) === ',')
) {
break;
} elseif ($token === self::OPERATOR_ARRAY) {
end($types);
$last = key($types);
$lastItem = $types[$last];
if ($lastItem instanceof Expression) {
$lastItem = $lastItem->getValueType();
}
$types[$last] = new Array_($lastItem);
$tokens->next();
} else {
$type = $this->resolveSingleType($token, $context);
$tokens->next();
if ($parserContext === self::PARSER_IN_NULLABLE) {
return $type;
}
$types[] = $type;
}
}
if ($token === '|' || $token === '&') {
throw new RuntimeException(
'A type is missing after a type separator'
);
}
if (count($types) === 0) {
if ($parserContext === self::PARSER_IN_NULLABLE) {
throw new RuntimeException(
'A type is missing after a nullable character'
);
}
if ($parserContext === self::PARSER_IN_ARRAY_EXPRESSION) {
throw new RuntimeException(
'A type is missing in an array expression'
);
}
if ($parserContext === self::PARSER_IN_COLLECTION_EXPRESSION) {
throw new RuntimeException(
'A type is missing in a collection expression'
);
}
} elseif (count($types) === 1) {
return $types[0];
}
if ($compoundToken === '|') {
return new Compound(array_values($types));
}
return new Intersection(array_values($types));
}
/**
* resolve the given type into a type object
*
* @param string $type the type string, representing a single type
*
* @return Type|Array_|Object_
*
* @psalm-mutation-free
*/
private function resolveSingleType(string $type, Context $context) : object
{
switch (true) {
case $this->isKeyword($type):
return $this->resolveKeyword($type);
case $this->isFqsen($type):
return $this->resolveTypedObject($type);
case $this->isPartialStructuralElementName($type):
return $this->resolveTypedObject($type, $context);
// @codeCoverageIgnoreStart
default:
// I haven't got the foggiest how the logic would come here but added this as a defense.
throw new RuntimeException(
'Unable to resolve type "' . $type . '", there is no known method to resolve it'
);
}
// @codeCoverageIgnoreEnd
}
/**
* Adds a keyword to the list of Keywords and associates it with a specific Value Object.
*
* @psalm-param class-string<Type> $typeClassName
*/
public function addKeyword(string $keyword, string $typeClassName) : void
{
if (!class_exists($typeClassName)) {
throw new InvalidArgumentException(
'The Value Object that needs to be created with a keyword "' . $keyword . '" must be an existing class'
. ' but we could not find the class ' . $typeClassName
);
}
if (!in_array(Type::class, class_implements($typeClassName), true)) {
throw new InvalidArgumentException(
'The class "' . $typeClassName . '" must implement the interface "phpDocumentor\Reflection\Type"'
);
}
$this->keywords[$keyword] = $typeClassName;
}
/**
* Detects whether the given type represents a PHPDoc keyword.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @psalm-mutation-free
*/
private function isKeyword(string $type) : bool
{
return array_key_exists(strtolower($type), $this->keywords);
}
/**
* Detects whether the given type represents a relative structural element name.
*
* @param string $type A relative or absolute type as defined in the phpDocumentor documentation.
*
* @psalm-mutation-free
*/
private function isPartialStructuralElementName(string $type) : bool
{
return ($type[0] !== self::OPERATOR_NAMESPACE) && !$this->isKeyword($type);
}
/**
* Tests whether the given type is a Fully Qualified Structural Element Name.
*
* @psalm-mutation-free
*/
private function isFqsen(string $type) : bool
{
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
}
/**
* Resolves the given keyword (such as `string`) into a Type object representing that keyword.
*
* @psalm-mutation-free
*/
private function resolveKeyword(string $type) : Type
{
$className = $this->keywords[strtolower($type)];
return new $className();
}
/**
* Resolves the given FQSEN string into an FQSEN object.
*
* @psalm-mutation-free
*/
private function resolveTypedObject(string $type, ?Context $context = null) : Object_
{
return new Object_($this->fqsenResolver->resolve($type, $context));
}
/**
* Resolves class string
*
* @param ArrayIterator<int, (string|null)> $tokens
*/
private function resolveClassString(ArrayIterator $tokens, Context $context) : Type
{
$tokens->next();
$classType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION);
if (!$classType instanceof Object_ || $classType->getFqsen() === null) {
throw new RuntimeException(
$classType . ' is not a class string'
);
}
$token = $tokens->current();
if ($token !== '>') {
if (empty($token)) {
throw new RuntimeException(
'class-string: ">" is missing'
);
}
throw new RuntimeException(
'Unexpected character "' . $token . '", ">" is missing'
);
}
return new ClassString($classType->getFqsen());
}
/**
* Resolves the collection values and keys
*
* @param ArrayIterator<int, (string|null)> $tokens
*
* @return Array_|Iterable_|Collection
*/
private function resolveCollection(ArrayIterator $tokens, Type $classType, Context $context) : Type
{
$isArray = ((string) $classType === 'array');
$isIterable = ((string) $classType === 'iterable');
// allow only "array", "iterable" or class name before "<"
if (!$isArray && !$isIterable
&& (!$classType instanceof Object_ || $classType->getFqsen() === null)) {
throw new RuntimeException(
$classType . ' is not a collection'
);
}
$tokens->next();
$valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION);
$keyType = null;
$token = $tokens->current();
if ($token !== null && trim($token) === ',') {
// if we have a comma, then we just parsed the key type, not the value type
$keyType = $valueType;
if ($isArray) {
// check the key type for an "array" collection. We allow only
// strings or integers.
if (!$keyType instanceof String_ &&
!$keyType instanceof Integer &&
!$keyType instanceof Compound
) {
throw new RuntimeException(
'An array can have only integers or strings as keys'
);
}
if ($keyType instanceof Compound) {
foreach ($keyType->getIterator() as $item) {
if (!$item instanceof String_ &&
!$item instanceof Integer
) {
throw new RuntimeException(
'An array can have only integers or strings as keys'
);
}
}
}
}
$tokens->next();
// now let's parse the value type
$valueType = $this->parseTypes($tokens, $context, self::PARSER_IN_COLLECTION_EXPRESSION);
}
$token = $tokens->current();
if ($token !== '>') {
if (empty($token)) {
throw new RuntimeException(
'Collection: ">" is missing'
);
}
throw new RuntimeException(
'Unexpected character "' . $token . '", ">" is missing'
);
}
if ($isArray) {
return new Array_($valueType, $keyType);
}
if ($isIterable) {
return new Iterable_($valueType, $keyType);
}
if ($classType instanceof Object_) {
return $this->makeCollectionFromObject($classType, $valueType, $keyType);
}
throw new RuntimeException('Invalid $classType provided');
}
/**
* @psalm-pure
*/
private function makeCollectionFromObject(Object_ $object, Type $valueType, ?Type $keyType = null) : Collection
{
return new Collection($object->getFqsen(), $valueType, $keyType);
}
}
The MIT License (MIT)
Copyright (c) 2010 Mike van Riel
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.
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Interface for project factories. A project factory shall convert a set of files
* into an object implementing the Project interface.
*/
interface ProjectFactory
{
/**
* Creates a project from the set of files.
*
* @param File[] $files
*/
public function create(string $name, array $files) : Project;
}
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Interface for project. Since the definition of a project can be different per factory this interface will be small.
*/
interface Project
{
/**
* Returns the name of the project.
*/
public function getName() : string;
}
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use InvalidArgumentException;
use function assert;
use function end;
use function explode;
use function is_string;
use function preg_match;
use function sprintf;
use function trim;
/**
* Value Object for Fqsen.
*
* @link https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc-meta.md
*
* @psalm-immutable
*/
final class Fqsen
{
/** @var string full quallified class name */
private $fqsen;
/** @var string name of the element without path. */
private $name;
/**
* Initializes the object.
*
* @throws InvalidArgumentException when $fqsen is not matching the format.
*/
public function __construct(string $fqsen)
{
$matches = [];
$result = preg_match(
//phpcs:ignore Generic.Files.LineLength.TooLong
'/^\\\\([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff\\\\]*)?(?:[:]{2}\\$?([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*))?(?:\\(\\))?$/',
$fqsen,
$matches
);
if ($result === 0) {
throw new InvalidArgumentException(
sprintf('"%s" is not a valid Fqsen.', $fqsen)
);
}
$this->fqsen = $fqsen;
if (isset($matches[2])) {
$this->name = $matches[2];
} else {
$matches = explode('\\', $fqsen);
$name = end($matches);
assert(is_string($name));
$this->name = trim($name, '()');
}
}
/**
* converts this class to string.
*/
public function __toString() : string
{
return $this->fqsen;
}
/**
* Returns the name of the element without path.
*/
public function getName() : string
{
return $this->name;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* The location where an element occurs within a file.
*
* @psalm-immutable
*/
final class Location
{
/** @var int */
private $lineNumber = 0;
/** @var int */
private $columnNumber = 0;
/**
* Initializes the location for an element using its line number in the file and optionally the column number.
*/
public function __construct(int $lineNumber, int $columnNumber = 0)
{
$this->lineNumber = $lineNumber;
$this->columnNumber = $columnNumber;
}
/**
* Returns the line number that is covered by this location.
*/
public function getLineNumber() : int
{
return $this->lineNumber;
}
/**
* Returns the column number (character position on a line) for this location object.
*/
public function getColumnNumber() : int
{
return $this->columnNumber;
}
}
<?php
declare(strict_types=1);
/**
* phpDocumentor
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Interface for Api Elements
*/
interface Element
{
/**
* Returns the Fqsen of the element.
*/
public function getFqsen() : Fqsen;
/**
* Returns the name of the element.
*/
public function getName() : string;
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
/**
* Interface for files processed by the ProjectFactory
*/
interface File
{
/**
* Returns the content of the file as a string.
*/
public function getContents() : string;
/**
* Returns md5 hash of the file.
*/
public function md5() : string;
/**
* Returns an relative path to the file.
*/
public function path() : string;
}
The MIT License (MIT)
Copyright (c) 2015 phpDocumentor
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.
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\DocBlock\Tag;
// phpcs:ignore SlevomatCodingStandard.Classes.SuperfluousInterfaceNaming.SuperfluousSuffix
interface DocBlockFactoryInterface
{
/**
* Factory method for easy instantiation.
*
* @param array<string, class-string<Tag>> $additionalTags
*/
public static function createInstance(array $additionalTags = []) : DocBlockFactory;
/**
* @param string|object $docblock
*/
public function create($docblock, ?Types\Context $context = null, ?Location $location = null) : DocBlock;
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\DocBlock\Tag;
use Webmozart\Assert\Assert;
final class DocBlock
{
/** @var string The opening line for this docblock. */
private $summary;
/** @var DocBlock\Description The actual description for this docblock. */
private $description;
/** @var Tag[] An array containing all the tags in this docblock; except inline. */
private $tags = [];
/** @var Types\Context|null Information about the context of this DocBlock. */
private $context;
/** @var Location|null Information about the location of this DocBlock. */
private $location;
/** @var bool Is this DocBlock (the start of) a template? */
private $isTemplateStart;
/** @var bool Does this DocBlock signify the end of a DocBlock template? */
private $isTemplateEnd;
/**
* @param DocBlock\Tag[] $tags
* @param Types\Context $context The context in which the DocBlock occurs.
* @param Location $location The location within the file that this DocBlock occurs in.
*/
public function __construct(
string $summary = '',
?DocBlock\Description $description = null,
array $tags = [],
?Types\Context $context = null,
?Location $location = null,
bool $isTemplateStart = false,
bool $isTemplateEnd = false
) {
Assert::allIsInstanceOf($tags, Tag::class);
$this->summary = $summary;
$this->description = $description ?: new DocBlock\Description('');
foreach ($tags as $tag) {
$this->addTag($tag);
}
$this->context = $context;
$this->location = $location;
$this->isTemplateEnd = $isTemplateEnd;
$this->isTemplateStart = $isTemplateStart;
}
public function getSummary() : string
{
return $this->summary;
}
public function getDescription() : DocBlock\Description
{
return $this->description;
}
/**
* Returns the current context.
*/
public function getContext() : ?Types\Context
{
return $this->context;
}
/**
* Returns the current location.
*/
public function getLocation() : ?Location
{
return $this->location;
}
/**
* Returns whether this DocBlock is the start of a Template section.
*
* A Docblock may serve as template for a series of subsequent DocBlocks. This is indicated by a special marker
* (`#@+`) that is appended directly after the opening `/**` of a DocBlock.
*
* An example of such an opening is:
*
* ```
* /**#@+
* * My DocBlock
* * /
* ```
*
* The description and tags (not the summary!) are copied onto all subsequent DocBlocks and also applied to all
* elements that follow until another DocBlock is found that contains the closing marker (`#@-`).
*
* @see self::isTemplateEnd() for the check whether a closing marker was provided.
*/
public function isTemplateStart() : bool
{
return $this->isTemplateStart;
}
/**
* Returns whether this DocBlock is the end of a Template section.
*
* @see self::isTemplateStart() for a more complete description of the Docblock Template functionality.
*/
public function isTemplateEnd() : bool
{
return $this->isTemplateEnd;
}
/**
* Returns the tags for this DocBlock.
*
* @return Tag[]
*/
public function getTags() : array
{
return $this->tags;
}
/**
* Returns an array of tags matching the given name. If no tags are found
* an empty array is returned.
*
* @param string $name String to search by.
*
* @return Tag[]
*/
public function getTagsByName(string $name) : array
{
$result = [];
foreach ($this->getTags() as $tag) {
if ($tag->getName() !== $name) {
continue;
}
$result[] = $tag;
}
return $result;
}
/**
* Checks if a tag of a certain type is present in this DocBlock.
*
* @param string $name Tag name to check for.
*/
public function hasTag(string $name) : bool
{
foreach ($this->getTags() as $tag) {
if ($tag->getName() === $name) {
return true;
}
}
return false;
}
/**
* Remove a tag from this DocBlock.
*
* @param Tag $tagToRemove The tag to remove.
*/
public function removeTag(Tag $tagToRemove) : void
{
foreach ($this->tags as $key => $tag) {
if ($tag === $tagToRemove) {
unset($this->tags[$key]);
break;
}
}
}
/**
* Adds a tag to this DocBlock.
*
* @param Tag $tag The tag to add.
*/
private function addTag(Tag $tag) : void
{
$this->tags[] = $tag;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use InvalidArgumentException;
use LogicException;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\TagFactory;
use Webmozart\Assert\Assert;
use function array_shift;
use function count;
use function explode;
use function is_object;
use function method_exists;
use function preg_match;
use function preg_replace;
use function str_replace;
use function strpos;
use function substr;
use function trim;
final class DocBlockFactory implements DocBlockFactoryInterface
{
/** @var DocBlock\DescriptionFactory */
private $descriptionFactory;
/** @var DocBlock\TagFactory */
private $tagFactory;
/**
* Initializes this factory with the required subcontractors.
*/
public function __construct(DescriptionFactory $descriptionFactory, TagFactory $tagFactory)
{
$this->descriptionFactory = $descriptionFactory;
$this->tagFactory = $tagFactory;
}
/**
* Factory method for easy instantiation.
*
* @param array<string, class-string<Tag>> $additionalTags
*/
public static function createInstance(array $additionalTags = []) : self
{
$fqsenResolver = new FqsenResolver();
$tagFactory = new StandardTagFactory($fqsenResolver);
$descriptionFactory = new DescriptionFactory($tagFactory);
$tagFactory->addService($descriptionFactory);
$tagFactory->addService(new TypeResolver($fqsenResolver));
$docBlockFactory = new self($descriptionFactory, $tagFactory);
foreach ($additionalTags as $tagName => $tagHandler) {
$docBlockFactory->registerTagHandler($tagName, $tagHandler);
}
return $docBlockFactory;
}
/**
* @param object|string $docblock A string containing the DocBlock to parse or an object supporting the
* getDocComment method (such as a ReflectionClass object).
*/
public function create($docblock, ?Types\Context $context = null, ?Location $location = null) : DocBlock
{
if (is_object($docblock)) {
if (!method_exists($docblock, 'getDocComment')) {
$exceptionMessage = 'Invalid object passed; the given object must support the getDocComment method';
throw new InvalidArgumentException($exceptionMessage);
}
$docblock = $docblock->getDocComment();
Assert::string($docblock);
}
Assert::stringNotEmpty($docblock);
if ($context === null) {
$context = new Types\Context('');
}
$parts = $this->splitDocBlock($this->stripDocComment($docblock));
[$templateMarker, $summary, $description, $tags] = $parts;
return new DocBlock(
$summary,
$description ? $this->descriptionFactory->create($description, $context) : null,
$this->parseTagBlock($tags, $context),
$context,
$location,
$templateMarker === '#@+',
$templateMarker === '#@-'
);
}
/**
* @param class-string<Tag> $handler
*/
public function registerTagHandler(string $tagName, string $handler) : void
{
$this->tagFactory->registerTagHandler($tagName, $handler);
}
/**
* Strips the asterisks from the DocBlock comment.
*
* @param string $comment String containing the comment text.
*/
private function stripDocComment(string $comment) : string
{
$comment = preg_replace('#[ \t]*(?:\/\*\*|\*\/|\*)?[ \t]?(.*)?#u', '$1', $comment);
Assert::string($comment);
$comment = trim($comment);
// reg ex above is not able to remove */ from a single line docblock
if (substr($comment, -2) === '*/') {
$comment = trim(substr($comment, 0, -2));
}
return str_replace(["\r\n", "\r"], "\n", $comment);
}
// phpcs:disable
/**
* Splits the DocBlock into a template marker, summary, description and block of tags.
*
* @param string $comment Comment to split into the sub-parts.
*
* @return string[] containing the template marker (if any), summary, description and a string containing the tags.
*
* @author Mike van Riel <me@mikevanriel.com> for extending the regex with template marker support.
*
* @author Richard van Velzen (@_richardJ) Special thanks to Richard for the regex responsible for the split.
*/
private function splitDocBlock(string $comment) : array
{
// phpcs:enable
// Performance improvement cheat: if the first character is an @ then only tags are in this DocBlock. This
// method does not split tags so we return this verbatim as the fourth result (tags). This saves us the
// performance impact of running a regular expression
if (strpos($comment, '@') === 0) {
return ['', '', '', $comment];
}
// clears all extra horizontal whitespace from the line endings to prevent parsing issues
$comment = preg_replace('/\h*$/Sum', '', $comment);
Assert::string($comment);
/*
* Splits the docblock into a template marker, summary, description and tags section.
*
* - The template marker is empty, #@+ or #@- if the DocBlock starts with either of those (a newline may
* occur after it and will be stripped).
* - The short description is started from the first character until a dot is encountered followed by a
* newline OR two consecutive newlines (horizontal whitespace is taken into account to consider spacing
* errors). This is optional.
* - The long description, any character until a new line is encountered followed by an @ and word
* characters (a tag). This is optional.
* - Tags; the remaining characters
*
* Big thanks to RichardJ for contributing this Regular Expression
*/
preg_match(
'/
\A
# 1. Extract the template marker
(?:(\#\@\+|\#\@\-)\n?)?
# 2. Extract the summary
(?:
(?! @\pL ) # The summary may not start with an @
(
[^\n.]+
(?:
(?! \. \n | \n{2} ) # End summary upon a dot followed by newline or two newlines
[\n.]* (?! [ \t]* @\pL ) # End summary when an @ is found as first character on a new line
[^\n.]+ # Include anything else
)*
\.?
)?
)
# 3. Extract the description
(?:
\s* # Some form of whitespace _must_ precede a description because a summary must be there
(?! @\pL ) # The description may not start with an @
(
[^\n]+
(?: \n+
(?! [ \t]* @\pL ) # End description when an @ is found as first character on a new line
[^\n]+ # Include anything else
)*
)
)?
# 4. Extract the tags (anything that follows)
(\s+ [\s\S]*)? # everything that follows
/ux',
$comment,
$matches
);
array_shift($matches);
while (count($matches) < 4) {
$matches[] = '';
}
return $matches;
}
/**
* Creates the tag objects.
*
* @param string $tags Tag block to parse.
* @param Types\Context $context Context of the parsed Tag
*
* @return DocBlock\Tag[]
*/
private function parseTagBlock(string $tags, Types\Context $context) : array
{
$tags = $this->filterTagBlock($tags);
if ($tags === null) {
return [];
}
$result = [];
$lines = $this->splitTagBlockIntoTagLines($tags);
foreach ($lines as $key => $tagLine) {
$result[$key] = $this->tagFactory->create(trim($tagLine), $context);
}
return $result;
}
/**
* @return string[]
*/
private function splitTagBlockIntoTagLines(string $tags) : array
{
$result = [];
foreach (explode("\n", $tags) as $tagLine) {
if ($tagLine !== '' && strpos($tagLine, '@') === 0) {
$result[] = $tagLine;
} else {
$result[count($result) - 1] .= "\n" . $tagLine;
}
}
return $result;
}
private function filterTagBlock(string $tags) : ?string
{
$tags = trim($tags);
if (!$tags) {
return null;
}
if ($tags[0] !== '@') {
// @codeCoverageIgnoreStart
// Can't simulate this; this only happens if there is an error with the parsing of the DocBlock that
// we didn't foresee.
throw new LogicException('A tag block started with text instead of an at-sign(@): ' . $tags);
// @codeCoverageIgnoreEnd
}
return $tags;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection;
use phpDocumentor\Reflection\Exception\PcreException;
use function preg_last_error;
use function preg_split as php_preg_split;
abstract class Utils
{
/**
* Wrapper function for phps preg_split
*
* This function is inspired by {@link https://github.com/thecodingmachine/safe/blob/master/generated/pcre.php}. But
* since this library is all about performance we decided to strip everything we don't need. Reducing the amount
* of files that have to be loaded, ect.
*
* @param string $pattern The pattern to search for, as a string.
* @param string $subject The input string.
* @param int|null $limit If specified, then only substrings up to limit are returned with the
* rest of the string being placed in the last substring. A limit of -1 or 0 means "no limit".
* @param int $flags flags can be any combination of the following flags (combined with the | bitwise operator):
* *PREG_SPLIT_NO_EMPTY*
* If this flag is set, only non-empty pieces will be returned by preg_split().
* *PREG_SPLIT_DELIM_CAPTURE*
* If this flag is set, parenthesized expression in the delimiter pattern will be captured
* and returned as well.
* *PREG_SPLIT_OFFSET_CAPTURE*
* If this flag is set, for every occurring match the appendant string offset will also be returned.
* Note that this changes the return value in an array where every element is an array consisting of the
* matched string at offset 0 and its string offset into subject at offset 1.
*
* @return string[] Returns an array containing substrings of subject split along boundaries matched by pattern
*
* @throws PcreException
*/
public static function pregSplit(string $pattern, string $subject, ?int $limit = -1, int $flags = 0) : array
{
$parts = php_preg_split($pattern, $subject, $limit, $flags);
if ($parts === false) {
throw PcreException::createFromPhpError(preg_last_error());
}
return $parts;
}
}
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\Exception;
use InvalidArgumentException;
use const PREG_BACKTRACK_LIMIT_ERROR;
use const PREG_BAD_UTF8_ERROR;
use const PREG_BAD_UTF8_OFFSET_ERROR;
use const PREG_INTERNAL_ERROR;
use const PREG_JIT_STACKLIMIT_ERROR;
use const PREG_NO_ERROR;
use const PREG_RECURSION_LIMIT_ERROR;
final class PcreException extends InvalidArgumentException
{
public static function createFromPhpError(int $errorCode) : self
{
switch ($errorCode) {
case PREG_BACKTRACK_LIMIT_ERROR:
return new self('Backtrack limit error');
case PREG_RECURSION_LIMIT_ERROR:
return new self('Recursion limit error');
case PREG_BAD_UTF8_ERROR:
return new self('Bad UTF8 error');
case PREG_BAD_UTF8_OFFSET_ERROR:
return new self('Bad UTF8 offset error');
case PREG_JIT_STACKLIMIT_ERROR:
return new self('Jit stacklimit error');
case PREG_NO_ERROR:
case PREG_INTERNAL_ERROR:
default:
}
return new self('Unknown Pcre error');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use function count;
use function explode;
use function implode;
use function ltrim;
use function min;
use function str_replace;
use function strlen;
use function strpos;
use function substr;
use function trim;
use const PREG_SPLIT_DELIM_CAPTURE;
/**
* Creates a new Description object given a body of text.
*
* Descriptions in phpDocumentor are somewhat complex entities as they can contain one or more tags inside their
* body that can be replaced with a readable output. The replacing is done by passing a Formatter object to the
* Description object's `render` method.
*
* In addition to the above does a Description support two types of escape sequences:
*
* 1. `{@}` to escape the `@` character to prevent it from being interpreted as part of a tag, i.e. `{{@}link}`
* 2. `{}` to escape the `}` character, this can be used if you want to use the `}` character in the description
* of an inline tag.
*
* If a body consists of multiple lines then this factory will also remove any superfluous whitespace at the beginning
* of each line while maintaining any indentation that is used. This will prevent formatting parsers from tripping
* over unexpected spaces as can be observed with tag descriptions.
*/
class DescriptionFactory
{
/** @var TagFactory */
private $tagFactory;
/**
* Initializes this factory with the means to construct (inline) tags.
*/
public function __construct(TagFactory $tagFactory)
{
$this->tagFactory = $tagFactory;
}
/**
* Returns the parsed text of this description.
*/
public function create(string $contents, ?TypeContext $context = null) : Description
{
$tokens = $this->lex($contents);
$count = count($tokens);
$tagCount = 0;
$tags = [];
for ($i = 1; $i < $count; $i += 2) {
$tags[] = $this->tagFactory->create($tokens[$i], $context);
$tokens[$i] = '%' . ++$tagCount . '$s';
}
//In order to allow "literal" inline tags, the otherwise invalid
//sequence "{@}" is changed to "@", and "{}" is changed to "}".
//"%" is escaped to "%%" because of vsprintf.
//See unit tests for examples.
for ($i = 0; $i < $count; $i += 2) {
$tokens[$i] = str_replace(['{@}', '{}', '%'], ['@', '}', '%%'], $tokens[$i]);
}
return new Description(implode('', $tokens), $tags);
}
/**
* Strips the contents from superfluous whitespace and splits the description into a series of tokens.
*
* @return string[] A series of tokens of which the description text is composed.
*/
private function lex(string $contents) : array
{
$contents = $this->removeSuperfluousStartingWhitespace($contents);
// performance optimalization; if there is no inline tag, don't bother splitting it up.
if (strpos($contents, '{@') === false) {
return [$contents];
}
return Utils::pregSplit(
'/\{
# "{@}" is not a valid inline tag. This ensures that we do not treat it as one, but treat it literally.
(?!@\})
# We want to capture the whole tag line, but without the inline tag delimiters.
(\@
# Match everything up to the next delimiter.
[^{}]*
# Nested inline tag content should not be captured, or it will appear in the result separately.
(?:
# Match nested inline tags.
(?:
# Because we did not catch the tag delimiters earlier, we must be explicit with them here.
# Notice that this also matches "{}", as a way to later introduce it as an escape sequence.
\{(?1)?\}
|
# Make sure we match hanging "{".
\{
)
# Match content after the nested inline tag.
[^{}]*
)* # If there are more inline tags, match them as well. We use "*" since there may not be any
# nested inline tags.
)
\}/Sux',
$contents,
0,
PREG_SPLIT_DELIM_CAPTURE
);
}
/**
* Removes the superfluous from a multi-line description.
*
* When a description has more than one line then it can happen that the second and subsequent lines have an
* additional indentation. This is commonly in use with tags like this:
*
* {@}since 1.1.0 This is an example
* description where we have an
* indentation in the second and
* subsequent lines.
*
* If we do not normalize the indentation then we have superfluous whitespace on the second and subsequent
* lines and this may cause rendering issues when, for example, using a Markdown converter.
*/
private function removeSuperfluousStartingWhitespace(string $contents) : string
{
$lines = explode("\n", $contents);
// if there is only one line then we don't have lines with superfluous whitespace and
// can use the contents as-is
if (count($lines) <= 1) {
return $contents;
}
// determine how many whitespace characters need to be stripped
$startingSpaceCount = 9999999;
for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) {
// lines with a no length do not count as they are not indented at all
if (trim($lines[$i]) === '') {
continue;
}
// determine the number of prefixing spaces by checking the difference in line length before and after
// an ltrim
$startingSpaceCount = min($startingSpaceCount, strlen($lines[$i]) - strlen(ltrim($lines[$i])));
}
// strip the number of spaces from each line
if ($startingSpaceCount > 0) {
for ($i = 1, $iMax = count($lines); $i < $iMax; ++$i) {
$lines[$i] = substr($lines[$i], $startingSpaceCount);
}
}
return implode("\n", $lines);
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
interface Tag
{
public function getName() : string;
/**
* @return Tag|mixed Class that implements Tag
*
* @phpstan-return ?Tag
*/
public static function create(string $body);
public function render(?Formatter $formatter = null) : string;
public function __toString() : string;
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use InvalidArgumentException;
use phpDocumentor\Reflection\Types\Context as TypeContext;
interface TagFactory
{
/**
* Adds a parameter to the service locator that can be injected in a tag's factory method.
*
* When calling a tag's "create" method we always check the signature for dependencies to inject. One way is to
* typehint a parameter in the signature so that we can use that interface or class name to inject a dependency
* (see {@see addService()} for more information on that).
*
* Another way is to check the name of the argument against the names in the Service Locator. With this method
* you can add a variable that will be inserted when a tag's create method is not typehinted and has a matching
* name.
*
* Be aware that there are two reserved names:
*
* - name, representing the name of the tag.
* - body, representing the complete body of the tag.
*
* These parameters are injected at the last moment and will override any existing parameter with those names.
*
* @param mixed $value
*/
public function addParameter(string $name, $value) : void;
/**
* Factory method responsible for instantiating the correct sub type.
*
* @param string $tagLine The text for this tag, including description.
*
* @return Tag A new tag object.
*
* @throws InvalidArgumentException If an invalid tag line was presented.
*/
public function create(string $tagLine, ?TypeContext $context = null) : Tag;
/**
* Registers a service with the Service Locator using the FQCN of the class or the alias, if provided.
*
* When calling a tag's "create" method we always check the signature for dependencies to inject. If a parameter
* has a typehint then the ServiceLocator is queried to see if a Service is registered for that typehint.
*
* Because interfaces are regularly used as type-hints this method provides an alias parameter; if the FQCN of the
* interface is passed as alias then every time that interface is requested the provided service will be returned.
*/
public function addService(object $service) : void;
/**
* Registers a handler for tags.
*
* If you want to use your own tags then you can use this method to instruct the TagFactory
* to register the name of a tag with the FQCN of a 'Tag Handler'. The Tag handler should implement
* the {@see Tag} interface (and thus the create method).
*
* @param string $tagName Name of tag to register a handler for. When registering a namespaced
* tag, the full name, along with a prefixing slash MUST be provided.
* @param class-string<Tag> $handler FQCN of handler.
*
* @throws InvalidArgumentException If the tag name is not a string.
* @throws InvalidArgumentException If the tag name is namespaced (contains backslashes) but
* does not start with a backslash.
* @throws InvalidArgumentException If the handler is not a string.
* @throws InvalidArgumentException If the handler is not an existing class.
* @throws InvalidArgumentException If the handler does not implement the {@see Tag} interface.
*/
public function registerTagHandler(string $tagName, string $handler) : void;
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use InvalidArgumentException;
use function filter_var;
use function preg_match;
use function trim;
use const FILTER_VALIDATE_EMAIL;
/**
* Reflection class for an {@}author tag in a Docblock.
*/
final class Author extends BaseTag implements Factory\StaticMethod
{
/** @var string register that this is the author tag. */
protected $name = 'author';
/** @var string The name of the author */
private $authorName;
/** @var string The email of the author */
private $authorEmail;
/**
* Initializes this tag with the author name and e-mail.
*/
public function __construct(string $authorName, string $authorEmail)
{
if ($authorEmail && !filter_var($authorEmail, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('The author tag does not have a valid e-mail address');
}
$this->authorName = $authorName;
$this->authorEmail = $authorEmail;
}
/**
* Gets the author's name.
*
* @return string The author's name.
*/
public function getAuthorName() : string
{
return $this->authorName;
}
/**
* Returns the author's email.
*
* @return string The author's email.
*/
public function getEmail() : string
{
return $this->authorEmail;
}
/**
* Returns this tag in string form.
*/
public function __toString() : string
{
if ($this->authorEmail) {
$authorEmail = '<' . $this->authorEmail . '>';
} else {
$authorEmail = '';
}
$authorName = (string) $this->authorName;
return $authorName . ($authorEmail !== '' ? ($authorName !== '' ? ' ' : '') . $authorEmail : '');
}
/**
* Attempts to create a new Author object based on †he tag body.
*/
public static function create(string $body) : ?self
{
$splitTagContent = preg_match('/^([^\<]*)(?:\<([^\>]*)\>)?$/u', $body, $matches);
if (!$splitTagContent) {
return null;
}
$authorName = trim($matches[1]);
$email = isset($matches[2]) ? trim($matches[2]) : '';
return new static($authorName, $email);
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
use function array_shift;
use function array_unshift;
use function implode;
use function strpos;
use function substr;
use const PREG_SPLIT_DELIM_CAPTURE;
/**
* Reflection class for a {@}property-write tag in a Docblock.
*/
final class PropertyWrite extends TagWithType implements Factory\StaticMethod
{
/** @var string */
protected $variableName;
public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null)
{
Assert::string($variableName);
$this->name = 'property-write';
$this->variableName = $variableName;
$this->type = $type;
$this->description = $description;
}
public static function create(
string $body,
?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::stringNotEmpty($body);
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
[$firstPart, $body] = self::extractTypeFromBody($body);
$type = null;
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
$variableName = '';
// if the first item that is encountered is not a variable; it is a type
if ($firstPart && $firstPart[0] !== '$') {
$type = $typeResolver->resolve($firstPart, $context);
} else {
// first part is not a type; we should prepend it to the parts array for further processing
array_unshift($parts, $firstPart);
}
// if the next item starts with a $ it must be the variable name
if (isset($parts[0]) && strpos($parts[0], '$') === 0) {
$variableName = array_shift($parts);
if ($type) {
array_shift($parts);
}
Assert::notNull($variableName);
$variableName = substr($variableName, 1);
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $description);
}
/**
* Returns the variable's name.
*/
public function getVariableName() : ?string
{
return $this->variableName;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
if ($this->variableName) {
$variableName = '$' . $this->variableName;
} else {
$variableName = '';
}
$type = (string) $this->type;
return $type
. ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '')
. ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}link tag in a Docblock.
*/
final class Link extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'link';
/** @var string */
private $link;
/**
* Initializes a link to a URL.
*/
public function __construct(string $link, ?Description $description = null)
{
$this->link = $link;
$this->description = $description;
}
public static function create(
string $body,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::notNull($descriptionFactory);
$parts = Utils::pregSplit('/\s+/Su', $body, 2);
$description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;
return new static($parts[0], $description);
}
/**
* Gets the link
*/
public function getLink() : string
{
return $this->link;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$link = (string) $this->link;
return $link . ($description !== '' ? ($link !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
use function preg_match;
/**
* Reflection class for a {@}deprecated tag in a Docblock.
*/
final class Deprecated extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'deprecated';
/**
* PCRE regular expression matching a version vector.
* Assumes the "x" modifier.
*/
public const REGEX_VECTOR = '(?:
# Normal release vectors.
\d\S*
|
# VCS version vectors. Per PHPCS, they are expected to
# follow the form of the VCS name, followed by ":", followed
# by the version vector itself.
# By convention, popular VCSes like CVS, SVN and GIT use "$"
# around the actual version vector.
[^\s\:]+\:\s*\$[^\$]+\$
)';
/** @var string|null The version vector. */
private $version;
public function __construct(?string $version = null, ?Description $description = null)
{
Assert::nullOrNotEmpty($version);
$this->version = $version;
$this->description = $description;
}
/**
* @return static
*/
public static function create(
?string $body,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
if (empty($body)) {
return new static();
}
$matches = [];
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
return new static(
null,
$descriptionFactory !== null ? $descriptionFactory->create($body, $context) : null
);
}
Assert::notNull($descriptionFactory);
return new static(
$matches[1],
$descriptionFactory->create($matches[2] ?? '', $context)
);
}
/**
* Gets the version section of the tag.
*/
public function getVersion() : ?string
{
return $this->version;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$version = (string) $this->version;
return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
use function array_key_exists;
use function explode;
/**
* Reflection class for a @covers tag in a Docblock.
*/
final class Covers extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'covers';
/** @var Fqsen */
private $refers;
/**
* Initializes this tag.
*/
public function __construct(Fqsen $refers, ?Description $description = null)
{
$this->refers = $refers;
$this->description = $description;
}
public static function create(
string $body,
?DescriptionFactory $descriptionFactory = null,
?FqsenResolver $resolver = null,
?TypeContext $context = null
) : self {
Assert::stringNotEmpty($body);
Assert::notNull($descriptionFactory);
Assert::notNull($resolver);
$parts = Utils::pregSplit('/\s+/Su', $body, 2);
return new static(
self::resolveFqsen($parts[0], $resolver, $context),
$descriptionFactory->create($parts[1] ?? '', $context)
);
}
private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen
{
Assert::notNull($fqsenResolver);
$fqsenParts = explode('::', $parts);
$resolved = $fqsenResolver->resolve($fqsenParts[0], $context);
if (!array_key_exists(1, $fqsenParts)) {
return $resolved;
}
return new Fqsen($resolved . '::' . $fqsenParts[1]);
}
/**
* Returns the structural element this tag refers to.
*/
public function getReference() : Fqsen
{
return $this->refers;
}
/**
* Returns a string representation of this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$refers = (string) $this->refers;
return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
use function preg_match;
/**
* Reflection class for a {@}since tag in a Docblock.
*/
final class Since extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'since';
/**
* PCRE regular expression matching a version vector.
* Assumes the "x" modifier.
*/
public const REGEX_VECTOR = '(?:
# Normal release vectors.
\d\S*
|
# VCS version vectors. Per PHPCS, they are expected to
# follow the form of the VCS name, followed by ":", followed
# by the version vector itself.
# By convention, popular VCSes like CVS, SVN and GIT use "$"
# around the actual version vector.
[^\s\:]+\:\s*\$[^\$]+\$
)';
/** @var string|null The version vector. */
private $version;
public function __construct(?string $version = null, ?Description $description = null)
{
Assert::nullOrNotEmpty($version);
$this->version = $version;
$this->description = $description;
}
public static function create(
?string $body,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : ?self {
if (empty($body)) {
return new static();
}
$matches = [];
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
return null;
}
Assert::notNull($descriptionFactory);
return new static(
$matches[1],
$descriptionFactory->create($matches[2] ?? '', $context)
);
}
/**
* Gets the version section of the tag.
*/
public function getVersion() : ?string
{
return $this->version;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$version = (string) $this->version;
return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
use function array_shift;
use function array_unshift;
use function implode;
use function strpos;
use function substr;
use const PREG_SPLIT_DELIM_CAPTURE;
/**
* Reflection class for a {@}property tag in a Docblock.
*/
final class Property extends TagWithType implements Factory\StaticMethod
{
/** @var string|null */
protected $variableName;
public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null)
{
Assert::string($variableName);
$this->name = 'property';
$this->variableName = $variableName;
$this->type = $type;
$this->description = $description;
}
public static function create(
string $body,
?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::stringNotEmpty($body);
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
[$firstPart, $body] = self::extractTypeFromBody($body);
$type = null;
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
$variableName = '';
// if the first item that is encountered is not a variable; it is a type
if ($firstPart && $firstPart[0] !== '$') {
$type = $typeResolver->resolve($firstPart, $context);
} else {
// first part is not a type; we should prepend it to the parts array for further processing
array_unshift($parts, $firstPart);
}
// if the next item starts with a $ it must be the variable name
if (isset($parts[0]) && strpos($parts[0], '$') === 0) {
$variableName = array_shift($parts);
if ($type) {
array_shift($parts);
}
Assert::notNull($variableName);
$variableName = substr($variableName, 1);
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $description);
}
/**
* Returns the variable's name.
*/
public function getVariableName() : ?string
{
return $this->variableName;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
if ($this->variableName) {
$variableName = '$' . $this->variableName;
} else {
$variableName = '';
}
$type = (string) $this->type;
return $type
. ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '')
. ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Fqsen as FqsenRef;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Reference;
use phpDocumentor\Reflection\DocBlock\Tags\Reference\Url;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
use function array_key_exists;
use function explode;
use function preg_match;
/**
* Reflection class for an {@}see tag in a Docblock.
*/
final class See extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'see';
/** @var Reference */
protected $refers;
/**
* Initializes this tag.
*/
public function __construct(Reference $refers, ?Description $description = null)
{
$this->refers = $refers;
$this->description = $description;
}
public static function create(
string $body,
?FqsenResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::notNull($descriptionFactory);
$parts = Utils::pregSplit('/\s+/Su', $body, 2);
$description = isset($parts[1]) ? $descriptionFactory->create($parts[1], $context) : null;
// https://tools.ietf.org/html/rfc2396#section-3
if (preg_match('/\w:\/\/\w/i', $parts[0])) {
return new static(new Url($parts[0]), $description);
}
return new static(new FqsenRef(self::resolveFqsen($parts[0], $typeResolver, $context)), $description);
}
private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen
{
Assert::notNull($fqsenResolver);
$fqsenParts = explode('::', $parts);
$resolved = $fqsenResolver->resolve($fqsenParts[0], $context);
if (!array_key_exists(1, $fqsenParts)) {
return $resolved;
}
return new Fqsen($resolved . '::' . $fqsenParts[1]);
}
/**
* Returns the ref of this tag.
*/
public function getReference() : Reference
{
return $this->refers;
}
/**
* Returns a string representation of this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$refers = (string) $this->refers;
return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use InvalidArgumentException;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Types\Mixed_;
use phpDocumentor\Reflection\Types\Void_;
use Webmozart\Assert\Assert;
use function array_keys;
use function explode;
use function implode;
use function is_string;
use function preg_match;
use function sort;
use function strpos;
use function substr;
use function trim;
use function var_export;
/**
* Reflection class for an {@}method in a Docblock.
*/
final class Method extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'method';
/** @var string */
private $methodName;
/**
* @phpstan-var array<int, array{name: string, type: Type}>
* @var array<int, array<string, Type|string>>
*/
private $arguments;
/** @var bool */
private $isStatic;
/** @var Type */
private $returnType;
/**
* @param array<int, array<string, Type|string>> $arguments
*
* @phpstan-param array<int, array{name: string, type: Type}|string> $arguments
*/
public function __construct(
string $methodName,
array $arguments = [],
?Type $returnType = null,
bool $static = false,
?Description $description = null
) {
Assert::stringNotEmpty($methodName);
if ($returnType === null) {
$returnType = new Void_();
}
$this->methodName = $methodName;
$this->arguments = $this->filterArguments($arguments);
$this->returnType = $returnType;
$this->isStatic = $static;
$this->description = $description;
}
public static function create(
string $body,
?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : ?self {
Assert::stringNotEmpty($body);
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
// 1. none or more whitespace
// 2. optionally the keyword "static" followed by whitespace
// 3. optionally a word with underscores followed by whitespace : as
// type for the return value
// 4. then optionally a word with underscores followed by () and
// whitespace : as method name as used by phpDocumentor
// 5. then a word with underscores, followed by ( and any character
// until a ) and whitespace : as method name with signature
// 6. any remaining text : as description
if (!preg_match(
'/^
# Static keyword
# Declares a static method ONLY if type is also present
(?:
(static)
\s+
)?
# Return type
(?:
(
(?:[\w\|_\\\\]*\$this[\w\|_\\\\]*)
|
(?:
(?:[\w\|_\\\\]+)
# array notation
(?:\[\])*
)*+
)
\s+
)?
# Method name
([\w_]+)
# Arguments
(?:
\(([^\)]*)\)
)?
\s*
# Description
(.*)
$/sux',
$body,
$matches
)) {
return null;
}
[, $static, $returnType, $methodName, $argumentLines, $description] = $matches;
$static = $static === 'static';
if ($returnType === '') {
$returnType = 'void';
}
$returnType = $typeResolver->resolve($returnType, $context);
$description = $descriptionFactory->create($description, $context);
/** @phpstan-var array<int, array{name: string, type: Type}> $arguments */
$arguments = [];
if ($argumentLines !== '') {
$argumentsExploded = explode(',', $argumentLines);
foreach ($argumentsExploded as $argument) {
$argument = explode(' ', self::stripRestArg(trim($argument)), 2);
if (strpos($argument[0], '$') === 0) {
$argumentName = substr($argument[0], 1);
$argumentType = new Mixed_();
} else {
$argumentType = $typeResolver->resolve($argument[0], $context);
$argumentName = '';
if (isset($argument[1])) {
$argument[1] = self::stripRestArg($argument[1]);
$argumentName = substr($argument[1], 1);
}
}
$arguments[] = ['name' => $argumentName, 'type' => $argumentType];
}
}
return new static($methodName, $arguments, $returnType, $static, $description);
}
/**
* Retrieves the method name.
*/
public function getMethodName() : string
{
return $this->methodName;
}
/**
* @return array<int, array<string, Type|string>>
*
* @phpstan-return array<int, array{name: string, type: Type}>
*/
public function getArguments() : array
{
return $this->arguments;
}
/**
* Checks whether the method tag describes a static method or not.
*
* @return bool TRUE if the method declaration is for a static method, FALSE otherwise.
*/
public function isStatic() : bool
{
return $this->isStatic;
}
public function getReturnType() : Type
{
return $this->returnType;
}
public function __toString() : string
{
$arguments = [];
foreach ($this->arguments as $argument) {
$arguments[] = $argument['type'] . ' $' . $argument['name'];
}
$argumentStr = '(' . implode(', ', $arguments) . ')';
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$static = $this->isStatic ? 'static' : '';
$returnType = (string) $this->returnType;
$methodName = (string) $this->methodName;
return $static
. ($returnType !== '' ? ($static !== '' ? ' ' : '') . $returnType : '')
. ($methodName !== '' ? ($static !== '' || $returnType !== '' ? ' ' : '') . $methodName : '')
. $argumentStr
. ($description !== '' ? ' ' . $description : '');
}
/**
* @param mixed[][]|string[] $arguments
*
* @return mixed[][]
*
* @phpstan-param array<int, array{name: string, type: Type}|string> $arguments
* @phpstan-return array<int, array{name: string, type: Type}>
*/
private function filterArguments(array $arguments = []) : array
{
$result = [];
foreach ($arguments as $argument) {
if (is_string($argument)) {
$argument = ['name' => $argument];
}
if (!isset($argument['type'])) {
$argument['type'] = new Mixed_();
}
$keys = array_keys($argument);
sort($keys);
if ($keys !== ['name', 'type']) {
throw new InvalidArgumentException(
'Arguments can only have the "name" and "type" fields, found: ' . var_export($keys, true)
);
}
$result[] = $argument;
}
return $result;
}
private static function stripRestArg(string $argument) : string
{
if (strpos($argument, '...') === 0) {
$argument = trim(substr($argument, 3));
}
return $argument;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
use function preg_match;
/**
* Reflection class for a {@}source tag in a Docblock.
*/
final class Source extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'source';
/** @var int The starting line, relative to the structural element's location. */
private $startingLine;
/** @var int|null The number of lines, relative to the starting line. NULL means "to the end". */
private $lineCount;
/**
* @param int|string $startingLine should be a to int convertible value
* @param int|string|null $lineCount should be a to int convertible value
*/
public function __construct($startingLine, $lineCount = null, ?Description $description = null)
{
Assert::integerish($startingLine);
Assert::nullOrIntegerish($lineCount);
$this->startingLine = (int) $startingLine;
$this->lineCount = $lineCount !== null ? (int) $lineCount : null;
$this->description = $description;
}
public static function create(
string $body,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::stringNotEmpty($body);
Assert::notNull($descriptionFactory);
$startingLine = 1;
$lineCount = null;
$description = null;
// Starting line / Number of lines / Description
if (preg_match('/^([1-9]\d*)\s*(?:((?1))\s+)?(.*)$/sux', $body, $matches)) {
$startingLine = (int) $matches[1];
if (isset($matches[2]) && $matches[2] !== '') {
$lineCount = (int) $matches[2];
}
$description = $matches[3];
}
return new static($startingLine, $lineCount, $descriptionFactory->create($description??'', $context));
}
/**
* Gets the starting line.
*
* @return int The starting line, relative to the structural element's
* location.
*/
public function getStartingLine() : int
{
return $this->startingLine;
}
/**
* Returns the number of lines.
*
* @return int|null The number of lines, relative to the starting line. NULL
* means "to the end".
*/
public function getLineCount() : ?int
{
return $this->lineCount;
}
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$startingLine = (string) $this->startingLine;
$lineCount = $this->lineCount !== null ? '' . $this->lineCount : '';
return $startingLine
. ($lineCount !== ''
? ($startingLine || $startingLine === '0' ? ' ' : '') . $lineCount
: '')
. ($description !== ''
? ($startingLine || $startingLine === '0' || $lineCount !== '' ? ' ' : '') . $description
: '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
use function array_shift;
use function array_unshift;
use function implode;
use function strpos;
use function substr;
use const PREG_SPLIT_DELIM_CAPTURE;
/**
* Reflection class for the {@}param tag in a Docblock.
*/
final class Param extends TagWithType implements Factory\StaticMethod
{
/** @var string|null */
private $variableName;
/** @var bool determines whether this is a variadic argument */
private $isVariadic;
/** @var bool determines whether this is passed by reference */
private $isReference;
public function __construct(
?string $variableName,
?Type $type = null,
bool $isVariadic = false,
?Description $description = null,
bool $isReference = false
) {
$this->name = 'param';
$this->variableName = $variableName;
$this->type = $type;
$this->isVariadic = $isVariadic;
$this->description = $description;
$this->isReference = $isReference;
}
public static function create(
string $body,
?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::stringNotEmpty($body);
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
[$firstPart, $body] = self::extractTypeFromBody($body);
$type = null;
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
$variableName = '';
$isVariadic = false;
$isReference = false;
// if the first item that is encountered is not a variable; it is a type
if ($firstPart && !self::strStartsWithVariable($firstPart)) {
$type = $typeResolver->resolve($firstPart, $context);
} else {
// first part is not a type; we should prepend it to the parts array for further processing
array_unshift($parts, $firstPart);
}
// if the next item starts with a $ or ...$ or &$ or &...$ it must be the variable name
if (isset($parts[0]) && self::strStartsWithVariable($parts[0])) {
$variableName = array_shift($parts);
if ($type) {
array_shift($parts);
}
Assert::notNull($variableName);
if (strpos($variableName, '$') === 0) {
$variableName = substr($variableName, 1);
} elseif (strpos($variableName, '&$') === 0) {
$isReference = true;
$variableName = substr($variableName, 2);
} elseif (strpos($variableName, '...$') === 0) {
$isVariadic = true;
$variableName = substr($variableName, 4);
} elseif (strpos($variableName, '&...$') === 0) {
$isVariadic = true;
$isReference = true;
$variableName = substr($variableName, 5);
}
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $isVariadic, $description, $isReference);
}
/**
* Returns the variable's name.
*/
public function getVariableName() : ?string
{
return $this->variableName;
}
/**
* Returns whether this tag is variadic.
*/
public function isVariadic() : bool
{
return $this->isVariadic;
}
/**
* Returns whether this tag is passed by reference.
*/
public function isReference() : bool
{
return $this->isReference;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$variableName = '';
if ($this->variableName) {
$variableName .= ($this->isReference ? '&' : '') . ($this->isVariadic ? '...' : '');
$variableName .= '$' . $this->variableName;
}
$type = (string) $this->type;
return $type
. ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '')
. ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : '');
}
private static function strStartsWithVariable(string $str) : bool
{
return strpos($str, '$') === 0
||
strpos($str, '...$') === 0
||
strpos($str, '&$') === 0
||
strpos($str, '&...$') === 0;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use function trim;
class PassthroughFormatter implements Formatter
{
/**
* Formats the given tag to return a simple plain text version.
*/
public function format(Tag $tag) : string
{
return trim('@' . $tag->getName() . ' ' . $tag);
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use function max;
use function str_repeat;
use function strlen;
class AlignFormatter implements Formatter
{
/** @var int The maximum tag name length. */
protected $maxLen = 0;
/**
* @param Tag[] $tags All tags that should later be aligned with the formatter.
*/
public function __construct(array $tags)
{
foreach ($tags as $tag) {
$this->maxLen = max($this->maxLen, strlen($tag->getName()));
}
}
/**
* Formats the given tag to return a simple plain text version.
*/
public function format(Tag $tag) : string
{
return '@' . $tag->getName() .
str_repeat(
' ',
$this->maxLen - strlen($tag->getName()) + 1
) .
$tag;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
use function array_key_exists;
use function explode;
/**
* Reflection class for a {@}uses tag in a Docblock.
*/
final class Uses extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'uses';
/** @var Fqsen */
protected $refers;
/**
* Initializes this tag.
*/
public function __construct(Fqsen $refers, ?Description $description = null)
{
$this->refers = $refers;
$this->description = $description;
}
public static function create(
string $body,
?FqsenResolver $resolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::notNull($resolver);
Assert::notNull($descriptionFactory);
$parts = Utils::pregSplit('/\s+/Su', $body, 2);
return new static(
self::resolveFqsen($parts[0], $resolver, $context),
$descriptionFactory->create($parts[1] ?? '', $context)
);
}
private static function resolveFqsen(string $parts, ?FqsenResolver $fqsenResolver, ?TypeContext $context) : Fqsen
{
Assert::notNull($fqsenResolver);
$fqsenParts = explode('::', $parts);
$resolved = $fqsenResolver->resolve($fqsenParts[0], $context);
if (!array_key_exists(1, $fqsenParts)) {
return $resolved;
}
return new Fqsen($resolved . '::' . $fqsenParts[1]);
}
/**
* Returns the structural element this tag refers to.
*/
public function getReference() : Fqsen
{
return $this->refers;
}
/**
* Returns a string representation of this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$refers = (string) $this->refers;
return $refers . ($description !== '' ? ($refers !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
use function array_shift;
use function array_unshift;
use function implode;
use function strpos;
use function substr;
use const PREG_SPLIT_DELIM_CAPTURE;
/**
* Reflection class for a {@}property-read tag in a Docblock.
*/
final class PropertyRead extends TagWithType implements Factory\StaticMethod
{
/** @var string|null */
protected $variableName;
public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null)
{
Assert::string($variableName);
$this->name = 'property-read';
$this->variableName = $variableName;
$this->type = $type;
$this->description = $description;
}
public static function create(
string $body,
?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::stringNotEmpty($body);
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
[$firstPart, $body] = self::extractTypeFromBody($body);
$type = null;
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
$variableName = '';
// if the first item that is encountered is not a variable; it is a type
if ($firstPart && $firstPart[0] !== '$') {
$type = $typeResolver->resolve($firstPart, $context);
} else {
// first part is not a type; we should prepend it to the parts array for further processing
array_unshift($parts, $firstPart);
}
// if the next item starts with a $ it must be the variable name
if (isset($parts[0]) && strpos($parts[0], '$') === 0) {
$variableName = array_shift($parts);
if ($type) {
array_shift($parts);
}
Assert::notNull($variableName);
$variableName = substr($variableName, 1);
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $description);
}
/**
* Returns the variable's name.
*/
public function getVariableName() : ?string
{
return $this->variableName;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
if ($this->variableName) {
$variableName = '$' . $this->variableName;
} else {
$variableName = '';
}
$type = (string) $this->type;
return $type
. ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '')
. ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
namespace phpDocumentor\Reflection\DocBlock\Tags;
use Closure;
use Exception;
use phpDocumentor\Reflection\DocBlock\Tag;
use ReflectionClass;
use ReflectionException;
use ReflectionFunction;
use Throwable;
use function array_map;
use function get_class;
use function get_resource_type;
use function is_array;
use function is_object;
use function is_resource;
use function sprintf;
/**
* This class represents an exception during the tag creation
*
* Since the internals of the library are relaying on the correct syntax of a docblock
* we cannot simply throw exceptions at all time because the exceptions will break the creation of a
* docklock. Just silently ignore the exceptions is not an option because the user as an issue to fix.
*
* This tag holds that error information until a using application is able to display it. The object wil just behave
* like any normal tag. So the normal application flow will not break.
*/
final class InvalidTag implements Tag
{
/** @var string */
private $name;
/** @var string */
private $body;
/** @var Throwable|null */
private $throwable;
private function __construct(string $name, string $body)
{
$this->name = $name;
$this->body = $body;
}
public function getException() : ?Throwable
{
return $this->throwable;
}
public function getName() : string
{
return $this->name;
}
public static function create(string $body, string $name = '') : self
{
return new self($name, $body);
}
public function withError(Throwable $exception) : self
{
$this->flattenExceptionBacktrace($exception);
$tag = new self($this->name, $this->body);
$tag->throwable = $exception;
return $tag;
}
/**
* Removes all complex types from backtrace
*
* Not all objects are serializable. So we need to remove them from the
* stored exception to be sure that we do not break existing library usage.
*/
private function flattenExceptionBacktrace(Throwable $exception) : void
{
$traceProperty = (new ReflectionClass(Exception::class))->getProperty('trace');
$traceProperty->setAccessible(true);
do {
$trace = $exception->getTrace();
if (isset($trace[0]['args'])) {
$trace = array_map(
function (array $call) : array {
$call['args'] = array_map([$this, 'flattenArguments'], $call['args']);
return $call;
},
$trace
);
}
$traceProperty->setValue($exception, $trace);
$exception = $exception->getPrevious();
} while ($exception !== null);
$traceProperty->setAccessible(false);
}
/**
* @param mixed $value
*
* @return mixed
*
* @throws ReflectionException
*/
private function flattenArguments($value)
{
if ($value instanceof Closure) {
$closureReflection = new ReflectionFunction($value);
$value = sprintf(
'(Closure at %s:%s)',
$closureReflection->getFileName(),
$closureReflection->getStartLine()
);
} elseif (is_object($value)) {
$value = sprintf('object(%s)', get_class($value));
} elseif (is_resource($value)) {
$value = sprintf('resource(%s)', get_resource_type($value));
} elseif (is_array($value)) {
$value = array_map([$this, 'flattenArguments'], $value);
}
return $value;
}
public function render(?Formatter $formatter = null) : string
{
if ($formatter === null) {
$formatter = new Formatter\PassthroughFormatter();
}
return $formatter->format($this);
}
public function __toString() : string
{
return $this->body;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Factory;
/**
* @deprecated This contract is totally covered by Tag contract. Every class using StaticMethod also use Tag
*/
interface StaticMethod
{
/**
* @return mixed
*/
public static function create(string $body);
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Tag;
use Webmozart\Assert\Assert;
use function array_key_exists;
use function preg_match;
use function rawurlencode;
use function str_replace;
use function strpos;
use function trim;
/**
* Reflection class for a {@}example tag in a Docblock.
*/
final class Example implements Tag, Factory\StaticMethod
{
/** @var string Path to a file to use as an example. May also be an absolute URI. */
private $filePath;
/**
* @var bool Whether the file path component represents an URI. This determines how the file portion
* appears at {@link getContent()}.
*/
private $isURI;
/** @var int */
private $startingLine;
/** @var int */
private $lineCount;
/** @var string|null */
private $content;
public function __construct(
string $filePath,
bool $isURI,
int $startingLine,
int $lineCount,
?string $content
) {
Assert::stringNotEmpty($filePath);
Assert::greaterThanEq($startingLine, 1);
Assert::greaterThanEq($lineCount, 0);
$this->filePath = $filePath;
$this->startingLine = $startingLine;
$this->lineCount = $lineCount;
if ($content !== null) {
$this->content = trim($content);
}
$this->isURI = $isURI;
}
public function getContent() : string
{
if ($this->content === null || $this->content === '') {
$filePath = $this->filePath;
if ($this->isURI) {
$filePath = $this->isUriRelative($this->filePath)
? str_replace('%2F', '/', rawurlencode($this->filePath))
: $this->filePath;
}
return trim($filePath);
}
return $this->content;
}
public function getDescription() : ?string
{
return $this->content;
}
public static function create(string $body) : ?Tag
{
// File component: File path in quotes or File URI / Source information
if (!preg_match('/^\s*(?:(\"[^\"]+\")|(\S+))(?:\s+(.*))?$/sux', $body, $matches)) {
return null;
}
$filePath = null;
$fileUri = null;
if ($matches[1] !== '') {
$filePath = $matches[1];
} else {
$fileUri = $matches[2];
}
$startingLine = 1;
$lineCount = 0;
$description = null;
if (array_key_exists(3, $matches)) {
$description = $matches[3];
// Starting line / Number of lines / Description
if (preg_match('/^([1-9]\d*)(?:\s+((?1))\s*)?(.*)$/sux', $matches[3], $contentMatches)) {
$startingLine = (int) $contentMatches[1];
if (isset($contentMatches[2])) {
$lineCount = (int) $contentMatches[2];
}
if (array_key_exists(3, $contentMatches)) {
$description = $contentMatches[3];
}
}
}
return new static(
$filePath ?? ($fileUri ?? ''),
$fileUri !== null,
$startingLine,
$lineCount,
$description
);
}
/**
* Returns the file path.
*
* @return string Path to a file to use as an example.
* May also be an absolute URI.
*/
public function getFilePath() : string
{
return trim($this->filePath, '"');
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
$filePath = (string) $this->filePath;
$isDefaultLine = $this->startingLine === 1 && $this->lineCount === 0;
$startingLine = !$isDefaultLine ? (string) $this->startingLine : '';
$lineCount = !$isDefaultLine ? (string) $this->lineCount : '';
$content = (string) $this->content;
return $filePath
. ($startingLine !== ''
? ($filePath !== '' ? ' ' : '') . $startingLine
: '')
. ($lineCount !== ''
? ($filePath !== '' || $startingLine !== '' ? ' ' : '') . $lineCount
: '')
. ($content !== ''
? ($filePath !== '' || $startingLine !== '' || $lineCount !== '' ? ' ' : '') . $content
: '');
}
/**
* Returns true if the provided URI is relative or contains a complete scheme (and thus is absolute).
*/
private function isUriRelative(string $uri) : bool
{
return strpos($uri, ':') === false;
}
public function getStartingLine() : int
{
return $this->startingLine;
}
public function getLineCount() : int
{
return $this->lineCount;
}
public function getName() : string
{
return 'example';
}
public function render(?Formatter $formatter = null) : string
{
if ($formatter === null) {
$formatter = new Formatter\PassthroughFormatter();
}
return $formatter->format($this);
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
use function preg_match;
/**
* Reflection class for a {@}version tag in a Docblock.
*/
final class Version extends BaseTag implements Factory\StaticMethod
{
/** @var string */
protected $name = 'version';
/**
* PCRE regular expression matching a version vector.
* Assumes the "x" modifier.
*/
public const REGEX_VECTOR = '(?:
# Normal release vectors.
\d\S*
|
# VCS version vectors. Per PHPCS, they are expected to
# follow the form of the VCS name, followed by ":", followed
# by the version vector itself.
# By convention, popular VCSes like CVS, SVN and GIT use "$"
# around the actual version vector.
[^\s\:]+\:\s*\$[^\$]+\$
)';
/** @var string|null The version vector. */
private $version;
public function __construct(?string $version = null, ?Description $description = null)
{
Assert::nullOrStringNotEmpty($version);
$this->version = $version;
$this->description = $description;
}
public static function create(
?string $body,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : ?self {
if (empty($body)) {
return new static();
}
$matches = [];
if (!preg_match('/^(' . self::REGEX_VECTOR . ')\s*(.+)?$/sux', $body, $matches)) {
return null;
}
$description = null;
if ($descriptionFactory !== null) {
$description = $descriptionFactory->create($matches[2] ?? '', $context);
}
return new static(
$matches[1],
$description
);
}
/**
* Gets the version section of the tag.
*/
public function getVersion() : ?string
{
return $this->version;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$version = (string) $this->version;
return $version . ($description !== '' ? ($version !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}return tag in a Docblock.
*/
final class Return_ extends TagWithType implements Factory\StaticMethod
{
public function __construct(Type $type, ?Description $description = null)
{
$this->name = 'return';
$this->type = $type;
$this->description = $description;
}
public static function create(
string $body,
?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
[$type, $description] = self::extractTypeFromBody($body);
$type = $typeResolver->resolve($type, $context);
$description = $descriptionFactory->create($description, $context);
return new static($type, $description);
}
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$type = $this->type ? '' . $this->type : 'mixed';
return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Tag;
interface Formatter
{
/**
* Formats a tag into a string representation according to a specific format, such as Markdown.
*/
public function format(Tag $tag) : string;
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
/**
* Reflection class for a {@}throws tag in a Docblock.
*/
final class Throws extends TagWithType implements Factory\StaticMethod
{
public function __construct(Type $type, ?Description $description = null)
{
$this->name = 'throws';
$this->type = $type;
$this->description = $description;
}
public static function create(
string $body,
?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
[$type, $description] = self::extractTypeFromBody($body);
$type = $typeResolver->resolve($type, $context);
$description = $descriptionFactory->create($description, $context);
return new static($type, $description);
}
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
$type = (string) $this->type;
return $type . ($description !== '' ? ($type !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Description;
/**
* Parses a tag definition for a DocBlock.
*/
abstract class BaseTag implements DocBlock\Tag
{
/** @var string Name of the tag */
protected $name = '';
/** @var Description|null Description of the tag. */
protected $description;
/**
* Gets the name of this tag.
*
* @return string The name of this tag.
*/
public function getName() : string
{
return $this->name;
}
public function getDescription() : ?Description
{
return $this->description;
}
public function render(?Formatter $formatter = null) : string
{
if ($formatter === null) {
$formatter = new Formatter\PassthroughFormatter();
}
return $formatter->format($this);
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;
/**
* Interface for references in {@see \phpDocumentor\Reflection\DocBlock\Tags\See}
*/
interface Reference
{
public function __toString() : string;
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;
use phpDocumentor\Reflection\Fqsen as RealFqsen;
/**
* Fqsen reference used by {@see \phpDocumentor\Reflection\DocBlock\Tags\See}
*/
final class Fqsen implements Reference
{
/** @var RealFqsen */
private $fqsen;
public function __construct(RealFqsen $fqsen)
{
$this->fqsen = $fqsen;
}
/**
* @return string string representation of the referenced fqsen
*/
public function __toString() : string
{
return (string) $this->fqsen;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags\Reference;
use Webmozart\Assert\Assert;
/**
* Url reference used by {@see \phpDocumentor\Reflection\DocBlock\Tags\See}
*/
final class Url implements Reference
{
/** @var string */
private $uri;
public function __construct(string $uri)
{
Assert::stringNotEmpty($uri);
$this->uri = $uri;
}
public function __toString() : string
{
return $this->uri;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\Type;
use function in_array;
use function strlen;
use function substr;
use function trim;
abstract class TagWithType extends BaseTag
{
/** @var ?Type */
protected $type;
/**
* Returns the type section of the variable.
*/
public function getType() : ?Type
{
return $this->type;
}
/**
* @return string[]
*/
protected static function extractTypeFromBody(string $body) : array
{
$type = '';
$nestingLevel = 0;
for ($i = 0, $iMax = strlen($body); $i < $iMax; $i++) {
$character = $body[$i];
if ($nestingLevel === 0 && trim($character) === '') {
break;
}
$type .= $character;
if (in_array($character, ['<', '(', '[', '{'])) {
$nestingLevel++;
continue;
}
if (in_array($character, ['>', ')', ']', '}'])) {
$nestingLevel--;
continue;
}
}
$description = trim(substr($body, strlen($type)));
return [$type, $description];
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use InvalidArgumentException;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use Webmozart\Assert\Assert;
use function preg_match;
/**
* Parses a tag definition for a DocBlock.
*/
final class Generic extends BaseTag implements Factory\StaticMethod
{
/**
* Parses a tag and populates the member variables.
*
* @param string $name Name of the tag.
* @param Description $description The contents of the given tag.
*/
public function __construct(string $name, ?Description $description = null)
{
$this->validateTagName($name);
$this->name = $name;
$this->description = $description;
}
/**
* Creates a new tag that represents any unknown tag type.
*
* @return static
*/
public static function create(
string $body,
string $name = '',
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::stringNotEmpty($name);
Assert::notNull($descriptionFactory);
$description = $body !== '' ? $descriptionFactory->create($body, $context) : null;
return new static($name, $description);
}
/**
* Returns the tag as a serialized string
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
return $description;
}
/**
* Validates if the tag name matches the expected format, otherwise throws an exception.
*/
private function validateTagName(string $name) : void
{
if (!preg_match('/^' . StandardTagFactory::REGEX_TAGNAME . '$/u', $name)) {
throw new InvalidArgumentException(
'The tag name "' . $name . '" is not wellformed. Tags may only consist of letters, underscores, '
. 'hyphens and backslashes.'
);
}
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock\Tags;
use phpDocumentor\Reflection\DocBlock\Description;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\Type;
use phpDocumentor\Reflection\TypeResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use phpDocumentor\Reflection\Utils;
use Webmozart\Assert\Assert;
use function array_shift;
use function array_unshift;
use function implode;
use function strpos;
use function substr;
use const PREG_SPLIT_DELIM_CAPTURE;
/**
* Reflection class for a {@}var tag in a Docblock.
*/
final class Var_ extends TagWithType implements Factory\StaticMethod
{
/** @var string|null */
protected $variableName = '';
public function __construct(?string $variableName, ?Type $type = null, ?Description $description = null)
{
Assert::string($variableName);
$this->name = 'var';
$this->variableName = $variableName;
$this->type = $type;
$this->description = $description;
}
public static function create(
string $body,
?TypeResolver $typeResolver = null,
?DescriptionFactory $descriptionFactory = null,
?TypeContext $context = null
) : self {
Assert::stringNotEmpty($body);
Assert::notNull($typeResolver);
Assert::notNull($descriptionFactory);
[$firstPart, $body] = self::extractTypeFromBody($body);
$parts = Utils::pregSplit('/(\s+)/Su', $body, 2, PREG_SPLIT_DELIM_CAPTURE);
$type = null;
$variableName = '';
// if the first item that is encountered is not a variable; it is a type
if ($firstPart && $firstPart[0] !== '$') {
$type = $typeResolver->resolve($firstPart, $context);
} else {
// first part is not a type; we should prepend it to the parts array for further processing
array_unshift($parts, $firstPart);
}
// if the next item starts with a $ it must be the variable name
if (isset($parts[0]) && strpos($parts[0], '$') === 0) {
$variableName = array_shift($parts);
if ($type) {
array_shift($parts);
}
Assert::notNull($variableName);
$variableName = substr($variableName, 1);
}
$description = $descriptionFactory->create(implode('', $parts), $context);
return new static($variableName, $type, $description);
}
/**
* Returns the variable's name.
*/
public function getVariableName() : ?string
{
return $this->variableName;
}
/**
* Returns a string representation for this tag.
*/
public function __toString() : string
{
if ($this->description) {
$description = $this->description->render();
} else {
$description = '';
}
if ($this->variableName) {
$variableName = '$' . $this->variableName;
} else {
$variableName = '';
}
$type = (string) $this->type;
return $type
. ($variableName !== '' ? ($type !== '' ? ' ' : '') . $variableName : '')
. ($description !== '' ? ($type !== '' || $variableName !== '' ? ' ' : '') . $description : '');
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
use function vsprintf;
/**
* Object representing to description for a DocBlock.
*
* A Description object can consist of plain text but can also include tags. A Description Formatter can then combine
* a body template with sprintf-style placeholders together with formatted tags in order to reconstitute a complete
* description text using the format that you would prefer.
*
* Because parsing a Description text can be a verbose process this is handled by the {@see DescriptionFactory}. It is
* thus recommended to use that to create a Description object, like this:
*
* $description = $descriptionFactory->create('This is a {@see Description}', $context);
*
* The description factory will interpret the given body and create a body template and list of tags from them, and pass
* that onto the constructor if this class.
*
* > The $context variable is a class of type {@see \phpDocumentor\Reflection\Types\Context} and contains the namespace
* > and the namespace aliases that apply to this DocBlock. These are used by the Factory to resolve and expand partial
* > type names and FQSENs.
*
* If you do not want to use the DescriptionFactory you can pass a body template and tag listing like this:
*
* $description = new Description(
* 'This is a %1$s',
* [ new See(new Fqsen('\phpDocumentor\Reflection\DocBlock\Description')) ]
* );
*
* It is generally recommended to use the Factory as that will also apply escaping rules, while the Description object
* is mainly responsible for rendering.
*
* @see DescriptionFactory to create a new Description.
* @see Description\Formatter for the formatting of the body and tags.
*/
class Description
{
/** @var string */
private $bodyTemplate;
/** @var Tag[] */
private $tags;
/**
* Initializes a Description with its body (template) and a listing of the tags used in the body template.
*
* @param Tag[] $tags
*/
public function __construct(string $bodyTemplate, array $tags = [])
{
$this->bodyTemplate = $bodyTemplate;
$this->tags = $tags;
}
/**
* Returns the body template.
*/
public function getBodyTemplate() : string
{
return $this->bodyTemplate;
}
/**
* Returns the tags for this DocBlock.
*
* @return Tag[]
*/
public function getTags() : array
{
return $this->tags;
}
/**
* Renders this description as a string where the provided formatter will format the tags in the expected string
* format.
*/
public function render(?Formatter $formatter = null) : string
{
if ($formatter === null) {
$formatter = new PassthroughFormatter();
}
$tags = [];
foreach ($this->tags as $tag) {
$tags[] = '{' . $formatter->format($tag) . '}';
}
return vsprintf($this->bodyTemplate, $tags);
}
/**
* Returns a plain string representation of this description.
*/
public function __toString() : string
{
return $this->render();
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Example;
use function array_slice;
use function file;
use function getcwd;
use function implode;
use function is_readable;
use function rtrim;
use function sprintf;
use function trim;
use const DIRECTORY_SEPARATOR;
/**
* Class used to find an example file's location based on a given ExampleDescriptor.
*/
class ExampleFinder
{
/** @var string */
private $sourceDirectory = '';
/** @var string[] */
private $exampleDirectories = [];
/**
* Attempts to find the example contents for the given descriptor.
*/
public function find(Example $example) : string
{
$filename = $example->getFilePath();
$file = $this->getExampleFileContents($filename);
if (!$file) {
return sprintf('** File not found : %s **', $filename);
}
return implode('', array_slice($file, $example->getStartingLine() - 1, $example->getLineCount()));
}
/**
* Registers the project's root directory where an 'examples' folder can be expected.
*/
public function setSourceDirectory(string $directory = '') : void
{
$this->sourceDirectory = $directory;
}
/**
* Returns the project's root directory where an 'examples' folder can be expected.
*/
public function getSourceDirectory() : string
{
return $this->sourceDirectory;
}
/**
* Registers a series of directories that may contain examples.
*
* @param string[] $directories
*/
public function setExampleDirectories(array $directories) : void
{
$this->exampleDirectories = $directories;
}
/**
* Returns a series of directories that may contain examples.
*
* @return string[]
*/
public function getExampleDirectories() : array
{
return $this->exampleDirectories;
}
/**
* Attempts to find the requested example file and returns its contents or null if no file was found.
*
* This method will try several methods in search of the given example file, the first one it encounters is
* returned:
*
* 1. Iterates through all examples folders for the given filename
* 2. Checks the source folder for the given filename
* 3. Checks the 'examples' folder in the current working directory for examples
* 4. Checks the path relative to the current working directory for the given filename
*
* @return string[] all lines of the example file
*/
private function getExampleFileContents(string $filename) : ?array
{
$normalizedPath = null;
foreach ($this->exampleDirectories as $directory) {
$exampleFileFromConfig = $this->constructExamplePath($directory, $filename);
if (is_readable($exampleFileFromConfig)) {
$normalizedPath = $exampleFileFromConfig;
break;
}
}
if (!$normalizedPath) {
if (is_readable($this->getExamplePathFromSource($filename))) {
$normalizedPath = $this->getExamplePathFromSource($filename);
} elseif (is_readable($this->getExamplePathFromExampleDirectory($filename))) {
$normalizedPath = $this->getExamplePathFromExampleDirectory($filename);
} elseif (is_readable($filename)) {
$normalizedPath = $filename;
}
}
$lines = $normalizedPath && is_readable($normalizedPath) ? file($normalizedPath) : false;
return $lines !== false ? $lines : null;
}
/**
* Get example filepath based on the example directory inside your project.
*/
private function getExamplePathFromExampleDirectory(string $file) : string
{
return getcwd() . DIRECTORY_SEPARATOR . 'examples' . DIRECTORY_SEPARATOR . $file;
}
/**
* Returns a path to the example file in the given directory..
*/
private function constructExamplePath(string $directory, string $file) : string
{
return rtrim($directory, '\\/') . DIRECTORY_SEPARATOR . $file;
}
/**
* Get example filepath based on sourcecode.
*/
private function getExamplePathFromSource(string $file) : string
{
return sprintf(
'%s%s%s',
trim($this->getSourceDirectory(), '\\/'),
DIRECTORY_SEPARATOR,
trim($file, '"')
);
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use InvalidArgumentException;
use phpDocumentor\Reflection\DocBlock\Tags\Author;
use phpDocumentor\Reflection\DocBlock\Tags\Covers;
use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;
use phpDocumentor\Reflection\DocBlock\Tags\Generic;
use phpDocumentor\Reflection\DocBlock\Tags\InvalidTag;
use phpDocumentor\Reflection\DocBlock\Tags\Link as LinkTag;
use phpDocumentor\Reflection\DocBlock\Tags\Method;
use phpDocumentor\Reflection\DocBlock\Tags\Param;
use phpDocumentor\Reflection\DocBlock\Tags\Property;
use phpDocumentor\Reflection\DocBlock\Tags\PropertyRead;
use phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite;
use phpDocumentor\Reflection\DocBlock\Tags\Return_;
use phpDocumentor\Reflection\DocBlock\Tags\See as SeeTag;
use phpDocumentor\Reflection\DocBlock\Tags\Since;
use phpDocumentor\Reflection\DocBlock\Tags\Source;
use phpDocumentor\Reflection\DocBlock\Tags\Throws;
use phpDocumentor\Reflection\DocBlock\Tags\Uses;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use phpDocumentor\Reflection\DocBlock\Tags\Version;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context as TypeContext;
use ReflectionMethod;
use ReflectionNamedType;
use ReflectionParameter;
use Webmozart\Assert\Assert;
use function array_merge;
use function array_slice;
use function call_user_func_array;
use function count;
use function get_class;
use function preg_match;
use function strpos;
use function trim;
/**
* Creates a Tag object given the contents of a tag.
*
* This Factory is capable of determining the appropriate class for a tag and instantiate it using its `create`
* factory method. The `create` factory method of a Tag can have a variable number of arguments; this way you can
* pass the dependencies that you need to construct a tag object.
*
* > Important: each parameter in addition to the body variable for the `create` method must default to null, otherwise
* > it violates the constraint with the interface; it is recommended to use the {@see Assert::notNull()} method to
* > verify that a dependency is actually passed.
*
* This Factory also features a Service Locator component that is used to pass the right dependencies to the
* `create` method of a tag; each dependency should be registered as a service or as a parameter.
*
* When you want to use a Tag of your own with custom handling you need to call the `registerTagHandler` method, pass
* the name of the tag and a Fully Qualified Class Name pointing to a class that implements the Tag interface.
*/
final class StandardTagFactory implements TagFactory
{
/** PCRE regular expression matching a tag name. */
public const REGEX_TAGNAME = '[\w\-\_\\\\:]+';
/**
* @var array<class-string<Tag>> An array with a tag as a key, and an
* FQCN to a class that handles it as an array value.
*/
private $tagHandlerMappings = [
'author' => Author::class,
'covers' => Covers::class,
'deprecated' => Deprecated::class,
// 'example' => '\phpDocumentor\Reflection\DocBlock\Tags\Example',
'link' => LinkTag::class,
'method' => Method::class,
'param' => Param::class,
'property-read' => PropertyRead::class,
'property' => Property::class,
'property-write' => PropertyWrite::class,
'return' => Return_::class,
'see' => SeeTag::class,
'since' => Since::class,
'source' => Source::class,
'throw' => Throws::class,
'throws' => Throws::class,
'uses' => Uses::class,
'var' => Var_::class,
'version' => Version::class,
];
/**
* @var array<class-string<Tag>> An array with a anotation s a key, and an
* FQCN to a class that handles it as an array value.
*/
private $annotationMappings = [];
/**
* @var ReflectionParameter[][] a lazy-loading cache containing parameters
* for each tagHandler that has been used.
*/
private $tagHandlerParameterCache = [];
/** @var FqsenResolver */
private $fqsenResolver;
/**
* @var mixed[] an array representing a simple Service Locator where we can store parameters and
* services that can be inserted into the Factory Methods of Tag Handlers.
*/
private $serviceLocator = [];
/**
* Initialize this tag factory with the means to resolve an FQSEN and optionally a list of tag handlers.
*
* If no tag handlers are provided than the default list in the {@see self::$tagHandlerMappings} property
* is used.
*
* @see self::registerTagHandler() to add a new tag handler to the existing default list.
*
* @param array<class-string<Tag>> $tagHandlers
*/
public function __construct(FqsenResolver $fqsenResolver, ?array $tagHandlers = null)
{
$this->fqsenResolver = $fqsenResolver;
if ($tagHandlers !== null) {
$this->tagHandlerMappings = $tagHandlers;
}
$this->addService($fqsenResolver, FqsenResolver::class);
}
public function create(string $tagLine, ?TypeContext $context = null) : Tag
{
if (!$context) {
$context = new TypeContext('');
}
[$tagName, $tagBody] = $this->extractTagParts($tagLine);
return $this->createTag(trim($tagBody), $tagName, $context);
}
/**
* @param mixed $value
*/
public function addParameter(string $name, $value) : void
{
$this->serviceLocator[$name] = $value;
}
public function addService(object $service, ?string $alias = null) : void
{
$this->serviceLocator[$alias ?: get_class($service)] = $service;
}
public function registerTagHandler(string $tagName, string $handler) : void
{
Assert::stringNotEmpty($tagName);
Assert::classExists($handler);
Assert::implementsInterface($handler, Tag::class);
if (strpos($tagName, '\\') && $tagName[0] !== '\\') {
throw new InvalidArgumentException(
'A namespaced tag must have a leading backslash as it must be fully qualified'
);
}
$this->tagHandlerMappings[$tagName] = $handler;
}
/**
* Extracts all components for a tag.
*
* @return string[]
*/
private function extractTagParts(string $tagLine) : array
{
$matches = [];
if (!preg_match('/^@(' . self::REGEX_TAGNAME . ')((?:[\s\(\{])\s*([^\s].*)|$)/us', $tagLine, $matches)) {
throw new InvalidArgumentException(
'The tag "' . $tagLine . '" does not seem to be wellformed, please check it for errors'
);
}
if (count($matches) < 3) {
$matches[] = '';
}
return array_slice($matches, 1);
}
/**
* Creates a new tag object with the given name and body or returns null if the tag name was recognized but the
* body was invalid.
*/
private function createTag(string $body, string $name, TypeContext $context) : Tag
{
$handlerClassName = $this->findHandlerClassName($name, $context);
$arguments = $this->getArgumentsForParametersFromWiring(
$this->fetchParametersForHandlerFactoryMethod($handlerClassName),
$this->getServiceLocatorWithDynamicParameters($context, $name, $body)
);
try {
$callable = [$handlerClassName, 'create'];
Assert::isCallable($callable);
/** @phpstan-var callable(string): ?Tag $callable */
$tag = call_user_func_array($callable, $arguments);
return $tag ?? InvalidTag::create($body, $name);
} catch (InvalidArgumentException $e) {
return InvalidTag::create($body, $name)->withError($e);
}
}
/**
* Determines the Fully Qualified Class Name of the Factory or Tag (containing a Factory Method `create`).
*
* @return class-string<Tag>
*/
private function findHandlerClassName(string $tagName, TypeContext $context) : string
{
$handlerClassName = Generic::class;
if (isset($this->tagHandlerMappings[$tagName])) {
$handlerClassName = $this->tagHandlerMappings[$tagName];
} elseif ($this->isAnnotation($tagName)) {
// TODO: Annotation support is planned for a later stage and as such is disabled for now
$tagName = (string) $this->fqsenResolver->resolve($tagName, $context);
if (isset($this->annotationMappings[$tagName])) {
$handlerClassName = $this->annotationMappings[$tagName];
}
}
return $handlerClassName;
}
/**
* Retrieves the arguments that need to be passed to the Factory Method with the given Parameters.
*
* @param ReflectionParameter[] $parameters
* @param mixed[] $locator
*
* @return mixed[] A series of values that can be passed to the Factory Method of the tag whose parameters
* is provided with this method.
*/
private function getArgumentsForParametersFromWiring(array $parameters, array $locator) : array
{
$arguments = [];
foreach ($parameters as $parameter) {
$type = $parameter->getType();
$typeHint = null;
if ($type instanceof ReflectionNamedType) {
$typeHint = $type->getName();
if ($typeHint === 'self') {
$declaringClass = $parameter->getDeclaringClass();
if ($declaringClass !== null) {
$typeHint = $declaringClass->getName();
}
}
}
if (isset($locator[$typeHint])) {
$arguments[] = $locator[$typeHint];
continue;
}
$parameterName = $parameter->getName();
if (isset($locator[$parameterName])) {
$arguments[] = $locator[$parameterName];
continue;
}
$arguments[] = null;
}
return $arguments;
}
/**
* Retrieves a series of ReflectionParameter objects for the static 'create' method of the given
* tag handler class name.
*
* @param class-string $handlerClassName
*
* @return ReflectionParameter[]
*/
private function fetchParametersForHandlerFactoryMethod(string $handlerClassName) : array
{
if (!isset($this->tagHandlerParameterCache[$handlerClassName])) {
$methodReflection = new ReflectionMethod($handlerClassName, 'create');
$this->tagHandlerParameterCache[$handlerClassName] = $methodReflection->getParameters();
}
return $this->tagHandlerParameterCache[$handlerClassName];
}
/**
* Returns a copy of this class' Service Locator with added dynamic parameters,
* such as the tag's name, body and Context.
*
* @param TypeContext $context The Context (namespace and aliasses) that may be
* passed and is used to resolve FQSENs.
* @param string $tagName The name of the tag that may be
* passed onto the factory method of the Tag class.
* @param string $tagBody The body of the tag that may be
* passed onto the factory method of the Tag class.
*
* @return mixed[]
*/
private function getServiceLocatorWithDynamicParameters(
TypeContext $context,
string $tagName,
string $tagBody
) : array {
return array_merge(
$this->serviceLocator,
[
'name' => $tagName,
'body' => $tagBody,
TypeContext::class => $context,
]
);
}
/**
* Returns whether the given tag belongs to an annotation.
*
* @todo this method should be populated once we implement Annotation notation support.
*/
private function isAnnotation(string $tagContent) : bool
{
// 1. Contains a namespace separator
// 2. Contains parenthesis
// 3. Is present in a list of known annotations (make the algorithm smart by first checking is the last part
// of the annotation class name matches the found tag name
return false;
}
}
<?php
declare(strict_types=1);
/**
* This file is part of phpDocumentor.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @link http://phpdoc.org
*/
namespace phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter;
use phpDocumentor\Reflection\DocBlock\Tags\Formatter\PassthroughFormatter;
use function sprintf;
use function str_repeat;
use function str_replace;
use function strlen;
use function wordwrap;
/**
* Converts a DocBlock back from an object to a complete DocComment including Asterisks.
*/
class Serializer
{
/** @var string The string to indent the comment with. */
protected $indentString = ' ';
/** @var int The number of times the indent string is repeated. */
protected $indent = 0;
/** @var bool Whether to indent the first line with the given indent amount and string. */
protected $isFirstLineIndented = true;
/** @var int|null The max length of a line. */
protected $lineLength;
/** @var Formatter A custom tag formatter. */
protected $tagFormatter;
/**
* Create a Serializer instance.
*
* @param int $indent The number of times the indent string is repeated.
* @param string $indentString The string to indent the comment with.
* @param bool $indentFirstLine Whether to indent the first line.
* @param int|null $lineLength The max length of a line or NULL to disable line wrapping.
* @param Formatter $tagFormatter A custom tag formatter, defaults to PassthroughFormatter.
*/
public function __construct(
int $indent = 0,
string $indentString = ' ',
bool $indentFirstLine = true,
?int $lineLength = null,
?Formatter $tagFormatter = null
) {
$this->indent = $indent;
$this->indentString = $indentString;
$this->isFirstLineIndented = $indentFirstLine;
$this->lineLength = $lineLength;
$this->tagFormatter = $tagFormatter ?: new PassthroughFormatter();
}
/**
* Generate a DocBlock comment.
*
* @param DocBlock $docblock The DocBlock to serialize.
*
* @return string The serialized doc block.
*/
public function getDocComment(DocBlock $docblock) : string
{
$indent = str_repeat($this->indentString, $this->indent);
$firstIndent = $this->isFirstLineIndented ? $indent : '';
// 3 === strlen(' * ')
$wrapLength = $this->lineLength ? $this->lineLength - strlen($indent) - 3 : null;
$text = $this->removeTrailingSpaces(
$indent,
$this->addAsterisksForEachLine(
$indent,
$this->getSummaryAndDescriptionTextBlock($docblock, $wrapLength)
)
);
$comment = $firstIndent . "/**\n";
if ($text) {
$comment .= $indent . ' * ' . $text . "\n";
$comment .= $indent . " *\n";
}
$comment = $this->addTagBlock($docblock, $wrapLength, $indent, $comment);
return $comment . $indent . ' */';
}
private function removeTrailingSpaces(string $indent, string $text) : string
{
return str_replace(
sprintf("\n%s * \n", $indent),
sprintf("\n%s *\n", $indent),
$text
);
}
private function addAsterisksForEachLine(string $indent, string $text) : string
{
return str_replace(
"\n",
sprintf("\n%s * ", $indent),
$text
);
}
private function getSummaryAndDescriptionTextBlock(DocBlock $docblock, ?int $wrapLength) : string
{
$text = $docblock->getSummary() . ((string) $docblock->getDescription() ? "\n\n" . $docblock->getDescription()
: '');
if ($wrapLength !== null) {
$text = wordwrap($text, $wrapLength);
return $text;
}
return $text;
}
private function addTagBlock(DocBlock $docblock, ?int $wrapLength, string $indent, string $comment) : string
{
foreach ($docblock->getTags() as $tag) {
$tagText = $this->tagFormatter->format($tag);
if ($wrapLength !== null) {
$tagText = wordwrap($tagText, $wrapLength);
}
$tagText = str_replace(
"\n",
sprintf("\n%s * ", $indent),
$tagText
);
$comment .= sprintf("%s * %s\n", $indent, $tagText);
}
return $comment;
}
}
The MIT License (MIT)
Copyright (c) 2010 Mike van Riel
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.
<?php
/*
* This file is part of the webmozart/assert package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\Assert;
use ArrayAccess;
use BadMethodCallException;
use Closure;
use Countable;
use DateTime;
use DateTimeImmutable;
use Exception;
use ResourceBundle;
use SimpleXMLElement;
use Throwable;
use Traversable;
/**
* Efficient assertions to validate the input/output of your methods.
*
* @since 1.0
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class Assert
{
use Mixin;
/**
* @psalm-pure
* @psalm-assert string $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function string($value, $message = '')
{
if (!\is_string($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a string. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert non-empty-string $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function stringNotEmpty($value, $message = '')
{
static::string($value, $message);
static::notEq($value, '', $message);
}
/**
* @psalm-pure
* @psalm-assert int $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function integer($value, $message = '')
{
if (!\is_int($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an integer. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert numeric $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function integerish($value, $message = '')
{
if (!\is_numeric($value) || $value != (int) $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an integerish value. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert positive-int $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function positiveInteger($value, $message = '')
{
if (!(\is_int($value) && $value > 0)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a positive integer. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert float $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function float($value, $message = '')
{
if (!\is_float($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a float. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert numeric $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function numeric($value, $message = '')
{
if (!\is_numeric($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a numeric. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert positive-int|0 $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function natural($value, $message = '')
{
if (!\is_int($value) || $value < 0) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a non-negative integer. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert bool $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function boolean($value, $message = '')
{
if (!\is_bool($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a boolean. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert scalar $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function scalar($value, $message = '')
{
if (!\is_scalar($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a scalar. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert object $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function object($value, $message = '')
{
if (!\is_object($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an object. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert resource $value
*
* @param mixed $value
* @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function resource($value, $type = null, $message = '')
{
if (!\is_resource($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a resource. Got: %s',
static::typeToString($value)
));
}
if ($type && $type !== \get_resource_type($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a resource of type %2$s. Got: %s',
static::typeToString($value),
$type
));
}
}
/**
* @psalm-pure
* @psalm-assert callable $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isCallable($value, $message = '')
{
if (!\is_callable($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a callable. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert array $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isArray($value, $message = '')
{
if (!\is_array($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert iterable $value
*
* @deprecated use "isIterable" or "isInstanceOf" instead
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isTraversable($value, $message = '')
{
@\trigger_error(
\sprintf(
'The "%s" assertion is deprecated. You should stop using it, as it will soon be removed in 2.0 version. Use "isIterable" or "isInstanceOf" instead.',
__METHOD__
),
\E_USER_DEPRECATED
);
if (!\is_array($value) && !($value instanceof Traversable)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a traversable. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert array|ArrayAccess $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isArrayAccessible($value, $message = '')
{
if (!\is_array($value) && !($value instanceof ArrayAccess)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array accessible. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert countable $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isCountable($value, $message = '')
{
if (
!\is_array($value)
&& !($value instanceof Countable)
&& !($value instanceof ResourceBundle)
&& !($value instanceof SimpleXMLElement)
) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a countable. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert iterable $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isIterable($value, $message = '')
{
if (!\is_array($value) && !($value instanceof Traversable)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an iterable. Got: %s',
static::typeToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert ExpectedType $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isInstanceOf($value, $class, $message = '')
{
if (!($value instanceof $class)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an instance of %2$s. Got: %s',
static::typeToString($value),
$class
));
}
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert !ExpectedType $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notInstanceOf($value, $class, $message = '')
{
if ($value instanceof $class) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an instance other than %2$s. Got: %s',
static::typeToString($value),
$class
));
}
}
/**
* @psalm-pure
* @psalm-param array<class-string> $classes
*
* @param mixed $value
* @param array<object|string> $classes
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isInstanceOfAny($value, array $classes, $message = '')
{
foreach ($classes as $class) {
if ($value instanceof $class) {
return;
}
}
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an instance of any of %2$s. Got: %s',
static::typeToString($value),
\implode(', ', \array_map(array('static', 'valueToString'), $classes))
));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert ExpectedType|class-string<ExpectedType> $value
*
* @param object|string $value
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isAOf($value, $class, $message = '')
{
static::string($class, 'Expected class as a string. Got: %s');
if (!\is_a($value, $class, \is_string($value))) {
static::reportInvalidArgument(sprintf(
$message ?: 'Expected an instance of this class or to this class among his parents %2$s. Got: %s',
static::typeToString($value),
$class
));
}
}
/**
* @psalm-pure
* @psalm-template UnexpectedType of object
* @psalm-param class-string<UnexpectedType> $class
* @psalm-assert !UnexpectedType $value
* @psalm-assert !class-string<UnexpectedType> $value
*
* @param object|string $value
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isNotA($value, $class, $message = '')
{
static::string($class, 'Expected class as a string. Got: %s');
if (\is_a($value, $class, \is_string($value))) {
static::reportInvalidArgument(sprintf(
$message ?: 'Expected an instance of this class or to this class among his parents other than %2$s. Got: %s',
static::typeToString($value),
$class
));
}
}
/**
* @psalm-pure
* @psalm-param array<class-string> $classes
*
* @param object|string $value
* @param string[] $classes
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isAnyOf($value, array $classes, $message = '')
{
foreach ($classes as $class) {
static::string($class, 'Expected class as a string. Got: %s');
if (\is_a($value, $class, \is_string($value))) {
return;
}
}
static::reportInvalidArgument(sprintf(
$message ?: 'Expected an any of instance of this class or to this class among his parents other than %2$s. Got: %s',
static::typeToString($value),
\implode(', ', \array_map(array('static', 'valueToString'), $classes))
));
}
/**
* @psalm-pure
* @psalm-assert empty $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isEmpty($value, $message = '')
{
if (!empty($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an empty value. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert !empty $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notEmpty($value, $message = '')
{
if (empty($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a non-empty value. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function null($value, $message = '')
{
if (null !== $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected null. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert !null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notNull($value, $message = '')
{
if (null === $value) {
static::reportInvalidArgument(
$message ?: 'Expected a value other than null.'
);
}
}
/**
* @psalm-pure
* @psalm-assert true $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function true($value, $message = '')
{
if (true !== $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be true. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert false $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function false($value, $message = '')
{
if (false !== $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be false. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert !false $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notFalse($value, $message = '')
{
if (false === $value) {
static::reportInvalidArgument(
$message ?: 'Expected a value other than false.'
);
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function ip($value, $message = '')
{
if (false === \filter_var($value, \FILTER_VALIDATE_IP)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be an IP. Got: %s',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function ipv4($value, $message = '')
{
if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV4)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be an IPv4. Got: %s',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function ipv6($value, $message = '')
{
if (false === \filter_var($value, \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be an IPv6. Got: %s',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function email($value, $message = '')
{
if (false === \filter_var($value, FILTER_VALIDATE_EMAIL)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to be a valid e-mail address. Got: %s',
static::valueToString($value)
));
}
}
/**
* Does non strict comparisons on the items, so ['3', 3] will not pass the assertion.
*
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function uniqueValues(array $values, $message = '')
{
$allValues = \count($values);
$uniqueValues = \count(\array_unique($values));
if ($allValues !== $uniqueValues) {
$difference = $allValues - $uniqueValues;
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array of unique values, but %s of them %s duplicated',
$difference,
(1 === $difference ? 'is' : 'are')
));
}
}
/**
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function eq($value, $expect, $message = '')
{
if ($expect != $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value equal to %2$s. Got: %s',
static::valueToString($value),
static::valueToString($expect)
));
}
}
/**
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notEq($value, $expect, $message = '')
{
if ($expect == $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a different value than %s.',
static::valueToString($expect)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function same($value, $expect, $message = '')
{
if ($expect !== $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value identical to %2$s. Got: %s',
static::valueToString($value),
static::valueToString($expect)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notSame($value, $expect, $message = '')
{
if ($expect === $value) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value not identical to %s.',
static::valueToString($expect)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function greaterThan($value, $limit, $message = '')
{
if ($value <= $limit) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value greater than %2$s. Got: %s',
static::valueToString($value),
static::valueToString($limit)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function greaterThanEq($value, $limit, $message = '')
{
if ($value < $limit) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value greater than or equal to %2$s. Got: %s',
static::valueToString($value),
static::valueToString($limit)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function lessThan($value, $limit, $message = '')
{
if ($value >= $limit) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value less than %2$s. Got: %s',
static::valueToString($value),
static::valueToString($limit)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function lessThanEq($value, $limit, $message = '')
{
if ($value > $limit) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value less than or equal to %2$s. Got: %s',
static::valueToString($value),
static::valueToString($limit)
));
}
}
/**
* Inclusive range, so Assert::(3, 3, 5) passes.
*
* @psalm-pure
*
* @param mixed $value
* @param mixed $min
* @param mixed $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function range($value, $min, $max, $message = '')
{
if ($value < $min || $value > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value between %2$s and %3$s. Got: %s',
static::valueToString($value),
static::valueToString($min),
static::valueToString($max)
));
}
}
/**
* A more human-readable alias of Assert::inArray().
*
* @psalm-pure
*
* @param mixed $value
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function oneOf($value, array $values, $message = '')
{
static::inArray($value, $values, $message);
}
/**
* Does strict comparison, so Assert::inArray(3, ['3']) does not pass the assertion.
*
* @psalm-pure
*
* @param mixed $value
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function inArray($value, array $values, $message = '')
{
if (!\in_array($value, $values, true)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected one of: %2$s. Got: %s',
static::valueToString($value),
\implode(', ', \array_map(array('static', 'valueToString'), $values))
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $subString
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function contains($value, $subString, $message = '')
{
if (false === \strpos($value, $subString)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain %2$s. Got: %s',
static::valueToString($value),
static::valueToString($subString)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $subString
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notContains($value, $subString, $message = '')
{
if (false !== \strpos($value, $subString)) {
static::reportInvalidArgument(\sprintf(
$message ?: '%2$s was not expected to be contained in a value. Got: %s',
static::valueToString($value),
static::valueToString($subString)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notWhitespaceOnly($value, $message = '')
{
if (\preg_match('/^\s*$/', $value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a non-whitespace string. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $prefix
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function startsWith($value, $prefix, $message = '')
{
if (0 !== \strpos($value, $prefix)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to start with %2$s. Got: %s',
static::valueToString($value),
static::valueToString($prefix)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $prefix
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notStartsWith($value, $prefix, $message = '')
{
if (0 === \strpos($value, $prefix)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value not to start with %2$s. Got: %s',
static::valueToString($value),
static::valueToString($prefix)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function startsWithLetter($value, $message = '')
{
static::string($value);
$valid = isset($value[0]);
if ($valid) {
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = \ctype_alpha($value[0]);
\setlocale(LC_CTYPE, $locale);
}
if (!$valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to start with a letter. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $suffix
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function endsWith($value, $suffix, $message = '')
{
if ($suffix !== \substr($value, -\strlen($suffix))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to end with %2$s. Got: %s',
static::valueToString($value),
static::valueToString($suffix)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $suffix
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notEndsWith($value, $suffix, $message = '')
{
if ($suffix === \substr($value, -\strlen($suffix))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value not to end with %2$s. Got: %s',
static::valueToString($value),
static::valueToString($suffix)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $pattern
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function regex($value, $pattern, $message = '')
{
if (!\preg_match($pattern, $value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The value %s does not match the expected pattern.',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $pattern
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function notRegex($value, $pattern, $message = '')
{
if (\preg_match($pattern, $value, $matches, PREG_OFFSET_CAPTURE)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The value %s matches the pattern %s (at offset %d).',
static::valueToString($value),
static::valueToString($pattern),
$matches[0][1]
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function unicodeLetters($value, $message = '')
{
static::string($value);
if (!\preg_match('/^\p{L}+$/u', $value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain only Unicode letters. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function alpha($value, $message = '')
{
static::string($value);
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_alpha($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain only letters. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function digits($value, $message = '')
{
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_digit($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain digits only. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function alnum($value, $message = '')
{
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_alnum($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain letters and digits only. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert lowercase-string $value
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function lower($value, $message = '')
{
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_lower($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain lowercase characters only. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-assert !lowercase-string $value
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function upper($value, $message = '')
{
$locale = \setlocale(LC_CTYPE, 0);
\setlocale(LC_CTYPE, 'C');
$valid = !\ctype_upper($value);
\setlocale(LC_CTYPE, $locale);
if ($valid) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain uppercase characters only. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
*
* @param string $value
* @param int $length
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function length($value, $length, $message = '')
{
if ($length !== static::strlen($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain %2$s characters. Got: %s',
static::valueToString($value),
$length
));
}
}
/**
* Inclusive min.
*
* @psalm-pure
*
* @param string $value
* @param int|float $min
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function minLength($value, $min, $message = '')
{
if (static::strlen($value) < $min) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain at least %2$s characters. Got: %s',
static::valueToString($value),
$min
));
}
}
/**
* Inclusive max.
*
* @psalm-pure
*
* @param string $value
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function maxLength($value, $max, $message = '')
{
if (static::strlen($value) > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain at most %2$s characters. Got: %s',
static::valueToString($value),
$max
));
}
}
/**
* Inclusive , so Assert::lengthBetween('asd', 3, 5); passes the assertion.
*
* @psalm-pure
*
* @param string $value
* @param int|float $min
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function lengthBetween($value, $min, $max, $message = '')
{
$length = static::strlen($value);
if ($length < $min || $length > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a value to contain between %2$s and %3$s characters. Got: %s',
static::valueToString($value),
$min,
$max
));
}
}
/**
* Will also pass if $value is a directory, use Assert::file() instead if you need to be sure it is a file.
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function fileExists($value, $message = '')
{
static::string($value);
if (!\file_exists($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The file %s does not exist.',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function file($value, $message = '')
{
static::fileExists($value, $message);
if (!\is_file($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The path %s is not a file.',
static::valueToString($value)
));
}
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function directory($value, $message = '')
{
static::fileExists($value, $message);
if (!\is_dir($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The path %s is no directory.',
static::valueToString($value)
));
}
}
/**
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function readable($value, $message = '')
{
if (!\is_readable($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The path %s is not readable.',
static::valueToString($value)
));
}
}
/**
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function writable($value, $message = '')
{
if (!\is_writable($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'The path %s is not writable.',
static::valueToString($value)
));
}
}
/**
* @psalm-assert class-string $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function classExists($value, $message = '')
{
if (!\class_exists($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an existing class name. Got: %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert class-string<ExpectedType>|ExpectedType $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function subclassOf($value, $class, $message = '')
{
if (!\is_subclass_of($value, $class)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected a sub-class of %2$s. Got: %s',
static::valueToString($value),
static::valueToString($class)
));
}
}
/**
* @psalm-assert class-string $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function interfaceExists($value, $message = '')
{
if (!\interface_exists($value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an existing interface name. got %s',
static::valueToString($value)
));
}
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $interface
* @psalm-assert class-string<ExpectedType> $value
*
* @param mixed $value
* @param mixed $interface
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function implementsInterface($value, $interface, $message = '')
{
if (!\in_array($interface, \class_implements($value))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an implementation of %2$s. Got: %s',
static::valueToString($value),
static::valueToString($interface)
));
}
}
/**
* @psalm-pure
* @psalm-param class-string|object $classOrObject
*
* @param string|object $classOrObject
* @param mixed $property
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function propertyExists($classOrObject, $property, $message = '')
{
if (!\property_exists($classOrObject, $property)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the property %s to exist.',
static::valueToString($property)
));
}
}
/**
* @psalm-pure
* @psalm-param class-string|object $classOrObject
*
* @param string|object $classOrObject
* @param mixed $property
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function propertyNotExists($classOrObject, $property, $message = '')
{
if (\property_exists($classOrObject, $property)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the property %s to not exist.',
static::valueToString($property)
));
}
}
/**
* @psalm-pure
* @psalm-param class-string|object $classOrObject
*
* @param string|object $classOrObject
* @param mixed $method
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function methodExists($classOrObject, $method, $message = '')
{
if (!(\is_string($classOrObject) || \is_object($classOrObject)) || !\method_exists($classOrObject, $method)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the method %s to exist.',
static::valueToString($method)
));
}
}
/**
* @psalm-pure
* @psalm-param class-string|object $classOrObject
*
* @param string|object $classOrObject
* @param mixed $method
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function methodNotExists($classOrObject, $method, $message = '')
{
if ((\is_string($classOrObject) || \is_object($classOrObject)) && \method_exists($classOrObject, $method)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the method %s to not exist.',
static::valueToString($method)
));
}
}
/**
* @psalm-pure
*
* @param array $array
* @param string|int $key
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function keyExists($array, $key, $message = '')
{
if (!(isset($array[$key]) || \array_key_exists($key, $array))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the key %s to exist.',
static::valueToString($key)
));
}
}
/**
* @psalm-pure
*
* @param array $array
* @param string|int $key
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function keyNotExists($array, $key, $message = '')
{
if (isset($array[$key]) || \array_key_exists($key, $array)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected the key %s to not exist.',
static::valueToString($key)
));
}
}
/**
* Checks if a value is a valid array key (int or string).
*
* @psalm-pure
* @psalm-assert array-key $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function validArrayKey($value, $message = '')
{
if (!(\is_int($value) || \is_string($value))) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected string or integer. Got: %s',
static::typeToString($value)
));
}
}
/**
* Does not check if $array is countable, this can generate a warning on php versions after 7.2.
*
* @param Countable|array $array
* @param int $number
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function count($array, $number, $message = '')
{
static::eq(
\count($array),
$number,
\sprintf(
$message ?: 'Expected an array to contain %d elements. Got: %d.',
$number,
\count($array)
)
);
}
/**
* Does not check if $array is countable, this can generate a warning on php versions after 7.2.
*
* @param Countable|array $array
* @param int|float $min
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function minCount($array, $min, $message = '')
{
if (\count($array) < $min) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array to contain at least %2$d elements. Got: %d',
\count($array),
$min
));
}
}
/**
* Does not check if $array is countable, this can generate a warning on php versions after 7.2.
*
* @param Countable|array $array
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function maxCount($array, $max, $message = '')
{
if (\count($array) > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array to contain at most %2$d elements. Got: %d',
\count($array),
$max
));
}
}
/**
* Does not check if $array is countable, this can generate a warning on php versions after 7.2.
*
* @param Countable|array $array
* @param int|float $min
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function countBetween($array, $min, $max, $message = '')
{
$count = \count($array);
if ($count < $min || $count > $max) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Expected an array to contain between %2$d and %3$d elements. Got: %d',
$count,
$min,
$max
));
}
}
/**
* @psalm-pure
* @psalm-assert list $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isList($array, $message = '')
{
if (!\is_array($array) || $array !== \array_values($array)) {
static::reportInvalidArgument(
$message ?: 'Expected list - non-associative array.'
);
}
}
/**
* @psalm-pure
* @psalm-assert non-empty-list $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isNonEmptyList($array, $message = '')
{
static::isList($array, $message);
static::notEmpty($array, $message);
}
/**
* @psalm-pure
* @psalm-template T
* @psalm-param mixed|array<T> $array
* @psalm-assert array<string, T> $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isMap($array, $message = '')
{
if (
!\is_array($array) ||
\array_keys($array) !== \array_filter(\array_keys($array), '\is_string')
) {
static::reportInvalidArgument(
$message ?: 'Expected map - associative array with string keys.'
);
}
}
/**
* @psalm-pure
* @psalm-template T
* @psalm-param mixed|array<T> $array
* @psalm-assert array<string, T> $array
* @psalm-assert !empty $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function isNonEmptyMap($array, $message = '')
{
static::isMap($array, $message);
static::notEmpty($array, $message);
}
/**
* @psalm-pure
*
* @param string $value
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function uuid($value, $message = '')
{
$value = \str_replace(array('urn:', 'uuid:', '{', '}'), '', $value);
// The nil UUID is special form of UUID that is specified to have all
// 128 bits set to zero.
if ('00000000-0000-0000-0000-000000000000' === $value) {
return;
}
if (!\preg_match('/^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/', $value)) {
static::reportInvalidArgument(\sprintf(
$message ?: 'Value %s is not a valid UUID.',
static::valueToString($value)
));
}
}
/**
* @psalm-param class-string<Throwable> $class
*
* @param Closure $expression
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*/
public static function throws(Closure $expression, $class = 'Exception', $message = '')
{
static::string($class);
$actual = 'none';
try {
$expression();
} catch (Exception $e) {
$actual = \get_class($e);
if ($e instanceof $class) {
return;
}
} catch (Throwable $e) {
$actual = \get_class($e);
if ($e instanceof $class) {
return;
}
}
static::reportInvalidArgument($message ?: \sprintf(
'Expected to throw "%s", got "%s"',
$class,
$actual
));
}
/**
* @throws BadMethodCallException
*/
public static function __callStatic($name, $arguments)
{
if ('nullOr' === \substr($name, 0, 6)) {
if (null !== $arguments[0]) {
$method = \lcfirst(\substr($name, 6));
\call_user_func_array(array('static', $method), $arguments);
}
return;
}
if ('all' === \substr($name, 0, 3)) {
static::isIterable($arguments[0]);
$method = \lcfirst(\substr($name, 3));
$args = $arguments;
foreach ($arguments[0] as $entry) {
$args[0] = $entry;
\call_user_func_array(array('static', $method), $args);
}
return;
}
throw new BadMethodCallException('No such method: '.$name);
}
/**
* @param mixed $value
*
* @return string
*/
protected static function valueToString($value)
{
if (null === $value) {
return 'null';
}
if (true === $value) {
return 'true';
}
if (false === $value) {
return 'false';
}
if (\is_array($value)) {
return 'array';
}
if (\is_object($value)) {
if (\method_exists($value, '__toString')) {
return \get_class($value).': '.self::valueToString($value->__toString());
}
if ($value instanceof DateTime || $value instanceof DateTimeImmutable) {
return \get_class($value).': '.self::valueToString($value->format('c'));
}
return \get_class($value);
}
if (\is_resource($value)) {
return 'resource';
}
if (\is_string($value)) {
return '"'.$value.'"';
}
return (string) $value;
}
/**
* @param mixed $value
*
* @return string
*/
protected static function typeToString($value)
{
return \is_object($value) ? \get_class($value) : \gettype($value);
}
protected static function strlen($value)
{
if (!\function_exists('mb_detect_encoding')) {
return \strlen($value);
}
if (false === $encoding = \mb_detect_encoding($value)) {
return \strlen($value);
}
return \mb_strlen($value, $encoding);
}
/**
* @param string $message
*
* @throws InvalidArgumentException
*
* @psalm-pure this method is not supposed to perform side-effects
*/
protected static function reportInvalidArgument($message)
{
throw new InvalidArgumentException($message);
}
private function __construct()
{
}
}
<?php
/*
* This file is part of the webmozart/assert package.
*
* (c) Bernhard Schussek <bschussek@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Webmozart\Assert;
class InvalidArgumentException extends \InvalidArgumentException
{
}
<?php
/**
* provides type inference and auto-completion for magic static methods of Assert.
*/
namespace Webmozart\Assert;
use ArrayAccess;
use Closure;
use Countable;
use Throwable;
/**
* This trait aids static analysis tooling in introspecting assertion magic methods.
* Do not use this trait directly: it will change, and is not designed for reuse.
*/
trait Mixin
{
/**
* @psalm-pure
* @psalm-assert string|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrString($value, $message = '')
{
static::__callStatic('nullOrString', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<string> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allString($value, $message = '')
{
static::__callStatic('allString', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert non-empty-string|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrStringNotEmpty($value, $message = '')
{
static::__callStatic('nullOrStringNotEmpty', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<non-empty-string> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allStringNotEmpty($value, $message = '')
{
static::__callStatic('allStringNotEmpty', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert int|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrInteger($value, $message = '')
{
static::__callStatic('nullOrInteger', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<int> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allInteger($value, $message = '')
{
static::__callStatic('allInteger', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert numeric|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIntegerish($value, $message = '')
{
static::__callStatic('nullOrIntegerish', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<numeric> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIntegerish($value, $message = '')
{
static::__callStatic('allIntegerish', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert positive-int|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrPositiveInteger($value, $message = '')
{
static::__callStatic('nullOrPositiveInteger', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<positive-int> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allPositiveInteger($value, $message = '')
{
static::__callStatic('allPositiveInteger', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert float|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrFloat($value, $message = '')
{
static::__callStatic('nullOrFloat', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<float> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allFloat($value, $message = '')
{
static::__callStatic('allFloat', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert numeric|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNumeric($value, $message = '')
{
static::__callStatic('nullOrNumeric', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<numeric> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNumeric($value, $message = '')
{
static::__callStatic('allNumeric', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert positive-int|0|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNatural($value, $message = '')
{
static::__callStatic('nullOrNatural', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<positive-int|0> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNatural($value, $message = '')
{
static::__callStatic('allNatural', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert bool|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrBoolean($value, $message = '')
{
static::__callStatic('nullOrBoolean', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<bool> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allBoolean($value, $message = '')
{
static::__callStatic('allBoolean', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert scalar|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrScalar($value, $message = '')
{
static::__callStatic('nullOrScalar', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<scalar> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allScalar($value, $message = '')
{
static::__callStatic('allScalar', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert object|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrObject($value, $message = '')
{
static::__callStatic('nullOrObject', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<object> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allObject($value, $message = '')
{
static::__callStatic('allObject', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert resource|null $value
*
* @param mixed $value
* @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrResource($value, $type = null, $message = '')
{
static::__callStatic('nullOrResource', array($value, $type, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<resource> $value
*
* @param mixed $value
* @param string|null $type type of resource this should be. @see https://www.php.net/manual/en/function.get-resource-type.php
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allResource($value, $type = null, $message = '')
{
static::__callStatic('allResource', array($value, $type, $message));
}
/**
* @psalm-pure
* @psalm-assert callable|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsCallable($value, $message = '')
{
static::__callStatic('nullOrIsCallable', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<callable> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsCallable($value, $message = '')
{
static::__callStatic('allIsCallable', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert array|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsArray($value, $message = '')
{
static::__callStatic('nullOrIsArray', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<array> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsArray($value, $message = '')
{
static::__callStatic('allIsArray', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable|null $value
*
* @deprecated use "isIterable" or "isInstanceOf" instead
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsTraversable($value, $message = '')
{
static::__callStatic('nullOrIsTraversable', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<iterable> $value
*
* @deprecated use "isIterable" or "isInstanceOf" instead
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsTraversable($value, $message = '')
{
static::__callStatic('allIsTraversable', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert array|ArrayAccess|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsArrayAccessible($value, $message = '')
{
static::__callStatic('nullOrIsArrayAccessible', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<array|ArrayAccess> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsArrayAccessible($value, $message = '')
{
static::__callStatic('allIsArrayAccessible', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert countable|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsCountable($value, $message = '')
{
static::__callStatic('nullOrIsCountable', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<countable> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsCountable($value, $message = '')
{
static::__callStatic('allIsCountable', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsIterable($value, $message = '')
{
static::__callStatic('nullOrIsIterable', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<iterable> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsIterable($value, $message = '')
{
static::__callStatic('allIsIterable', array($value, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert ExpectedType|null $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsInstanceOf($value, $class, $message = '')
{
static::__callStatic('nullOrIsInstanceOf', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert iterable<ExpectedType> $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsInstanceOf($value, $class, $message = '')
{
static::__callStatic('allIsInstanceOf', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotInstanceOf($value, $class, $message = '')
{
static::__callStatic('nullOrNotInstanceOf', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotInstanceOf($value, $class, $message = '')
{
static::__callStatic('allNotInstanceOf', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-param array<class-string> $classes
*
* @param mixed $value
* @param array<object|string> $classes
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsInstanceOfAny($value, $classes, $message = '')
{
static::__callStatic('nullOrIsInstanceOfAny', array($value, $classes, $message));
}
/**
* @psalm-pure
* @psalm-param array<class-string> $classes
*
* @param mixed $value
* @param array<object|string> $classes
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsInstanceOfAny($value, $classes, $message = '')
{
static::__callStatic('allIsInstanceOfAny', array($value, $classes, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert ExpectedType|class-string<ExpectedType>|null $value
*
* @param object|string|null $value
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsAOf($value, $class, $message = '')
{
static::__callStatic('nullOrIsAOf', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert iterable<ExpectedType|class-string<ExpectedType>> $value
*
* @param iterable<object|string> $value
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsAOf($value, $class, $message = '')
{
static::__callStatic('allIsAOf', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-template UnexpectedType of object
* @psalm-param class-string<UnexpectedType> $class
*
* @param object|string|null $value
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsNotA($value, $class, $message = '')
{
static::__callStatic('nullOrIsNotA', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-template UnexpectedType of object
* @psalm-param class-string<UnexpectedType> $class
*
* @param iterable<object|string> $value
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsNotA($value, $class, $message = '')
{
static::__callStatic('allIsNotA', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-param array<class-string> $classes
*
* @param object|string|null $value
* @param string[] $classes
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsAnyOf($value, $classes, $message = '')
{
static::__callStatic('nullOrIsAnyOf', array($value, $classes, $message));
}
/**
* @psalm-pure
* @psalm-param array<class-string> $classes
*
* @param iterable<object|string> $value
* @param string[] $classes
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsAnyOf($value, $classes, $message = '')
{
static::__callStatic('allIsAnyOf', array($value, $classes, $message));
}
/**
* @psalm-pure
* @psalm-assert empty $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsEmpty($value, $message = '')
{
static::__callStatic('nullOrIsEmpty', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<empty> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsEmpty($value, $message = '')
{
static::__callStatic('allIsEmpty', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotEmpty($value, $message = '')
{
static::__callStatic('nullOrNotEmpty', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotEmpty($value, $message = '')
{
static::__callStatic('allNotEmpty', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<null> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNull($value, $message = '')
{
static::__callStatic('allNull', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotNull($value, $message = '')
{
static::__callStatic('allNotNull', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert true|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrTrue($value, $message = '')
{
static::__callStatic('nullOrTrue', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<true> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allTrue($value, $message = '')
{
static::__callStatic('allTrue', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert false|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrFalse($value, $message = '')
{
static::__callStatic('nullOrFalse', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<false> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allFalse($value, $message = '')
{
static::__callStatic('allFalse', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotFalse($value, $message = '')
{
static::__callStatic('nullOrNotFalse', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotFalse($value, $message = '')
{
static::__callStatic('allNotFalse', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIp($value, $message = '')
{
static::__callStatic('nullOrIp', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIp($value, $message = '')
{
static::__callStatic('allIp', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIpv4($value, $message = '')
{
static::__callStatic('nullOrIpv4', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIpv4($value, $message = '')
{
static::__callStatic('allIpv4', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIpv6($value, $message = '')
{
static::__callStatic('nullOrIpv6', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIpv6($value, $message = '')
{
static::__callStatic('allIpv6', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrEmail($value, $message = '')
{
static::__callStatic('nullOrEmail', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allEmail($value, $message = '')
{
static::__callStatic('allEmail', array($value, $message));
}
/**
* @param array|null $values
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrUniqueValues($values, $message = '')
{
static::__callStatic('nullOrUniqueValues', array($values, $message));
}
/**
* @param iterable<array> $values
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allUniqueValues($values, $message = '')
{
static::__callStatic('allUniqueValues', array($values, $message));
}
/**
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrEq($value, $expect, $message = '')
{
static::__callStatic('nullOrEq', array($value, $expect, $message));
}
/**
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allEq($value, $expect, $message = '')
{
static::__callStatic('allEq', array($value, $expect, $message));
}
/**
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotEq($value, $expect, $message = '')
{
static::__callStatic('nullOrNotEq', array($value, $expect, $message));
}
/**
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotEq($value, $expect, $message = '')
{
static::__callStatic('allNotEq', array($value, $expect, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrSame($value, $expect, $message = '')
{
static::__callStatic('nullOrSame', array($value, $expect, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allSame($value, $expect, $message = '')
{
static::__callStatic('allSame', array($value, $expect, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotSame($value, $expect, $message = '')
{
static::__callStatic('nullOrNotSame', array($value, $expect, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $expect
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotSame($value, $expect, $message = '')
{
static::__callStatic('allNotSame', array($value, $expect, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrGreaterThan($value, $limit, $message = '')
{
static::__callStatic('nullOrGreaterThan', array($value, $limit, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allGreaterThan($value, $limit, $message = '')
{
static::__callStatic('allGreaterThan', array($value, $limit, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrGreaterThanEq($value, $limit, $message = '')
{
static::__callStatic('nullOrGreaterThanEq', array($value, $limit, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allGreaterThanEq($value, $limit, $message = '')
{
static::__callStatic('allGreaterThanEq', array($value, $limit, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrLessThan($value, $limit, $message = '')
{
static::__callStatic('nullOrLessThan', array($value, $limit, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allLessThan($value, $limit, $message = '')
{
static::__callStatic('allLessThan', array($value, $limit, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrLessThanEq($value, $limit, $message = '')
{
static::__callStatic('nullOrLessThanEq', array($value, $limit, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $limit
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allLessThanEq($value, $limit, $message = '')
{
static::__callStatic('allLessThanEq', array($value, $limit, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $min
* @param mixed $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrRange($value, $min, $max, $message = '')
{
static::__callStatic('nullOrRange', array($value, $min, $max, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param mixed $min
* @param mixed $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allRange($value, $min, $max, $message = '')
{
static::__callStatic('allRange', array($value, $min, $max, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrOneOf($value, $values, $message = '')
{
static::__callStatic('nullOrOneOf', array($value, $values, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allOneOf($value, $values, $message = '')
{
static::__callStatic('allOneOf', array($value, $values, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrInArray($value, $values, $message = '')
{
static::__callStatic('nullOrInArray', array($value, $values, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param array $values
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allInArray($value, $values, $message = '')
{
static::__callStatic('allInArray', array($value, $values, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $subString
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrContains($value, $subString, $message = '')
{
static::__callStatic('nullOrContains', array($value, $subString, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $subString
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allContains($value, $subString, $message = '')
{
static::__callStatic('allContains', array($value, $subString, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $subString
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotContains($value, $subString, $message = '')
{
static::__callStatic('nullOrNotContains', array($value, $subString, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $subString
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotContains($value, $subString, $message = '')
{
static::__callStatic('allNotContains', array($value, $subString, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotWhitespaceOnly($value, $message = '')
{
static::__callStatic('nullOrNotWhitespaceOnly', array($value, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotWhitespaceOnly($value, $message = '')
{
static::__callStatic('allNotWhitespaceOnly', array($value, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $prefix
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrStartsWith($value, $prefix, $message = '')
{
static::__callStatic('nullOrStartsWith', array($value, $prefix, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $prefix
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allStartsWith($value, $prefix, $message = '')
{
static::__callStatic('allStartsWith', array($value, $prefix, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $prefix
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotStartsWith($value, $prefix, $message = '')
{
static::__callStatic('nullOrNotStartsWith', array($value, $prefix, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $prefix
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotStartsWith($value, $prefix, $message = '')
{
static::__callStatic('allNotStartsWith', array($value, $prefix, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrStartsWithLetter($value, $message = '')
{
static::__callStatic('nullOrStartsWithLetter', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allStartsWithLetter($value, $message = '')
{
static::__callStatic('allStartsWithLetter', array($value, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $suffix
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrEndsWith($value, $suffix, $message = '')
{
static::__callStatic('nullOrEndsWith', array($value, $suffix, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $suffix
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allEndsWith($value, $suffix, $message = '')
{
static::__callStatic('allEndsWith', array($value, $suffix, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $suffix
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotEndsWith($value, $suffix, $message = '')
{
static::__callStatic('nullOrNotEndsWith', array($value, $suffix, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $suffix
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotEndsWith($value, $suffix, $message = '')
{
static::__callStatic('allNotEndsWith', array($value, $suffix, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $pattern
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrRegex($value, $pattern, $message = '')
{
static::__callStatic('nullOrRegex', array($value, $pattern, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $pattern
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allRegex($value, $pattern, $message = '')
{
static::__callStatic('allRegex', array($value, $pattern, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $pattern
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrNotRegex($value, $pattern, $message = '')
{
static::__callStatic('nullOrNotRegex', array($value, $pattern, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $pattern
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allNotRegex($value, $pattern, $message = '')
{
static::__callStatic('allNotRegex', array($value, $pattern, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrUnicodeLetters($value, $message = '')
{
static::__callStatic('nullOrUnicodeLetters', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allUnicodeLetters($value, $message = '')
{
static::__callStatic('allUnicodeLetters', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrAlpha($value, $message = '')
{
static::__callStatic('nullOrAlpha', array($value, $message));
}
/**
* @psalm-pure
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allAlpha($value, $message = '')
{
static::__callStatic('allAlpha', array($value, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrDigits($value, $message = '')
{
static::__callStatic('nullOrDigits', array($value, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allDigits($value, $message = '')
{
static::__callStatic('allDigits', array($value, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrAlnum($value, $message = '')
{
static::__callStatic('nullOrAlnum', array($value, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allAlnum($value, $message = '')
{
static::__callStatic('allAlnum', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert lowercase-string|null $value
*
* @param string|null $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrLower($value, $message = '')
{
static::__callStatic('nullOrLower', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<lowercase-string> $value
*
* @param iterable<string> $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allLower($value, $message = '')
{
static::__callStatic('allLower', array($value, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrUpper($value, $message = '')
{
static::__callStatic('nullOrUpper', array($value, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allUpper($value, $message = '')
{
static::__callStatic('allUpper', array($value, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param int $length
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrLength($value, $length, $message = '')
{
static::__callStatic('nullOrLength', array($value, $length, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param int $length
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allLength($value, $length, $message = '')
{
static::__callStatic('allLength', array($value, $length, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param int|float $min
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrMinLength($value, $min, $message = '')
{
static::__callStatic('nullOrMinLength', array($value, $min, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param int|float $min
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allMinLength($value, $min, $message = '')
{
static::__callStatic('allMinLength', array($value, $min, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrMaxLength($value, $max, $message = '')
{
static::__callStatic('nullOrMaxLength', array($value, $max, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allMaxLength($value, $max, $message = '')
{
static::__callStatic('allMaxLength', array($value, $max, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param int|float $min
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrLengthBetween($value, $min, $max, $message = '')
{
static::__callStatic('nullOrLengthBetween', array($value, $min, $max, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param int|float $min
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allLengthBetween($value, $min, $max, $message = '')
{
static::__callStatic('allLengthBetween', array($value, $min, $max, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrFileExists($value, $message = '')
{
static::__callStatic('nullOrFileExists', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allFileExists($value, $message = '')
{
static::__callStatic('allFileExists', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrFile($value, $message = '')
{
static::__callStatic('nullOrFile', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allFile($value, $message = '')
{
static::__callStatic('allFile', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrDirectory($value, $message = '')
{
static::__callStatic('nullOrDirectory', array($value, $message));
}
/**
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allDirectory($value, $message = '')
{
static::__callStatic('allDirectory', array($value, $message));
}
/**
* @param string|null $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrReadable($value, $message = '')
{
static::__callStatic('nullOrReadable', array($value, $message));
}
/**
* @param iterable<string> $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allReadable($value, $message = '')
{
static::__callStatic('allReadable', array($value, $message));
}
/**
* @param string|null $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrWritable($value, $message = '')
{
static::__callStatic('nullOrWritable', array($value, $message));
}
/**
* @param iterable<string> $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allWritable($value, $message = '')
{
static::__callStatic('allWritable', array($value, $message));
}
/**
* @psalm-assert class-string|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrClassExists($value, $message = '')
{
static::__callStatic('nullOrClassExists', array($value, $message));
}
/**
* @psalm-assert iterable<class-string> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allClassExists($value, $message = '')
{
static::__callStatic('allClassExists', array($value, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert class-string<ExpectedType>|ExpectedType|null $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrSubclassOf($value, $class, $message = '')
{
static::__callStatic('nullOrSubclassOf', array($value, $class, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $class
* @psalm-assert iterable<class-string<ExpectedType>|ExpectedType> $value
*
* @param mixed $value
* @param string|object $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allSubclassOf($value, $class, $message = '')
{
static::__callStatic('allSubclassOf', array($value, $class, $message));
}
/**
* @psalm-assert class-string|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrInterfaceExists($value, $message = '')
{
static::__callStatic('nullOrInterfaceExists', array($value, $message));
}
/**
* @psalm-assert iterable<class-string> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allInterfaceExists($value, $message = '')
{
static::__callStatic('allInterfaceExists', array($value, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $interface
* @psalm-assert class-string<ExpectedType>|null $value
*
* @param mixed $value
* @param mixed $interface
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrImplementsInterface($value, $interface, $message = '')
{
static::__callStatic('nullOrImplementsInterface', array($value, $interface, $message));
}
/**
* @psalm-pure
* @psalm-template ExpectedType of object
* @psalm-param class-string<ExpectedType> $interface
* @psalm-assert iterable<class-string<ExpectedType>> $value
*
* @param mixed $value
* @param mixed $interface
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allImplementsInterface($value, $interface, $message = '')
{
static::__callStatic('allImplementsInterface', array($value, $interface, $message));
}
/**
* @psalm-pure
* @psalm-param class-string|object|null $classOrObject
*
* @param string|object|null $classOrObject
* @param mixed $property
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrPropertyExists($classOrObject, $property, $message = '')
{
static::__callStatic('nullOrPropertyExists', array($classOrObject, $property, $message));
}
/**
* @psalm-pure
* @psalm-param iterable<class-string|object> $classOrObject
*
* @param iterable<string|object> $classOrObject
* @param mixed $property
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allPropertyExists($classOrObject, $property, $message = '')
{
static::__callStatic('allPropertyExists', array($classOrObject, $property, $message));
}
/**
* @psalm-pure
* @psalm-param class-string|object|null $classOrObject
*
* @param string|object|null $classOrObject
* @param mixed $property
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrPropertyNotExists($classOrObject, $property, $message = '')
{
static::__callStatic('nullOrPropertyNotExists', array($classOrObject, $property, $message));
}
/**
* @psalm-pure
* @psalm-param iterable<class-string|object> $classOrObject
*
* @param iterable<string|object> $classOrObject
* @param mixed $property
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allPropertyNotExists($classOrObject, $property, $message = '')
{
static::__callStatic('allPropertyNotExists', array($classOrObject, $property, $message));
}
/**
* @psalm-pure
* @psalm-param class-string|object|null $classOrObject
*
* @param string|object|null $classOrObject
* @param mixed $method
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrMethodExists($classOrObject, $method, $message = '')
{
static::__callStatic('nullOrMethodExists', array($classOrObject, $method, $message));
}
/**
* @psalm-pure
* @psalm-param iterable<class-string|object> $classOrObject
*
* @param iterable<string|object> $classOrObject
* @param mixed $method
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allMethodExists($classOrObject, $method, $message = '')
{
static::__callStatic('allMethodExists', array($classOrObject, $method, $message));
}
/**
* @psalm-pure
* @psalm-param class-string|object|null $classOrObject
*
* @param string|object|null $classOrObject
* @param mixed $method
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrMethodNotExists($classOrObject, $method, $message = '')
{
static::__callStatic('nullOrMethodNotExists', array($classOrObject, $method, $message));
}
/**
* @psalm-pure
* @psalm-param iterable<class-string|object> $classOrObject
*
* @param iterable<string|object> $classOrObject
* @param mixed $method
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allMethodNotExists($classOrObject, $method, $message = '')
{
static::__callStatic('allMethodNotExists', array($classOrObject, $method, $message));
}
/**
* @psalm-pure
*
* @param array|null $array
* @param string|int $key
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrKeyExists($array, $key, $message = '')
{
static::__callStatic('nullOrKeyExists', array($array, $key, $message));
}
/**
* @psalm-pure
*
* @param iterable<array> $array
* @param string|int $key
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allKeyExists($array, $key, $message = '')
{
static::__callStatic('allKeyExists', array($array, $key, $message));
}
/**
* @psalm-pure
*
* @param array|null $array
* @param string|int $key
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrKeyNotExists($array, $key, $message = '')
{
static::__callStatic('nullOrKeyNotExists', array($array, $key, $message));
}
/**
* @psalm-pure
*
* @param iterable<array> $array
* @param string|int $key
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allKeyNotExists($array, $key, $message = '')
{
static::__callStatic('allKeyNotExists', array($array, $key, $message));
}
/**
* @psalm-pure
* @psalm-assert array-key|null $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrValidArrayKey($value, $message = '')
{
static::__callStatic('nullOrValidArrayKey', array($value, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<array-key> $value
*
* @param mixed $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allValidArrayKey($value, $message = '')
{
static::__callStatic('allValidArrayKey', array($value, $message));
}
/**
* @param Countable|array|null $array
* @param int $number
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrCount($array, $number, $message = '')
{
static::__callStatic('nullOrCount', array($array, $number, $message));
}
/**
* @param iterable<Countable|array> $array
* @param int $number
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allCount($array, $number, $message = '')
{
static::__callStatic('allCount', array($array, $number, $message));
}
/**
* @param Countable|array|null $array
* @param int|float $min
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrMinCount($array, $min, $message = '')
{
static::__callStatic('nullOrMinCount', array($array, $min, $message));
}
/**
* @param iterable<Countable|array> $array
* @param int|float $min
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allMinCount($array, $min, $message = '')
{
static::__callStatic('allMinCount', array($array, $min, $message));
}
/**
* @param Countable|array|null $array
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrMaxCount($array, $max, $message = '')
{
static::__callStatic('nullOrMaxCount', array($array, $max, $message));
}
/**
* @param iterable<Countable|array> $array
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allMaxCount($array, $max, $message = '')
{
static::__callStatic('allMaxCount', array($array, $max, $message));
}
/**
* @param Countable|array|null $array
* @param int|float $min
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrCountBetween($array, $min, $max, $message = '')
{
static::__callStatic('nullOrCountBetween', array($array, $min, $max, $message));
}
/**
* @param iterable<Countable|array> $array
* @param int|float $min
* @param int|float $max
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allCountBetween($array, $min, $max, $message = '')
{
static::__callStatic('allCountBetween', array($array, $min, $max, $message));
}
/**
* @psalm-pure
* @psalm-assert list|null $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsList($array, $message = '')
{
static::__callStatic('nullOrIsList', array($array, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<list> $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsList($array, $message = '')
{
static::__callStatic('allIsList', array($array, $message));
}
/**
* @psalm-pure
* @psalm-assert non-empty-list|null $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsNonEmptyList($array, $message = '')
{
static::__callStatic('nullOrIsNonEmptyList', array($array, $message));
}
/**
* @psalm-pure
* @psalm-assert iterable<non-empty-list> $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsNonEmptyList($array, $message = '')
{
static::__callStatic('allIsNonEmptyList', array($array, $message));
}
/**
* @psalm-pure
* @psalm-template T
* @psalm-param mixed|array<T>|null $array
* @psalm-assert array<string, T>|null $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsMap($array, $message = '')
{
static::__callStatic('nullOrIsMap', array($array, $message));
}
/**
* @psalm-pure
* @psalm-template T
* @psalm-param iterable<mixed|array<T>> $array
* @psalm-assert iterable<array<string, T>> $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsMap($array, $message = '')
{
static::__callStatic('allIsMap', array($array, $message));
}
/**
* @psalm-pure
* @psalm-template T
* @psalm-param mixed|array<T>|null $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrIsNonEmptyMap($array, $message = '')
{
static::__callStatic('nullOrIsNonEmptyMap', array($array, $message));
}
/**
* @psalm-pure
* @psalm-template T
* @psalm-param iterable<mixed|array<T>> $array
*
* @param mixed $array
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allIsNonEmptyMap($array, $message = '')
{
static::__callStatic('allIsNonEmptyMap', array($array, $message));
}
/**
* @psalm-pure
*
* @param string|null $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrUuid($value, $message = '')
{
static::__callStatic('nullOrUuid', array($value, $message));
}
/**
* @psalm-pure
*
* @param iterable<string> $value
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allUuid($value, $message = '')
{
static::__callStatic('allUuid', array($value, $message));
}
/**
* @psalm-param class-string<Throwable> $class
*
* @param Closure|null $expression
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function nullOrThrows($expression, $class = 'Exception', $message = '')
{
static::__callStatic('nullOrThrows', array($expression, $class, $message));
}
/**
* @psalm-param class-string<Throwable> $class
*
* @param iterable<Closure> $expression
* @param string $class
* @param string $message
*
* @throws InvalidArgumentException
*
* @return void
*/
public static function allThrows($expression, $class = 'Exception', $message = '')
{
static::__callStatic('allThrows', array($expression, $class, $message));
}
}
The MIT License (MIT)
Copyright (c) 2014 Bernhard Schussek
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.
<?php
/*
* This file is part of Twig I18n extension.
*
* (c) 2010-2019 Fabien Potencier
* (c) 2019-2021 phpMyAdmin contributors
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpMyAdmin\Twig\Extensions\Node;
use Twig\Compiler;
use Twig\Node\CheckToStringNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\TempNameExpression;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use function array_merge;
use function count;
use function sprintf;
use function str_replace;
use function trim;
/**
* Represents a trans node.
*
* Author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class TransNode extends Node
{
/**
* The label for gettext notes to be exported
*
* @var string
*/
public static $notesLabel = '// notes: ';
/**
* Enable MoTranslator functions
*
* @var bool
*/
public static $enableMoTranslator = false;
/**
* Enable calls to addDebugInfo
*
* @var bool
*/
public static $enableAddDebugInfo = false;
/**
* Enables context functions usage
*
* @var bool
*/
public static $hasContextFunctions = false;
public function __construct(
Node $body,
?Node $plural,
?AbstractExpression $count,
?Node $context = null,
?Node $notes,
?Node $domain = null,
int $lineno = 0,
?string $tag = null
) {
$nodes = ['body' => $body];
if ($count !== null) {
$nodes['count'] = $count;
}
if ($plural !== null) {
$nodes['plural'] = $plural;
}
if ($notes !== null) {
$nodes['notes'] = $notes;
}
if ($domain !== null) {
$nodes['domain'] = $domain;
}
if ($context !== null) {
$nodes['context'] = $context;
}
parent::__construct($nodes, [], $lineno, $tag);
}
/**
* {@inheritdoc}
*/
public function compile(Compiler $compiler)
{
if (self::$enableAddDebugInfo) {
$compiler->addDebugInfo($this);
}
[$msg, $vars] = $this->compileString($this->getNode('body'));
$hasPlural = $this->hasNode('plural');
if ($hasPlural) {
[$msg1, $vars1] = $this->compileString($this->getNode('plural'));
$vars = array_merge($vars, $vars1);
}
$hasDomain = $this->hasNode('domain');
$hasContext = $this->hasNode('context');
$function = $this->getTransFunction($hasPlural, $hasContext, $hasDomain);
if ($this->hasNode('notes')) {
$message = trim($this->getNode('notes')->getAttribute('data'));
// line breaks are not allowed cause we want a single line comment
$message = str_replace(["\n", "\r"], ' ', $message);
$compiler->raw(static::$notesLabel . $message . "\n");
}
if ($vars) {
$compiler
->raw('echo strtr(' . $function . '(');
if ($hasDomain) {
[$domain] = $this->compileString($this->getNode('domain'));
$compiler
->subcompile($domain)
->raw(', ');
}
if ($hasContext
&& (static::$hasContextFunctions || static::$enableMoTranslator)
) {
[$context] = $this->compileString($this->getNode('context'));
$compiler
->subcompile($context)
->raw(', ');
}
$compiler
->subcompile($msg);
if ($hasPlural) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->getNode('count'))
->raw(')');
}
$compiler->raw('), array(');
foreach ($vars as $var) {
$attributeName = $var->getAttribute('name');
if ($attributeName === 'count') {
$compiler
->string('%count%')
->raw(' => abs(')
->subcompile($this->getNode('count'))
->raw('), ');
} else {
$compiler
->string('%' . $attributeName . '%')
->raw(' => ')
->subcompile($var)
->raw(', ');
}
}
$compiler->raw("));\n");
} else {
$compiler
->raw('echo ' . $function . '(');
if ($hasDomain) {
[$domain] = $this->compileString($this->getNode('domain'));
$compiler
->subcompile($domain)
->raw(', ');
}
if ($hasContext) {
if (static::$hasContextFunctions || static::$enableMoTranslator) {
[$context] = $this->compileString($this->getNode('context'));
$compiler
->subcompile($context)
->raw(', ');
}
}
$compiler
->subcompile($msg);
if ($hasPlural) {
$compiler
->raw(', ')
->subcompile($msg1)
->raw(', abs(')
->subcompile($this->getNode('count'))
->raw(')');
}
$compiler->raw(");\n");
}
}
/**
* Keep this method protected instead of private some implementations may use it
*/
protected function compileString(Node $body): array
{
if ($body instanceof NameExpression || $body instanceof ConstantExpression || $body instanceof TempNameExpression) {
return [$body, []];
}
$vars = [];
if (count($body)) {
$msg = '';
foreach ($body as $node) {
if ($node instanceof PrintNode) {
$n = $node->getNode('expr');
while ($n instanceof FilterExpression) {
$n = $n->getNode('node');
}
while ($n instanceof CheckToStringNode) {
$n = $n->getNode('expr');
}
$attributeName = $n->getAttribute('name');
$msg .= sprintf('%%%s%%', $attributeName);
$vars[] = new NameExpression($attributeName, $n->getTemplateLine());
} else {
$msg .= $node->getAttribute('data');
}
}
} else {
$msg = $body->getAttribute('data');
}
return [new Node([new ConstantExpression(trim($msg), $body->getTemplateLine())]), $vars];
}
/**
* Keep this protected to allow people to override it with their own logic
*/
protected function getTransFunction(bool $hasPlural, bool $hasContext, bool $hasDomain): string
{
$functionPrefix = '';
if (static::$enableMoTranslator) {
// The functions are prefixed with an underscore
$functionPrefix = '_';
}
// If it has not context function support or not MoTranslator
if (! static::$hasContextFunctions && ! static::$enableMoTranslator) {
// Not found on native PHP: dnpgettext, npgettext, dpgettext, pgettext
// No domain plural context support
// No domain context support
// No context support
// No plural context support
if ($hasDomain) {
// dngettext($domain, $msgid, $msgidPlural, $number);
// dgettext($domain, $msgid);
return $functionPrefix . ($hasPlural ? 'dngettext' : 'dgettext');
}
// ngettext($msgid, $msgidPlural, $number);
// gettext($msgid);
return $functionPrefix . ($hasPlural ? 'ngettext' : 'gettext');
}
if ($hasDomain) {
if ($hasPlural) {
// dnpgettext($domain, $msgctxt, $msgid, $msgidPlural, $number);
// dngettext($domain, $msgid, $msgidPlural, $number);
return $functionPrefix . ($hasContext ? 'dnpgettext' : 'dngettext');
}
// dpgettext($domain, $msgctxt, $msgid);
// dgettext($domain, $msgid);
return $functionPrefix . ($hasContext ? 'dpgettext' : 'dgettext');
}
if ($hasPlural) {
// npgettext($msgctxt, $msgid, $msgidPlural, $number);
// ngettext($msgid, $msgidPlural, $number);
return $functionPrefix . ($hasContext ? 'npgettext' : 'ngettext');
}
// pgettext($msgctxt, $msgid);
// gettext($msgid);
return $functionPrefix . ($hasContext ? 'pgettext' : 'gettext');
}
}
<?php
/*
* This file is part of Twig I18n extension.
*
* (c) 2010-2019 Fabien Potencier
* (c) 2019-2021 phpMyAdmin contributors
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpMyAdmin\Twig\Extensions;
use PhpMyAdmin\Twig\Extensions\TokenParser\TransTokenParser;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use function gettext;
use function dgettext;
class I18nExtension extends AbstractExtension
{
/**
* {@inheritdoc}
*/
public function getTokenParsers()
{
return [new TransTokenParser()];
}
/**
* {@inheritdoc}
*/
public function getFilters()
{
return [
new TwigFilter('trans', [$this, 'translate']), /* Note, the filter does not handle plurals */
];
}
/**
* {@inheritdoc}
*
* @return string
*/
public function getName()
{
return 'i18n';
}
/**
* Translate a GetText string via filter
*
* @param string $message The message to translate
* @param string|null $domain The GetText domain
*/
public function translate(string $message, ?string $domain = null): string
{
/* If we don't have a domain, assume we're just using the default */
if ($domain === null) {
return gettext($message);
}
/* Otherwise specify where the message comes from */
return dgettext($domain, $message);
}
}
<?php
/*
* This file is part of Twig I18n extension.
*
* (c) 2010-2019 Fabien Potencier
* (c) 2019-2021 phpMyAdmin contributors
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PhpMyAdmin\Twig\Extensions\TokenParser;
use PhpMyAdmin\Twig\Extensions\Node\TransNode;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\Node\TextNode;
use Twig\Token;
use Twig\TokenParser\AbstractTokenParser;
class TransTokenParser extends AbstractTokenParser
{
/**
* {@inheritdoc}
*/
public function parse(Token $token)
{
[
$body,
$plural,
$count,
$context,
$notes,
$domain,
$lineno,
$tag,
] = $this->preParse($token);
return new TransNode($body, $plural, $count, $context, $notes, $domain, $lineno, $tag);
}
protected function preParse(Token $token): array
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$domain = null;
$count = null;
$plural = null;
$notes = null;
$context = null;
/* If we aren't closing the block, do we have a domain? */
if ($stream->test(Token::NAME_TYPE)) {
$stream->expect(Token::NAME_TYPE, 'from');
$domain = $this->parser->getExpressionParser()->parseExpression();
}
if (! $stream->test(Token::BLOCK_END_TYPE)) {
$body = $this->parser->getExpressionParser()->parseExpression();
} else {
$stream->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse([$this, 'decideForFork']);
$next = $stream->next()->getValue();
if ($next === 'plural') {
$count = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(Token::BLOCK_END_TYPE);
$plural = $this->parser->subparse([$this, 'decideForFork']);
$next = $stream->next()->getValue();
if ($next === 'notes') {
$stream->expect(Token::BLOCK_END_TYPE);
$notes = $this->parser->subparse([$this, 'decideForEnd'], true);
} elseif ($next === 'context') {
$stream->expect(Token::BLOCK_END_TYPE);
$context = $this->parser->subparse([$this, 'decideForEnd'], true);
}
} elseif ($next === 'context') {
$stream->expect(Token::BLOCK_END_TYPE);
$context = $this->parser->subparse([$this, 'decideForEnd'], true);
} elseif ($next === 'notes') {
$stream->expect(Token::BLOCK_END_TYPE);
$notes = $this->parser->subparse([$this, 'decideForEnd'], true);
}
}
$stream->expect(Token::BLOCK_END_TYPE);
$this->checkTransString($body, $lineno);
return [$body, $plural, $count, $context, $notes, $domain, $lineno, $this->getTag()];
}
/**
* @return bool
*/
public function decideForFork(Token $token)
{
return $token->test(['plural', 'context', 'notes', 'endtrans']);
}
/**
* @return bool
*/
public function decideForEnd(Token $token)
{
return $token->test('endtrans');
}
/**
* {@inheritdoc}
*/
public function getTag()
{
return 'trans';
}
/**
* @return void
*
* @throws SyntaxError
*/
protected function checkTransString(Node $body, int $lineno)
{
foreach ($body as $i => $node) {
if ($node instanceof TextNode
||
($node instanceof PrintNode && $node->getNode('expr') instanceof NameExpression)
) {
continue;
}
throw new SyntaxError('The text to be translated with "trans" can only contain references to simple variables.', $lineno);
}
}
}
Copyright (c) 2010-2019 Fabien Potencier
Copyright (c) 2019-2021 phpMyAdmin contributors
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.
<?php
declare(strict_types = 1);
namespace CodeLts\CliTools;
/**
* ANSI escape sequences
* @see https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x361.html
* @see http://ascii-table.com/ansi-escape-sequences.php
*/
class AnsiEscapeSequences
{
/**
* Move the cursor up 1 lines
*/
public const MOVE_CURSOR_UP_1 = "\033[1A";
/**
* Move the cursor up 2 lines
*/
public const MOVE_CURSOR_UP_2 = "\033[2A";
/**
* Move the cursor down 1 lines
*/
public const MOVE_CURSOR_DOWN_1 = "\033[1B";
/**
* Move the cursor down 2 lines
*/
public const MOVE_CURSOR_DOWN_2 = "\033[2B";
/**
* Erase to the end of the line
*/
public const ERASE_TO_LINE_END = "\033[K";
/**
* Clear the screen, move to (0,0)
*/
public const CLEAR_SCREEN_MOVE_0_0 = "\033[2J";
}
<?php
declare(strict_types = 1);
namespace CodeLts\CliTools;
use CodeLts\CliTools\ErrorFormatter\CheckstyleErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\ErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\GithubErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\GitlabErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\JsonErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\JunitErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\RawErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\RawTextErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\TableErrorFormatter;
use CodeLts\CliTools\ErrorFormatter\TeamcityErrorFormatter;
use CodeLts\CliTools\Exceptions\FormatNotFoundException;
use CodeLts\CliTools\File\FuzzyRelativePathHelper;
use CodeLts\CliTools\File\NullRelativePathHelper;
use CodeLts\CliTools\File\RelativePathHelper;
use CodeLts\CliTools\File\SimpleRelativePathHelper;
class OutputFormat
{
public const OUTPUT_FORMAT_RAW = 'raw';
public const OUTPUT_FORMAT_RAW_TEXT = 'rawtext';
public const OUTPUT_FORMAT_JSON = 'json';
public const OUTPUT_FORMAT_JSON_PRETTY = 'prettyJson';
public const OUTPUT_FORMAT_JUNIT = 'junit';
public const OUTPUT_FORMAT_TABLE = 'table';
public const OUTPUT_FORMAT_CHECKSTYLE = 'checkstyle';
public const OUTPUT_FORMAT_GITLAB = 'gitlab';
public const OUTPUT_FORMAT_GITHUB = 'github';
public const OUTPUT_FORMAT_TEAMCITY = 'teamcity';
public const VALID_OUTPUT_FORMATS = [
OutputFormat::OUTPUT_FORMAT_RAW,
OutputFormat::OUTPUT_FORMAT_RAW_TEXT,
OutputFormat::OUTPUT_FORMAT_TABLE,
OutputFormat::OUTPUT_FORMAT_CHECKSTYLE,
OutputFormat::OUTPUT_FORMAT_JSON,
OutputFormat::OUTPUT_FORMAT_JUNIT,
OutputFormat::OUTPUT_FORMAT_JSON_PRETTY,
OutputFormat::OUTPUT_FORMAT_GITLAB,
OutputFormat::OUTPUT_FORMAT_GITHUB,
OutputFormat::OUTPUT_FORMAT_TEAMCITY,
];
/**
* Checks that the format is valid
*
* @param string $outputFormat The format to check
* @return true
*
* @throws FormatNotFoundException if the format does not exist
*/
public static function checkOutputFormatIsValid(string $outputFormat): bool
{
if (in_array($outputFormat, OutputFormat::VALID_OUTPUT_FORMATS)) {
return true;
}
throw new FormatNotFoundException($outputFormat);
}
/**
* Display to the output the AnalysisResult for the format
*/
public static function displayUserChoiceFormat(
string $outputFormat,
AnalysisResult $analysisResult,
?string $sourceRootDirectory,
Output $output
): void {
$pathHelper = new NullRelativePathHelper();
if ($sourceRootDirectory !== null) {
$pathHelper = new FuzzyRelativePathHelper(
new SimpleRelativePathHelper(
$sourceRootDirectory
),
$sourceRootDirectory
);
}
$formatter = OutputFormat::getFormatterForChoice($outputFormat, $pathHelper);
$formatter->formatErrors(
$analysisResult,
$output
);
}
/**
* get an ErrorFormatter for the given format
*
* @throws FormatNotFoundException if the format is not implemented
*/
public static function getFormatterForChoice(
string $outputFormat,
RelativePathHelper $pathHelper
): ErrorFormatter {
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_RAW) {
return new RawErrorFormatter();
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_RAW_TEXT) {
return new RawTextErrorFormatter();
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_JSON) {
return new JsonErrorFormatter(false);
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_JSON_PRETTY) {
return new JsonErrorFormatter(true);
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_JUNIT) {
return new JunitErrorFormatter($pathHelper);
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_TABLE) {
return new TableErrorFormatter($pathHelper);
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_CHECKSTYLE) {
return new CheckstyleErrorFormatter($pathHelper);
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_GITLAB) {
return new GitlabErrorFormatter($pathHelper);
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_GITHUB) {
return new GithubErrorFormatter($pathHelper, new TableErrorFormatter($pathHelper));
}
if ($outputFormat === OutputFormat::OUTPUT_FORMAT_TEAMCITY) {
return new TeamcityErrorFormatter($pathHelper);
}
throw new FormatNotFoundException($outputFormat);
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class ErrorsConsoleStyle extends \Symfony\Component\Console\Style\SymfonyStyle
{
public const OPTION_NO_PROGRESS = 'no-progress';
/** @var bool */
private $showProgress;
/** @var ProgressBar */
private $progressBar;
/** @var bool|null */
private $isCiDetected = null;
public function __construct(InputInterface $input, OutputInterface $output)
{
parent::__construct($input, $output);
$this->showProgress = $input->hasOption(self::OPTION_NO_PROGRESS) && !(bool) $input->getOption(self::OPTION_NO_PROGRESS);
}
private function isCiDetected(): bool
{
if ($this->isCiDetected === null) {
$this->isCiDetected = Utils::isCiDetected();
}
return $this->isCiDetected;
}
/**
* @param string[] $headers
* @param string[][] $rows
*/
public function table(array $headers, array $rows): void
{
/** @var int $terminalWidth */
$terminalWidth = (new \Symfony\Component\Console\Terminal())->getWidth() - 2;
$maxHeaderWidth = strlen($headers[0]);
foreach ($rows as $row) {
$length = strlen($row[0]);
if ($maxHeaderWidth !== 0 && $length <= $maxHeaderWidth) {
continue;
}
$maxHeaderWidth = $length;
}
$wrap = static function ($rows) use ($terminalWidth, $maxHeaderWidth) {
return array_map(
static function ($row) use ($terminalWidth, $maxHeaderWidth) {
return array_map(
static function ($s) use ($terminalWidth, $maxHeaderWidth) {
if ($terminalWidth > $maxHeaderWidth + 5) {
return wordwrap(
$s,
$terminalWidth - $maxHeaderWidth - 5,
"\n",
true
);
}
return $s;
},
$row
);
},
$rows
);
};
parent::table($headers, $wrap($rows));
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $max
*/
public function createProgressBar($max = 0): ProgressBar
{
$this->progressBar = parent::createProgressBar($max);
$this->progressBar->setOverwrite(!$this->isCiDetected());
return $this->progressBar;
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $max
*/
public function progressStart($max = 0): void
{
if (!$this->showProgress) {
return;
}
parent::progressStart($max);
}
/**
* @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
* @param int $step
*/
public function progressAdvance($step = 1): void
{
if (!$this->showProgress) {
return;
}
if (!$this->isCiDetected() && $step > 0) {
$stepTime = (time() - $this->progressBar->getStartTime()) / $step;
if ($stepTime > 0 && $stepTime < 1) {
$this->progressBar->setRedrawFrequency((int) (1 / $stepTime));
} else {
$this->progressBar->setRedrawFrequency(1);
}
}
parent::progressAdvance($step);
}
public function progressFinish(): void
{
if (!$this->showProgress) {
return;
}
parent::progressFinish();
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools;
class AnalysisResult
{
/** @var \CodeLts\CliTools\Error[] sorted by their file name, line number and message */
private $fileSpecificErrors;
/** @var string[] */
private $notFileSpecificErrors;
/** @var string[] */
private $internalErrors;
/** @var string[] */
private $warnings;
/**
* @param \CodeLts\CliTools\Error[] $fileSpecificErrors
* @param string[] $notFileSpecificErrors
* @param string[] $internalErrors
* @param string[] $warnings
*/
public function __construct(
array $fileSpecificErrors,
array $notFileSpecificErrors,
array $internalErrors,
array $warnings
) {
usort(
$fileSpecificErrors,
static function (Error $a, Error $b): int {
return [
$a->getFile(),
$a->getLine(),
$a->getMessage(),
] <=> [
$b->getFile(),
$b->getLine(),
$b->getMessage(),
];
}
);
$this->fileSpecificErrors = $fileSpecificErrors;
$this->notFileSpecificErrors = $notFileSpecificErrors;
$this->internalErrors = $internalErrors;
$this->warnings = $warnings;
}
public function hasErrors(): bool
{
return $this->getTotalErrorsCount() > 0;
}
public function getTotalErrorsCount(): int
{
return count($this->fileSpecificErrors) + count($this->notFileSpecificErrors);
}
/**
* @return \CodeLts\CliTools\Error[] sorted by their file name, line number and message
*/
public function getFileSpecificErrors(): array
{
return $this->fileSpecificErrors;
}
/**
* @return string[]
*/
public function getNotFileSpecificErrors(): array
{
return $this->notFileSpecificErrors;
}
/**
* @return string[]
*/
public function getInternalErrors(): array
{
return $this->internalErrors;
}
/**
* @return string[]
*/
public function getWarnings(): array
{
return $this->warnings;
}
public function hasWarnings(): bool
{
return count($this->warnings) > 0;
}
public function hasInternalErrors(): bool
{
return count($this->internalErrors) > 0;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools;
interface OutputStyle
{
public function title(string $message): void;
public function section(string $message): void;
/**
* @param string[] $elements
*/
public function listing(array $elements): void;
public function success(string $message): void;
public function error(string $message): void;
public function warning(string $message): void;
public function note(string $message): void;
public function caution(string $message): void;
/**
* @param mixed[] $headers
* @param mixed[] $rows
*/
public function table(array $headers, array $rows): void;
public function newLine(int $count = 1): void;
public function progressStart(int $max = 0): void;
public function progressAdvance(int $step = 1): void;
public function progressFinish(): void;
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
use function file_get_contents;
class FileReader
{
public static function read(string $fileName): string
{
if (!is_file($fileName)) {
throw new \CodeLts\CliTools\File\CouldNotReadFileException($fileName);
}
$contents = @file_get_contents($fileName);
if ($contents === false) {
throw new \CodeLts\CliTools\File\CouldNotReadFileException($fileName);
}
return $contents;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
interface RelativePathHelper
{
public function getRelativePath(string $filename): string;
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
class SimpleRelativePathHelper implements RelativePathHelper
{
/**
* @var string
*/
private $currentWorkingDirectory;
public function __construct(string $currentWorkingDirectory)
{
$this->currentWorkingDirectory = $currentWorkingDirectory;
}
public function getRelativePath(string $filename): string
{
if ($this->currentWorkingDirectory !== '' && strpos($filename, $this->currentWorkingDirectory) === 0) {
return substr($filename, strlen($this->currentWorkingDirectory) + 1);
}
return $filename;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
class CouldNotWriteFileException extends \Exception
{
public function __construct(string $fileName, string $error)
{
parent::__construct(sprintf('Could not write file: %s (%s)', $fileName, $error));
}
public function getTip(): ?string
{
return null;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
class FuzzyRelativePathHelper implements RelativePathHelper
{
/**
* @var RelativePathHelper
*/
private $fallbackRelativePathHelper;
/**
* @var string
*/
private $directorySeparator;
/**
* @var string|null
*/
private $pathToTrim = null;
/**
* @param RelativePathHelper $fallbackRelativePathHelper
* @param string $currentWorkingDirectory
*/
public function __construct(
RelativePathHelper $fallbackRelativePathHelper,
string $currentWorkingDirectory
) {
$this->fallbackRelativePathHelper = $fallbackRelativePathHelper;
$this->directorySeparator = DIRECTORY_SEPARATOR;
$pathBeginning = null;
$pathToTrimArray = null;
$trimBeginning = static function (string $path): array {
if (substr($path, 0, 1) === '/') {
return [
'/',
substr($path, 1),
];
} elseif (substr($path, 1, 1) === ':') {
return [
substr($path, 0, 3),
substr($path, 3),
];
}
return ['', $path];
};
$cwdNotRootAndNotEmpty = ! in_array($currentWorkingDirectory, ['', '/'], true);
$length3AndFoundChar = !(strlen($currentWorkingDirectory) === 3 && substr($currentWorkingDirectory, 1, 1) === ':');
if ($cwdNotRootAndNotEmpty && $length3AndFoundChar) {
[$pathBeginning, $currentWorkingDirectory] = $trimBeginning($currentWorkingDirectory);
/** @var string[] $pathToTrimArray */
$pathToTrimArray = explode($this->directorySeparator, $currentWorkingDirectory);
}
if ($pathToTrimArray === null || count($pathToTrimArray) === 0) {
return;
}
$pathToTrim = $pathBeginning . implode($this->directorySeparator, $pathToTrimArray);
$realPathToTrim = realpath($pathToTrim);
if ($realPathToTrim !== false) {
$pathToTrim = $realPathToTrim;
}
$this->pathToTrim = $pathToTrim;
}
public function getRelativePath(string $filename): string
{
if ($this->pathToTrim !== null && strpos($filename, $this->pathToTrim) === 0) {
return ltrim(substr($filename, strlen($this->pathToTrim)), $this->directorySeparator);
}
return $this->fallbackRelativePathHelper->getRelativePath($filename);
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
class CouldNotReadFileException extends \Exception
{
public function __construct(string $fileName)
{
parent::__construct(sprintf('Could not read file: %s', $fileName));
}
public function getTip(): ?string
{
return null;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
class NullRelativePathHelper implements RelativePathHelper
{
public function getRelativePath(string $filename): string
{
return $filename;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
class PathNotFoundException extends \Exception
{
/**
* @var string
*/
private $path;
public function __construct(string $path)
{
parent::__construct(sprintf('Path %s does not exist', $path));
$this->path = $path;
}
public function getPath(): string
{
return $this->path;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\File;
class FileWriter
{
public static function write(string $fileName, string $contents): void
{
$success = @file_put_contents($fileName, $contents);
if ($success === false) {
$error = error_get_last();
throw new \CodeLts\CliTools\File\CouldNotWriteFileException(
$fileName,
$error !== null ? $error['message'] : 'unknown cause'
);
}
}
}
<?php
declare(strict_types = 1);
namespace CodeLts\CliTools;
class Error
{
/** @var string */
protected $message;
/** @var string|null */
protected $file;
/** @var int */
protected $line;
/** @var int */
protected $severity;
/** @var string|null */
protected $tip = null;
public const LEVEL_EMERGENCY = 0;
public const LEVEL_ALERT = 1;
public const LEVEL_CRITICAL = 2;
public const LEVEL_ERROR = 3;
public const LEVEL_WARNING = 4;
public const LEVEL_NOTICE = 5;
public const LEVEL_INFO = 6;
public const LEVEL_DEBUG = 7;
public function __construct(
string $message,
?string $file,
int $line,
int $severity = Error::LEVEL_ERROR,
?string $tip = null
) {
$this->message = $message;
$this->file = $file;
$this->line = $line;
$this->severity = $severity;
$this->tip = $tip;
}
public function getMessage(): string
{
return $this->message;
}
public function getFile(): ?string
{
return $this->file;
}
public function getLine(): int
{
return $this->line;
}
public function canBeIgnored(): bool
{
return $this->severity < Error::LEVEL_WARNING;
}
public function getTip(): ?string
{
return $this->tip;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\Symfony;
use CodeLts\CliTools\OutputStyle;
use Symfony\Component\Console\Style\StyleInterface;
/**
* @internal
*/
class SymfonyStyle implements OutputStyle
{
/**
* @var StyleInterface
*/
private $symfonyStyle;
public function __construct(StyleInterface $symfonyStyle)
{
$this->symfonyStyle = $symfonyStyle;
}
public function getSymfonyStyle(): StyleInterface
{
return $this->symfonyStyle;
}
public function title(string $message): void
{
$this->symfonyStyle->title($message);
}
public function section(string $message): void
{
$this->symfonyStyle->section($message);
}
public function listing(array $elements): void
{
$this->symfonyStyle->listing($elements);
}
public function success(string $message): void
{
$this->symfonyStyle->success($message);
}
public function error(string $message): void
{
$this->symfonyStyle->error($message);
}
public function warning(string $message): void
{
$this->symfonyStyle->warning($message);
}
public function note(string $message): void
{
$this->symfonyStyle->note($message);
}
public function caution(string $message): void
{
$this->symfonyStyle->caution($message);
}
public function table(array $headers, array $rows): void
{
$this->symfonyStyle->table($headers, $rows);
}
public function newLine(int $count = 1): void
{
$this->symfonyStyle->newLine($count);
}
public function progressStart(int $max = 0): void
{
$this->symfonyStyle->progressStart($max);
}
public function progressAdvance(int $step = 1): void
{
$this->symfonyStyle->progressAdvance($step);
}
public function progressFinish(): void
{
$this->symfonyStyle->progressFinish();
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\Symfony;
use CodeLts\CliTools\Output;
use CodeLts\CliTools\OutputStyle;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @internal
*/
class SymfonyOutput implements Output
{
/**
* @var OutputInterface
*/
private $symfonyOutput;
/**
* @var OutputStyle
*/
private $style;
public function __construct(
OutputInterface $symfonyOutput,
OutputStyle $style
) {
$this->symfonyOutput = $symfonyOutput;
$this->style = $style;
}
public function writeFormatted(string $message): void
{
$this->symfonyOutput->write($message, false, OutputInterface::OUTPUT_NORMAL);
}
public function writeLineFormatted(string $message): void
{
$this->symfonyOutput->writeln($message, OutputInterface::OUTPUT_NORMAL);
}
public function writeRaw(string $message): void
{
$this->symfonyOutput->write($message, false, OutputInterface::OUTPUT_RAW);
}
public function getStyle(): OutputStyle
{
return $this->style;
}
public function isVerbose(): bool
{
return $this->symfonyOutput->isVerbose();
}
public function isDebug(): bool
{
return $this->symfonyOutput->isDebug();
}
public function isDecorated(): bool
{
return $this->symfonyOutput->isDecorated();
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
class RawErrorFormatter implements ErrorFormatter
{
public function formatErrors(
AnalysisResult $analysisResult,
Output $output
): int {
foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) {
$output->writeRaw(sprintf('?:?:%s', $notFileSpecificError));
$output->writeLineFormatted('');
}
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
$output->writeRaw(
sprintf(
'%s:%d:%s',
$fileSpecificError->getFile(),
$fileSpecificError->getLine() ?? '?',
$fileSpecificError->getMessage()
)
);
$output->writeLineFormatted('');
}
foreach ($analysisResult->getWarnings() as $warning) {
$output->writeRaw(sprintf('?:?:%s', $warning));
$output->writeLineFormatted('');
}
return $analysisResult->hasErrors() ? 1 : 0;
}
}
<?php
declare(strict_types = 1);
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
class RawTextErrorFormatter implements ErrorFormatter
{
public function formatErrors(
AnalysisResult $analysisResult,
Output $output
): int {
foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) {
$output->writeFormatted('<fg=red>ERROR</>: ');
$output->writeLineFormatted($notFileSpecificError);
}
foreach ($analysisResult->getFileSpecificErrors() as $error) {
$output->writeFormatted('<fg=red>ERROR</>: ');
$output->writeLineFormatted(
sprintf('%s in %s:%d', $error->getMessage(), $error->getFile() ?? '', $error->getLine())
);
}
foreach ($analysisResult->getWarnings() as $warning) {
$output->writeFormatted('<fg=yellow>WARNING</>: ');
$output->writeLineFormatted($warning);
}
return $analysisResult->hasErrors() ? 1 : 0;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
use CodeLts\CliTools\File\RelativePathHelper;
/**
* Allow errors to be reported in pull-requests diff when run in a GitHub Action
* @see https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message
*/
class GithubErrorFormatter implements ErrorFormatter
{
/**
* @var RelativePathHelper
*/
private $relativePathHelper;
/**
* @var TableErrorFormatter
*/
private $tableErrorformatter;
public function __construct(
RelativePathHelper $relativePathHelper,
TableErrorFormatter $tableErrorformatter
) {
$this->relativePathHelper = $relativePathHelper;
$this->tableErrorformatter = $tableErrorformatter;
}
public function formatErrors(AnalysisResult $analysisResult, Output $output): int
{
$this->tableErrorformatter->formatErrors($analysisResult, $output);
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
$metas = [
'file' => $fileSpecificError->getFile() !== null ? $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()) : '?',
'line' => $fileSpecificError->getLine(),
'col' => 0,
];
array_walk(
$metas,
static function (&$value, string $key): void {
$value = sprintf('%s=%s', $key, (string) $value);
}
);
$message = $fileSpecificError->getMessage();
// newlines need to be encoded
// see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448
$message = str_replace("\n", '%0A', $message);
$line = sprintf('::error %s::%s', implode(',', $metas), $message);
$output->writeRaw($line);
$output->writeLineFormatted('');
}
foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) {
// newlines need to be encoded
// see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448
$notFileSpecificError = str_replace("\n", '%0A', $notFileSpecificError);
$line = sprintf('::error ::%s', $notFileSpecificError);
$output->writeRaw($line);
$output->writeLineFormatted('');
}
foreach ($analysisResult->getWarnings() as $warning) {
// newlines need to be encoded
// see https://github.com/actions/starter-workflows/issues/68#issuecomment-581479448
$warning = str_replace("\n", '%0A', $warning);
$line = sprintf('::warning ::%s', $warning);
$output->writeRaw($line);
$output->writeLineFormatted('');
}
return $analysisResult->hasErrors() ? 1 : 0;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
use CodeLts\CliTools\File\RelativePathHelper;
class CheckstyleErrorFormatter implements ErrorFormatter
{
/**
* @var RelativePathHelper
*/
private $relativePathHelper;
public function __construct(RelativePathHelper $relativePathHelper)
{
$this->relativePathHelper = $relativePathHelper;
}
public function formatErrors(
AnalysisResult $analysisResult,
Output $output
): int {
$output->writeRaw('<?xml version="1.0" encoding="UTF-8"?>');
$output->writeLineFormatted('');
$output->writeRaw('<checkstyle>');
$output->writeLineFormatted('');
foreach ($this->groupByFile($analysisResult) as $relativeFilePath => $errors) {
$output->writeRaw(
sprintf(
'<file name="%s">',
$this->escape($relativeFilePath)
)
);
$output->writeLineFormatted('');
foreach ($errors as $error) {
$output->writeRaw(
sprintf(
' <error line="%d" column="1" severity="error" message="%s" />',
$this->escape((string) $error->getLine()),
$this->escape((string) $error->getMessage())
)
);
$output->writeLineFormatted('');
}
$output->writeRaw('</file>');
$output->writeLineFormatted('');
}
$notFileSpecificErrors = $analysisResult->getNotFileSpecificErrors();
if (count($notFileSpecificErrors) > 0) {
$output->writeRaw('<file>');
$output->writeLineFormatted('');
foreach ($notFileSpecificErrors as $error) {
$output->writeRaw(sprintf(' <error severity="error" message="%s" />', $this->escape($error)));
$output->writeLineFormatted('');
}
$output->writeRaw('</file>');
$output->writeLineFormatted('');
}
if ($analysisResult->hasWarnings()) {
$output->writeRaw('<file>');
$output->writeLineFormatted('');
foreach ($analysisResult->getWarnings() as $warning) {
$output->writeRaw(sprintf(' <error severity="warning" message="%s" />', $this->escape($warning)));
$output->writeLineFormatted('');
}
$output->writeRaw('</file>');
$output->writeLineFormatted('');
}
$output->writeRaw('</checkstyle>');
$output->writeLineFormatted('');
return $analysisResult->hasErrors() ? 1 : 0;
}
/**
* Escapes values for using in XML
*
* @param string $string
* @return string
*/
protected function escape(string $string): string
{
return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}
/**
* Group errors by file
*
* @param AnalysisResult $analysisResult
* @return array<string, array> Array that have as key the relative path of file
* and as value an array with occurred errors.
*/
private function groupByFile(AnalysisResult $analysisResult): array
{
$files = [];
/** @var \CodeLts\CliTools\Error $fileSpecificError */
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
$absolutePath = $fileSpecificError->getFile();
if ($absolutePath === null) {
continue;
}
$relativeFilePath = $this->relativePathHelper->getRelativePath(
$absolutePath
);
$files[$relativeFilePath][] = $fileSpecificError;
}
return $files;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
use CodeLts\CliTools\File\RelativePathHelper;
class TableErrorFormatter implements ErrorFormatter
{
/**
* @var RelativePathHelper
*/
private $relativePathHelper;
public function __construct(
RelativePathHelper $relativePathHelper
) {
$this->relativePathHelper = $relativePathHelper;
}
public function formatErrors(
AnalysisResult $analysisResult,
Output $output
): int {
$style = $output->getStyle();
if (!$analysisResult->hasErrors() && !$analysisResult->hasWarnings()) {
$style->success('No errors');
return 0;
}
/** @var array<string, \CodeLts\CliTools\Error[]> $fileErrors */
$fileErrors = [];
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
if (!isset($fileErrors[$fileSpecificError->getFile()])) {
$fileErrors[$fileSpecificError->getFile()] = [];
}
$fileErrors[$fileSpecificError->getFile()][] = $fileSpecificError;
}
foreach ($fileErrors as $file => $errors) {
$rows = [];
foreach ($errors as $error) {
$message = $error->getMessage();
if ($error->getTip() !== null) {
$tip = $error->getTip();
$message .= "\n🡠" . $tip;
}
$rows[] = [
(string) $error->getLine(),
$message,
];
}
$relativeFilePath = $this->relativePathHelper->getRelativePath($file);
$style->table(['Line', $relativeFilePath], $rows);
}
if (count($analysisResult->getNotFileSpecificErrors()) > 0) {
$style->table(
['', 'Error'],
array_map(
static function (string $error): array {
return ['', $error];
},
$analysisResult->getNotFileSpecificErrors()
)
);
}
$warningsCount = count($analysisResult->getWarnings());
if ($warningsCount > 0) {
$style->table(
['', 'Warning'],
array_map(
static function (string $warning): array {
return ['', $warning];
},
$analysisResult->getWarnings()
)
);
}
$finalMessage = sprintf($analysisResult->getTotalErrorsCount() === 1 ? 'Found %d error' : 'Found %d errors', $analysisResult->getTotalErrorsCount());
if ($warningsCount > 0) {
$finalMessage .= sprintf($warningsCount === 1 ? ' and %d warning' : ' and %d warnings', $warningsCount);
}
if ($analysisResult->getTotalErrorsCount() > 0) {
$style->error($finalMessage);
} else {
$style->warning($finalMessage);
}
return $analysisResult->getTotalErrorsCount() > 0 ? 1 : 0;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
class JsonErrorFormatter implements ErrorFormatter
{
/**
* @var bool
*/
private $pretty;
public function __construct(bool $pretty)
{
$this->pretty = $pretty;
}
public function formatErrors(AnalysisResult $analysisResult, Output $output): int
{
$errorsArray = [
'totals' => [
'errors' => count($analysisResult->getNotFileSpecificErrors()),
'file_errors' => count($analysisResult->getFileSpecificErrors()),
],
'files' => [],
'errors' => [],
];
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
$file = $fileSpecificError->getFile();
if ($file === null) {
continue;
}
if (!array_key_exists($file, $errorsArray['files'])) {
$errorsArray['files'][$file] = [
'errors' => 0,
'messages' => [],
];
}
$errorsArray['files'][$file]['errors']++;
$errorsArray['files'][$file]['messages'][] = [
'message' => $fileSpecificError->getMessage(),
'line' => $fileSpecificError->getLine(),
'ignorable' => $fileSpecificError->canBeIgnored(),
];
}
foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) {
$errorsArray['errors'][] = $notFileSpecificError;
}
$json = json_encode($errorsArray, $this->pretty ? JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT : JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$output->writeRaw((string) $json);
return $analysisResult->hasErrors() ? 1 : 0;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
use CodeLts\CliTools\File\RelativePathHelper;
/**
* @see https://www.jetbrains.com/help/teamcity/build-script-interaction-with-teamcity.html#Reporting+Inspections
*/
class TeamcityErrorFormatter implements ErrorFormatter
{
/**
* @var RelativePathHelper
*/
private $relativePathHelper;
public function __construct(RelativePathHelper $relativePathHelper)
{
$this->relativePathHelper = $relativePathHelper;
}
public function formatErrors(AnalysisResult $analysisResult, Output $output): int
{
$result = '';
$fileSpecificErrors = $analysisResult->getFileSpecificErrors();
$notFileSpecificErrors = $analysisResult->getNotFileSpecificErrors();
$warnings = $analysisResult->getWarnings();
if (count($fileSpecificErrors) === 0 && count($notFileSpecificErrors) === 0 && count($warnings) === 0) {
return 0;
}
$result .= $this->createTeamcityLine(
'inspectionType',
[
'id' => 'cli-tools',
'name' => 'cli-tools',
'category' => 'cli-tools',
'description' => 'cli-tools Errors',
]
);
foreach ($fileSpecificErrors as $fileSpecificError) {
$result .= $this->createTeamcityLine(
'inspection',
[
'typeId' => 'cli-tools',
'message' => $fileSpecificError->getMessage(),
'file' => $fileSpecificError->getFile() !== null ? $this->relativePathHelper->getRelativePath($fileSpecificError->getFile()) : '?',
'line' => $fileSpecificError->getLine(),
// additional attributes
'SEVERITY' => 'ERROR',
'ignorable' => $fileSpecificError->canBeIgnored(),
'tip' => $fileSpecificError->getTip(),
]
);
}
foreach ($notFileSpecificErrors as $notFileSpecificError) {
$result .= $this->createTeamcityLine(
'inspection',
[
'typeId' => 'cli-tools',
'message' => $notFileSpecificError,
// the file is required
'file' => '.',
'SEVERITY' => 'ERROR',
]
);
}
foreach ($warnings as $warning) {
$result .= $this->createTeamcityLine(
'inspection',
[
'typeId' => 'cli-tools',
'message' => $warning,
// the file is required
'file' => '.',
'SEVERITY' => 'WARNING',
]
);
}
$output->writeRaw($result);
return $analysisResult->hasErrors() ? 1 : 0;
}
/**
* Creates a Teamcity report line
*
* @param string $messageName The message name
* @param mixed[] $keyValuePairs The key=>value pairs
* @return string The Teamcity report line
*/
private function createTeamcityLine(string $messageName, array $keyValuePairs): string
{
$string = '##teamcity[' . $messageName;
foreach ($keyValuePairs as $key => $value) {
if (is_string($value)) {
$value = $this->escape($value);
}
$string .= ' ' . $key . '=\'' . $value . '\'';
}
return $string . ']' . PHP_EOL;
}
/**
* Escapes the given string for Teamcity output
*
* @param string $string The string to escape
* @return string The escaped string
*/
private function escape(string $string): string
{
$replacements = [
'~\n~' => '|n',
'~\r~' => '|r',
'~([\'\|\[\]])~' => '|$1',
];
return (string) preg_replace(array_keys($replacements), array_values($replacements), $string);
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
interface ErrorFormatter
{
/**
* Formats the errors and outputs them to the console.
*
* @param \CodeLts\CliTools\AnalysisResult $analysisResult
* @param \CodeLts\CliTools\Output $output
* @return int Error code.
*/
public function formatErrors(
AnalysisResult $analysisResult,
Output $output
): int;
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
use CodeLts\CliTools\File\RelativePathHelper;
use function sprintf;
class JunitErrorFormatter implements ErrorFormatter
{
/**
* @var RelativePathHelper
*/
private $relativePathHelper;
public function __construct(RelativePathHelper $relativePathHelper)
{
$this->relativePathHelper = $relativePathHelper;
}
public function formatErrors(
AnalysisResult $analysisResult,
Output $output
): int {
$result = '<?xml version="1.0" encoding="UTF-8"?>';
$result .= sprintf(
'<testsuite failures="%d" name="cli-tools" tests="%d" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/junit-team/junit5/r5.5.1/platform-tests/src/test/resources/jenkins-junit.xsd">',
$analysisResult->getTotalErrorsCount(),
$analysisResult->getTotalErrorsCount()
);
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
$file = $fileSpecificError->getFile();
if ($file === null) {
continue;
}
$fileName = $this->relativePathHelper->getRelativePath($file);
$result .= $this->createTestCase(
sprintf('%s:%s', $fileName, (string) $fileSpecificError->getLine()),
'ERROR',
$this->escape($fileSpecificError->getMessage())
);
}
foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) {
$result .= $this->createTestCase('General error', 'ERROR', $this->escape($notFileSpecificError));
}
foreach ($analysisResult->getWarnings() as $warning) {
$result .= $this->createTestCase('Warning', 'WARNING', $this->escape($warning));
}
if (!$analysisResult->hasErrors()) {
$result .= $this->createTestCase('cli-tools', '');
}
$result .= '</testsuite>';
$output->writeRaw($result);
return $analysisResult->hasErrors() ? 1 : 0;
}
/**
* Format a single test case
*
* @param string $reference
* @param string|null $message
*
* @return string
*/
private function createTestCase(string $reference, string $type, ?string $message = null): string
{
$result = sprintf('<testcase name="%s">', $this->escape($reference));
if ($message !== null) {
$result .= sprintf('<failure type="%s" message="%s" />', $this->escape($type), $this->escape($message));
}
$result .= '</testcase>';
return $result;
}
/**
* Escapes values for using in XML
*
* @param string $string
* @return string
*/
protected function escape(string $string): string
{
return htmlspecialchars($string, ENT_XML1 | ENT_COMPAT, 'UTF-8');
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools\ErrorFormatter;
use CodeLts\CliTools\AnalysisResult;
use CodeLts\CliTools\Output;
use CodeLts\CliTools\File\RelativePathHelper;
/**
* @see https://docs.gitlab.com/ee/user/project/merge_requests/code_quality.html#implementing-a-custom-tool
*/
class GitlabErrorFormatter implements ErrorFormatter
{
/**
* @var RelativePathHelper
*/
private $relativePathHelper;
public function __construct(RelativePathHelper $relativePathHelper)
{
$this->relativePathHelper = $relativePathHelper;
}
public function formatErrors(AnalysisResult $analysisResult, Output $output): int
{
$errorsArray = [];
foreach ($analysisResult->getFileSpecificErrors() as $fileSpecificError) {
$file = $fileSpecificError->getFile();
if ($file === null) {
continue;
}
$error = [
'description' => $fileSpecificError->getMessage(),
'fingerprint' => hash(
'sha256',
implode(
[
$file,
$fileSpecificError->getLine(),
$fileSpecificError->getMessage(),
]
)
),
'location' => [
'path' => $this->relativePathHelper->getRelativePath($file),
'lines' => [
'begin' => $fileSpecificError->getLine(),
],
],
];
if (!$fileSpecificError->canBeIgnored()) {
$error['severity'] = 'blocker';
}
$errorsArray[] = $error;
}
foreach ($analysisResult->getNotFileSpecificErrors() as $notFileSpecificError) {
$errorsArray[] = [
'description' => $notFileSpecificError,
'fingerprint' => hash('sha256', $notFileSpecificError),
'location' => [
'path' => '',
'lines' => [
'begin' => 0,
],
],
];
}
$json = json_encode($errorsArray, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$output->writeRaw((string) $json);
return $analysisResult->hasErrors() ? 1 : 0;
}
}
<?php
declare(strict_types = 1);
namespace CodeLts\CliTools;
use OndraM\CiDetector\CiDetector;
/**
* Some utilities
*/
class Utils
{
public static function isCiDetected(): bool
{
$ciDetector = new CiDetector();
return $ciDetector->isCiDetected();
}
}
<?php
declare(strict_types = 1);
namespace CodeLts\CliTools\Exceptions;
use CodeLts\CliTools\OutputFormat;
class FormatNotFoundException extends \Exception
{
public function __construct(string $formatName)
{
parent::__construct(
sprintf(
'Error formatter "%s" is not implemented. Available error formatters are: %s',
$formatName,
implode(', ', OutputFormat::VALID_OUTPUT_FORMATS)
)
);
}
public function getTip(): ?string
{
return null;
}
}
<?php
declare(strict_types = 1);
/*
* (c) Copyright (c) 2016-2020 Ondřej Mirtes <ondrej@mirtes.cz>
*
* This source file is subject to the MIT license.
*
* 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.
*/
namespace CodeLts\CliTools;
interface Output
{
public function writeFormatted(string $message): void;
public function writeLineFormatted(string $message): void;
public function writeRaw(string $message): void;
public function getStyle(): OutputStyle;
public function isVerbose(): bool;
public function isDebug(): bool;
public function isDecorated(): bool;
}
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
<?php
namespace Psr\Container;
/**
* No entry was found in the container.
*/
interface NotFoundExceptionInterface extends ContainerExceptionInterface
{
}
<?php
namespace Psr\Container;
/**
* Base interface representing a generic exception in a container.
*/
interface ContainerExceptionInterface
{
}
<?php
declare(strict_types=1);
namespace Psr\Container;
/**
* Describes the interface of a container that exposes methods to read its entries.
*/
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get(string $id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has(string $id);
}
The MIT License (MIT)
Copyright (c) 2013-2016 container-interop
Copyright (c) 2016 PHP Framework Interoperability Group
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.
<?php declare(strict_types=1);
namespace OndraM\CiDetector;
use OndraM\CiDetector\Ci\CiInterface;
use OndraM\CiDetector\Exception\CiNotDetectedException;
/**
* Unified way to get environment variables from current continuous integration server
*/
interface CiDetectorInterface
{
/**
* Is current environment an recognized CI server?
*/
public function isCiDetected(): bool;
/**
* Detect current CI server and return instance of its settings
*
* @throws CiNotDetectedException
*/
public function detect(): CiInterface;
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector;
/**
* @see https://en.wikipedia.org/wiki/Three-valued_logic
* @see https://github.com/phpstan/phpstan-src/blob/cedc99f51f7b8d815036e983166d7cb51ab46734/src/TrinaryLogic.php
*
* @internal
*/
final class TrinaryLogic
{
private const YES = 1;
private const MAYBE = 0;
private const NO = -1;
/** @var self[] */
private static $registry = [];
/** @var int */
private $value;
private function __construct(int $value)
{
$this->value = $value;
}
public static function createMaybe(): self
{
return self::create(self::MAYBE);
}
public static function createFromBoolean(bool $value): self
{
return self::create($value ? self::YES : self::NO);
}
private static function create(int $value): self
{
return self::$registry[$value] = self::$registry[$value] ?? new self($value);
}
/**
* Return true if its known for sure that the value is true
*/
public function yes(): bool
{
return $this->value === self::YES;
}
/**
* Return true if its not known for sure whether the value is true or false
*/
public function maybe(): bool
{
return $this->value === self::MAYBE;
}
/**
* Return true if its known for sure that the value is false
*/
public function no(): bool
{
return $this->value === self::NO;
}
/**
* Return string representation of the value.
* "Yes" when the value is true, "No" when its false, "Maybe" when its not known for sure whether its true or false.
*/
public function describe(): string
{
static $labels = [
self::NO => 'No',
self::MAYBE => 'Maybe',
self::YES => 'Yes',
];
return $labels[$this->value];
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector;
/**
* Encapsulate access to the environment variables
*/
class Env
{
/**
* @return string|false Environment variable value or false if the variable does not exist
*/
public function get(string $name)
{
return getenv($name);
}
public function getString(string $name): string
{
return (string) $this->get($name);
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Exception;
class CiNotDetectedException extends \RuntimeException
{
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Jenkins extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('JENKINS_URL') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_JENKINS;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}
public function getBuildNumber(): string
{
return $this->env->getString('BUILD_NUMBER');
}
public function getBuildUrl(): string
{
return $this->env->getString('BUILD_URL');
}
public function getCommit(): string
{
return $this->env->getString('GIT_COMMIT');
}
public function getBranch(): string
{
return $this->env->getString('GIT_BRANCH');
}
public function getTargetBranch(): string
{
return ''; // unsupported
}
public function getRepositoryName(): string
{
return ''; // unsupported
}
public function getRepositoryUrl(): string
{
return $this->env->getString('GIT_URL');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Bamboo extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('bamboo_buildKey') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_BAMBOO;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->get('bamboo_repository_pr_key') !== false);
}
public function getBuildNumber(): string
{
return $this->env->getString('bamboo_buildNumber');
}
public function getBuildUrl(): string
{
return $this->env->getString('bamboo_resultsUrl');
}
public function getCommit(): string
{
return $this->env->getString('bamboo_planRepository_revision');
}
public function getBranch(): string
{
$prBranch = $this->env->getString('bamboo_repository_pr_sourceBranch');
if ($this->isPullRequest()->no() || empty($prBranch)) {
return $this->env->getString('bamboo_planRepository_branch');
}
return $prBranch;
}
public function getTargetBranch(): string
{
return $this->env->getString('bamboo_repository_pr_targetBranch');
}
public function getRepositoryName(): string
{
return $this->env->getString('bamboo_planRepository_name');
}
public function getRepositoryUrl(): string
{
return $this->env->getString('bamboo_planRepository_repositoryUrl');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class AppVeyor extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('APPVEYOR') === 'True';
}
public function getCiName(): string
{
return CiDetector::CI_APPVEYOR;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('APPVEYOR_PULL_REQUEST_NUMBER') !== '');
}
public function getBuildNumber(): string
{
return $this->env->getString('APPVEYOR_BUILD_NUMBER');
}
public function getBuildUrl(): string
{
return sprintf(
'%s/project/%s/%s/builds/%s',
$this->env->get('APPVEYOR_URL'),
$this->env->get('APPVEYOR_ACCOUNT_NAME'),
$this->env->get('APPVEYOR_PROJECT_SLUG'),
$this->env->get('APPVEYOR_BUILD_ID')
);
}
public function getCommit(): string
{
return $this->env->getString('APPVEYOR_REPO_COMMIT');
}
public function getBranch(): string
{
$prBranch = $this->env->getString('APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH');
if ($this->isPullRequest()->no() || empty($prBranch)) {
return $this->env->getString('APPVEYOR_REPO_BRANCH');
}
return $prBranch;
}
public function getTargetBranch(): string
{
if ($this->isPullRequest()->no()) {
return '';
}
return $this->env->getString('APPVEYOR_REPO_BRANCH');
}
public function getRepositoryName(): string
{
return $this->env->getString('APPVEYOR_REPO_NAME');
}
public function getRepositoryUrl(): string
{
return ''; // unsupported
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Codeship extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('CI_NAME') === 'codeship';
}
public function getCiName(): string
{
return CiDetector::CI_CODESHIP;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('CI_PULL_REQUEST') !== 'false');
}
public function getBuildNumber(): string
{
return $this->env->getString('CI_BUILD_NUMBER');
}
public function getBuildUrl(): string
{
return $this->env->getString('CI_BUILD_URL');
}
public function getCommit(): string
{
return $this->env->getString('COMMIT_ID');
}
public function getBranch(): string
{
return $this->env->getString('CI_BRANCH');
}
public function getTargetBranch(): string
{
return ''; // unsupported
}
public function getRepositoryName(): string
{
return $this->env->getString('CI_REPO_NAME');
}
public function getRepositoryUrl(): string
{
return ''; // unsupported
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class AwsCodeBuild extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('CODEBUILD_CI') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_AWS_CODEBUILD;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean(
mb_strpos($this->env->getString('CODEBUILD_WEBHOOK_EVENT'), 'PULL_REQUEST') === 0
);
}
public function getBuildNumber(): string
{
return $this->env->getString('CODEBUILD_BUILD_NUMBER');
}
public function getBuildUrl(): string
{
return $this->env->getString('CODEBUILD_BUILD_URL');
}
public function getCommit(): string
{
return $this->env->getString('CODEBUILD_RESOLVED_SOURCE_VERSION');
}
public function getBranch(): string
{
$gitReference = $this->env->getString('CODEBUILD_WEBHOOK_HEAD_REF');
return preg_replace('~^refs/heads/~', '', $gitReference) ?? '';
}
public function getTargetBranch(): string
{
return ''; // unsupported
}
public function getRepositoryName(): string
{
return ''; // unsupported
}
public function getRepositoryUrl(): string
{
return $this->env->getString('CODEBUILD_SOURCE_REPO_URL');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class BitbucketPipelines extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('BITBUCKET_COMMIT') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_BITBUCKET_PIPELINES;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('BITBUCKET_PR_ID') !== '');
}
public function getBuildNumber(): string
{
return $this->env->getString('BITBUCKET_BUILD_NUMBER');
}
public function getBuildUrl(): string
{
return sprintf(
'%s/addon/pipelines/home#!/results/%s',
$this->env->get('BITBUCKET_GIT_HTTP_ORIGIN'),
$this->env->get('BITBUCKET_BUILD_NUMBER')
);
}
public function getCommit(): string
{
return $this->env->getString('BITBUCKET_COMMIT');
}
public function getBranch(): string
{
return $this->env->getString('BITBUCKET_BRANCH');
}
public function getTargetBranch(): string
{
return $this->env->getString('BITBUCKET_PR_DESTINATION_BRANCH');
}
public function getRepositoryName(): string
{
return $this->env->getString('BITBUCKET_REPO_FULL_NAME');
}
public function getRepositoryUrl(): string
{
return sprintf(
'ssh://%s',
$this->env->get('BITBUCKET_GIT_SSH_ORIGIN')
);
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class AzurePipelines extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('BUILD_DEFINITIONVERSION') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_AZURE_PIPELINES;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('BUILD_REASON') === 'PullRequest');
}
public function getBuildNumber(): string
{
return $this->env->getString('BUILD_BUILDNUMBER');
}
public function getBuildUrl(): string
{
return sprintf(
'%s%s/_build/results?buildId=%s',
$this->env->get('SYSTEM_COLLECTIONURI'),
$this->env->get('SYSTEM_TEAMPROJECT'),
$this->env->get('BUILD_BUILDID')
);
}
public function getCommit(): string
{
return $this->env->getString('BUILD_SOURCEVERSION');
}
public function getBranch(): string
{
if ($this->isPullRequest()->no()) {
return $this->env->getString('BUILD_SOURCEBRANCHNAME');
}
return $this->env->getString('SYSTEM_PULLREQUEST_SOURCEBRANCH');
}
public function getTargetBranch(): string
{
return $this->env->getString('SYSTEM_PULLREQUEST_TARGETBRANCH');
}
public function getRepositoryName(): string
{
return $this->env->getString('BUILD_REPOSITORY_NAME');
}
public function getRepositoryUrl(): string
{
return $this->env->getString('BUILD_REPOSITORY_URI');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class SourceHut extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->getString('CI_NAME') === 'sourcehut';
}
public function getCiName(): string
{
return CiDetector::CI_SOURCEHUT;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('BUILD_REASON') === 'patchset');
}
public function getBuildNumber(): string
{
return $this->env->getString('JOB_ID');
}
public function getBuildUrl(): string
{
return $this->env->getString('JOB_URL');
}
public function getCommit(): string
{
return ''; // unsupported
}
public function getBranch(): string
{
return ''; // unsupported
}
public function getTargetBranch(): string
{
return ''; // unsupported
}
public function getRepositoryName(): string
{
return ''; // unsupported
}
public function getRepositoryUrl(): string
{
return ''; // unsupported
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Wercker extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('WERCKER') === 'true';
}
public function getCiName(): string
{
return CiDetector::CI_WERCKER;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}
public function getBuildNumber(): string
{
return $this->env->getString('WERCKER_RUN_ID');
}
public function getBuildUrl(): string
{
return $this->env->getString('WERCKER_RUN_URL');
}
public function getCommit(): string
{
return $this->env->getString('WERCKER_GIT_COMMIT');
}
public function getBranch(): string
{
return $this->env->getString('WERCKER_GIT_BRANCH');
}
public function getTargetBranch(): string
{
return ''; // unsupported
}
public function getRepositoryName(): string
{
return $this->env->getString('WERCKER_GIT_OWNER') . '/' . $this->env->getString('WERCKER_GIT_REPOSITORY');
}
public function getRepositoryUrl(): string
{
return ''; // unsupported
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\Env;
/**
* Unified adapter to retrieve environment variables from current continuous integration server
*/
abstract class AbstractCi implements CiInterface
{
/** @var Env */
protected $env;
public function __construct(Env $env)
{
$this->env = $env;
}
public function describe(): array
{
return [
'ci-name' => $this->getCiName(),
'build-number' => $this->getBuildNumber(),
'build-url' => $this->getBuildUrl(),
'commit' => $this->getCommit(),
'branch' => $this->getBranch(),
'target-branch' => $this->getTargetBranch(),
'repository-name' => $this->getRepositoryName(),
'repository-url' => $this->getRepositoryUrl(),
'is-pull-request' => $this->isPullRequest()->describe(),
];
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class TeamCity extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('TEAMCITY_VERSION') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_TEAMCITY;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createMaybe();
}
public function getBuildNumber(): string
{
return $this->env->getString('BUILD_NUMBER');
}
public function getBuildUrl(): string
{
return ''; // unsupported
}
public function getCommit(): string
{
return $this->env->getString('BUILD_VCS_NUMBER');
}
public function getBranch(): string
{
return ''; // unsupported
}
public function getTargetBranch(): string
{
return ''; // unsupported
}
public function getRepositoryName(): string
{
return ''; // unsupported
}
public function getRepositoryUrl(): string
{
return ''; // unsupported
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Drone extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('CI') === 'drone';
}
public function getCiName(): string
{
return CiDetector::CI_DRONE;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('DRONE_PULL_REQUEST') !== '');
}
public function getBuildNumber(): string
{
return $this->env->getString('DRONE_BUILD_NUMBER');
}
public function getBuildUrl(): string
{
return $this->env->getString('DRONE_BUILD_LINK');
}
public function getCommit(): string
{
return $this->env->getString('DRONE_COMMIT_SHA');
}
public function getBranch(): string
{
return $this->env->getString('DRONE_COMMIT_BRANCH');
}
public function getTargetBranch(): string
{
return $this->env->getString('DRONE_TARGET_BRANCH');
}
public function getRepositoryName(): string
{
return $this->env->getString('DRONE_REPO');
}
public function getRepositoryUrl(): string
{
return $this->env->getString('DRONE_REPO_LINK');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class GitLab extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('GITLAB_CI') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_GITLAB;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean(
$this->env->get('CI_MERGE_REQUEST_ID') !== false
|| $this->env->get('CI_EXTERNAL_PULL_REQUEST_IID') !== false
);
}
public function getBuildNumber(): string
{
return !empty($this->env->getString('CI_JOB_ID'))
? $this->env->getString('CI_JOB_ID')
: $this->env->getString('CI_BUILD_ID');
}
public function getBuildUrl(): string
{
return $this->env->getString('CI_PROJECT_URL') . '/builds/' . $this->getBuildNumber();
}
public function getCommit(): string
{
return !empty($this->env->getString('CI_COMMIT_SHA'))
? $this->env->getString('CI_COMMIT_SHA')
: $this->env->getString('CI_BUILD_REF');
}
public function getBranch(): string
{
return !empty($this->env->getString('CI_COMMIT_REF_NAME'))
? $this->env->getString('CI_COMMIT_REF_NAME')
: $this->env->getString('CI_BUILD_REF_NAME');
}
public function getTargetBranch(): string
{
return !empty($this->env->getString('CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME'))
? $this->env->getString('CI_EXTERNAL_PULL_REQUEST_TARGET_BRANCH_NAME')
: $this->env->getString('CI_MERGE_REQUEST_TARGET_BRANCH_NAME');
}
public function getRepositoryName(): string
{
return $this->env->getString('CI_PROJECT_PATH');
}
public function getRepositoryUrl(): string
{
return !empty($this->env->getString('CI_REPOSITORY_URL'))
? $this->env->getString('CI_REPOSITORY_URL')
: $this->env->getString('CI_BUILD_REPO');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Circle extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('CIRCLECI') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_CIRCLE;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('CI_PULL_REQUEST') !== '');
}
public function getBuildNumber(): string
{
return $this->env->getString('CIRCLE_BUILD_NUM');
}
public function getBuildUrl(): string
{
return $this->env->getString('CIRCLE_BUILD_URL');
}
public function getCommit(): string
{
return $this->env->getString('CIRCLE_SHA1');
}
public function getBranch(): string
{
return $this->env->getString('CIRCLE_BRANCH');
}
public function getTargetBranch(): string
{
return ''; // unsupported
}
public function getRepositoryName(): string
{
return sprintf(
'%s/%s',
$this->env->getString('CIRCLE_PROJECT_USERNAME'),
$this->env->getString('CIRCLE_PROJECT_REPONAME')
);
}
public function getRepositoryUrl(): string
{
return $this->env->getString('CIRCLE_REPOSITORY_URL');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Buddy extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('BUDDY') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_BUDDY;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('BUDDY_EXECUTION_PULL_REQUEST_ID') !== '');
}
public function getBuildNumber(): string
{
return $this->env->getString('BUDDY_EXECUTION_ID');
}
public function getBuildUrl(): string
{
return $this->env->getString('BUDDY_EXECUTION_URL');
}
public function getCommit(): string
{
return $this->env->getString('BUDDY_EXECUTION_REVISION');
}
public function getBranch(): string
{
$prBranch = $this->env->getString('BUDDY_EXECUTION_PULL_REQUEST_HEAD_BRANCH');
if ($this->isPullRequest()->no() || empty($prBranch)) {
return $this->env->getString('BUDDY_EXECUTION_BRANCH');
}
return $prBranch;
}
public function getTargetBranch(): string
{
return $this->env->getString('BUDDY_EXECUTION_PULL_REQUEST_BASE_BRANCH');
}
public function getRepositoryName(): string
{
return $this->env->getString('BUDDY_REPO_SLUG');
}
public function getRepositoryUrl(): string
{
return $this->env->getString('BUDDY_SCM_URL');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Travis extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('TRAVIS') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_TRAVIS;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('TRAVIS_PULL_REQUEST') !== 'false');
}
public function getBuildNumber(): string
{
return $this->env->getString('TRAVIS_JOB_NUMBER');
}
public function getBuildUrl(): string
{
return $this->env->getString('TRAVIS_JOB_WEB_URL');
}
public function getCommit(): string
{
return $this->env->getString('TRAVIS_COMMIT');
}
public function getBranch(): string
{
if ($this->isPullRequest()->no()) {
return $this->env->getString('TRAVIS_BRANCH');
}
// If the build is for PR, return name of the branch with the PR, not the target PR branch
// https://github.com/travis-ci/travis-ci/issues/6652
return $this->env->getString('TRAVIS_PULL_REQUEST_BRANCH');
}
public function getTargetBranch(): string
{
if ($this->isPullRequest()->no()) {
return '';
}
return $this->env->getString('TRAVIS_BRANCH');
}
public function getRepositoryName(): string
{
return $this->env->getString('TRAVIS_REPO_SLUG');
}
public function getRepositoryUrl(): string
{
return ''; // unsupported
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
interface CiInterface
{
/**
* Return true if this CI was detected.
*/
public static function isDetected(Env $env): bool;
/**
* Get name of the CI server type.
*/
public function getCiName(): string;
/**
* Return key-value map of all detected properties in human-readable form.
*
* @return array<string, string>
*/
public function describe(): array;
/**
* Get number of this concrete build.
*
* Build number is usually human-readable increasing number sequence. It should increase each time this particular
* job was run on the CI server. Most CIs use simple numbering sequence like: 1, 2, 3...
* However, some CIs do not provide this simple human-readable value and rather use for example alphanumeric hash.
*/
public function getBuildNumber(): string;
/**
* Get URL where this build can be found and viewed.
*/
public function getBuildUrl(): string;
/**
* Get hash of the git (or other VCS) commit being built.
*/
public function getCommit(): string;
/**
* Get name of the git (or other VCS) branch which is being built.
*
* Use `getTargetBranch()` to get name of the branch where this branch is targeted.
*/
public function getBranch(): string;
/**
* Get name of the target branch of a pull request.
*/
public function getTargetBranch(): string;
/**
* Get name of the git (or other VCS) repository which is being built.
*
* This is usually in form "user/repository", for example "OndraM/ci-detector".
*/
public function getRepositoryName(): string;
/**
* Get URL where the repository which is being built can be found.
*
* This is either HTTP URL like "https://github.com/OndraM/ci-detector"
* but may be a git ssh url like "ssh://git@bitbucket.org/OndraM/ci-detector".
*/
public function getRepositoryUrl(): string;
/**
* Returned TrinaryLogic object's value will be true if the current build is from a pull/merge request,
* false if it not, and maybe if we can't determine it.
*/
public function isPullRequest(): TrinaryLogic;
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class Continuousphp extends AbstractCi
{
public static function isDetected(Env $env): bool
{
return $env->get('CONTINUOUSPHP') === 'continuousphp';
}
public function getCiName(): string
{
return CiDetector::CI_CONTINUOUSPHP;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('CPHP_PR_ID') !== '');
}
public function getBuildNumber(): string
{
return $this->env->getString('CPHP_BUILD_ID');
}
public function getBuildUrl(): string
{
return $this->env->getString('');
}
public function getCommit(): string
{
return $this->env->getString('CPHP_GIT_COMMIT');
}
public function getBranch(): string
{
$gitReference = $this->env->getString('CPHP_GIT_REF');
return preg_replace('~^refs/heads/~', '', $gitReference) ?? '';
}
public function getTargetBranch(): string
{
return ''; // unsupported
}
public function getRepositoryName(): string
{
return ''; // unsupported
}
public function getRepositoryUrl(): string
{
return $this->env->getString('');
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector\Ci;
use OndraM\CiDetector\CiDetector;
use OndraM\CiDetector\Env;
use OndraM\CiDetector\TrinaryLogic;
class GitHubActions extends AbstractCi
{
public const GITHUB_BASE_URL = 'https://github.com';
public static function isDetected(Env $env): bool
{
return $env->get('GITHUB_ACTIONS') !== false;
}
public function getCiName(): string
{
return CiDetector::CI_GITHUB_ACTIONS;
}
public function isPullRequest(): TrinaryLogic
{
return TrinaryLogic::createFromBoolean($this->env->getString('GITHUB_EVENT_NAME') === 'pull_request');
}
public function getBuildNumber(): string
{
return $this->env->getString('GITHUB_RUN_NUMBER');
}
public function getBuildUrl(): string
{
return sprintf(
'%s/%s/commit/%s/checks',
self::GITHUB_BASE_URL,
$this->env->get('GITHUB_REPOSITORY'),
$this->env->get('GITHUB_SHA')
);
}
public function getCommit(): string
{
return $this->env->getString('GITHUB_SHA');
}
public function getBranch(): string
{
$prBranch = $this->env->getString('GITHUB_HEAD_REF');
if ($this->isPullRequest()->no() || empty($prBranch)) {
$gitReference = $this->env->getString('GITHUB_REF');
return preg_replace('~^refs/heads/~', '', $gitReference) ?? '';
}
return $prBranch;
}
public function getTargetBranch(): string
{
return $this->env->getString('GITHUB_BASE_REF');
}
public function getRepositoryName(): string
{
return $this->env->getString('GITHUB_REPOSITORY');
}
public function getRepositoryUrl(): string
{
return sprintf(
'%s/%s',
self::GITHUB_BASE_URL,
$this->env->get('GITHUB_REPOSITORY')
);
}
}
<?php declare(strict_types=1);
namespace OndraM\CiDetector;
use OndraM\CiDetector\Ci\CiInterface;
use OndraM\CiDetector\Exception\CiNotDetectedException;
/**
* Unified way to get environment variables from current continuous integration server
*/
class CiDetector implements CiDetectorInterface
{
public const CI_APPVEYOR = 'AppVeyor';
public const CI_AWS_CODEBUILD = 'AWS CodeBuild';
public const CI_AZURE_PIPELINES = 'Azure Pipelines';
public const CI_BAMBOO = 'Bamboo';
public const CI_BITBUCKET_PIPELINES = 'Bitbucket Pipelines';
public const CI_BUDDY = 'Buddy';
public const CI_CIRCLE = 'CircleCI';
public const CI_CODESHIP = 'Codeship';
public const CI_CONTINUOUSPHP = 'continuousphp';
public const CI_DRONE = 'drone';
public const CI_GITHUB_ACTIONS = 'GitHub Actions';
public const CI_GITLAB = 'GitLab';
public const CI_JENKINS = 'Jenkins';
public const CI_SOURCEHUT = 'SourceHut';
public const CI_TEAMCITY = 'TeamCity';
public const CI_TRAVIS = 'Travis CI';
public const CI_WERCKER = 'Wercker';
/** @var Env */
private $environment;
final public function __construct()
{
$this->environment = new Env();
}
public static function fromEnvironment(Env $environment): self
{
$detector = new static();
$detector->environment = $environment;
return $detector;
}
public function isCiDetected(): bool
{
$ciServer = $this->detectCurrentCiServer();
return ($ciServer !== null);
}
public function detect(): CiInterface
{
$ciServer = $this->detectCurrentCiServer();
if ($ciServer === null) {
throw new CiNotDetectedException('No CI server detected in current environment');
}
return $ciServer;
}
/**
* @return string[]
*/
protected function getCiServers(): array
{
return [
Ci\AppVeyor::class,
Ci\AwsCodeBuild::class,
Ci\AzurePipelines::class,
Ci\Bamboo::class,
Ci\BitbucketPipelines::class,
Ci\Buddy::class,
Ci\Circle::class,
Ci\Codeship::class,
Ci\Continuousphp::class,
Ci\Drone::class,
Ci\GitHubActions::class,
Ci\GitLab::class,
Ci\Jenkins::class,
Ci\SourceHut::class,
Ci\TeamCity::class,
Ci\Travis::class,
Ci\Wercker::class,
];
}
protected function detectCurrentCiServer(): ?CiInterface
{
$ciServers = $this->getCiServers();
foreach ($ciServers as $ciClass) {
$callback = [$ciClass, 'isDetected'];
if (is_callable($callback)) {
if ($callback($this->environment)) {
return new $ciClass($this->environment);
}
}
}
return null;
}
}
<?php
#
#
# Parsedown
# http://parsedown.org
#
# (c) Emanuil Rusev
# http://erusev.com
#
# For the full license information, view the LICENSE file that was distributed
# with this source code.
#
#
class Parsedown
{
# ~
const version = '1.7.4';
# ~
function text($text)
{
# make sure no definitions are set
$this->DefinitionData = array();
# standardize line breaks
$text = str_replace(array("\r\n", "\r"), "\n", $text);
# remove surrounding line breaks
$text = trim($text, "\n");
# split text into lines
$lines = explode("\n", $text);
# iterate through lines to identify blocks
$markup = $this->lines($lines);
# trim line breaks
$markup = trim($markup, "\n");
return $markup;
}
#
# Setters
#
function setBreaksEnabled($breaksEnabled)
{
$this->breaksEnabled = $breaksEnabled;
return $this;
}
protected $breaksEnabled;
function setMarkupEscaped($markupEscaped)
{
$this->markupEscaped = $markupEscaped;
return $this;
}
protected $markupEscaped;
function setUrlsLinked($urlsLinked)
{
$this->urlsLinked = $urlsLinked;
return $this;
}
protected $urlsLinked = true;
function setSafeMode($safeMode)
{
$this->safeMode = (bool) $safeMode;
return $this;
}
protected $safeMode;
protected $safeLinksWhitelist = array(
'http://',
'https://',
'ftp://',
'ftps://',
'mailto:',
'data:image/png;base64,',
'data:image/gif;base64,',
'data:image/jpeg;base64,',
'irc:',
'ircs:',
'git:',
'ssh:',
'news:',
'steam:',
);
#
# Lines
#
protected $BlockTypes = array(
'#' => array('Header'),
'*' => array('Rule', 'List'),
'+' => array('List'),
'-' => array('SetextHeader', 'Table', 'Rule', 'List'),
'0' => array('List'),
'1' => array('List'),
'2' => array('List'),
'3' => array('List'),
'4' => array('List'),
'5' => array('List'),
'6' => array('List'),
'7' => array('List'),
'8' => array('List'),
'9' => array('List'),
':' => array('Table'),
'<' => array('Comment', 'Markup'),
'=' => array('SetextHeader'),
'>' => array('Quote'),
'[' => array('Reference'),
'_' => array('Rule'),
'`' => array('FencedCode'),
'|' => array('Table'),
'~' => array('FencedCode'),
);
# ~
protected $unmarkedBlockTypes = array(
'Code',
);
#
# Blocks
#
protected function lines(array $lines)
{
$CurrentBlock = null;
foreach ($lines as $line)
{
if (chop($line) === '')
{
if (isset($CurrentBlock))
{
$CurrentBlock['interrupted'] = true;
}
continue;
}
if (strpos($line, "\t") !== false)
{
$parts = explode("\t", $line);
$line = $parts[0];
unset($parts[0]);
foreach ($parts as $part)
{
$shortage = 4 - mb_strlen($line, 'utf-8') % 4;
$line .= str_repeat(' ', $shortage);
$line .= $part;
}
}
$indent = 0;
while (isset($line[$indent]) and $line[$indent] === ' ')
{
$indent ++;
}
$text = $indent > 0 ? substr($line, $indent) : $line;
# ~
$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
# ~
if (isset($CurrentBlock['continuable']))
{
$Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
if (isset($Block))
{
$CurrentBlock = $Block;
continue;
}
else
{
if ($this->isBlockCompletable($CurrentBlock['type']))
{
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
}
}
}
# ~
$marker = $text[0];
# ~
$blockTypes = $this->unmarkedBlockTypes;
if (isset($this->BlockTypes[$marker]))
{
foreach ($this->BlockTypes[$marker] as $blockType)
{
$blockTypes []= $blockType;
}
}
#
# ~
foreach ($blockTypes as $blockType)
{
$Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
if (isset($Block))
{
$Block['type'] = $blockType;
if ( ! isset($Block['identified']))
{
$Blocks []= $CurrentBlock;
$Block['identified'] = true;
}
if ($this->isBlockContinuable($blockType))
{
$Block['continuable'] = true;
}
$CurrentBlock = $Block;
continue 2;
}
}
# ~
if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
{
$CurrentBlock['element']['text'] .= "\n".$text;
}
else
{
$Blocks []= $CurrentBlock;
$CurrentBlock = $this->paragraph($Line);
$CurrentBlock['identified'] = true;
}
}
# ~
if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
{
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
}
# ~
$Blocks []= $CurrentBlock;
unset($Blocks[0]);
# ~
$markup = '';
foreach ($Blocks as $Block)
{
if (isset($Block['hidden']))
{
continue;
}
$markup .= "\n";
$markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
}
$markup .= "\n";
# ~
return $markup;
}
protected function isBlockContinuable($Type)
{
return method_exists($this, 'block'.$Type.'Continue');
}
protected function isBlockCompletable($Type)
{
return method_exists($this, 'block'.$Type.'Complete');
}
#
# Code
protected function blockCode($Line, $Block = null)
{
if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
{
return;
}
if ($Line['indent'] >= 4)
{
$text = substr($Line['body'], 4);
$Block = array(
'element' => array(
'name' => 'pre',
'handler' => 'element',
'text' => array(
'name' => 'code',
'text' => $text,
),
),
);
return $Block;
}
}
protected function blockCodeContinue($Line, $Block)
{
if ($Line['indent'] >= 4)
{
if (isset($Block['interrupted']))
{
$Block['element']['text']['text'] .= "\n";
unset($Block['interrupted']);
}
$Block['element']['text']['text'] .= "\n";
$text = substr($Line['body'], 4);
$Block['element']['text']['text'] .= $text;
return $Block;
}
}
protected function blockCodeComplete($Block)
{
$text = $Block['element']['text']['text'];
$Block['element']['text']['text'] = $text;
return $Block;
}
#
# Comment
protected function blockComment($Line)
{
if ($this->markupEscaped or $this->safeMode)
{
return;
}
if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
{
$Block = array(
'markup' => $Line['body'],
);
if (preg_match('/-->$/', $Line['text']))
{
$Block['closed'] = true;
}
return $Block;
}
}
protected function blockCommentContinue($Line, array $Block)
{
if (isset($Block['closed']))
{
return;
}
$Block['markup'] .= "\n" . $Line['body'];
if (preg_match('/-->$/', $Line['text']))
{
$Block['closed'] = true;
}
return $Block;
}
#
# Fenced Code
protected function blockFencedCode($Line)
{
if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
{
$Element = array(
'name' => 'code',
'text' => '',
);
if (isset($matches[1]))
{
/**
* https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
* Every HTML element may have a class attribute specified.
* The attribute, if specified, must have a value that is a set
* of space-separated tokens representing the various classes
* that the element belongs to.
* [...]
* The space characters, for the purposes of this specification,
* are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
* U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
* U+000D CARRIAGE RETURN (CR).
*/
$language = substr($matches[1], 0, strcspn($matches[1], " \t\n\f\r"));
$class = 'language-'.$language;
$Element['attributes'] = array(
'class' => $class,
);
}
$Block = array(
'char' => $Line['text'][0],
'element' => array(
'name' => 'pre',
'handler' => 'element',
'text' => $Element,
),
);
return $Block;
}
}
protected function blockFencedCodeContinue($Line, $Block)
{
if (isset($Block['complete']))
{
return;
}
if (isset($Block['interrupted']))
{
$Block['element']['text']['text'] .= "\n";
unset($Block['interrupted']);
}
if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
{
$Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
$Block['complete'] = true;
return $Block;
}
$Block['element']['text']['text'] .= "\n".$Line['body'];
return $Block;
}
protected function blockFencedCodeComplete($Block)
{
$text = $Block['element']['text']['text'];
$Block['element']['text']['text'] = $text;
return $Block;
}
#
# Header
protected function blockHeader($Line)
{
if (isset($Line['text'][1]))
{
$level = 1;
while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
{
$level ++;
}
if ($level > 6)
{
return;
}
$text = trim($Line['text'], '# ');
$Block = array(
'element' => array(
'name' => 'h' . min(6, $level),
'text' => $text,
'handler' => 'line',
),
);
return $Block;
}
}
#
# List
protected function blockList($Line)
{
list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
{
$Block = array(
'indent' => $Line['indent'],
'pattern' => $pattern,
'element' => array(
'name' => $name,
'handler' => 'elements',
),
);
if($name === 'ol')
{
$listStart = stristr($matches[0], '.', true);
if($listStart !== '1')
{
$Block['element']['attributes'] = array('start' => $listStart);
}
}
$Block['li'] = array(
'name' => 'li',
'handler' => 'li',
'text' => array(
$matches[2],
),
);
$Block['element']['text'] []= & $Block['li'];
return $Block;
}
}
protected function blockListContinue($Line, array $Block)
{
if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
{
if (isset($Block['interrupted']))
{
$Block['li']['text'] []= '';
$Block['loose'] = true;
unset($Block['interrupted']);
}
unset($Block['li']);
$text = isset($matches[1]) ? $matches[1] : '';
$Block['li'] = array(
'name' => 'li',
'handler' => 'li',
'text' => array(
$text,
),
);
$Block['element']['text'] []= & $Block['li'];
return $Block;
}
if ($Line['text'][0] === '[' and $this->blockReference($Line))
{
return $Block;
}
if ( ! isset($Block['interrupted']))
{
$text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
$Block['li']['text'] []= $text;
return $Block;
}
if ($Line['indent'] > 0)
{
$Block['li']['text'] []= '';
$text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
$Block['li']['text'] []= $text;
unset($Block['interrupted']);
return $Block;
}
}
protected function blockListComplete(array $Block)
{
if (isset($Block['loose']))
{
foreach ($Block['element']['text'] as &$li)
{
if (end($li['text']) !== '')
{
$li['text'] []= '';
}
}
}
return $Block;
}
#
# Quote
protected function blockQuote($Line)
{
if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
{
$Block = array(
'element' => array(
'name' => 'blockquote',
'handler' => 'lines',
'text' => (array) $matches[1],
),
);
return $Block;
}
}
protected function blockQuoteContinue($Line, array $Block)
{
if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
{
if (isset($Block['interrupted']))
{
$Block['element']['text'] []= '';
unset($Block['interrupted']);
}
$Block['element']['text'] []= $matches[1];
return $Block;
}
if ( ! isset($Block['interrupted']))
{
$Block['element']['text'] []= $Line['text'];
return $Block;
}
}
#
# Rule
protected function blockRule($Line)
{
if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
{
$Block = array(
'element' => array(
'name' => 'hr'
),
);
return $Block;
}
}
#
# Setext
protected function blockSetextHeader($Line, array $Block = null)
{
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
{
return;
}
if (chop($Line['text'], $Line['text'][0]) === '')
{
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
return $Block;
}
}
#
# Markup
protected function blockMarkup($Line)
{
if ($this->markupEscaped or $this->safeMode)
{
return;
}
if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{
$element = strtolower($matches[1]);
if (in_array($element, $this->textLevelElements))
{
return;
}
$Block = array(
'name' => $matches[1],
'depth' => 0,
'markup' => $Line['text'],
);
$length = strlen($matches[0]);
$remainder = substr($Line['text'], $length);
if (trim($remainder) === '')
{
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
{
$Block['closed'] = true;
$Block['void'] = true;
}
}
else
{
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
{
return;
}
if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
{
$Block['closed'] = true;
}
}
return $Block;
}
}
protected function blockMarkupContinue($Line, array $Block)
{
if (isset($Block['closed']))
{
return;
}
if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
{
$Block['depth'] ++;
}
if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
{
if ($Block['depth'] > 0)
{
$Block['depth'] --;
}
else
{
$Block['closed'] = true;
}
}
if (isset($Block['interrupted']))
{
$Block['markup'] .= "\n";
unset($Block['interrupted']);
}
$Block['markup'] .= "\n".$Line['body'];
return $Block;
}
#
# Reference
protected function blockReference($Line)
{
if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
{
$id = strtolower($matches[1]);
$Data = array(
'url' => $matches[2],
'title' => null,
);
if (isset($matches[3]))
{
$Data['title'] = $matches[3];
}
$this->DefinitionData['Reference'][$id] = $Data;
$Block = array(
'hidden' => true,
);
return $Block;
}
}
#
# Table
protected function blockTable($Line, array $Block = null)
{
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
{
return;
}
if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
{
$alignments = array();
$divider = $Line['text'];
$divider = trim($divider);
$divider = trim($divider, '|');
$dividerCells = explode('|', $divider);
foreach ($dividerCells as $dividerCell)
{
$dividerCell = trim($dividerCell);
if ($dividerCell === '')
{
continue;
}
$alignment = null;
if ($dividerCell[0] === ':')
{
$alignment = 'left';
}
if (substr($dividerCell, - 1) === ':')
{
$alignment = $alignment === 'left' ? 'center' : 'right';
}
$alignments []= $alignment;
}
# ~
$HeaderElements = array();
$header = $Block['element']['text'];
$header = trim($header);
$header = trim($header, '|');
$headerCells = explode('|', $header);
foreach ($headerCells as $index => $headerCell)
{
$headerCell = trim($headerCell);
$HeaderElement = array(
'name' => 'th',
'text' => $headerCell,
'handler' => 'line',
);
if (isset($alignments[$index]))
{
$alignment = $alignments[$index];
$HeaderElement['attributes'] = array(
'style' => 'text-align: '.$alignment.';',
);
}
$HeaderElements []= $HeaderElement;
}
# ~
$Block = array(
'alignments' => $alignments,
'identified' => true,
'element' => array(
'name' => 'table',
'handler' => 'elements',
),
);
$Block['element']['text'] []= array(
'name' => 'thead',
'handler' => 'elements',
);
$Block['element']['text'] []= array(
'name' => 'tbody',
'handler' => 'elements',
'text' => array(),
);
$Block['element']['text'][0]['text'] []= array(
'name' => 'tr',
'handler' => 'elements',
'text' => $HeaderElements,
);
return $Block;
}
}
protected function blockTableContinue($Line, array $Block)
{
if (isset($Block['interrupted']))
{
return;
}
if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
{
$Elements = array();
$row = $Line['text'];
$row = trim($row);
$row = trim($row, '|');
preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
foreach ($matches[0] as $index => $cell)
{
$cell = trim($cell);
$Element = array(
'name' => 'td',
'handler' => 'line',
'text' => $cell,
);
if (isset($Block['alignments'][$index]))
{
$Element['attributes'] = array(
'style' => 'text-align: '.$Block['alignments'][$index].';',
);
}
$Elements []= $Element;
}
$Element = array(
'name' => 'tr',
'handler' => 'elements',
'text' => $Elements,
);
$Block['element']['text'][1]['text'] []= $Element;
return $Block;
}
}
#
# ~
#
protected function paragraph($Line)
{
$Block = array(
'element' => array(
'name' => 'p',
'text' => $Line['text'],
'handler' => 'line',
),
);
return $Block;
}
#
# Inline Elements
#
protected $InlineTypes = array(
'"' => array('SpecialCharacter'),
'!' => array('Image'),
'&' => array('SpecialCharacter'),
'*' => array('Emphasis'),
':' => array('Url'),
'<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
'>' => array('SpecialCharacter'),
'[' => array('Link'),
'_' => array('Emphasis'),
'`' => array('Code'),
'~' => array('Strikethrough'),
'\\' => array('EscapeSequence'),
);
# ~
protected $inlineMarkerList = '!"*_&[:<>`~\\';
#
# ~
#
public function line($text, $nonNestables=array())
{
$markup = '';
# $excerpt is based on the first occurrence of a marker
while ($excerpt = strpbrk($text, $this->inlineMarkerList))
{
$marker = $excerpt[0];
$markerPosition = strpos($text, $marker);
$Excerpt = array('text' => $excerpt, 'context' => $text);
foreach ($this->InlineTypes[$marker] as $inlineType)
{
# check to see if the current inline type is nestable in the current context
if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
{
continue;
}
$Inline = $this->{'inline'.$inlineType}($Excerpt);
if ( ! isset($Inline))
{
continue;
}
# makes sure that the inline belongs to "our" marker
if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
{
continue;
}
# sets a default inline position
if ( ! isset($Inline['position']))
{
$Inline['position'] = $markerPosition;
}
# cause the new element to 'inherit' our non nestables
foreach ($nonNestables as $non_nestable)
{
$Inline['element']['nonNestables'][] = $non_nestable;
}
# the text that comes before the inline
$unmarkedText = substr($text, 0, $Inline['position']);
# compile the unmarked text
$markup .= $this->unmarkedText($unmarkedText);
# compile the inline
$markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
# remove the examined text
$text = substr($text, $Inline['position'] + $Inline['extent']);
continue 2;
}
# the marker does not belong to an inline
$unmarkedText = substr($text, 0, $markerPosition + 1);
$markup .= $this->unmarkedText($unmarkedText);
$text = substr($text, $markerPosition + 1);
}
$markup .= $this->unmarkedText($text);
return $markup;
}
#
# ~
#
protected function inlineCode($Excerpt)
{
$marker = $Excerpt['text'][0];
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
{
$text = $matches[2];
$text = preg_replace("/[ ]*\n/", ' ', $text);
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'code',
'text' => $text,
),
);
}
}
protected function inlineEmailTag($Excerpt)
{
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
{
$url = $matches[1];
if ( ! isset($matches[2]))
{
$url = 'mailto:' . $url;
}
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'a',
'text' => $matches[1],
'attributes' => array(
'href' => $url,
),
),
);
}
}
protected function inlineEmphasis($Excerpt)
{
if ( ! isset($Excerpt['text'][1]))
{
return;
}
$marker = $Excerpt['text'][0];
if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
{
$emphasis = 'strong';
}
elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
{
$emphasis = 'em';
}
else
{
return;
}
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => $emphasis,
'handler' => 'line',
'text' => $matches[1],
),
);
}
protected function inlineEscapeSequence($Excerpt)
{
if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
{
return array(
'markup' => $Excerpt['text'][1],
'extent' => 2,
);
}
}
protected function inlineImage($Excerpt)
{
if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
{
return;
}
$Excerpt['text']= substr($Excerpt['text'], 1);
$Link = $this->inlineLink($Excerpt);
if ($Link === null)
{
return;
}
$Inline = array(
'extent' => $Link['extent'] + 1,
'element' => array(
'name' => 'img',
'attributes' => array(
'src' => $Link['element']['attributes']['href'],
'alt' => $Link['element']['text'],
),
),
);
$Inline['element']['attributes'] += $Link['element']['attributes'];
unset($Inline['element']['attributes']['href']);
return $Inline;
}
protected function inlineLink($Excerpt)
{
$Element = array(
'name' => 'a',
'handler' => 'line',
'nonNestables' => array('Url', 'Link'),
'text' => null,
'attributes' => array(
'href' => null,
'title' => null,
),
);
$extent = 0;
$remainder = $Excerpt['text'];
if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
{
$Element['text'] = $matches[1];
$extent += strlen($matches[0]);
$remainder = substr($remainder, $extent);
}
else
{
return;
}
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
{
$Element['attributes']['href'] = $matches[1];
if (isset($matches[2]))
{
$Element['attributes']['title'] = substr($matches[2], 1, - 1);
}
$extent += strlen($matches[0]);
}
else
{
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
{
$definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
$definition = strtolower($definition);
$extent += strlen($matches[0]);
}
else
{
$definition = strtolower($Element['text']);
}
if ( ! isset($this->DefinitionData['Reference'][$definition]))
{
return;
}
$Definition = $this->DefinitionData['Reference'][$definition];
$Element['attributes']['href'] = $Definition['url'];
$Element['attributes']['title'] = $Definition['title'];
}
return array(
'extent' => $extent,
'element' => $Element,
);
}
protected function inlineMarkup($Excerpt)
{
if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
{
return;
}
if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
{
return array(
'markup' => $matches[0],
'extent' => strlen($matches[0]),
);
}
if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
{
return array(
'markup' => $matches[0],
'extent' => strlen($matches[0]),
);
}
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
{
return array(
'markup' => $matches[0],
'extent' => strlen($matches[0]),
);
}
}
protected function inlineSpecialCharacter($Excerpt)
{
if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
{
return array(
'markup' => '&amp;',
'extent' => 1,
);
}
$SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
if (isset($SpecialCharacter[$Excerpt['text'][0]]))
{
return array(
'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
'extent' => 1,
);
}
}
protected function inlineStrikethrough($Excerpt)
{
if ( ! isset($Excerpt['text'][1]))
{
return;
}
if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
{
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'del',
'text' => $matches[1],
'handler' => 'line',
),
);
}
}
protected function inlineUrl($Excerpt)
{
if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
{
return;
}
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
{
$url = $matches[0][0];
$Inline = array(
'extent' => strlen($matches[0][0]),
'position' => $matches[0][1],
'element' => array(
'name' => 'a',
'text' => $url,
'attributes' => array(
'href' => $url,
),
),
);
return $Inline;
}
}
protected function inlineUrlTag($Excerpt)
{
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
{
$url = $matches[1];
return array(
'extent' => strlen($matches[0]),
'element' => array(
'name' => 'a',
'text' => $url,
'attributes' => array(
'href' => $url,
),
),
);
}
}
# ~
protected function unmarkedText($text)
{
if ($this->breaksEnabled)
{
$text = preg_replace('/[ ]*\n/', "<br />\n", $text);
}
else
{
$text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
$text = str_replace(" \n", "\n", $text);
}
return $text;
}
#
# Handlers
#
protected function element(array $Element)
{
if ($this->safeMode)
{
$Element = $this->sanitiseElement($Element);
}
$markup = '<'.$Element['name'];
if (isset($Element['attributes']))
{
foreach ($Element['attributes'] as $name => $value)
{
if ($value === null)
{
continue;
}
$markup .= ' '.$name.'="'.self::escape($value).'"';
}
}
$permitRawHtml = false;
if (isset($Element['text']))
{
$text = $Element['text'];
}
// very strongly consider an alternative if you're writing an
// extension
elseif (isset($Element['rawHtml']))
{
$text = $Element['rawHtml'];
$allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
$permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
}
if (isset($text))
{
$markup .= '>';
if (!isset($Element['nonNestables']))
{
$Element['nonNestables'] = array();
}
if (isset($Element['handler']))
{
$markup .= $this->{$Element['handler']}($text, $Element['nonNestables']);
}
elseif (!$permitRawHtml)
{
$markup .= self::escape($text, true);
}
else
{
$markup .= $text;
}
$markup .= '</'.$Element['name'].'>';
}
else
{
$markup .= ' />';
}
return $markup;
}
protected function elements(array $Elements)
{
$markup = '';
foreach ($Elements as $Element)
{
$markup .= "\n" . $this->element($Element);
}
$markup .= "\n";
return $markup;
}
# ~
protected function li($lines)
{
$markup = $this->lines($lines);
$trimmedMarkup = trim($markup);
if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
{
$markup = $trimmedMarkup;
$markup = substr($markup, 3);
$position = strpos($markup, "</p>");
$markup = substr_replace($markup, '', $position, 4);
}
return $markup;
}
#
# Deprecated Methods
#
function parse($text)
{
$markup = $this->text($text);
return $markup;
}
protected function sanitiseElement(array $Element)
{
static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
static $safeUrlNameToAtt = array(
'a' => 'href',
'img' => 'src',
);
if (isset($safeUrlNameToAtt[$Element['name']]))
{
$Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
}
if ( ! empty($Element['attributes']))
{
foreach ($Element['attributes'] as $att => $val)
{
# filter out badly parsed attribute
if ( ! preg_match($goodAttribute, $att))
{
unset($Element['attributes'][$att]);
}
# dump onevent attribute
elseif (self::striAtStart($att, 'on'))
{
unset($Element['attributes'][$att]);
}
}
}
return $Element;
}
protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
{
foreach ($this->safeLinksWhitelist as $scheme)
{
if (self::striAtStart($Element['attributes'][$attribute], $scheme))
{
return $Element;
}
}
$Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
return $Element;
}
#
# Static Methods
#
protected static function escape($text, $allowQuotes = false)
{
return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
}
protected static function striAtStart($string, $needle)
{
$len = strlen($needle);
if ($len > strlen($string))
{
return false;
}
else
{
return strtolower(substr($string, 0, $len)) === strtolower($needle);
}
}
static function instance($name = 'default')
{
if (isset(self::$instances[$name]))
{
return self::$instances[$name];
}
$instance = new static();
self::$instances[$name] = $instance;
return $instance;
}
private static $instances = array();
#
# Fields
#
protected $DefinitionData;
#
# Read-Only
protected $specialCharacters = array(
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
);
protected $StrongRegex = array(
'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
'_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
);
protected $EmRegex = array(
'*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
);
protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
protected $voidElements = array(
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
);
protected $textLevelElements = array(
'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
'i', 'rp', 'del', 'code', 'strike', 'marquee',
'q', 'rt', 'ins', 'font', 'strong',
's', 'tt', 'kbd', 'mark',
'u', 'xm', 'sub', 'nobr',
'sup', 'ruby',
'var', 'span',
'wbr', 'time',
);
}
The MIT License (MIT)
Copyright (c) 2013-2018 Emanuil Rusev, erusev.com
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.
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
/**
* Default autoescaping strategy based on file names.
*
* This strategy sets the HTML as the default autoescaping strategy,
* but changes it based on the template name.
*
* Note that there is no runtime performance impact as the
* default autoescaping strategy is set at compilation time.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FileExtensionEscapingStrategy
{
/**
* Guesses the best autoescaping strategy based on the file name.
*
* @param string $name The template name
*
* @return string|false The escaping strategy name to use or false to disable
*/
public static function guess(string $name)
{
if (\in_array(substr($name, -1), ['/', '\\'])) {
return 'html'; // return html for directories
}
if ('.twig' === substr($name, -5)) {
$name = substr($name, 0, -5);
}
$extension = pathinfo($name, PATHINFO_EXTENSION);
switch ($extension) {
case 'js':
return 'js';
case 'css':
return 'css';
case 'txt':
return false;
default:
return 'html';
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Node\Expression\TestExpression;
/**
* Represents a template test.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see https://twig.symfony.com/doc/templates.html#test-operator
*/
final class TwigTest
{
private $name;
private $callable;
private $options;
private $arguments = [];
/**
* @param callable|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation.
*/
public function __construct(string $name, $callable = null, array $options = [])
{
$this->name = $name;
$this->callable = $callable;
$this->options = array_merge([
'is_variadic' => false,
'node_class' => TestExpression::class,
'deprecated' => false,
'alternative' => null,
'one_mandatory_argument' => false,
], $options);
}
public function getName(): string
{
return $this->name;
}
/**
* Returns the callable to execute for this test.
*
* @return callable|null
*/
public function getCallable()
{
return $this->callable;
}
public function getNodeClass(): string
{
return $this->options['node_class'];
}
public function setArguments(array $arguments): void
{
$this->arguments = $arguments;
}
public function getArguments(): array
{
return $this->arguments;
}
public function isVariadic(): bool
{
return (bool) $this->options['is_variadic'];
}
public function isDeprecated(): bool
{
return (bool) $this->options['deprecated'];
}
public function getDeprecatedVersion(): string
{
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
}
public function getAlternative(): ?string
{
return $this->options['alternative'];
}
public function hasOneMandatoryArgument(): bool
{
return (bool) $this->options['one_mandatory_argument'];
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Loads a template from an array.
*
* When using this loader with a cache mechanism, you should know that a new cache
* key is generated each time a template content "changes" (the cache key being the
* source code of the template). If you don't want to see your cache grows out of
* control, you need to take care of clearing the old cache file by yourself.
*
* This loader should only be used for unit testing.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class ArrayLoader implements LoaderInterface
{
private $templates = [];
/**
* @param array $templates An array of templates (keys are the names, and values are the source code)
*/
public function __construct(array $templates = [])
{
$this->templates = $templates;
}
public function setTemplate(string $name, string $template): void
{
$this->templates[$name] = $template;
}
public function getSourceContext(string $name): Source
{
$name = (string) $name;
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
return new Source($this->templates[$name], $name);
}
public function exists(string $name): bool
{
return isset($this->templates[$name]);
}
public function getCacheKey(string $name): string
{
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
return $name.':'.$this->templates[$name];
}
public function isFresh(string $name, int $time): bool
{
if (!isset($this->templates[$name])) {
throw new LoaderError(sprintf('Template "%s" is not defined.', $name));
}
return true;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Loads template from the filesystem.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FilesystemLoader implements LoaderInterface
{
/** Identifier of the main namespace. */
const MAIN_NAMESPACE = '__main__';
protected $paths = [];
protected $cache = [];
protected $errorCache = [];
private $rootPath;
/**
* @param string|array $paths A path or an array of paths where to look for templates
* @param string|null $rootPath The root path common to all relative paths (null for getcwd())
*/
public function __construct($paths = [], string $rootPath = null)
{
$this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR;
if (false !== $realPath = realpath($rootPath)) {
$this->rootPath = $realPath.\DIRECTORY_SEPARATOR;
}
if ($paths) {
$this->setPaths($paths);
}
}
/**
* Returns the paths to the templates.
*/
public function getPaths(string $namespace = self::MAIN_NAMESPACE): array
{
return isset($this->paths[$namespace]) ? $this->paths[$namespace] : [];
}
/**
* Returns the path namespaces.
*
* The main namespace is always defined.
*/
public function getNamespaces(): array
{
return array_keys($this->paths);
}
/**
* @param string|array $paths A path or an array of paths where to look for templates
*/
public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE): void
{
if (!\is_array($paths)) {
$paths = [$paths];
}
$this->paths[$namespace] = [];
foreach ($paths as $path) {
$this->addPath($path, $namespace);
}
}
/**
* @throws LoaderError
*/
public function addPath(string $path, string $namespace = self::MAIN_NAMESPACE): void
{
// invalidate the cache
$this->cache = $this->errorCache = [];
$checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;
if (!is_dir($checkPath)) {
throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
}
$this->paths[$namespace][] = rtrim($path, '/\\');
}
/**
* @throws LoaderError
*/
public function prependPath(string $path, string $namespace = self::MAIN_NAMESPACE): void
{
// invalidate the cache
$this->cache = $this->errorCache = [];
$checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;
if (!is_dir($checkPath)) {
throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
}
$path = rtrim($path, '/\\');
if (!isset($this->paths[$namespace])) {
$this->paths[$namespace][] = $path;
} else {
array_unshift($this->paths[$namespace], $path);
}
}
public function getSourceContext(string $name): Source
{
if (null === $path = $this->findTemplate($name)) {
return new Source('', $name, '');
}
return new Source(file_get_contents($path), $name, $path);
}
public function getCacheKey(string $name): string
{
if (null === $path = $this->findTemplate($name)) {
return '';
}
$len = \strlen($this->rootPath);
if (0 === strncmp($this->rootPath, $path, $len)) {
return substr($path, $len);
}
return $path;
}
/**
* @return bool
*/
public function exists(string $name)
{
$name = $this->normalizeName($name);
if (isset($this->cache[$name])) {
return true;
}
return null !== $this->findTemplate($name, false);
}
public function isFresh(string $name, int $time): bool
{
// false support to be removed in 3.0
if (null === $path = $this->findTemplate($name)) {
return false;
}
return filemtime($path) < $time;
}
/**
* @return string|null
*/
protected function findTemplate(string $name, bool $throw = true)
{
$name = $this->normalizeName($name);
if (isset($this->cache[$name])) {
return $this->cache[$name];
}
if (isset($this->errorCache[$name])) {
if (!$throw) {
return null;
}
throw new LoaderError($this->errorCache[$name]);
}
try {
$this->validateName($name);
list($namespace, $shortname) = $this->parseName($name);
} catch (LoaderError $e) {
if (!$throw) {
return null;
}
throw $e;
}
if (!isset($this->paths[$namespace])) {
$this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace);
if (!$throw) {
return null;
}
throw new LoaderError($this->errorCache[$name]);
}
foreach ($this->paths[$namespace] as $path) {
if (!$this->isAbsolutePath($path)) {
$path = $this->rootPath.$path;
}
if (is_file($path.'/'.$shortname)) {
if (false !== $realpath = realpath($path.'/'.$shortname)) {
return $this->cache[$name] = $realpath;
}
return $this->cache[$name] = $path.'/'.$shortname;
}
}
$this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace]));
if (!$throw) {
return null;
}
throw new LoaderError($this->errorCache[$name]);
}
private function normalizeName(string $name): string
{
return preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name));
}
private function parseName(string $name, string $default = self::MAIN_NAMESPACE): array
{
if (isset($name[0]) && '@' == $name[0]) {
if (false === $pos = strpos($name, '/')) {
throw new LoaderError(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
}
$namespace = substr($name, 1, $pos - 1);
$shortname = substr($name, $pos + 1);
return [$namespace, $shortname];
}
return [$default, $name];
}
private function validateName(string $name): void
{
if (false !== strpos($name, "\0")) {
throw new LoaderError('A template name cannot contain NUL bytes.');
}
$name = ltrim($name, '/');
$parts = explode('/', $name);
$level = 0;
foreach ($parts as $part) {
if ('..' === $part) {
--$level;
} elseif ('.' !== $part) {
++$level;
}
if ($level < 0) {
throw new LoaderError(sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
}
}
}
private function isAbsolutePath(string $file): bool
{
return strspn($file, '/\\', 0, 1)
|| (\strlen($file) > 3 && ctype_alpha($file[0])
&& ':' === $file[1]
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, PHP_URL_SCHEME)
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Interface all loaders must implement.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LoaderInterface
{
/**
* Returns the source context for a given template logical name.
*
* @throws LoaderError When $name is not found
*/
public function getSourceContext(string $name): Source;
/**
* Gets the cache key to use for the cache for a given template name.
*
* @throws LoaderError When $name is not found
*/
public function getCacheKey(string $name): string;
/**
* @param int $time Timestamp of the last modification time of the cached template
*
* @throws LoaderError When $name is not found
*/
public function isFresh(string $name, int $time): bool;
/**
* @return bool
*/
public function exists(string $name);
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Loader;
use Twig\Error\LoaderError;
use Twig\Source;
/**
* Loads templates from other loaders.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class ChainLoader implements LoaderInterface
{
private $hasSourceCache = [];
private $loaders = [];
/**
* @param LoaderInterface[] $loaders
*/
public function __construct(array $loaders = [])
{
foreach ($loaders as $loader) {
$this->addLoader($loader);
}
}
public function addLoader(LoaderInterface $loader): void
{
$this->loaders[] = $loader;
$this->hasSourceCache = [];
}
/**
* @return LoaderInterface[]
*/
public function getLoaders(): array
{
return $this->loaders;
}
public function getSourceContext(string $name): Source
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if (!$loader->exists($name)) {
continue;
}
try {
return $loader->getSourceContext($name);
} catch (LoaderError $e) {
$exceptions[] = $e->getMessage();
}
}
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
}
public function exists(string $name): bool
{
if (isset($this->hasSourceCache[$name])) {
return $this->hasSourceCache[$name];
}
foreach ($this->loaders as $loader) {
if ($loader->exists($name)) {
return $this->hasSourceCache[$name] = true;
}
}
return $this->hasSourceCache[$name] = false;
}
public function getCacheKey(string $name): string
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if (!$loader->exists($name)) {
continue;
}
try {
return $loader->getCacheKey($name);
} catch (LoaderError $e) {
$exceptions[] = \get_class($loader).': '.$e->getMessage();
}
}
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
}
public function isFresh(string $name, int $time): bool
{
$exceptions = [];
foreach ($this->loaders as $loader) {
if (!$loader->exists($name)) {
continue;
}
try {
return $loader->isFresh($name, $time);
} catch (LoaderError $e) {
$exceptions[] = \get_class($loader).': '.$e->getMessage();
}
}
throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : ''));
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\ArrowFunctionExpression;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Node\Expression\Binary\ConcatBinary;
use Twig\Node\Expression\BlockReferenceExpression;
use Twig\Node\Expression\ConditionalExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\ParentExpression;
use Twig\Node\Expression\TestExpression;
use Twig\Node\Expression\Unary\NegUnary;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Expression\Unary\PosUnary;
use Twig\Node\Node;
/**
* Parses expressions.
*
* This parser implements a "Precedence climbing" algorithm.
*
* @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm
* @see https://en.wikipedia.org/wiki/Operator-precedence_parser
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
class ExpressionParser
{
const OPERATOR_LEFT = 1;
const OPERATOR_RIGHT = 2;
private $parser;
private $env;
private $unaryOperators;
private $binaryOperators;
public function __construct(Parser $parser, Environment $env)
{
$this->parser = $parser;
$this->env = $env;
$this->unaryOperators = $env->getUnaryOperators();
$this->binaryOperators = $env->getBinaryOperators();
}
public function parseExpression($precedence = 0, $allowArrow = false)
{
if ($allowArrow && $arrow = $this->parseArrow()) {
return $arrow;
}
$expr = $this->getPrimary();
$token = $this->parser->getCurrentToken();
while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
$op = $this->binaryOperators[$token->getValue()];
$this->parser->getStream()->next();
if ('is not' === $token->getValue()) {
$expr = $this->parseNotTestExpression($expr);
} elseif ('is' === $token->getValue()) {
$expr = $this->parseTestExpression($expr);
} elseif (isset($op['callable'])) {
$expr = $op['callable']($this->parser, $expr);
} else {
$expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
$class = $op['class'];
$expr = new $class($expr, $expr1, $token->getLine());
}
$token = $this->parser->getCurrentToken();
}
if (0 === $precedence) {
return $this->parseConditionalExpression($expr);
}
return $expr;
}
/**
* @return ArrowFunctionExpression|null
*/
private function parseArrow()
{
$stream = $this->parser->getStream();
// short array syntax (one argument, no parentheses)?
if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) {
$line = $stream->getCurrent()->getLine();
$token = $stream->expect(/* Token::NAME_TYPE */ 5);
$names = [new AssignNameExpression($token->getValue(), $token->getLine())];
$stream->expect(/* Token::ARROW_TYPE */ 12);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
}
// first, determine if we are parsing an arrow function by finding => (long form)
$i = 0;
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
return null;
}
++$i;
while (true) {
// variable name
++$i;
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
++$i;
}
if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
return null;
}
++$i;
if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) {
return null;
}
// yes, let's parse it properly
$token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(');
$line = $token->getLine();
$names = [];
while (true) {
$token = $stream->expect(/* Token::NAME_TYPE */ 5);
$names[] = new AssignNameExpression($token->getValue(), $token->getLine());
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')');
$stream->expect(/* Token::ARROW_TYPE */ 12);
return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line);
}
private function getPrimary(): AbstractExpression
{
$token = $this->parser->getCurrentToken();
if ($this->isUnary($token)) {
$operator = $this->unaryOperators[$token->getValue()];
$this->parser->getStream()->next();
$expr = $this->parseExpression($operator['precedence']);
$class = $operator['class'];
return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$this->parser->getStream()->next();
$expr = $this->parseExpression();
$this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed');
return $this->parsePostfixExpression($expr);
}
return $this->parsePrimaryExpression();
}
private function parseConditionalExpression($expr): AbstractExpression
{
while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) {
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$expr2 = $this->parseExpression();
if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$expr3 = $this->parseExpression();
} else {
$expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine());
}
} else {
$expr2 = $expr;
$expr3 = $this->parseExpression();
}
$expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
}
return $expr;
}
private function isUnary(Token $token): bool
{
return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]);
}
private function isBinary(Token $token): bool
{
return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]);
}
public function parsePrimaryExpression()
{
$token = $this->parser->getCurrentToken();
switch ($token->getType()) {
case /* Token::NAME_TYPE */ 5:
$this->parser->getStream()->next();
switch ($token->getValue()) {
case 'true':
case 'TRUE':
$node = new ConstantExpression(true, $token->getLine());
break;
case 'false':
case 'FALSE':
$node = new ConstantExpression(false, $token->getLine());
break;
case 'none':
case 'NONE':
case 'null':
case 'NULL':
$node = new ConstantExpression(null, $token->getLine());
break;
default:
if ('(' === $this->parser->getCurrentToken()->getValue()) {
$node = $this->getFunctionNode($token->getValue(), $token->getLine());
} else {
$node = new NameExpression($token->getValue(), $token->getLine());
}
}
break;
case /* Token::NUMBER_TYPE */ 6:
$this->parser->getStream()->next();
$node = new ConstantExpression($token->getValue(), $token->getLine());
break;
case /* Token::STRING_TYPE */ 7:
case /* Token::INTERPOLATION_START_TYPE */ 10:
$node = $this->parseStringExpression();
break;
case /* Token::OPERATOR_TYPE */ 8:
if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
$node = new NameExpression($token->getValue(), $token->getLine());
break;
} elseif (isset($this->unaryOperators[$token->getValue()])) {
$class = $this->unaryOperators[$token->getValue()]['class'];
if (!\in_array($class, [NegUnary::class, PosUnary::class])) {
throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
}
$this->parser->getStream()->next();
$expr = $this->parsePrimaryExpression();
$node = new $class($expr, $token->getLine());
break;
}
// no break
default:
if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) {
$node = $this->parseArrayExpression();
} elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) {
$node = $this->parseHashExpression();
} elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) {
throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
} else {
throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext());
}
}
return $this->parsePostfixExpression($node);
}
public function parseStringExpression()
{
$stream = $this->parser->getStream();
$nodes = [];
// a string cannot be followed by another string in a single expression
$nextCanBeString = true;
while (true) {
if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) {
$nodes[] = new ConstantExpression($token->getValue(), $token->getLine());
$nextCanBeString = false;
} elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) {
$nodes[] = $this->parseExpression();
$stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11);
$nextCanBeString = true;
} else {
break;
}
}
$expr = array_shift($nodes);
foreach ($nodes as $node) {
$expr = new ConcatBinary($expr, $node, $node->getTemplateLine());
}
return $expr;
}
public function parseArrayExpression()
{
$stream = $this->parser->getStream();
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
if (!$first) {
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma');
// trailing ,?
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
break;
}
}
$first = false;
$node->addElement($this->parseExpression());
}
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed');
return $node;
}
public function parseHashExpression()
{
$stream = $this->parser->getStream();
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected');
$node = new ArrayExpression([], $stream->getCurrent()->getLine());
$first = true;
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
if (!$first) {
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma');
// trailing ,?
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) {
break;
}
}
$first = false;
// a hash key can be:
//
// * a number -- 12
// * a string -- 'a'
// * a name, which is equivalent to a string -- a
// * an expression, which must be enclosed in parentheses -- (1 + 2)
if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
// {a} is a shortcut for {a:a}
if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) {
$value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine());
$node->addElement($value, $key);
continue;
}
} elseif (($token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) {
$key = new ConstantExpression($token->getValue(), $token->getLine());
} elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$key = $this->parseExpression();
} else {
$current = $stream->getCurrent();
throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext());
}
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)');
$value = $this->parseExpression();
$node->addElement($value, $key);
}
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed');
return $node;
}
public function parsePostfixExpression($node)
{
while (true) {
$token = $this->parser->getCurrentToken();
if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) {
if ('.' == $token->getValue() || '[' == $token->getValue()) {
$node = $this->parseSubscriptExpression($node);
} elseif ('|' == $token->getValue()) {
$node = $this->parseFilterExpression($node);
} else {
break;
}
} else {
break;
}
}
return $node;
}
public function getFunctionNode($name, $line)
{
switch ($name) {
case 'parent':
$this->parseArguments();
if (!\count($this->parser->getBlockStack())) {
throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext());
}
if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext());
}
return new ParentExpression($this->parser->peekBlockStack(), $line);
case 'block':
$args = $this->parseArguments();
if (\count($args) < 1) {
throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext());
}
return new BlockReferenceExpression($args->getNode(0), \count($args) > 1 ? $args->getNode(1) : null, $line);
case 'attribute':
$args = $this->parseArguments();
if (\count($args) < 2) {
throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext());
}
return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > 2 ? $args->getNode(2) : null, Template::ANY_CALL, $line);
default:
if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
$arguments = new ArrayExpression([], $line);
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
$node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line);
$node->setAttribute('safe', true);
return $node;
}
$args = $this->parseArguments(true);
$class = $this->getFunctionNodeClass($name, $line);
return new $class($name, $args, $line);
}
}
public function parseSubscriptExpression($node)
{
$stream = $this->parser->getStream();
$token = $stream->next();
$lineno = $token->getLine();
$arguments = new ArrayExpression([], $lineno);
$type = Template::ANY_CALL;
if ('.' == $token->getValue()) {
$token = $stream->next();
if (
/* Token::NAME_TYPE */ 5 == $token->getType()
||
/* Token::NUMBER_TYPE */ 6 == $token->getType()
||
(/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue()))
) {
$arg = new ConstantExpression($token->getValue(), $lineno);
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$type = Template::METHOD_CALL;
foreach ($this->parseArguments() as $n) {
$arguments->addElement($n);
}
}
} else {
throw new SyntaxError('Expected name or number.', $lineno, $stream->getSourceContext());
}
if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
if (!$arg instanceof ConstantExpression) {
throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext());
}
$name = $arg->getAttribute('value');
$node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno);
$node->setAttribute('safe', true);
return $node;
}
} else {
$type = Template::ARRAY_CALL;
// slice?
$slice = false;
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$slice = true;
$arg = new ConstantExpression(0, $token->getLine());
} else {
$arg = $this->parseExpression();
}
if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) {
$slice = true;
}
if ($slice) {
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) {
$length = new ConstantExpression(null, $token->getLine());
} else {
$length = $this->parseExpression();
}
$class = $this->getFilterNodeClass('slice', $token->getLine());
$arguments = new Node([$arg, $length]);
$filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine());
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
return $filter;
}
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']');
}
return new GetAttrExpression($node, $arg, $arguments, $type, $lineno);
}
public function parseFilterExpression($node)
{
$this->parser->getStream()->next();
return $this->parseFilterExpressionRaw($node);
}
public function parseFilterExpressionRaw($node, $tag = null)
{
while (true) {
$token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5);
$name = new ConstantExpression($token->getValue(), $token->getLine());
if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$arguments = new Node();
} else {
$arguments = $this->parseArguments(true, false, true);
}
$class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
$node = new $class($node, $name, $arguments, $token->getLine(), $tag);
if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) {
break;
}
$this->parser->getStream()->next();
}
return $node;
}
/**
* Parses arguments.
*
* @param bool $namedArguments Whether to allow named arguments or not
* @param bool $definition Whether we are parsing arguments for a function definition
*
* @return Node
*
* @throws SyntaxError
*/
public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false)
{
$args = [];
$stream = $this->parser->getStream();
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis');
while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
if (!empty($args)) {
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma');
// if the comma above was a trailing comma, early exit the argument parse loop
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) {
break;
}
}
if ($definition) {
$token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name');
$value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine());
} else {
$value = $this->parseExpression(0, $allowArrow);
}
$name = null;
if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) {
if (!$value instanceof NameExpression) {
throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext());
}
$name = $value->getAttribute('name');
if ($definition) {
$value = $this->parsePrimaryExpression();
if (!$this->checkConstantExpression($value)) {
throw new SyntaxError(sprintf('A default value for an argument must be a constant (a boolean, a string, a number, or an array).'), $token->getLine(), $stream->getSourceContext());
}
} else {
$value = $this->parseExpression(0, $allowArrow);
}
}
if ($definition) {
if (null === $name) {
$name = $value->getAttribute('name');
$value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine());
}
$args[$name] = $value;
} else {
if (null === $name) {
$args[] = $value;
} else {
$args[$name] = $value;
}
}
}
$stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis');
return new Node($args);
}
public function parseAssignmentExpression()
{
$stream = $this->parser->getStream();
$targets = [];
while (true) {
$token = $this->parser->getCurrentToken();
if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) {
// in this context, string operators are variable names
$this->parser->getStream()->next();
} else {
$stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to');
}
$value = $token->getValue();
if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) {
throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext());
}
$targets[] = new AssignNameExpression($value, $token->getLine());
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
return new Node($targets);
}
public function parseMultitargetExpression()
{
$targets = [];
while (true) {
$targets[] = $this->parseExpression();
if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
}
return new Node($targets);
}
private function parseNotTestExpression(Node $node): NotUnary
{
return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine());
}
private function parseTestExpression(Node $node): TestExpression
{
$stream = $this->parser->getStream();
list($name, $test) = $this->getTest($node->getTemplateLine());
$class = $this->getTestNodeClass($test);
$arguments = null;
if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) {
$arguments = $this->parseArguments(true);
} elseif ($test->hasOneMandatoryArgument()) {
$arguments = new Node([0 => $this->parsePrimaryExpression()]);
}
if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) {
$node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine());
$node->setAttribute('safe', true);
}
return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine());
}
private function getTest(int $line): array
{
$stream = $this->parser->getStream();
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
if ($test = $this->env->getTest($name)) {
return [$name, $test];
}
if ($stream->test(/* Token::NAME_TYPE */ 5)) {
// try 2-words tests
$name = $name.' '.$this->parser->getCurrentToken()->getValue();
if ($test = $this->env->getTest($name)) {
$stream->next();
return [$name, $test];
}
}
$e = new SyntaxError(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext());
$e->addSuggestions($name, array_keys($this->env->getTests()));
throw $e;
}
private function getTestNodeClass(TwigTest $test): string
{
if ($test->isDeprecated()) {
$stream = $this->parser->getStream();
$message = sprintf('Twig Test "%s" is deprecated', $test->getName());
if ($test->getDeprecatedVersion()) {
$message .= sprintf(' since version %s', $test->getDeprecatedVersion());
}
if ($test->getAlternative()) {
$message .= sprintf('. Use "%s" instead', $test->getAlternative());
}
$src = $stream->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine());
@trigger_error($message, E_USER_DEPRECATED);
}
return $test->getNodeClass();
}
private function getFunctionNodeClass(string $name, int $line): string
{
if (!$function = $this->env->getFunction($name)) {
$e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext());
$e->addSuggestions($name, array_keys($this->env->getFunctions()));
throw $e;
}
if ($function->isDeprecated()) {
$message = sprintf('Twig Function "%s" is deprecated', $function->getName());
if ($function->getDeprecatedVersion()) {
$message .= sprintf(' since version %s', $function->getDeprecatedVersion());
}
if ($function->getAlternative()) {
$message .= sprintf('. Use "%s" instead', $function->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, E_USER_DEPRECATED);
}
return $function->getNodeClass();
}
private function getFilterNodeClass(string $name, int $line): string
{
if (!$filter = $this->env->getFilter($name)) {
$e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext());
$e->addSuggestions($name, array_keys($this->env->getFilters()));
throw $e;
}
if ($filter->isDeprecated()) {
$message = sprintf('Twig Filter "%s" is deprecated', $filter->getName());
if ($filter->getDeprecatedVersion()) {
$message .= sprintf(' since version %s', $filter->getDeprecatedVersion());
}
if ($filter->getAlternative()) {
$message .= sprintf('. Use "%s" instead', $filter->getAlternative());
}
$src = $this->parser->getStream()->getSourceContext();
$message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line);
@trigger_error($message, E_USER_DEPRECATED);
}
return $filter->getNodeClass();
}
// checks that the node only contains "constant" elements
private function checkConstantExpression(Node $node): bool
{
if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression
|| $node instanceof NegUnary || $node instanceof PosUnary
)) {
return false;
}
foreach ($node as $n) {
if (!$this->checkConstantExpression($n)) {
return false;
}
}
return true;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
/**
* Marks a content as safe.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Markup implements \Countable, \JsonSerializable
{
private $content;
private $charset;
public function __construct($content, $charset)
{
$this->content = (string) $content;
$this->charset = $charset;
}
public function __toString()
{
return $this->content;
}
/**
* @return int
*/
public function count()
{
return mb_strlen($this->content, $this->charset);
}
public function jsonSerialize()
{
return $this->content;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
/**
* Holds information about a non-compiled Twig template.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class Source
{
private $code;
private $name;
private $path;
/**
* @param string $code The template source code
* @param string $name The template logical name
* @param string $path The filesystem path of the template if any
*/
public function __construct(string $code, string $name, string $path = '')
{
$this->code = $code;
$this->name = $name;
$this->path = $path;
}
public function getCode(): string
{
return $this->code;
}
public function getName(): string
{
return $this->name;
}
public function getPath(): string
{
return $this->path;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Node\Node;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Compiler
{
private $lastLine;
private $source;
private $indentation;
private $env;
private $debugInfo = [];
private $sourceOffset;
private $sourceLine;
private $varNameSalt = 0;
public function __construct(Environment $env)
{
$this->env = $env;
}
public function getEnvironment(): Environment
{
return $this->env;
}
public function getSource(): string
{
return $this->source;
}
/**
* @return $this
*/
public function compile(Node $node, int $indentation = 0)
{
$this->lastLine = null;
$this->source = '';
$this->debugInfo = [];
$this->sourceOffset = 0;
// source code starts at 1 (as we then increment it when we encounter new lines)
$this->sourceLine = 1;
$this->indentation = $indentation;
$this->varNameSalt = 0;
$node->compile($this);
return $this;
}
/**
* @return $this
*/
public function subcompile(Node $node, bool $raw = true)
{
if (false === $raw) {
$this->source .= str_repeat(' ', $this->indentation * 4);
}
$node->compile($this);
return $this;
}
/**
* Adds a raw string to the compiled code.
*
* @return $this
*/
public function raw(string $string)
{
$this->source .= $string;
return $this;
}
/**
* Writes a string to the compiled code by adding indentation.
*
* @return $this
*/
public function write(...$strings)
{
foreach ($strings as $string) {
$this->source .= str_repeat(' ', $this->indentation * 4).$string;
}
return $this;
}
/**
* Adds a quoted string to the compiled code.
*
* @return $this
*/
public function string(string $value)
{
$this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
return $this;
}
/**
* Returns a PHP representation of a given value.
*
* @return $this
*/
public function repr($value)
{
if (\is_int($value) || \is_float($value)) {
if (false !== $locale = setlocale(LC_NUMERIC, '0')) {
setlocale(LC_NUMERIC, 'C');
}
$this->raw(var_export($value, true));
if (false !== $locale) {
setlocale(LC_NUMERIC, $locale);
}
} elseif (null === $value) {
$this->raw('null');
} elseif (\is_bool($value)) {
$this->raw($value ? 'true' : 'false');
} elseif (\is_array($value)) {
$this->raw('array(');
$first = true;
foreach ($value as $key => $v) {
if (!$first) {
$this->raw(', ');
}
$first = false;
$this->repr($key);
$this->raw(' => ');
$this->repr($v);
}
$this->raw(')');
} else {
$this->string($value);
}
return $this;
}
/**
* @return $this
*/
public function addDebugInfo(Node $node)
{
if ($node->getTemplateLine() != $this->lastLine) {
$this->write(sprintf("// line %d\n", $node->getTemplateLine()));
$this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
$this->sourceOffset = \strlen($this->source);
$this->debugInfo[$this->sourceLine] = $node->getTemplateLine();
$this->lastLine = $node->getTemplateLine();
}
return $this;
}
public function getDebugInfo(): array
{
ksort($this->debugInfo);
return $this->debugInfo;
}
/**
* @return $this
*/
public function indent(int $step = 1)
{
$this->indentation += $step;
return $this;
}
/**
* @return $this
*
* @throws \LogicException When trying to outdent too much so the indentation would become negative
*/
public function outdent(int $step = 1)
{
// can't outdent by more steps than the current indentation level
if ($this->indentation < $step) {
throw new \LogicException('Unable to call outdent() as the indentation would become negative.');
}
$this->indentation -= $step;
return $this;
}
public function getVarName(): string
{
return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->varNameSalt++));
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\Error;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
/**
* Default base class for compiled templates.
*
* This class is an implementation detail of how template compilation currently
* works, which might change. It should never be used directly. Use $twig->load()
* instead, which returns an instance of \Twig\TemplateWrapper.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
abstract class Template
{
const ANY_CALL = 'any';
const ARRAY_CALL = 'array';
const METHOD_CALL = 'method';
protected $parent;
protected $parents = [];
protected $env;
protected $blocks = [];
protected $traits = [];
protected $extensions = [];
protected $sandbox;
public function __construct(Environment $env)
{
$this->env = $env;
$this->extensions = $env->getExtensions();
}
/**
* Returns the template name.
*
* @return string The template name
*/
abstract public function getTemplateName();
/**
* Returns debug information about the template.
*
* @return array Debug information
*/
abstract public function getDebugInfo();
/**
* Returns information about the original template source code.
*
* @return Source
*/
abstract public function getSourceContext();
/**
* Returns the parent template.
*
* This method is for internal use only and should never be called
* directly.
*
* @return Template|TemplateWrapper|false The parent template or false if there is no parent
*/
public function getParent(array $context)
{
if (null !== $this->parent) {
return $this->parent;
}
try {
$parent = $this->doGetParent($context);
if (false === $parent) {
return false;
}
if ($parent instanceof self || $parent instanceof TemplateWrapper) {
return $this->parents[$parent->getSourceContext()->getName()] = $parent;
}
if (!isset($this->parents[$parent])) {
$this->parents[$parent] = $this->loadTemplate($parent);
}
} catch (LoaderError $e) {
$e->setSourceContext(null);
$e->guess();
throw $e;
}
return $this->parents[$parent];
}
protected function doGetParent(array $context)
{
return false;
}
public function isTraitable()
{
return true;
}
/**
* Displays a parent block.
*
* This method is for internal use only and should never be called
* directly.
*
* @param string $name The block name to display from the parent
* @param array $context The context
* @param array $blocks The current set of blocks
*/
public function displayParentBlock($name, array $context, array $blocks = [])
{
if (isset($this->traits[$name])) {
$this->traits[$name][0]->displayBlock($name, $context, $blocks, false);
} elseif (false !== $parent = $this->getParent($context)) {
$parent->displayBlock($name, $context, $blocks, false);
} else {
throw new RuntimeError(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext());
}
}
/**
* Displays a block.
*
* This method is for internal use only and should never be called
* directly.
*
* @param string $name The block name to display
* @param array $context The context
* @param array $blocks The current set of blocks
* @param bool $useBlocks Whether to use the current set of blocks
*/
public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, self $templateContext = null)
{
if ($useBlocks && isset($blocks[$name])) {
$template = $blocks[$name][0];
$block = $blocks[$name][1];
} elseif (isset($this->blocks[$name])) {
$template = $this->blocks[$name][0];
$block = $this->blocks[$name][1];
} else {
$template = null;
$block = null;
}
// avoid RCEs when sandbox is enabled
if (null !== $template && !$template instanceof self) {
throw new \LogicException('A block must be a method on a \Twig\Template instance.');
}
if (null !== $template) {
try {
$template->$block($context, $blocks);
} catch (Error $e) {
if (!$e->getSourceContext()) {
$e->setSourceContext($template->getSourceContext());
}
// this is mostly useful for \Twig\Error\LoaderError exceptions
// see \Twig\Error\LoaderError
if (-1 === $e->getTemplateLine()) {
$e->guess();
}
throw $e;
} catch (\Exception $e) {
$e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e);
$e->guess();
throw $e;
}
} elseif (false !== $parent = $this->getParent($context)) {
$parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this);
} elseif (isset($blocks[$name])) {
throw new RuntimeError(sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext());
} else {
throw new RuntimeError(sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());
}
}
/**
* Renders a parent block.
*
* This method is for internal use only and should never be called
* directly.
*
* @param string $name The block name to render from the parent
* @param array $context The context
* @param array $blocks The current set of blocks
*
* @return string The rendered block
*/
public function renderParentBlock($name, array $context, array $blocks = [])
{
if ($this->env->isDebug()) {
ob_start();
} else {
ob_start(function () { return ''; });
}
$this->displayParentBlock($name, $context, $blocks);
return ob_get_clean();
}
/**
* Renders a block.
*
* This method is for internal use only and should never be called
* directly.
*
* @param string $name The block name to render
* @param array $context The context
* @param array $blocks The current set of blocks
* @param bool $useBlocks Whether to use the current set of blocks
*
* @return string The rendered block
*/
public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true)
{
if ($this->env->isDebug()) {
ob_start();
} else {
ob_start(function () { return ''; });
}
$this->displayBlock($name, $context, $blocks, $useBlocks);
return ob_get_clean();
}
/**
* Returns whether a block exists or not in the current context of the template.
*
* This method checks blocks defined in the current template
* or defined in "used" traits or defined in parent templates.
*
* @param string $name The block name
* @param array $context The context
* @param array $blocks The current set of blocks
*
* @return bool true if the block exists, false otherwise
*/
public function hasBlock($name, array $context, array $blocks = [])
{
if (isset($blocks[$name])) {
return $blocks[$name][0] instanceof self;
}
if (isset($this->blocks[$name])) {
return true;
}
if (false !== $parent = $this->getParent($context)) {
return $parent->hasBlock($name, $context);
}
return false;
}
/**
* Returns all block names in the current context of the template.
*
* This method checks blocks defined in the current template
* or defined in "used" traits or defined in parent templates.
*
* @param array $context The context
* @param array $blocks The current set of blocks
*
* @return array An array of block names
*/
public function getBlockNames(array $context, array $blocks = [])
{
$names = array_merge(array_keys($blocks), array_keys($this->blocks));
if (false !== $parent = $this->getParent($context)) {
$names = array_merge($names, $parent->getBlockNames($context));
}
return array_unique($names);
}
/**
* @return Template|TemplateWrapper
*/
protected function loadTemplate($template, $templateName = null, $line = null, $index = null)
{
try {
if (\is_array($template)) {
return $this->env->resolveTemplate($template);
}
if ($template instanceof self || $template instanceof TemplateWrapper) {
return $template;
}
if ($template === $this->getTemplateName()) {
$class = static::class;
if (false !== $pos = strrpos($class, '___', -1)) {
$class = substr($class, 0, $pos);
}
} else {
$class = $this->env->getTemplateClass($template);
}
return $this->env->loadTemplate($class, $template, $index);
} catch (Error $e) {
if (!$e->getSourceContext()) {
$e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext());
}
if ($e->getTemplateLine() > 0) {
throw $e;
}
if (!$line) {
$e->guess();
} else {
$e->setTemplateLine($line);
}
throw $e;
}
}
/**
* @internal
*
* @return Template
*/
public function unwrap()
{
return $this;
}
/**
* Returns all blocks.
*
* This method is for internal use only and should never be called
* directly.
*
* @return array An array of blocks
*/
public function getBlocks()
{
return $this->blocks;
}
public function display(array $context, array $blocks = [])
{
$this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks));
}
public function render(array $context)
{
$level = ob_get_level();
if ($this->env->isDebug()) {
ob_start();
} else {
ob_start(function () { return ''; });
}
try {
$this->display($context);
} catch (\Throwable $e) {
while (ob_get_level() > $level) {
ob_end_clean();
}
throw $e;
}
return ob_get_clean();
}
protected function displayWithErrorHandling(array $context, array $blocks = [])
{
try {
$this->doDisplay($context, $blocks);
} catch (Error $e) {
if (!$e->getSourceContext()) {
$e->setSourceContext($this->getSourceContext());
}
// this is mostly useful for \Twig\Error\LoaderError exceptions
// see \Twig\Error\LoaderError
if (-1 === $e->getTemplateLine()) {
$e->guess();
}
throw $e;
} catch (\Exception $e) {
$e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e);
$e->guess();
throw $e;
}
}
/**
* Auto-generated method to display the template with the given context.
*
* @param array $context An array of parameters to pass to the template
* @param array $blocks An array of blocks to pass to the template
*/
abstract protected function doDisplay(array $context, array $blocks = []);
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
/**
* Exposes a template to userland.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class TemplateWrapper
{
private $env;
private $template;
/**
* This method is for internal use only and should never be called
* directly (use Twig\Environment::load() instead).
*
* @internal
*/
public function __construct(Environment $env, Template $template)
{
$this->env = $env;
$this->template = $template;
}
public function render(array $context = []): string
{
// using func_get_args() allows to not expose the blocks argument
// as it should only be used by internal code
return $this->template->render($context, \func_get_args()[1] ?? []);
}
public function display(array $context = [])
{
// using func_get_args() allows to not expose the blocks argument
// as it should only be used by internal code
$this->template->display($context, \func_get_args()[1] ?? []);
}
public function hasBlock(string $name, array $context = []): bool
{
return $this->template->hasBlock($name, $context);
}
/**
* @return string[] An array of defined template block names
*/
public function getBlockNames(array $context = []): array
{
return $this->template->getBlockNames($context);
}
public function renderBlock(string $name, array $context = []): string
{
$context = $this->env->mergeGlobals($context);
$level = ob_get_level();
if ($this->env->isDebug()) {
ob_start();
} else {
ob_start(function () { return ''; });
}
try {
$this->template->displayBlock($name, $context);
} catch (\Throwable $e) {
while (ob_get_level() > $level) {
ob_end_clean();
}
throw $e;
}
return ob_get_clean();
}
public function displayBlock(string $name, array $context = [])
{
$this->template->displayBlock($name, $this->env->mergeGlobals($context));
}
public function getSourceContext(): Source
{
return $this->template->getSourceContext();
}
public function getTemplateName(): string
{
return $this->template->getTemplateName();
}
/**
* @internal
*
* @return Template
*/
public function unwrap()
{
return $this->template;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Node;
/**
* Represents a template function.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see https://twig.symfony.com/doc/templates.html#functions
*/
final class TwigFunction
{
private $name;
private $callable;
private $options;
private $arguments = [];
/**
* @param callable|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation.
*/
public function __construct(string $name, $callable = null, array $options = [])
{
$this->name = $name;
$this->callable = $callable;
$this->options = array_merge([
'needs_environment' => false,
'needs_context' => false,
'is_variadic' => false,
'is_safe' => null,
'is_safe_callback' => null,
'node_class' => FunctionExpression::class,
'deprecated' => false,
'alternative' => null,
], $options);
}
public function getName(): string
{
return $this->name;
}
/**
* Returns the callable to execute for this function.
*
* @return callable|null
*/
public function getCallable()
{
return $this->callable;
}
public function getNodeClass(): string
{
return $this->options['node_class'];
}
public function setArguments(array $arguments): void
{
$this->arguments = $arguments;
}
public function getArguments(): array
{
return $this->arguments;
}
public function needsEnvironment(): bool
{
return $this->options['needs_environment'];
}
public function needsContext(): bool
{
return $this->options['needs_context'];
}
public function getSafe(Node $functionArgs): ?array
{
if (null !== $this->options['is_safe']) {
return $this->options['is_safe'];
}
if (null !== $this->options['is_safe_callback']) {
return $this->options['is_safe_callback']($functionArgs);
}
return [];
}
public function isVariadic(): bool
{
return (bool) $this->options['is_variadic'];
}
public function isDeprecated(): bool
{
return (bool) $this->options['deprecated'];
}
public function getDeprecatedVersion(): string
{
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
}
public function getAlternative(): ?string
{
return $this->options['alternative'];
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Util;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class TemplateDirIterator extends \IteratorIterator
{
public function current()
{
return file_get_contents(parent::current());
}
public function key()
{
return (string) parent::key();
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Util;
use Twig\Environment;
use Twig\Error\SyntaxError;
use Twig\Source;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class DeprecationCollector
{
private $twig;
public function __construct(Environment $twig)
{
$this->twig = $twig;
}
/**
* Returns deprecations for templates contained in a directory.
*
* @param string $dir A directory where templates are stored
* @param string $ext Limit the loaded templates by extension
*
* @return array An array of deprecations
*/
public function collectDir(string $dir, string $ext = '.twig'): array
{
$iterator = new \RegexIterator(
new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::LEAVES_ONLY
), '{'.preg_quote($ext).'$}'
);
return $this->collect(new TemplateDirIterator($iterator));
}
/**
* Returns deprecations for passed templates.
*
* @param \Traversable $iterator An iterator of templates (where keys are template names and values the contents of the template)
*
* @return array An array of deprecations
*/
public function collect(\Traversable $iterator): array
{
$deprecations = [];
set_error_handler(function ($type, $msg) use (&$deprecations) {
if (E_USER_DEPRECATED === $type) {
$deprecations[] = $msg;
}
});
foreach ($iterator as $name => $contents) {
try {
$this->twig->parse($this->twig->tokenize(new Source($contents, $name)));
} catch (SyntaxError $e) {
// ignore templates containing syntax errors
}
}
restore_error_handler();
return $deprecations;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class Token
{
private $value;
private $type;
private $lineno;
const EOF_TYPE = -1;
const TEXT_TYPE = 0;
const BLOCK_START_TYPE = 1;
const VAR_START_TYPE = 2;
const BLOCK_END_TYPE = 3;
const VAR_END_TYPE = 4;
const NAME_TYPE = 5;
const NUMBER_TYPE = 6;
const STRING_TYPE = 7;
const OPERATOR_TYPE = 8;
const PUNCTUATION_TYPE = 9;
const INTERPOLATION_START_TYPE = 10;
const INTERPOLATION_END_TYPE = 11;
const ARROW_TYPE = 12;
public function __construct(int $type, $value, int $lineno)
{
$this->type = $type;
$this->value = $value;
$this->lineno = $lineno;
}
public function __toString()
{
return sprintf('%s(%s)', self::typeToString($this->type, true), $this->value);
}
/**
* Tests the current token for a type and/or a value.
*
* Parameters may be:
* * just type
* * type and value (or array of possible values)
* * just value (or array of possible values) (NAME_TYPE is used as type)
*
* @param array|string|int $type The type to test
* @param array|string|null $values The token value
*/
public function test($type, $values = null): bool
{
if (null === $values && !\is_int($type)) {
$values = $type;
$type = self::NAME_TYPE;
}
return ($this->type === $type) && (
null === $values ||
(\is_array($values) && \in_array($this->value, $values)) ||
$this->value == $values
);
}
public function getLine(): int
{
return $this->lineno;
}
public function getType(): int
{
return $this->type;
}
public function getValue()
{
return $this->value;
}
public static function typeToString(int $type, bool $short = false): string
{
switch ($type) {
case self::EOF_TYPE:
$name = 'EOF_TYPE';
break;
case self::TEXT_TYPE:
$name = 'TEXT_TYPE';
break;
case self::BLOCK_START_TYPE:
$name = 'BLOCK_START_TYPE';
break;
case self::VAR_START_TYPE:
$name = 'VAR_START_TYPE';
break;
case self::BLOCK_END_TYPE:
$name = 'BLOCK_END_TYPE';
break;
case self::VAR_END_TYPE:
$name = 'VAR_END_TYPE';
break;
case self::NAME_TYPE:
$name = 'NAME_TYPE';
break;
case self::NUMBER_TYPE:
$name = 'NUMBER_TYPE';
break;
case self::STRING_TYPE:
$name = 'STRING_TYPE';
break;
case self::OPERATOR_TYPE:
$name = 'OPERATOR_TYPE';
break;
case self::PUNCTUATION_TYPE:
$name = 'PUNCTUATION_TYPE';
break;
case self::INTERPOLATION_START_TYPE:
$name = 'INTERPOLATION_START_TYPE';
break;
case self::INTERPOLATION_END_TYPE:
$name = 'INTERPOLATION_END_TYPE';
break;
case self::ARROW_TYPE:
$name = 'ARROW_TYPE';
break;
default:
throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type));
}
return $short ? $name : 'Twig\Token::'.$name;
}
public static function typeToEnglish(int $type): string
{
switch ($type) {
case self::EOF_TYPE:
return 'end of template';
case self::TEXT_TYPE:
return 'text';
case self::BLOCK_START_TYPE:
return 'begin of statement block';
case self::VAR_START_TYPE:
return 'begin of print statement';
case self::BLOCK_END_TYPE:
return 'end of statement block';
case self::VAR_END_TYPE:
return 'end of print statement';
case self::NAME_TYPE:
return 'name';
case self::NUMBER_TYPE:
return 'number';
case self::STRING_TYPE:
return 'string';
case self::OPERATOR_TYPE:
return 'operator';
case self::PUNCTUATION_TYPE:
return 'punctuation';
case self::INTERPOLATION_START_TYPE:
return 'begin of string interpolation';
case self::INTERPOLATION_END_TYPE:
return 'end of string interpolation';
case self::ARROW_TYPE:
return 'arrow function';
default:
throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type));
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\SyntaxError;
/**
* Represents a token stream.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class TokenStream
{
private $tokens;
private $current = 0;
private $source;
public function __construct(array $tokens, Source $source = null)
{
$this->tokens = $tokens;
$this->source = $source ?: new Source('', '');
}
public function __toString()
{
return implode("\n", $this->tokens);
}
public function injectTokens(array $tokens)
{
$this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current));
}
/**
* Sets the pointer to the next token and returns the old one.
*/
public function next(): Token
{
if (!isset($this->tokens[++$this->current])) {
throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source);
}
return $this->tokens[$this->current - 1];
}
/**
* Tests a token, sets the pointer to the next one and returns it or throws a syntax error.
*
* @return Token|null The next token if the condition is true, null otherwise
*/
public function nextIf($primary, $secondary = null)
{
if ($this->tokens[$this->current]->test($primary, $secondary)) {
return $this->next();
}
}
/**
* Tests a token and returns it or throws a syntax error.
*/
public function expect($type, $value = null, string $message = null): Token
{
$token = $this->tokens[$this->current];
if (!$token->test($type, $value)) {
$line = $token->getLine();
throw new SyntaxError(sprintf('%sUnexpected token "%s"%s ("%s" expected%s).',
$message ? $message.'. ' : '',
Token::typeToEnglish($token->getType()),
$token->getValue() ? sprintf(' of value "%s"', $token->getValue()) : '',
Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''),
$line,
$this->source
);
}
$this->next();
return $token;
}
/**
* Looks at the next token.
*/
public function look(int $number = 1): Token
{
if (!isset($this->tokens[$this->current + $number])) {
throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source);
}
return $this->tokens[$this->current + $number];
}
/**
* Tests the current token.
*/
public function test($primary, $secondary = null): bool
{
return $this->tokens[$this->current]->test($primary, $secondary);
}
/**
* Checks if end of stream was reached.
*/
public function isEOF(): bool
{
return /* Token::EOF_TYPE */ -1 === $this->tokens[$this->current]->getType();
}
public function getCurrent(): Token
{
return $this->tokens[$this->current];
}
/**
* Gets the source associated with this stream.
*
* @internal
*/
public function getSourceContext(): Source
{
return $this->source;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
interface RuntimeExtensionInterface
{
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension;
use Twig\Profiler\NodeVisitor\ProfilerNodeVisitor;
use Twig\Profiler\Profile;
class ProfilerExtension extends AbstractExtension
{
private $actives = [];
public function __construct(Profile $profile)
{
$this->actives[] = $profile;
}
/**
* @return void
*/
public function enter(Profile $profile)
{
$this->actives[0]->addProfile($profile);
array_unshift($this->actives, $profile);
}
/**
* @return void
*/
public function leave(Profile $profile)
{
$profile->leave();
array_shift($this->actives);
if (1 === \count($this->actives)) {
$this->actives[0]->leave();
}
}
public function getNodeVisitors(): array
{
return [new ProfilerNodeVisitor(static::class)];
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
/**
* Interface implemented by extension classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExtensionInterface
{
/**
* Returns the token parser instances to add to the existing list.
*
* @return TokenParserInterface[]
*/
public function getTokenParsers();
/**
* Returns the node visitor instances to add to the existing list.
*
* @return NodeVisitorInterface[]
*/
public function getNodeVisitors();
/**
* Returns a list of filters to add to the existing list.
*
* @return TwigFilter[]
*/
public function getFilters();
/**
* Returns a list of tests to add to the existing list.
*
* @return TwigTest[]
*/
public function getTests();
/**
* Returns a list of functions to add to the existing list.
*
* @return TwigFunction[]
*/
public function getFunctions();
/**
* Returns a list of operators to add to the existing list.
*
* @return array<array> First array of unary operators, second array of binary operators
*/
public function getOperators();
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension {
use Twig\TwigFunction;
final class DebugExtension extends AbstractExtension
{
public function getFunctions(): array
{
// dump is safe if var_dump is overridden by xdebug
$isDumpOutputHtmlSafe = \extension_loaded('xdebug')
// false means that it was not set (and the default is on) or it explicitly enabled
&& (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump'))
// false means that it was not set (and the default is on) or it explicitly enabled
// xdebug.overload_var_dump produces HTML only when html_errors is also enabled
&& (false === ini_get('html_errors') || ini_get('html_errors'))
|| 'cli' === \PHP_SAPI
;
return [
new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]),
];
}
}
}
namespace {
use Twig\Environment;
use Twig\Template;
use Twig\TemplateWrapper;
function twig_var_dump(Environment $env, $context, ...$vars)
{
if (!$env->isDebug()) {
return;
}
ob_start();
if (!$vars) {
$vars = [];
foreach ($context as $key => $value) {
if (!$value instanceof Template && !$value instanceof TemplateWrapper) {
$vars[$key] = $value;
}
}
var_dump($vars);
} else {
var_dump(...$vars);
}
return ob_get_clean();
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension;
abstract class AbstractExtension implements ExtensionInterface
{
public function getTokenParsers()
{
return [];
}
public function getNodeVisitors()
{
return [];
}
public function getFilters()
{
return [];
}
public function getTests()
{
return [];
}
public function getFunctions()
{
return [];
}
public function getOperators()
{
return [];
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension {
use Twig\TwigFunction;
final class StringLoaderExtension extends AbstractExtension
{
public function getFunctions(): array
{
return [
new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]),
];
}
}
}
namespace {
use Twig\Environment;
use Twig\TemplateWrapper;
/**
* Loads a template from a string.
*
* {{ include(template_from_string("Hello {{ name }}")) }}
*
* @param string $template A template as a string or object implementing __toString()
* @param string $name An optional name of the template to be used in error messages
*/
function twig_template_from_string(Environment $env, $template, string $name = null): TemplateWrapper
{
return $env->createTemplate((string) $template, $name);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
/**
* Used by \Twig\Environment as a staging area.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class StagingExtension extends AbstractExtension
{
private $functions = [];
private $filters = [];
private $visitors = [];
private $tokenParsers = [];
private $tests = [];
public function addFunction(TwigFunction $function): void
{
if (isset($this->functions[$function->getName()])) {
throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName()));
}
$this->functions[$function->getName()] = $function;
}
public function getFunctions(): array
{
return $this->functions;
}
public function addFilter(TwigFilter $filter): void
{
if (isset($this->filters[$filter->getName()])) {
throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName()));
}
$this->filters[$filter->getName()] = $filter;
}
public function getFilters(): array
{
return $this->filters;
}
public function addNodeVisitor(NodeVisitorInterface $visitor): void
{
$this->visitors[] = $visitor;
}
public function getNodeVisitors(): array
{
return $this->visitors;
}
public function addTokenParser(TokenParserInterface $parser): void
{
if (isset($this->tokenParsers[$parser->getTag()])) {
throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag()));
}
$this->tokenParsers[$parser->getTag()] = $parser;
}
public function getTokenParsers(): array
{
return $this->tokenParsers;
}
public function addTest(TwigTest $test): void
{
if (isset($this->tests[$test->getName()])) {
throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName()));
}
$this->tests[$test->getName()] = $test;
}
public function getTests(): array
{
return $this->tests;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension {
use Twig\FileExtensionEscapingStrategy;
use Twig\NodeVisitor\EscaperNodeVisitor;
use Twig\TokenParser\AutoEscapeTokenParser;
use Twig\TwigFilter;
final class EscaperExtension extends AbstractExtension
{
private $defaultStrategy;
private $escapers = [];
/** @internal */
public $safeClasses = [];
/** @internal */
public $safeLookup = [];
/**
* @param string|false|callable $defaultStrategy An escaping strategy
*
* @see setDefaultStrategy()
*/
public function __construct($defaultStrategy = 'html')
{
$this->setDefaultStrategy($defaultStrategy);
}
public function getTokenParsers(): array
{
return [new AutoEscapeTokenParser()];
}
public function getNodeVisitors(): array
{
return [new EscaperNodeVisitor()];
}
public function getFilters(): array
{
return [
new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']),
new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]),
];
}
/**
* Sets the default strategy to use when not defined by the user.
*
* The strategy can be a valid PHP callback that takes the template
* name as an argument and returns the strategy to use.
*
* @param string|false|callable $defaultStrategy An escaping strategy
*/
public function setDefaultStrategy($defaultStrategy): void
{
if ('name' === $defaultStrategy) {
$defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess'];
}
$this->defaultStrategy = $defaultStrategy;
}
/**
* Gets the default strategy to use when not defined by the user.
*
* @param string $name The template name
*
* @return string|false The default strategy to use for the template
*/
public function getDefaultStrategy(string $name)
{
// disable string callables to avoid calling a function named html or js,
// or any other upcoming escaping strategy
if (!\is_string($this->defaultStrategy) && false !== $this->defaultStrategy) {
return \call_user_func($this->defaultStrategy, $name);
}
return $this->defaultStrategy;
}
/**
* Defines a new escaper to be used via the escape filter.
*
* @param string $strategy The strategy name that should be used as a strategy in the escape call
* @param callable $callable A valid PHP callable
*/
public function setEscaper($strategy, callable $callable)
{
$this->escapers[$strategy] = $callable;
}
/**
* Gets all defined escapers.
*
* @return callable[] An array of escapers
*/
public function getEscapers()
{
return $this->escapers;
}
public function setSafeClasses(array $safeClasses = [])
{
$this->safeClasses = [];
$this->safeLookup = [];
foreach ($safeClasses as $class => $strategies) {
$this->addSafeClass($class, $strategies);
}
}
public function addSafeClass(string $class, array $strategies)
{
$class = ltrim($class, '\\');
if (!isset($this->safeClasses[$class])) {
$this->safeClasses[$class] = [];
}
$this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies);
foreach ($strategies as $strategy) {
$this->safeLookup[$strategy][$class] = true;
}
}
}
}
namespace {
use Twig\Environment;
use Twig\Error\RuntimeError;
use Twig\Extension\EscaperExtension;
use Twig\Markup;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Node;
/**
* Marks a variable as being safe.
*
* @param string $string A PHP variable
*/
function twig_raw_filter($string)
{
return $string;
}
/**
* Escapes a string.
*
* @param mixed $string The value to be escaped
* @param string $strategy The escaping strategy
* @param string $charset The charset
* @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
*
* @return string
*/
function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
{
if ($autoescape && $string instanceof Markup) {
return $string;
}
if (!\is_string($string)) {
if (\is_object($string) && method_exists($string, '__toString')) {
if ($autoescape) {
$c = \get_class($string);
$ext = $env->getExtension(EscaperExtension::class);
if (!isset($ext->safeClasses[$c])) {
$ext->safeClasses[$c] = [];
foreach (class_parents($string) + class_implements($string) as $class) {
if (isset($ext->safeClasses[$class])) {
$ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class]));
foreach ($ext->safeClasses[$class] as $s) {
$ext->safeLookup[$s][$c] = true;
}
}
}
}
if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) {
return (string) $string;
}
}
$string = (string) $string;
} elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) {
return $string;
}
}
if ('' === $string) {
return '';
}
if (null === $charset) {
$charset = $env->getCharset();
}
switch ($strategy) {
case 'html':
// see https://secure.php.net/htmlspecialchars
// Using a static variable to avoid initializing the array
// each time the function is called. Moving the declaration on the
// top of the function slow downs other escaping strategies.
static $htmlspecialcharsCharsets = [
'ISO-8859-1' => true, 'ISO8859-1' => true,
'ISO-8859-15' => true, 'ISO8859-15' => true,
'utf-8' => true, 'UTF-8' => true,
'CP866' => true, 'IBM866' => true, '866' => true,
'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
'1251' => true,
'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
'BIG5' => true, '950' => true,
'GB2312' => true, '936' => true,
'BIG5-HKSCS' => true,
'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
'EUC-JP' => true, 'EUCJP' => true,
'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
];
if (isset($htmlspecialcharsCharsets[$charset])) {
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
}
if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
// cache the lowercase variant for future iterations
$htmlspecialcharsCharsets[$charset] = true;
return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
}
$string = twig_convert_encoding($string, 'UTF-8', $charset);
$string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
return iconv('UTF-8', $charset, $string);
case 'js':
// escape all non-alphanumeric characters
// into their \x or \uHHHH representations
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
$char = $matches[0];
/*
* A few characters have short escape sequences in JSON and JavaScript.
* Escape sequences supported only by JavaScript, not JSON, are omitted.
* \" is also supported but omitted, because the resulting string is not HTML safe.
*/
static $shortMap = [
'\\' => '\\\\',
'/' => '\\/',
"\x08" => '\b',
"\x0C" => '\f',
"\x0A" => '\n',
"\x0D" => '\r',
"\x09" => '\t',
];
if (isset($shortMap[$char])) {
return $shortMap[$char];
}
$codepoint = mb_ord($char);
if (0x10000 > $codepoint) {
return sprintf('\u%04X', $codepoint);
}
// Split characters outside the BMP into surrogate pairs
// https://tools.ietf.org/html/rfc2781.html#section-2.1
$u = $codepoint - 0x10000;
$high = 0xD800 | ($u >> 10);
$low = 0xDC00 | ($u & 0x3FF);
return sprintf('\u%04X\u%04X', $high, $low);
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'css':
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
$char = $matches[0];
return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8'));
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'html_attr':
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, 'UTF-8', $charset);
}
if (!preg_match('//u', $string)) {
throw new RuntimeError('The string to escape is not a valid UTF-8 string.');
}
$string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
/**
* This function is adapted from code coming from Zend Framework.
*
* @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
* @license https://framework.zend.com/license/new-bsd New BSD License
*/
$chr = $matches[0];
$ord = \ord($chr);
/*
* The following replaces characters undefined in HTML with the
* hex entity for the Unicode replacement character.
*/
if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) {
return '&#xFFFD;';
}
/*
* Check if the current character to escape has a name entity we should
* replace it with while grabbing the hex value of the character.
*/
if (1 === \strlen($chr)) {
/*
* While HTML supports far more named entities, the lowest common denominator
* has become HTML5's XML Serialisation which is restricted to the those named
* entities that XML supports. Using HTML entities would result in this error:
* XML Parsing Error: undefined entity
*/
static $entityMap = [
34 => '&quot;', /* quotation mark */
38 => '&amp;', /* ampersand */
60 => '&lt;', /* less-than sign */
62 => '&gt;', /* greater-than sign */
];
if (isset($entityMap[$ord])) {
return $entityMap[$ord];
}
return sprintf('&#x%02X;', $ord);
}
/*
* Per OWASP recommendations, we'll use hex entities for any other
* characters where a named entity does not exist.
*/
return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8'));
}, $string);
if ('UTF-8' !== $charset) {
$string = iconv('UTF-8', $charset, $string);
}
return $string;
case 'url':
return rawurlencode($string);
default:
static $escapers;
if (null === $escapers) {
$escapers = $env->getExtension(EscaperExtension::class)->getEscapers();
}
if (isset($escapers[$strategy])) {
return $escapers[$strategy]($env, $string, $charset);
}
$validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers)));
throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
}
}
/**
* @internal
*/
function twig_escape_filter_is_safe(Node $filterArgs)
{
foreach ($filterArgs as $arg) {
if ($arg instanceof ConstantExpression) {
return [$arg->getAttribute('value')];
}
return [];
}
return ['html'];
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension;
use Twig\NodeVisitor\SandboxNodeVisitor;
use Twig\Sandbox\SecurityNotAllowedMethodError;
use Twig\Sandbox\SecurityNotAllowedPropertyError;
use Twig\Sandbox\SecurityPolicyInterface;
use Twig\Source;
use Twig\TokenParser\SandboxTokenParser;
final class SandboxExtension extends AbstractExtension
{
private $sandboxedGlobally;
private $sandboxed;
private $policy;
public function __construct(SecurityPolicyInterface $policy, $sandboxed = false)
{
$this->policy = $policy;
$this->sandboxedGlobally = $sandboxed;
}
public function getTokenParsers(): array
{
return [new SandboxTokenParser()];
}
public function getNodeVisitors(): array
{
return [new SandboxNodeVisitor()];
}
public function enableSandbox(): void
{
$this->sandboxed = true;
}
public function disableSandbox(): void
{
$this->sandboxed = false;
}
public function isSandboxed(): bool
{
return $this->sandboxedGlobally || $this->sandboxed;
}
public function isSandboxedGlobally(): bool
{
return $this->sandboxedGlobally;
}
public function setSecurityPolicy(SecurityPolicyInterface $policy)
{
$this->policy = $policy;
}
public function getSecurityPolicy(): SecurityPolicyInterface
{
return $this->policy;
}
public function checkSecurity($tags, $filters, $functions): void
{
if ($this->isSandboxed()) {
$this->policy->checkSecurity($tags, $filters, $functions);
}
}
public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null): void
{
if ($this->isSandboxed()) {
try {
$this->policy->checkMethodAllowed($obj, $method);
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
}
public function checkPropertyAllowed($obj, $method, int $lineno = -1, Source $source = null): void
{
if ($this->isSandboxed()) {
try {
$this->policy->checkPropertyAllowed($obj, $method);
} catch (SecurityNotAllowedPropertyError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
}
public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null)
{
if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) {
try {
$this->policy->checkMethodAllowed($obj, '__toString');
} catch (SecurityNotAllowedMethodError $e) {
$e->setSourceContext($source);
$e->setTemplateLine($lineno);
throw $e;
}
}
return $obj;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension;
use Twig\NodeVisitor\OptimizerNodeVisitor;
final class OptimizerExtension extends AbstractExtension
{
private $optimizers;
public function __construct(int $optimizers = -1)
{
$this->optimizers = $optimizers;
}
public function getNodeVisitors(): array
{
return [new OptimizerNodeVisitor($this->optimizers)];
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension;
/**
* Enables usage of the deprecated Twig\Extension\AbstractExtension::getGlobals() method.
*
* Explicitly implement this interface if you really need to implement the
* deprecated getGlobals() method in your extensions.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface GlobalsInterface
{
public function getGlobals(): array;
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Extension {
use Twig\ExpressionParser;
use Twig\Node\Expression\Binary\AddBinary;
use Twig\Node\Expression\Binary\AndBinary;
use Twig\Node\Expression\Binary\BitwiseAndBinary;
use Twig\Node\Expression\Binary\BitwiseOrBinary;
use Twig\Node\Expression\Binary\BitwiseXorBinary;
use Twig\Node\Expression\Binary\ConcatBinary;
use Twig\Node\Expression\Binary\DivBinary;
use Twig\Node\Expression\Binary\EndsWithBinary;
use Twig\Node\Expression\Binary\EqualBinary;
use Twig\Node\Expression\Binary\FloorDivBinary;
use Twig\Node\Expression\Binary\GreaterBinary;
use Twig\Node\Expression\Binary\GreaterEqualBinary;
use Twig\Node\Expression\Binary\InBinary;
use Twig\Node\Expression\Binary\LessBinary;
use Twig\Node\Expression\Binary\LessEqualBinary;
use Twig\Node\Expression\Binary\MatchesBinary;
use Twig\Node\Expression\Binary\ModBinary;
use Twig\Node\Expression\Binary\MulBinary;
use Twig\Node\Expression\Binary\NotEqualBinary;
use Twig\Node\Expression\Binary\NotInBinary;
use Twig\Node\Expression\Binary\OrBinary;
use Twig\Node\Expression\Binary\PowerBinary;
use Twig\Node\Expression\Binary\RangeBinary;
use Twig\Node\Expression\Binary\SpaceshipBinary;
use Twig\Node\Expression\Binary\StartsWithBinary;
use Twig\Node\Expression\Binary\SubBinary;
use Twig\Node\Expression\Filter\DefaultFilter;
use Twig\Node\Expression\NullCoalesceExpression;
use Twig\Node\Expression\Test\ConstantTest;
use Twig\Node\Expression\Test\DefinedTest;
use Twig\Node\Expression\Test\DivisiblebyTest;
use Twig\Node\Expression\Test\EvenTest;
use Twig\Node\Expression\Test\NullTest;
use Twig\Node\Expression\Test\OddTest;
use Twig\Node\Expression\Test\SameasTest;
use Twig\Node\Expression\Unary\NegUnary;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Expression\Unary\PosUnary;
use Twig\NodeVisitor\MacroAutoImportNodeVisitor;
use Twig\TokenParser\ApplyTokenParser;
use Twig\TokenParser\BlockTokenParser;
use Twig\TokenParser\DeprecatedTokenParser;
use Twig\TokenParser\DoTokenParser;
use Twig\TokenParser\EmbedTokenParser;
use Twig\TokenParser\ExtendsTokenParser;
use Twig\TokenParser\FlushTokenParser;
use Twig\TokenParser\ForTokenParser;
use Twig\TokenParser\FromTokenParser;
use Twig\TokenParser\IfTokenParser;
use Twig\TokenParser\ImportTokenParser;
use Twig\TokenParser\IncludeTokenParser;
use Twig\TokenParser\MacroTokenParser;
use Twig\TokenParser\SetTokenParser;
use Twig\TokenParser\UseTokenParser;
use Twig\TokenParser\WithTokenParser;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
final class CoreExtension extends AbstractExtension
{
private $dateFormats = ['F j, Y H:i', '%d days'];
private $numberFormat = [0, '.', ','];
private $timezone = null;
/**
* Sets the default format to be used by the date filter.
*
* @param string $format The default date format string
* @param string $dateIntervalFormat The default date interval format string
*/
public function setDateFormat($format = null, $dateIntervalFormat = null)
{
if (null !== $format) {
$this->dateFormats[0] = $format;
}
if (null !== $dateIntervalFormat) {
$this->dateFormats[1] = $dateIntervalFormat;
}
}
/**
* Gets the default format to be used by the date filter.
*
* @return array The default date format string and the default date interval format string
*/
public function getDateFormat()
{
return $this->dateFormats;
}
/**
* Sets the default timezone to be used by the date filter.
*
* @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object
*/
public function setTimezone($timezone)
{
$this->timezone = $timezone instanceof \DateTimeZone ? $timezone : new \DateTimeZone($timezone);
}
/**
* Gets the default timezone to be used by the date filter.
*
* @return \DateTimeZone The default timezone currently in use
*/
public function getTimezone()
{
if (null === $this->timezone) {
$this->timezone = new \DateTimeZone(date_default_timezone_get());
}
return $this->timezone;
}
/**
* Sets the default format to be used by the number_format filter.
*
* @param int $decimal the number of decimal places to use
* @param string $decimalPoint the character(s) to use for the decimal point
* @param string $thousandSep the character(s) to use for the thousands separator
*/
public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
{
$this->numberFormat = [$decimal, $decimalPoint, $thousandSep];
}
/**
* Get the default format used by the number_format filter.
*
* @return array The arguments for number_format()
*/
public function getNumberFormat()
{
return $this->numberFormat;
}
public function getTokenParsers(): array
{
return [
new ApplyTokenParser(),
new ForTokenParser(),
new IfTokenParser(),
new ExtendsTokenParser(),
new IncludeTokenParser(),
new BlockTokenParser(),
new UseTokenParser(),
new MacroTokenParser(),
new ImportTokenParser(),
new FromTokenParser(),
new SetTokenParser(),
new FlushTokenParser(),
new DoTokenParser(),
new EmbedTokenParser(),
new WithTokenParser(),
new DeprecatedTokenParser(),
];
}
public function getFilters(): array
{
return [
// formatting filters
new TwigFilter('date', 'twig_date_format_filter', ['needs_environment' => true]),
new TwigFilter('date_modify', 'twig_date_modify_filter', ['needs_environment' => true]),
new TwigFilter('format', 'sprintf'),
new TwigFilter('replace', 'twig_replace_filter'),
new TwigFilter('number_format', 'twig_number_format_filter', ['needs_environment' => true]),
new TwigFilter('abs', 'abs'),
new TwigFilter('round', 'twig_round'),
// encoding
new TwigFilter('url_encode', 'twig_urlencode_filter'),
new TwigFilter('json_encode', 'json_encode'),
new TwigFilter('convert_encoding', 'twig_convert_encoding'),
// string filters
new TwigFilter('title', 'twig_title_string_filter', ['needs_environment' => true]),
new TwigFilter('capitalize', 'twig_capitalize_string_filter', ['needs_environment' => true]),
new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]),
new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]),
new TwigFilter('striptags', 'strip_tags'),
new TwigFilter('trim', 'twig_trim_filter'),
new TwigFilter('nl2br', 'nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]),
new TwigFilter('spaceless', 'twig_spaceless', ['is_safe' => ['html']]),
// array helpers
new TwigFilter('join', 'twig_join_filter'),
new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]),
new TwigFilter('sort', 'twig_sort_filter'),
new TwigFilter('merge', 'twig_array_merge'),
new TwigFilter('batch', 'twig_array_batch'),
new TwigFilter('column', 'twig_array_column'),
new TwigFilter('filter', 'twig_array_filter', ['needs_environment' => true]),
new TwigFilter('map', 'twig_array_map', ['needs_environment' => true]),
new TwigFilter('reduce', 'twig_array_reduce', ['needs_environment' => true]),
// string/array filters
new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]),
new TwigFilter('length', 'twig_length_filter', ['needs_environment' => true]),
new TwigFilter('slice', 'twig_slice', ['needs_environment' => true]),
new TwigFilter('first', 'twig_first', ['needs_environment' => true]),
new TwigFilter('last', 'twig_last', ['needs_environment' => true]),
// iteration and runtime
new TwigFilter('default', '_twig_default_filter', ['node_class' => DefaultFilter::class]),
new TwigFilter('keys', 'twig_get_array_keys_filter'),
];
}
public function getFunctions(): array
{
return [
new TwigFunction('max', 'max'),
new TwigFunction('min', 'min'),
new TwigFunction('range', 'range'),
new TwigFunction('constant', 'twig_constant'),
new TwigFunction('cycle', 'twig_cycle'),
new TwigFunction('random', 'twig_random', ['needs_environment' => true]),
new TwigFunction('date', 'twig_date_converter', ['needs_environment' => true]),
new TwigFunction('include', 'twig_include', ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]),
new TwigFunction('source', 'twig_source', ['needs_environment' => true, 'is_safe' => ['all']]),
];
}
public function getTests(): array
{
return [
new TwigTest('even', null, ['node_class' => EvenTest::class]),
new TwigTest('odd', null, ['node_class' => OddTest::class]),
new TwigTest('defined', null, ['node_class' => DefinedTest::class]),
new TwigTest('same as', null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]),
new TwigTest('none', null, ['node_class' => NullTest::class]),
new TwigTest('null', null, ['node_class' => NullTest::class]),
new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]),
new TwigTest('constant', null, ['node_class' => ConstantTest::class]),
new TwigTest('empty', 'twig_test_empty'),
new TwigTest('iterable', 'twig_test_iterable'),
];
}
public function getNodeVisitors(): array
{
return [new MacroAutoImportNodeVisitor()];
}
public function getOperators(): array
{
return [
[
'not' => ['precedence' => 50, 'class' => NotUnary::class],
'-' => ['precedence' => 500, 'class' => NegUnary::class],
'+' => ['precedence' => 500, 'class' => PosUnary::class],
],
[
'or' => ['precedence' => 10, 'class' => OrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'and' => ['precedence' => 15, 'class' => AndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'b-or' => ['precedence' => 16, 'class' => BitwiseOrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'b-xor' => ['precedence' => 17, 'class' => BitwiseXorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'b-and' => ['precedence' => 18, 'class' => BitwiseAndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'==' => ['precedence' => 20, 'class' => EqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'!=' => ['precedence' => 20, 'class' => NotEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'<=>' => ['precedence' => 20, 'class' => SpaceshipBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'<' => ['precedence' => 20, 'class' => LessBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'>' => ['precedence' => 20, 'class' => GreaterBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'>=' => ['precedence' => 20, 'class' => GreaterEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'<=' => ['precedence' => 20, 'class' => LessEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'not in' => ['precedence' => 20, 'class' => NotInBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'in' => ['precedence' => 20, 'class' => InBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'matches' => ['precedence' => 20, 'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'starts with' => ['precedence' => 20, 'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'ends with' => ['precedence' => 20, 'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'~' => ['precedence' => 40, 'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'*' => ['precedence' => 60, 'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'/' => ['precedence' => 60, 'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'//' => ['precedence' => 60, 'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'%' => ['precedence' => 60, 'class' => ModBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'is' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'is not' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT],
'**' => ['precedence' => 200, 'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
'??' => ['precedence' => 300, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
],
];
}
}
}
namespace {
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
use Twig\Extension\SandboxExtension;
use Twig\Markup;
use Twig\Source;
use Twig\Template;
use Twig\TemplateWrapper;
/**
* Cycles over a value.
*
* @param \ArrayAccess|array $values
* @param int $position The cycle position
*
* @return string The next value in the cycle
*/
function twig_cycle($values, $position)
{
if (!\is_array($values) && !$values instanceof \ArrayAccess) {
return $values;
}
return $values[$position % \count($values)];
}
/**
* Returns a random value depending on the supplied parameter type:
* - a random item from a \Traversable or array
* - a random character from a string
* - a random integer between 0 and the integer parameter.
*
* @param \Traversable|array|int|float|string $values The values to pick a random item from
* @param int|null $max Maximum value used when $values is an int
*
* @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is)
*
* @return mixed A random value from the given sequence
*/
function twig_random(Environment $env, $values = null, $max = null)
{
if (null === $values) {
return null === $max ? mt_rand() : mt_rand(0, $max);
}
if (\is_int($values) || \is_float($values)) {
if (null === $max) {
if ($values < 0) {
$max = 0;
$min = $values;
} else {
$max = $values;
$min = 0;
}
} else {
$min = $values;
$max = $max;
}
return mt_rand($min, $max);
}
if (\is_string($values)) {
if ('' === $values) {
return '';
}
$charset = $env->getCharset();
if ('UTF-8' !== $charset) {
$values = twig_convert_encoding($values, 'UTF-8', $charset);
}
// unicode version of str_split()
// split at all positions, but not after the start and not before the end
$values = preg_split('/(?<!^)(?!$)/u', $values);
if ('UTF-8' !== $charset) {
foreach ($values as $i => $value) {
$values[$i] = twig_convert_encoding($value, $charset, 'UTF-8');
}
}
}
if (!twig_test_iterable($values)) {
return $values;
}
$values = twig_to_array($values);
if (0 === \count($values)) {
throw new RuntimeError('The random function cannot pick from an empty array.');
}
return $values[array_rand($values, 1)];
}
/**
* Converts a date to the given format.
*
* {{ post.published_at|date("m/d/Y") }}
*
* @param \DateTimeInterface|\DateInterval|string $date A date
* @param string|null $format The target format, null to use the default
* @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
*
* @return string The formatted date
*/
function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null)
{
if (null === $format) {
$formats = $env->getExtension(CoreExtension::class)->getDateFormat();
$format = $date instanceof \DateInterval ? $formats[1] : $formats[0];
}
if ($date instanceof \DateInterval) {
return $date->format($format);
}
return twig_date_converter($env, $date, $timezone)->format($format);
}
/**
* Returns a new date object modified.
*
* {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
*
* @param \DateTimeInterface|string $date A date
* @param string $modifier A modifier string
*
* @return \DateTimeInterface
*/
function twig_date_modify_filter(Environment $env, $date, $modifier)
{
$date = twig_date_converter($env, $date, false);
return $date->modify($modifier);
}
/**
* Converts an input to a \DateTime instance.
*
* {% if date(user.created_at) < date('+2days') %}
* {# do something #}
* {% endif %}
*
* @param \DateTimeInterface|string|null $date A date or null to use the current time
* @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
*
* @return \DateTimeInterface
*/
function twig_date_converter(Environment $env, $date = null, $timezone = null)
{
// determine the timezone
if (false !== $timezone) {
if (null === $timezone) {
$timezone = $env->getExtension(CoreExtension::class)->getTimezone();
} elseif (!$timezone instanceof \DateTimeZone) {
$timezone = new \DateTimeZone($timezone);
}
}
// immutable dates
if ($date instanceof \DateTimeImmutable) {
return false !== $timezone ? $date->setTimezone($timezone) : $date;
}
if ($date instanceof \DateTimeInterface) {
$date = clone $date;
if (false !== $timezone) {
$date->setTimezone($timezone);
}
return $date;
}
if (null === $date || 'now' === $date) {
return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension(CoreExtension::class)->getTimezone());
}
$asString = (string) $date;
if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {
$date = new \DateTime('@'.$date);
} else {
$date = new \DateTime($date, $env->getExtension(CoreExtension::class)->getTimezone());
}
if (false !== $timezone) {
$date->setTimezone($timezone);
}
return $date;
}
/**
* Replaces strings within a string.
*
* @param string $str String to replace in
* @param array|\Traversable $from Replace values
*
* @return string
*/
function twig_replace_filter($str, $from)
{
if (!twig_test_iterable($from)) {
throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from)));
}
return strtr($str, twig_to_array($from));
}
/**
* Rounds a number.
*
* @param int|float $value The value to round
* @param int|float $precision The rounding precision
* @param string $method The method to use for rounding
*
* @return int|float The rounded number
*/
function twig_round($value, $precision = 0, $method = 'common')
{
if ('common' === $method) {
return round($value, $precision);
}
if ('ceil' !== $method && 'floor' !== $method) {
throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.');
}
return $method($value * 10 ** $precision) / 10 ** $precision;
}
/**
* Number format filter.
*
* All of the formatting options can be left null, in that case the defaults will
* be used. Supplying any of the parameters will override the defaults set in the
* environment object.
*
* @param mixed $number A float/int/string of the number to format
* @param int $decimal the number of decimal points to display
* @param string $decimalPoint the character(s) to use for the decimal point
* @param string $thousandSep the character(s) to use for the thousands separator
*
* @return string The formatted number
*/
function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)
{
$defaults = $env->getExtension(CoreExtension::class)->getNumberFormat();
if (null === $decimal) {
$decimal = $defaults[0];
}
if (null === $decimalPoint) {
$decimalPoint = $defaults[1];
}
if (null === $thousandSep) {
$thousandSep = $defaults[2];
}
return number_format((float) $number, $decimal, $decimalPoint, $thousandSep);
}
/**
* URL encodes (RFC 3986) a string as a path segment or an array as a query string.
*
* @param string|array $url A URL or an array of query parameters
*
* @return string The URL encoded value
*/
function twig_urlencode_filter($url)
{
if (\is_array($url)) {
return http_build_query($url, '', '&', PHP_QUERY_RFC3986);
}
return rawurlencode($url);
}
/**
* Merges an array with another one.
*
* {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
*
* {% set items = items|merge({ 'peugeot': 'car' }) %}
*
* {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
*
* @param array|\Traversable $arr1 An array
* @param array|\Traversable $arr2 An array
*
* @return array The merged array
*/
function twig_array_merge($arr1, $arr2)
{
if (!twig_test_iterable($arr1)) {
throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1)));
}
if (!twig_test_iterable($arr2)) {
throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2)));
}
return array_merge(twig_to_array($arr1), twig_to_array($arr2));
}
/**
* Slices a variable.
*
* @param mixed $item A variable
* @param int $start Start of the slice
* @param int $length Size of the slice
* @param bool $preserveKeys Whether to preserve key or not (when the input is an array)
*
* @return mixed The sliced variable
*/
function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false)
{
if ($item instanceof \Traversable) {
while ($item instanceof \IteratorAggregate) {
$item = $item->getIterator();
}
if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) {
try {
return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys);
} catch (\OutOfBoundsException $e) {
return [];
}
}
$item = iterator_to_array($item, $preserveKeys);
}
if (\is_array($item)) {
return \array_slice($item, $start, $length, $preserveKeys);
}
$item = (string) $item;
return (string) mb_substr($item, $start, $length, $env->getCharset());
}
/**
* Returns the first element of the item.
*
* @param mixed $item A variable
*
* @return mixed The first element of the item
*/
function twig_first(Environment $env, $item)
{
$elements = twig_slice($env, $item, 0, 1, false);
return \is_string($elements) ? $elements : current($elements);
}
/**
* Returns the last element of the item.
*
* @param mixed $item A variable
*
* @return mixed The last element of the item
*/
function twig_last(Environment $env, $item)
{
$elements = twig_slice($env, $item, -1, 1, false);
return \is_string($elements) ? $elements : current($elements);
}
/**
* Joins the values to a string.
*
* The separators between elements are empty strings per default, you can define them with the optional parameters.
*
* {{ [1, 2, 3]|join(', ', ' and ') }}
* {# returns 1, 2 and 3 #}
*
* {{ [1, 2, 3]|join('|') }}
* {# returns 1|2|3 #}
*
* {{ [1, 2, 3]|join }}
* {# returns 123 #}
*
* @param array $value An array
* @param string $glue The separator
* @param string|null $and The separator for the last pair
*
* @return string The concatenated string
*/
function twig_join_filter($value, $glue = '', $and = null)
{
if (!twig_test_iterable($value)) {
$value = (array) $value;
}
$value = twig_to_array($value, false);
if (0 === \count($value)) {
return '';
}
if (null === $and || $and === $glue) {
return implode($glue, $value);
}
if (1 === \count($value)) {
return $value[0];
}
return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1];
}
/**
* Splits the string into an array.
*
* {{ "one,two,three"|split(',') }}
* {# returns [one, two, three] #}
*
* {{ "one,two,three,four,five"|split(',', 3) }}
* {# returns [one, two, "three,four,five"] #}
*
* {{ "123"|split('') }}
* {# returns [1, 2, 3] #}
*
* {{ "aabbcc"|split('', 2) }}
* {# returns [aa, bb, cc] #}
*
* @param string $value A string
* @param string $delimiter The delimiter
* @param int $limit The limit
*
* @return array The split string as an array
*/
function twig_split_filter(Environment $env, $value, $delimiter, $limit = null)
{
if (\strlen($delimiter) > 0) {
return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
}
if ($limit <= 1) {
return preg_split('/(?<!^)(?!$)/u', $value);
}
$length = mb_strlen($value, $env->getCharset());
if ($length < $limit) {
return [$value];
}
$r = [];
for ($i = 0; $i < $length; $i += $limit) {
$r[] = mb_substr($value, $i, $limit, $env->getCharset());
}
return $r;
}
// The '_default' filter is used internally to avoid using the ternary operator
// which costs a lot for big contexts (before PHP 5.4). So, on average,
// a function call is cheaper.
/**
* @internal
*/
function _twig_default_filter($value, $default = '')
{
if (twig_test_empty($value)) {
return $default;
}
return $value;
}
/**
* Returns the keys for the given array.
*
* It is useful when you want to iterate over the keys of an array:
*
* {% for key in array|keys %}
* {# ... #}
* {% endfor %}
*
* @param array $array An array
*
* @return array The keys
*/
function twig_get_array_keys_filter($array)
{
if ($array instanceof \Traversable) {
while ($array instanceof \IteratorAggregate) {
$array = $array->getIterator();
}
if ($array instanceof \Iterator) {
$keys = [];
$array->rewind();
while ($array->valid()) {
$keys[] = $array->key();
$array->next();
}
return $keys;
}
$keys = [];
foreach ($array as $key => $item) {
$keys[] = $key;
}
return $keys;
}
if (!\is_array($array)) {
return [];
}
return array_keys($array);
}
/**
* Reverses a variable.
*
* @param array|\Traversable|string $item An array, a \Traversable instance, or a string
* @param bool $preserveKeys Whether to preserve key or not
*
* @return mixed The reversed input
*/
function twig_reverse_filter(Environment $env, $item, $preserveKeys = false)
{
if ($item instanceof \Traversable) {
return array_reverse(iterator_to_array($item), $preserveKeys);
}
if (\is_array($item)) {
return array_reverse($item, $preserveKeys);
}
$string = (string) $item;
$charset = $env->getCharset();
if ('UTF-8' !== $charset) {
$item = twig_convert_encoding($string, 'UTF-8', $charset);
}
preg_match_all('/./us', $item, $matches);
$string = implode('', array_reverse($matches[0]));
if ('UTF-8' !== $charset) {
$string = twig_convert_encoding($string, $charset, 'UTF-8');
}
return $string;
}
/**
* Sorts an array.
*
* @param array|\Traversable $array
*
* @return array
*/
function twig_sort_filter($array, $arrow = null)
{
if ($array instanceof \Traversable) {
$array = iterator_to_array($array);
} elseif (!\is_array($array)) {
throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array)));
}
if (null !== $arrow) {
uasort($array, $arrow);
} else {
asort($array);
}
return $array;
}
/**
* @internal
*/
function twig_in_filter($value, $compare)
{
if ($value instanceof Markup) {
$value = (string) $value;
}
if ($compare instanceof Markup) {
$compare = (string) $compare;
}
if (\is_string($compare)) {
if (\is_string($value) || \is_int($value) || \is_float($value)) {
return '' === $value || false !== strpos($compare, (string) $value);
}
return false;
}
if (!is_iterable($compare)) {
return false;
}
if (\is_object($value) || \is_resource($value)) {
if (!\is_array($compare)) {
foreach ($compare as $item) {
if ($item === $value) {
return true;
}
}
return false;
}
return \in_array($value, $compare, true);
}
foreach ($compare as $item) {
if (0 === twig_compare($value, $item)) {
return true;
}
}
return false;
}
/**
* Compares two values using a more strict version of the PHP non-strict comparison operator.
*
* @see https://wiki.php.net/rfc/string_to_number_comparison
* @see https://wiki.php.net/rfc/trailing_whitespace_numerics
*
* @internal
*/
function twig_compare($a, $b)
{
// int <=> string
if (\is_int($a) && \is_string($b)) {
$bTrim = trim($b, " \t\n\r\v\f");
if (!is_numeric($bTrim)) {
return (string) $a <=> $b;
}
if ((int) $bTrim == $bTrim) {
return $a <=> (int) $bTrim;
} else {
return (float) $a <=> (float) $bTrim;
}
}
if (\is_string($a) && \is_int($b)) {
$aTrim = trim($a, " \t\n\r\v\f");
if (!is_numeric($aTrim)) {
return $a <=> (string) $b;
}
if ((int) $aTrim == $aTrim) {
return (int) $aTrim <=> $b;
} else {
return (float) $aTrim <=> (float) $b;
}
}
// float <=> string
if (\is_float($a) && \is_string($b)) {
if (is_nan($a)) {
return 1;
}
$bTrim = trim($b, " \t\n\r\v\f");
if (!is_numeric($bTrim)) {
return (string) $a <=> $b;
}
return $a <=> (float) $bTrim;
}
if (\is_string($a) && \is_float($b)) {
if (is_nan($b)) {
return 1;
}
$aTrim = trim($a, " \t\n\r\v\f");
if (!is_numeric($aTrim)) {
return $a <=> (string) $b;
}
return (float) $aTrim <=> $b;
}
// fallback to <=>
return $a <=> $b;
}
/**
* Returns a trimmed string.
*
* @return string
*
* @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both')
*/
function twig_trim_filter($string, $characterMask = null, $side = 'both')
{
if (null === $characterMask) {
$characterMask = " \t\n\r\0\x0B";
}
switch ($side) {
case 'both':
return trim($string, $characterMask);
case 'left':
return ltrim($string, $characterMask);
case 'right':
return rtrim($string, $characterMask);
default:
throw new RuntimeError('Trimming side must be "left", "right" or "both".');
}
}
/**
* Removes whitespaces between HTML tags.
*
* @return string
*/
function twig_spaceless($content)
{
return trim(preg_replace('/>\s+</', '><', $content));
}
function twig_convert_encoding($string, $to, $from)
{
if (!\function_exists('iconv')) {
throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
}
return iconv($from, $to, $string);
}
/**
* Returns the length of a variable.
*
* @param mixed $thing A variable
*
* @return int The length of the value
*/
function twig_length_filter(Environment $env, $thing)
{
if (null === $thing) {
return 0;
}
if (is_scalar($thing)) {
return mb_strlen($thing, $env->getCharset());
}
if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) {
return \count($thing);
}
if ($thing instanceof \Traversable) {
return iterator_count($thing);
}
if (method_exists($thing, '__toString') && !$thing instanceof \Countable) {
return mb_strlen((string) $thing, $env->getCharset());
}
return 1;
}
/**
* Converts a string to uppercase.
*
* @param string $string A string
*
* @return string The uppercased string
*/
function twig_upper_filter(Environment $env, $string)
{
return mb_strtoupper($string, $env->getCharset());
}
/**
* Converts a string to lowercase.
*
* @param string $string A string
*
* @return string The lowercased string
*/
function twig_lower_filter(Environment $env, $string)
{
return mb_strtolower($string, $env->getCharset());
}
/**
* Returns a titlecased string.
*
* @param string $string A string
*
* @return string The titlecased string
*/
function twig_title_string_filter(Environment $env, $string)
{
if (null !== $charset = $env->getCharset()) {
return mb_convert_case($string, MB_CASE_TITLE, $charset);
}
return ucwords(strtolower($string));
}
/**
* Returns a capitalized string.
*
* @param string $string A string
*
* @return string The capitalized string
*/
function twig_capitalize_string_filter(Environment $env, $string)
{
$charset = $env->getCharset();
return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, null, $charset), $charset);
}
/**
* @internal
*/
function twig_call_macro(Template $template, string $method, array $args, int $lineno, array $context, Source $source)
{
if (!method_exists($template, $method)) {
$parent = $template;
while ($parent = $parent->getParent($context)) {
if (method_exists($parent, $method)) {
return $parent->$method(...$args);
}
}
throw new RuntimeError(sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source);
}
return $template->$method(...$args);
}
/**
* @internal
*/
function twig_ensure_traversable($seq)
{
if ($seq instanceof \Traversable || \is_array($seq)) {
return $seq;
}
return [];
}
/**
* @internal
*/
function twig_to_array($seq, $preserveKeys = true)
{
if ($seq instanceof \Traversable) {
return iterator_to_array($seq, $preserveKeys);
}
if (!\is_array($seq)) {
return $seq;
}
return $preserveKeys ? $seq : array_values($seq);
}
/**
* Checks if a variable is empty.
*
* {# evaluates to true if the foo variable is null, false, or the empty string #}
* {% if foo is empty %}
* {# ... #}
* {% endif %}
*
* @param mixed $value A variable
*
* @return bool true if the value is empty, false otherwise
*/
function twig_test_empty($value)
{
if ($value instanceof \Countable) {
return 0 === \count($value);
}
if ($value instanceof \Traversable) {
return !iterator_count($value);
}
if (\is_object($value) && method_exists($value, '__toString')) {
return '' === (string) $value;
}
return '' === $value || false === $value || null === $value || [] === $value;
}
/**
* Checks if a variable is traversable.
*
* {# evaluates to true if the foo variable is an array or a traversable object #}
* {% if foo is iterable %}
* {# ... #}
* {% endif %}
*
* @param mixed $value A variable
*
* @return bool true if the value is traversable
*/
function twig_test_iterable($value)
{
return $value instanceof \Traversable || \is_array($value);
}
/**
* Renders a template.
*
* @param array $context
* @param string|array $template The template to render or an array of templates to try consecutively
* @param array $variables The variables to pass to the template
* @param bool $withContext
* @param bool $ignoreMissing Whether to ignore missing templates or not
* @param bool $sandboxed Whether to sandbox the template or not
*
* @return string The rendered template
*/
function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false)
{
$alreadySandboxed = false;
$sandbox = null;
if ($withContext) {
$variables = array_merge($context, $variables);
}
if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) {
$sandbox = $env->getExtension(SandboxExtension::class);
if (!$alreadySandboxed = $sandbox->isSandboxed()) {
$sandbox->enableSandbox();
}
foreach ((\is_array($template) ? $template : [$template]) as $name) {
// if a Template instance is passed, it might have been instantiated outside of a sandbox, check security
if ($name instanceof TemplateWrapper || $name instanceof Template) {
$name->unwrap()->checkSecurity();
}
}
}
try {
$loaded = null;
try {
$loaded = $env->resolveTemplate($template);
} catch (LoaderError $e) {
if (!$ignoreMissing) {
throw $e;
}
}
return $loaded ? $loaded->render($variables) : '';
} finally {
if ($isSandboxed && !$alreadySandboxed) {
$sandbox->disableSandbox();
}
}
}
/**
* Returns a template content without rendering it.
*
* @param string $name The template name
* @param bool $ignoreMissing Whether to ignore missing templates or not
*
* @return string The template source
*/
function twig_source(Environment $env, $name, $ignoreMissing = false)
{
$loader = $env->getLoader();
try {
return $loader->getSourceContext($name)->getCode();
} catch (LoaderError $e) {
if (!$ignoreMissing) {
throw $e;
}
}
}
/**
* Provides the ability to get constants from instances as well as class/global constants.
*
* @param string $constant The name of the constant
* @param object|null $object The object to get the constant from
*
* @return string
*/
function twig_constant($constant, $object = null)
{
if (null !== $object) {
$constant = \get_class($object).'::'.$constant;
}
return \constant($constant);
}
/**
* Checks if a constant exists.
*
* @param string $constant The name of the constant
* @param object|null $object The object to get the constant from
*
* @return bool
*/
function twig_constant_is_defined($constant, $object = null)
{
if (null !== $object) {
$constant = \get_class($object).'::'.$constant;
}
return \defined($constant);
}
/**
* Batches item.
*
* @param array $items An array of items
* @param int $size The size of the batch
* @param mixed $fill A value used to fill missing items
*
* @return array
*/
function twig_array_batch($items, $size, $fill = null, $preserveKeys = true)
{
if (!twig_test_iterable($items)) {
throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items)));
}
$size = ceil($size);
$result = array_chunk(twig_to_array($items, $preserveKeys), $size, $preserveKeys);
if (null !== $fill && $result) {
$last = \count($result) - 1;
if ($fillCount = $size - \count($result[$last])) {
for ($i = 0; $i < $fillCount; ++$i) {
$result[$last][] = $fill;
}
}
}
return $result;
}
/**
* Returns the attribute value for a given array/object.
*
* @param mixed $object The object or array from where to get the item
* @param mixed $item The item to get from the array or object
* @param array $arguments An array of arguments to pass if the item is an object method
* @param string $type The type of attribute (@see \Twig\Template constants)
* @param bool $isDefinedTest Whether this is only a defined check
* @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not
* @param int $lineno The template line where the attribute was called
*
* @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
*
* @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
*
* @internal
*/
function twig_get_attribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = /* Template::ANY_CALL */ 'any', $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1)
{
// array
if (/* Template::METHOD_CALL */ 'method' !== $type) {
$arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item;
if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object)))
|| ($object instanceof ArrayAccess && isset($object[$arrayItem]))
) {
if ($isDefinedTest) {
return true;
}
return $object[$arrayItem];
}
if (/* Template::ARRAY_CALL */ 'array' === $type || !\is_object($object)) {
if ($isDefinedTest) {
return false;
}
if ($ignoreStrictCheck || !$env->isStrictVariables()) {
return;
}
if ($object instanceof ArrayAccess) {
$message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object));
} elseif (\is_object($object)) {
$message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object));
} elseif (\is_array($object)) {
if (empty($object)) {
$message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem);
} else {
$message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object)));
}
} elseif (/* Template::ARRAY_CALL */ 'array' === $type) {
if (null === $object) {
$message = sprintf('Impossible to access a key ("%s") on a null variable.', $item);
} else {
$message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object);
}
} elseif (null === $object) {
$message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item);
} else {
$message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object);
}
throw new RuntimeError($message, $lineno, $source);
}
}
if (!\is_object($object)) {
if ($isDefinedTest) {
return false;
}
if ($ignoreStrictCheck || !$env->isStrictVariables()) {
return;
}
if (null === $object) {
$message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item);
} elseif (\is_array($object)) {
$message = sprintf('Impossible to invoke a method ("%s") on an array.', $item);
} else {
$message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object);
}
throw new RuntimeError($message, $lineno, $source);
}
if ($object instanceof Template) {
throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source);
}
// object property
if (/* Template::METHOD_CALL */ 'method' !== $type) {
if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) {
if ($isDefinedTest) {
return true;
}
if ($sandboxed) {
$env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source);
}
return $object->$item;
}
}
static $cache = [];
$class = \get_class($object);
// object method
// precedence: getXxx() > isXxx() > hasXxx()
if (!isset($cache[$class])) {
$methods = get_class_methods($object);
sort($methods);
$lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods);
$classCache = [];
foreach ($methods as $i => $method) {
$classCache[$method] = $method;
$classCache[$lcName = $lcMethods[$i]] = $method;
if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) {
$name = substr($method, 3);
$lcName = substr($lcName, 3);
} elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) {
$name = substr($method, 2);
$lcName = substr($lcName, 2);
} elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) {
$name = substr($method, 3);
$lcName = substr($lcName, 3);
if (\in_array('is'.$lcName, $lcMethods)) {
continue;
}
} else {
continue;
}
// skip get() and is() methods (in which case, $name is empty)
if ($name) {
if (!isset($classCache[$name])) {
$classCache[$name] = $method;
}
if (!isset($classCache[$lcName])) {
$classCache[$lcName] = $method;
}
}
}
$cache[$class] = $classCache;
}
$call = false;
if (isset($cache[$class][$item])) {
$method = $cache[$class][$item];
} elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) {
$method = $cache[$class][$lcItem];
} elseif (isset($cache[$class]['__call'])) {
$method = $item;
$call = true;
} else {
if ($isDefinedTest) {
return false;
}
if ($ignoreStrictCheck || !$env->isStrictVariables()) {
return;
}
throw new RuntimeError(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source);
}
if ($isDefinedTest) {
return true;
}
if ($sandboxed) {
$env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source);
}
// Some objects throw exceptions when they have __call, and the method we try
// to call is not supported. If ignoreStrictCheck is true, we should return null.
try {
$ret = $object->$method(...$arguments);
} catch (\BadMethodCallException $e) {
if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) {
return;
}
throw $e;
}
return $ret;
}
/**
* Returns the values from a single column in the input array.
*
* <pre>
* {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
*
* {% set fruits = items|column('fruit') %}
*
* {# fruits now contains ['apple', 'orange'] #}
* </pre>
*
* @param array|Traversable $array An array
* @param mixed $name The column name
* @param mixed $index The column to use as the index/keys for the returned array
*
* @return array The array of values
*/
function twig_array_column($array, $name, $index = null): array
{
if ($array instanceof Traversable) {
$array = iterator_to_array($array);
} elseif (!\is_array($array)) {
throw new RuntimeError(sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array)));
}
return array_column($array, $name, $index);
}
function twig_array_filter(Environment $env, $array, $arrow)
{
if (!twig_test_iterable($array)) {
throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array)));
}
if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) {
throw new RuntimeError('The callable passed to "filter" filter must be a Closure in sandbox mode.');
}
if (\is_array($array)) {
return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH);
}
// the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator
return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow);
}
function twig_array_map(Environment $env, $array, $arrow)
{
if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) {
throw new RuntimeError('The callable passed to the "map" filter must be a Closure in sandbox mode.');
}
$r = [];
foreach ($array as $k => $v) {
$r[$k] = $arrow($v, $k);
}
return $r;
}
function twig_array_reduce(Environment $env, $array, $arrow, $initial = null)
{
if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) {
throw new RuntimeError('The callable passed to the "reduce" filter must be a Closure in sandbox mode.');
}
if (!\is_array($array)) {
if (!$array instanceof \Traversable) {
throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array)));
}
$array = iterator_to_array($array);
}
return array_reduce($array, $arrow, $initial);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Profiler;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class Profile implements \IteratorAggregate, \Serializable
{
const ROOT = 'ROOT';
const BLOCK = 'block';
const TEMPLATE = 'template';
const MACRO = 'macro';
private $template;
private $name;
private $type;
private $starts = [];
private $ends = [];
private $profiles = [];
public function __construct(string $template = 'main', string $type = self::ROOT, string $name = 'main')
{
$this->template = $template;
$this->type = $type;
$this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name;
$this->enter();
}
public function getTemplate(): string
{
return $this->template;
}
public function getType(): string
{
return $this->type;
}
public function getName(): string
{
return $this->name;
}
public function isRoot(): bool
{
return self::ROOT === $this->type;
}
public function isTemplate(): bool
{
return self::TEMPLATE === $this->type;
}
public function isBlock(): bool
{
return self::BLOCK === $this->type;
}
public function isMacro(): bool
{
return self::MACRO === $this->type;
}
/**
* @return Profile[]
*/
public function getProfiles(): array
{
return $this->profiles;
}
public function addProfile(self $profile): void
{
$this->profiles[] = $profile;
}
/**
* Returns the duration in microseconds.
*/
public function getDuration(): float
{
if ($this->isRoot() && $this->profiles) {
// for the root node with children, duration is the sum of all child durations
$duration = 0;
foreach ($this->profiles as $profile) {
$duration += $profile->getDuration();
}
return $duration;
}
return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0;
}
/**
* Returns the memory usage in bytes.
*/
public function getMemoryUsage(): int
{
return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0;
}
/**
* Returns the peak memory usage in bytes.
*/
public function getPeakMemoryUsage(): int
{
return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0;
}
/**
* Starts the profiling.
*/
public function enter(): void
{
$this->starts = [
'wt' => microtime(true),
'mu' => memory_get_usage(),
'pmu' => memory_get_peak_usage(),
];
}
/**
* Stops the profiling.
*/
public function leave(): void
{
$this->ends = [
'wt' => microtime(true),
'mu' => memory_get_usage(),
'pmu' => memory_get_peak_usage(),
];
}
public function reset(): void
{
$this->starts = $this->ends = $this->profiles = [];
$this->enter();
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->profiles);
}
public function serialize(): string
{
return serialize($this->__serialize());
}
public function unserialize($data): void
{
$this->__unserialize(unserialize($data));
}
/**
* @internal
*/
public function __serialize(): array
{
return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles];
}
/**
* @internal
*/
public function __unserialize(array $data): void
{
list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = $data;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Profiler\Node;
use Twig\Compiler;
use Twig\Node\Node;
/**
* Represents a profile enter node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EnterProfileNode extends Node
{
public function __construct(string $extensionName, string $type, string $name, string $varName)
{
parent::__construct([], ['extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName]);
}
public function compile(Compiler $compiler): void
{
$compiler
->write(sprintf('$%s = $this->extensions[', $this->getAttribute('var_name')))
->repr($this->getAttribute('extension_name'))
->raw("];\n")
->write(sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof'))
->repr($this->getAttribute('type'))
->raw(', ')
->repr($this->getAttribute('name'))
->raw("));\n\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Profiler\Node;
use Twig\Compiler;
use Twig\Node\Node;
/**
* Represents a profile leave node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LeaveProfileNode extends Node
{
public function __construct(string $varName)
{
parent::__construct([], ['var_name' => $varName]);
}
public function compile(Compiler $compiler): void
{
$compiler
->write("\n")
->write(sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof'))
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Profiler\NodeVisitor;
use Twig\Environment;
use Twig\Node\BlockNode;
use Twig\Node\BodyNode;
use Twig\Node\MacroNode;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\Profiler\Node\EnterProfileNode;
use Twig\Profiler\Node\LeaveProfileNode;
use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class ProfilerNodeVisitor implements NodeVisitorInterface
{
private $extensionName;
public function __construct(string $extensionName)
{
$this->extensionName = $extensionName;
}
public function enterNode(Node $node, Environment $env): Node
{
return $node;
}
public function leaveNode(Node $node, Environment $env): ?Node
{
if ($node instanceof ModuleNode) {
$varName = $this->getVarName();
$node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $varName), $node->getNode('display_start')]));
$node->setNode('display_end', new Node([new LeaveProfileNode($varName), $node->getNode('display_end')]));
} elseif ($node instanceof BlockNode) {
$varName = $this->getVarName();
$node->setNode('body', new BodyNode([
new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $varName),
$node->getNode('body'),
new LeaveProfileNode($varName),
]));
} elseif ($node instanceof MacroNode) {
$varName = $this->getVarName();
$node->setNode('body', new BodyNode([
new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $varName),
$node->getNode('body'),
new LeaveProfileNode($varName),
]));
}
return $node;
}
private function getVarName(): string
{
return sprintf('__internal_%s', hash('sha256', $this->extensionName));
}
public function getPriority(): int
{
return 0;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Profiler\Dumper;
use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class BaseDumper
{
private $root;
public function dump(Profile $profile): string
{
return $this->dumpProfile($profile);
}
abstract protected function formatTemplate(Profile $profile, $prefix): string;
abstract protected function formatNonTemplate(Profile $profile, $prefix): string;
abstract protected function formatTime(Profile $profile, $percent): string;
private function dumpProfile(Profile $profile, $prefix = '', $sibling = false): string
{
if ($profile->isRoot()) {
$this->root = $profile->getDuration();
$start = $profile->getName();
} else {
if ($profile->isTemplate()) {
$start = $this->formatTemplate($profile, $prefix);
} else {
$start = $this->formatNonTemplate($profile, $prefix);
}
$prefix .= $sibling ? '│ ' : ' ';
}
$percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0;
if ($profile->getDuration() * 1000 < 1) {
$str = $start."\n";
} else {
$str = sprintf("%s %s\n", $start, $this->formatTime($profile, $percent));
}
$nCount = \count($profile->getProfiles());
foreach ($profile as $i => $p) {
$str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount);
}
return $str;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Profiler\Dumper;
use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class HtmlDumper extends BaseDumper
{
private static $colors = [
'block' => '#dfd',
'macro' => '#ddf',
'template' => '#ffd',
'big' => '#d44',
];
public function dump(Profile $profile): string
{
return '<pre>'.parent::dump($profile).'</pre>';
}
protected function formatTemplate(Profile $profile, $prefix): string
{
return sprintf('%sâ”” <span style="background-color: %s">%s</span>', $prefix, self::$colors['template'], $profile->getTemplate());
}
protected function formatNonTemplate(Profile $profile, $prefix): string
{
return sprintf('%sâ”” %s::%s(<span style="background-color: %s">%s</span>)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName());
}
protected function formatTime(Profile $profile, $percent): string
{
return sprintf('<span style="color: %s">%.2fms/%.0f%%</span>', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Profiler\Dumper;
use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class BlackfireDumper
{
public function dump(Profile $profile): string
{
$data = [];
$this->dumpProfile('main()', $profile, $data);
$this->dumpChildren('main()', $profile, $data);
$start = sprintf('%f', microtime(true));
$str = <<<EOF
file-format: BlackfireProbe
cost-dimensions: wt mu pmu
request-start: {$start}
EOF;
foreach ($data as $name => $values) {
$str .= "{$name}//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n";
}
return $str;
}
private function dumpChildren(string $parent, Profile $profile, &$data)
{
foreach ($profile as $p) {
if ($p->isTemplate()) {
$name = $p->getTemplate();
} else {
$name = sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName());
}
$this->dumpProfile(sprintf('%s==>%s', $parent, $name), $p, $data);
$this->dumpChildren($name, $p, $data);
}
}
private function dumpProfile(string $edge, Profile $profile, &$data)
{
if (isset($data[$edge])) {
++$data[$edge]['ct'];
$data[$edge]['wt'] += floor($profile->getDuration() * 1000000);
$data[$edge]['mu'] += $profile->getMemoryUsage();
$data[$edge]['pmu'] += $profile->getPeakMemoryUsage();
} else {
$data[$edge] = [
'ct' => 1,
'wt' => floor($profile->getDuration() * 1000000),
'mu' => $profile->getMemoryUsage(),
'pmu' => $profile->getPeakMemoryUsage(),
];
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Profiler\Dumper;
use Twig\Profiler\Profile;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
final class TextDumper extends BaseDumper
{
protected function formatTemplate(Profile $profile, $prefix): string
{
return sprintf('%sâ”” %s', $prefix, $profile->getTemplate());
}
protected function formatNonTemplate(Profile $profile, $prefix): string
{
return sprintf('%sâ”” %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName());
}
protected function formatTime(Profile $profile, $percent): string
{
return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\NameExpression;
/**
* Represents an import node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ImportNode extends Node
{
public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true)
{
parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write('$macros[')
->repr($this->getNode('var')->getAttribute('name'))
->raw('] = ')
;
if ($this->getAttribute('global')) {
$compiler
->raw('$this->macros[')
->repr($this->getNode('var')->getAttribute('name'))
->raw('] = ')
;
}
if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) {
$compiler->raw('$this');
} else {
$compiler
->raw('$this->loadTemplate(')
->subcompile($this->getNode('expr'))
->raw(', ')
->repr($this->getTemplateName())
->raw(', ')
->repr($this->getTemplateLine())
->raw(')->unwrap()')
;
}
$compiler->raw(";\n");
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Represents an if node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class IfNode extends Node
{
public function __construct(Node $tests, ?Node $else, int $lineno, string $tag = null)
{
$nodes = ['tests' => $tests];
if (null !== $else) {
$nodes['else'] = $else;
}
parent::__construct($nodes, [], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);
for ($i = 0, $count = \count($this->getNode('tests')); $i < $count; $i += 2) {
if ($i > 0) {
$compiler
->outdent()
->write('} elseif (')
;
} else {
$compiler
->write('if (')
;
}
$compiler
->subcompile($this->getNode('tests')->getNode($i))
->raw(") {\n")
->indent()
->subcompile($this->getNode('tests')->getNode($i + 1))
;
}
if ($this->hasNode('else')) {
$compiler
->outdent()
->write("} else {\n")
->indent()
->subcompile($this->getNode('else'))
;
}
$compiler
->outdent()
->write("}\n");
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Source;
/**
* Represents a node in the AST.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Node implements \Countable, \IteratorAggregate
{
protected $nodes;
protected $attributes;
protected $lineno;
protected $tag;
private $name;
private $sourceContext;
/**
* @param array $nodes An array of named nodes
* @param array $attributes An array of attributes (should not be nodes)
* @param int $lineno The line number
* @param string $tag The tag name associated with the Node
*/
public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, string $tag = null)
{
foreach ($nodes as $name => $node) {
if (!$node instanceof self) {
throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class));
}
}
$this->nodes = $nodes;
$this->attributes = $attributes;
$this->lineno = $lineno;
$this->tag = $tag;
}
public function __toString()
{
$attributes = [];
foreach ($this->attributes as $name => $value) {
$attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
}
$repr = [static::class.'('.implode(', ', $attributes)];
if (\count($this->nodes)) {
foreach ($this->nodes as $name => $node) {
$len = \strlen($name) + 4;
$noderepr = [];
foreach (explode("\n", (string) $node) as $line) {
$noderepr[] = str_repeat(' ', $len).$line;
}
$repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr)));
}
$repr[] = ')';
} else {
$repr[0] .= ')';
}
return implode("\n", $repr);
}
/**
* @return void
*/
public function compile(Compiler $compiler)
{
foreach ($this->nodes as $node) {
$node->compile($compiler);
}
}
public function getTemplateLine(): int
{
return $this->lineno;
}
public function getNodeTag(): ?string
{
return $this->tag;
}
public function hasAttribute(string $name): bool
{
return \array_key_exists($name, $this->attributes);
}
public function getAttribute(string $name)
{
if (!\array_key_exists($name, $this->attributes)) {
throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class));
}
return $this->attributes[$name];
}
public function setAttribute(string $name, $value): void
{
$this->attributes[$name] = $value;
}
public function removeAttribute(string $name): void
{
unset($this->attributes[$name]);
}
public function hasNode(string $name): bool
{
return isset($this->nodes[$name]);
}
public function getNode(string $name): self
{
if (!isset($this->nodes[$name])) {
throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, static::class));
}
return $this->nodes[$name];
}
public function setNode(string $name, self $node): void
{
$this->nodes[$name] = $node;
}
public function removeNode(string $name): void
{
unset($this->nodes[$name]);
}
/**
* @return int
*/
public function count()
{
return \count($this->nodes);
}
public function getIterator(): \Traversable
{
return new \ArrayIterator($this->nodes);
}
public function getTemplateName(): ?string
{
return $this->sourceContext ? $this->sourceContext->getName() : null;
}
public function setSourceContext(Source $source): void
{
$this->sourceContext = $source;
foreach ($this->nodes as $node) {
$node->setSourceContext($source);
}
}
public function getSourceContext(): ?Source
{
return $this->sourceContext;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Represents a block call node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class BlockReferenceNode extends Node implements NodeOutputInterface
{
public function __construct(string $name, int $lineno, string $tag = null)
{
parent::__construct([], ['name' => $name], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name')))
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Represents a block node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class BlockNode extends Node
{
public function __construct(string $name, Node $body, int $lineno, string $tag = null)
{
parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n")
->indent()
->write("\$macros = \$this->macros;\n")
;
$compiler
->subcompile($this->getNode('body'))
->outdent()
->write("}\n\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
/**
* Represents a node that captures any nested displayable nodes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface NodeCaptureInterface
{
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Error\SyntaxError;
/**
* Represents a macro node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class MacroNode extends Node
{
const VARARGS_NAME = 'varargs';
public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null)
{
foreach ($arguments as $argumentName => $argument) {
if (self::VARARGS_NAME === $argumentName) {
throw new SyntaxError(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext());
}
}
parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write(sprintf('public function macro_%s(', $this->getAttribute('name')))
;
$count = \count($this->getNode('arguments'));
$pos = 0;
foreach ($this->getNode('arguments') as $name => $default) {
$compiler
->raw('$__'.$name.'__ = ')
->subcompile($default)
;
if (++$pos < $count) {
$compiler->raw(', ');
}
}
if ($count) {
$compiler->raw(', ');
}
$compiler
->raw('...$__varargs__')
->raw(")\n")
->write("{\n")
->indent()
->write("\$macros = \$this->macros;\n")
->write("\$context = \$this->env->mergeGlobals([\n")
->indent()
;
foreach ($this->getNode('arguments') as $name => $default) {
$compiler
->write('')
->string($name)
->raw(' => $__'.$name.'__')
->raw(",\n")
;
}
$compiler
->write('')
->string(self::VARARGS_NAME)
->raw(' => ')
;
$compiler
->raw("\$__varargs__,\n")
->outdent()
->write("]);\n\n")
->write("\$blocks = [];\n\n")
;
if ($compiler->getEnvironment()->isDebug()) {
$compiler->write("ob_start();\n");
} else {
$compiler->write("ob_start(function () { return ''; });\n");
}
$compiler
->write("try {\n")
->indent()
->subcompile($this->getNode('body'))
->raw("\n")
->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n")
->outdent()
->write("} finally {\n")
->indent()
->write("ob_end_clean();\n")
->outdent()
->write("}\n")
->outdent()
->write("}\n\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\AssignNameExpression;
/**
* Represents a for node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ForNode extends Node
{
private $loop;
public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno, string $tag = null)
{
$body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]);
$nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body];
if (null !== $else) {
$nodes['else'] = $else;
}
parent::__construct($nodes, ['with_loop' => true], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write("\$context['_parent'] = \$context;\n")
->write("\$context['_seq'] = twig_ensure_traversable(")
->subcompile($this->getNode('seq'))
->raw(");\n")
;
if ($this->hasNode('else')) {
$compiler->write("\$context['_iterated'] = false;\n");
}
if ($this->getAttribute('with_loop')) {
$compiler
->write("\$context['loop'] = [\n")
->write(" 'parent' => \$context['_parent'],\n")
->write(" 'index0' => 0,\n")
->write(" 'index' => 1,\n")
->write(" 'first' => true,\n")
->write("];\n")
->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof \Countable)) {\n")
->indent()
->write("\$length = count(\$context['_seq']);\n")
->write("\$context['loop']['revindex0'] = \$length - 1;\n")
->write("\$context['loop']['revindex'] = \$length;\n")
->write("\$context['loop']['length'] = \$length;\n")
->write("\$context['loop']['last'] = 1 === \$length;\n")
->outdent()
->write("}\n")
;
}
$this->loop->setAttribute('else', $this->hasNode('else'));
$this->loop->setAttribute('with_loop', $this->getAttribute('with_loop'));
$compiler
->write("foreach (\$context['_seq'] as ")
->subcompile($this->getNode('key_target'))
->raw(' => ')
->subcompile($this->getNode('value_target'))
->raw(") {\n")
->indent()
->subcompile($this->getNode('body'))
->outdent()
->write("}\n")
;
if ($this->hasNode('else')) {
$compiler
->write("if (!\$context['_iterated']) {\n")
->indent()
->subcompile($this->getNode('else'))
->outdent()
->write("}\n")
;
}
$compiler->write("\$_parent = \$context['_parent'];\n");
// remove some "private" loop variables (needed for nested loops)
$compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n");
// keep the values set in the inner context for variables defined in the outer context
$compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n");
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Source;
/**
* Represents a module node.
*
* Consider this class as being final. If you need to customize the behavior of
* the generated class, consider adding nodes to the following nodes: display_start,
* display_end, constructor_start, constructor_end, and class_end.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class ModuleNode extends Node
{
public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source)
{
$nodes = [
'body' => $body,
'blocks' => $blocks,
'macros' => $macros,
'traits' => $traits,
'display_start' => new Node(),
'display_end' => new Node(),
'constructor_start' => new Node(),
'constructor_end' => new Node(),
'class_end' => new Node(),
];
if (null !== $parent) {
$nodes['parent'] = $parent;
}
// embedded templates are set as attributes so that they are only visited once by the visitors
parent::__construct($nodes, [
'index' => null,
'embedded_templates' => $embeddedTemplates,
], 1);
// populate the template name of all node children
$this->setSourceContext($source);
}
public function setIndex($index)
{
$this->setAttribute('index', $index);
}
public function compile(Compiler $compiler): void
{
$this->compileTemplate($compiler);
foreach ($this->getAttribute('embedded_templates') as $template) {
$compiler->subcompile($template);
}
}
protected function compileTemplate(Compiler $compiler)
{
if (!$this->getAttribute('index')) {
$compiler->write('<?php');
}
$this->compileClassHeader($compiler);
$this->compileConstructor($compiler);
$this->compileGetParent($compiler);
$this->compileDisplay($compiler);
$compiler->subcompile($this->getNode('blocks'));
$this->compileMacros($compiler);
$this->compileGetTemplateName($compiler);
$this->compileIsTraitable($compiler);
$this->compileDebugInfo($compiler);
$this->compileGetSourceContext($compiler);
$this->compileClassFooter($compiler);
}
protected function compileGetParent(Compiler $compiler)
{
if (!$this->hasNode('parent')) {
return;
}
$parent = $this->getNode('parent');
$compiler
->write("protected function doGetParent(array \$context)\n", "{\n")
->indent()
->addDebugInfo($parent)
->write('return ')
;
if ($parent instanceof ConstantExpression) {
$compiler->subcompile($parent);
} else {
$compiler
->raw('$this->loadTemplate(')
->subcompile($parent)
->raw(', ')
->repr($this->getSourceContext()->getName())
->raw(', ')
->repr($parent->getTemplateLine())
->raw(')')
;
}
$compiler
->raw(";\n")
->outdent()
->write("}\n\n")
;
}
protected function compileClassHeader(Compiler $compiler)
{
$compiler
->write("\n\n")
;
if (!$this->getAttribute('index')) {
$compiler
->write("use Twig\Environment;\n")
->write("use Twig\Error\LoaderError;\n")
->write("use Twig\Error\RuntimeError;\n")
->write("use Twig\Extension\SandboxExtension;\n")
->write("use Twig\Markup;\n")
->write("use Twig\Sandbox\SecurityError;\n")
->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n")
->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n")
->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n")
->write("use Twig\Source;\n")
->write("use Twig\Template;\n\n")
;
}
$compiler
// if the template name contains */, add a blank to avoid a PHP parse error
->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n")
->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index')))
->raw(" extends Template\n")
->write("{\n")
->indent()
->write("private \$source;\n")
->write("private \$macros = [];\n\n")
;
}
protected function compileConstructor(Compiler $compiler)
{
$compiler
->write("public function __construct(Environment \$env)\n", "{\n")
->indent()
->subcompile($this->getNode('constructor_start'))
->write("parent::__construct(\$env);\n\n")
->write("\$this->source = \$this->getSourceContext();\n\n")
;
// parent
if (!$this->hasNode('parent')) {
$compiler->write("\$this->parent = false;\n\n");
}
$countTraits = \count($this->getNode('traits'));
if ($countTraits) {
// traits
foreach ($this->getNode('traits') as $i => $trait) {
$node = $trait->getNode('template');
$compiler
->addDebugInfo($node)
->write(sprintf('$_trait_%s = $this->loadTemplate(', $i))
->subcompile($node)
->raw(', ')
->repr($node->getTemplateName())
->raw(', ')
->repr($node->getTemplateLine())
->raw(");\n")
->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
->indent()
->write("throw new RuntimeError('Template \"'.")
->subcompile($trait->getNode('template'))
->raw(".'\" cannot be used as a trait.', ")
->repr($node->getTemplateLine())
->raw(", \$this->source);\n")
->outdent()
->write("}\n")
->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
;
foreach ($trait->getNode('targets') as $key => $value) {
$compiler
->write(sprintf('if (!isset($_trait_%s_blocks[', $i))
->string($key)
->raw("])) {\n")
->indent()
->write("throw new RuntimeError('Block ")
->string($key)
->raw(' is not defined in trait ')
->subcompile($trait->getNode('template'))
->raw(".', ")
->repr($node->getTemplateLine())
->raw(", \$this->source);\n")
->outdent()
->write("}\n\n")
->write(sprintf('$_trait_%s_blocks[', $i))
->subcompile($value)
->raw(sprintf('] = $_trait_%s_blocks[', $i))
->string($key)
->raw(sprintf(']; unset($_trait_%s_blocks[', $i))
->string($key)
->raw("]);\n\n")
;
}
}
if ($countTraits > 1) {
$compiler
->write("\$this->traits = array_merge(\n")
->indent()
;
for ($i = 0; $i < $countTraits; ++$i) {
$compiler
->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i))
;
}
$compiler
->outdent()
->write(");\n\n")
;
} else {
$compiler
->write("\$this->traits = \$_trait_0_blocks;\n\n")
;
}
$compiler
->write("\$this->blocks = array_merge(\n")
->indent()
->write("\$this->traits,\n")
->write("[\n")
;
} else {
$compiler
->write("\$this->blocks = [\n")
;
}
// blocks
$compiler
->indent()
;
foreach ($this->getNode('blocks') as $name => $node) {
$compiler
->write(sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name))
;
}
if ($countTraits) {
$compiler
->outdent()
->write("]\n")
->outdent()
->write(");\n")
;
} else {
$compiler
->outdent()
->write("];\n")
;
}
$compiler
->subcompile($this->getNode('constructor_end'))
->outdent()
->write("}\n\n")
;
}
protected function compileDisplay(Compiler $compiler)
{
$compiler
->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n")
->indent()
->write("\$macros = \$this->macros;\n")
->subcompile($this->getNode('display_start'))
->subcompile($this->getNode('body'))
;
if ($this->hasNode('parent')) {
$parent = $this->getNode('parent');
$compiler->addDebugInfo($parent);
if ($parent instanceof ConstantExpression) {
$compiler
->write('$this->parent = $this->loadTemplate(')
->subcompile($parent)
->raw(', ')
->repr($this->getSourceContext()->getName())
->raw(', ')
->repr($parent->getTemplateLine())
->raw(");\n")
;
$compiler->write('$this->parent');
} else {
$compiler->write('$this->getParent($context)');
}
$compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
}
$compiler
->subcompile($this->getNode('display_end'))
->outdent()
->write("}\n\n")
;
}
protected function compileClassFooter(Compiler $compiler)
{
$compiler
->subcompile($this->getNode('class_end'))
->outdent()
->write("}\n")
;
}
protected function compileMacros(Compiler $compiler)
{
$compiler->subcompile($this->getNode('macros'));
}
protected function compileGetTemplateName(Compiler $compiler)
{
$compiler
->write("public function getTemplateName()\n", "{\n")
->indent()
->write('return ')
->repr($this->getSourceContext()->getName())
->raw(";\n")
->outdent()
->write("}\n\n")
;
}
protected function compileIsTraitable(Compiler $compiler)
{
// A template can be used as a trait if:
// * it has no parent
// * it has no macros
// * it has no body
//
// Put another way, a template can be used as a trait if it
// only contains blocks and use statements.
$traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros'));
if ($traitable) {
if ($this->getNode('body') instanceof BodyNode) {
$nodes = $this->getNode('body')->getNode(0);
} else {
$nodes = $this->getNode('body');
}
if (!\count($nodes)) {
$nodes = new Node([$nodes]);
}
foreach ($nodes as $node) {
if (!\count($node)) {
continue;
}
if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) {
continue;
}
if ($node instanceof BlockReferenceNode) {
continue;
}
$traitable = false;
break;
}
}
if ($traitable) {
return;
}
$compiler
->write("public function isTraitable()\n", "{\n")
->indent()
->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
->outdent()
->write("}\n\n")
;
}
protected function compileDebugInfo(Compiler $compiler)
{
$compiler
->write("public function getDebugInfo()\n", "{\n")
->indent()
->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
->outdent()
->write("}\n\n")
;
}
protected function compileGetSourceContext(Compiler $compiler)
{
$compiler
->write("public function getSourceContext()\n", "{\n")
->indent()
->write('return new Source(')
->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '')
->raw(', ')
->string($this->getSourceContext()->getName())
->raw(', ')
->string($this->getSourceContext()->getPath())
->raw(");\n")
->outdent()
->write("}\n")
;
}
protected function compileLoadTemplate(Compiler $compiler, $node, $var)
{
if ($node instanceof ConstantExpression) {
$compiler
->write(sprintf('%s = $this->loadTemplate(', $var))
->subcompile($node)
->raw(', ')
->repr($node->getTemplateName())
->raw(', ')
->repr($node->getTemplateLine())
->raw(");\n")
;
} else {
throw new \LogicException('Trait templates can only be constant nodes.');
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Represents a flush node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class FlushNode extends Node
{
public function __construct(int $lineno, string $tag)
{
parent::__construct([], [], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write("flush();\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Represents an autoescape node.
*
* The value is the escaping strategy (can be html, js, ...)
*
* The true value is equivalent to html.
*
* If autoescaping is disabled, then the value is false.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class AutoEscapeNode extends Node
{
public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape')
{
parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler->subcompile($this->getNode('body'));
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
/**
* Checks if casting an expression to __toString() is allowed by the sandbox.
*
* For instance, when there is a simple Print statement, like {{ article }},
* and if the sandbox is enabled, we need to check that the __toString()
* method is allowed if 'article' is an object. The same goes for {{ article|upper }}
* or {{ random(article) }}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CheckToStringNode extends AbstractExpression
{
public function __construct(AbstractExpression $expr)
{
parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag());
}
public function compile(Compiler $compiler): void
{
$expr = $this->getNode('expr');
$compiler
->raw('$this->sandbox->ensureToStringAllowed(')
->subcompile($expr)
->raw(', ')
->repr($expr->getTemplateLine())
->raw(', $this->source)')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConstantExpression;
/**
* Represents an embed node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EmbedNode extends IncludeNode
{
// we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)
public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
{
parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
$this->setAttribute('name', $name);
$this->setAttribute('index', $index);
}
protected function addGetTemplate(Compiler $compiler): void
{
$compiler
->write('$this->loadTemplate(')
->string($this->getAttribute('name'))
->raw(', ')
->repr($this->getTemplateName())
->raw(', ')
->repr($this->getTemplateLine())
->raw(', ')
->string($this->getAttribute('index'))
->raw(')')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
/**
* Represents a do node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DoNode extends Node
{
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write('')
->subcompile($this->getNode('expr'))
->raw(";\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
/**
* Represents a node that outputs an expression.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PrintNode extends Node implements NodeOutputInterface
{
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write('echo ')
->subcompile($this->getNode('expr'))
->raw(";\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Represents a sandbox node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SandboxNode extends Node
{
public function __construct(Node $body, int $lineno, string $tag = null)
{
parent::__construct(['body' => $body], [], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write("if (!\$alreadySandboxed = \$this->sandbox->isSandboxed()) {\n")
->indent()
->write("\$this->sandbox->enableSandbox();\n")
->outdent()
->write("}\n")
->write("try {\n")
->indent()
->subcompile($this->getNode('body'))
->outdent()
->write("} finally {\n")
->indent()
->write("if (!\$alreadySandboxed) {\n")
->indent()
->write("\$this->sandbox->disableSandbox();\n")
->outdent()
->write("}\n")
->outdent()
->write("}\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Represents a text node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class TextNode extends Node implements NodeOutputInterface
{
public function __construct(string $data, int $lineno)
{
parent::__construct([], ['data' => $data], $lineno);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->write('echo ')
->string($this->getAttribute('data'))
->raw(";\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class CheckSecurityCallNode extends Node
{
public function compile(Compiler $compiler)
{
$compiler
->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n")
->write("\$this->checkSecurity();\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Node\Node;
/**
* Represents an arrow function.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ArrowFunctionExpression extends AbstractExpression
{
public function __construct(AbstractExpression $expr, Node $names, $lineno, $tag = null)
{
parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler
->addDebugInfo($this)
->raw('function (')
;
foreach ($this->getNode('names') as $i => $name) {
if ($i) {
$compiler->raw(', ');
}
$compiler
->raw('$__')
->raw($name->getAttribute('name'))
->raw('__')
;
}
$compiler
->raw(') use ($context, $macros) { ')
;
foreach ($this->getNode('names') as $name) {
$compiler
->raw('$context["')
->raw($name->getAttribute('name'))
->raw('"] = $__')
->raw($name->getAttribute('name'))
->raw('__; ')
;
}
$compiler
->raw('return ')
->subcompile($this->getNode('expr'))
->raw('; }')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Node\Node;
class FilterExpression extends CallExpression
{
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
{
parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$name = $this->getNode('filter')->getAttribute('value');
$filter = $compiler->getEnvironment()->getFilter($name);
$this->setAttribute('name', $name);
$this->setAttribute('type', 'filter');
$this->setAttribute('needs_environment', $filter->needsEnvironment());
$this->setAttribute('needs_context', $filter->needsContext());
$this->setAttribute('arguments', $filter->getArguments());
$this->setAttribute('callable', $filter->getCallable());
$this->setAttribute('is_variadic', $filter->isVariadic());
$this->compileCallable($compiler);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Extension\SandboxExtension;
use Twig\Template;
class GetAttrExpression extends AbstractExpression
{
public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno)
{
$nodes = ['node' => $node, 'attribute' => $attribute];
if (null !== $arguments) {
$nodes['arguments'] = $arguments;
}
parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno);
}
public function compile(Compiler $compiler): void
{
$env = $compiler->getEnvironment();
// optimize array calls
if (
$this->getAttribute('optimizable')
&& (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check'))
&& !$this->getAttribute('is_defined_test')
&& Template::ARRAY_CALL === $this->getAttribute('type')
) {
$var = '$'.$compiler->getVarName();
$compiler
->raw('(('.$var.' = ')
->subcompile($this->getNode('node'))
->raw(') && is_array(')
->raw($var)
->raw(') || ')
->raw($var)
->raw(' instanceof ArrayAccess ? (')
->raw($var)
->raw('[')
->subcompile($this->getNode('attribute'))
->raw('] ?? null) : null)')
;
return;
}
$compiler->raw('twig_get_attribute($this->env, $this->source, ');
if ($this->getAttribute('ignore_strict_check')) {
$this->getNode('node')->setAttribute('ignore_strict_check', true);
}
$compiler
->subcompile($this->getNode('node'))
->raw(', ')
->subcompile($this->getNode('attribute'))
;
if ($this->hasNode('arguments')) {
$compiler->raw(', ')->subcompile($this->getNode('arguments'));
} else {
$compiler->raw(', []');
}
$compiler->raw(', ')
->repr($this->getAttribute('type'))
->raw(', ')->repr($this->getAttribute('is_defined_test'))
->raw(', ')->repr($this->getAttribute('ignore_strict_check'))
->raw(', ')->repr($env->hasExtension(SandboxExtension::class))
->raw(', ')->repr($this->getNode('node')->getTemplateLine())
->raw(')')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Unary;
use Twig\Compiler;
class NotUnary extends AbstractUnary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('!');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Unary;
use Twig\Compiler;
class NegUnary extends AbstractUnary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('-');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Unary;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;
abstract class AbstractUnary extends AbstractExpression
{
public function __construct(Node $node, int $lineno)
{
parent::__construct(['node' => $node], [], $lineno);
}
public function compile(Compiler $compiler): void
{
$compiler->raw(' ');
$this->operator($compiler);
$compiler->subcompile($this->getNode('node'));
}
abstract public function operator(Compiler $compiler): Compiler;
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Unary;
use Twig\Compiler;
class PosUnary extends AbstractUnary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('+');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class VariadicExpression extends ArrayExpression
{
public function compile(Compiler $compiler): void
{
$compiler->raw('...');
parent::compile($compiler);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Node\Node;
/**
* Represents a block call node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class BlockReferenceExpression extends AbstractExpression
{
public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null)
{
$nodes = ['name' => $name];
if (null !== $template) {
$nodes['template'] = $template;
}
parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
if ($this->getAttribute('is_defined_test')) {
$this->compileTemplateCall($compiler, 'hasBlock');
} else {
if ($this->getAttribute('output')) {
$compiler->addDebugInfo($this);
$this
->compileTemplateCall($compiler, 'displayBlock')
->raw(";\n");
} else {
$this->compileTemplateCall($compiler, 'renderBlock');
}
}
}
private function compileTemplateCall(Compiler $compiler, string $method): Compiler
{
if (!$this->hasNode('template')) {
$compiler->write('$this');
} else {
$compiler
->write('$this->loadTemplate(')
->subcompile($this->getNode('template'))
->raw(', ')
->repr($this->getTemplateName())
->raw(', ')
->repr($this->getTemplateLine())
->raw(')')
;
}
$compiler->raw(sprintf('->%s', $method));
return $this->compileBlockArguments($compiler);
}
private function compileBlockArguments(Compiler $compiler): Compiler
{
$compiler
->raw('(')
->subcompile($this->getNode('name'))
->raw(', $context');
if (!$this->hasNode('template')) {
$compiler->raw(', $blocks');
}
return $compiler->raw(')');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class TempNameExpression extends AbstractExpression
{
public function __construct(string $name, int $lineno)
{
parent::__construct([], ['name' => $name], $lineno);
}
public function compile(Compiler $compiler): void
{
$compiler
->raw('$_')
->raw($this->getAttribute('name'))
->raw('_')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class ConditionalExpression extends AbstractExpression
{
public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno)
{
parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno);
}
public function compile(Compiler $compiler): void
{
$compiler
->raw('((')
->subcompile($this->getNode('expr1'))
->raw(') ? (')
->subcompile($this->getNode('expr2'))
->raw(') : (')
->subcompile($this->getNode('expr3'))
->raw('))')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class MethodCallExpression extends AbstractExpression
{
public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno)
{
parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno);
if ($node instanceof NameExpression) {
$node->setAttribute('always_defined', true);
}
}
public function compile(Compiler $compiler): void
{
if ($this->getAttribute('is_defined_test')) {
$compiler
->raw('method_exists($macros[')
->repr($this->getNode('node')->getAttribute('name'))
->raw('], ')
->repr($this->getAttribute('method'))
->raw(')')
;
return;
}
$compiler
->raw('twig_call_macro($macros[')
->repr($this->getNode('node')->getAttribute('name'))
->raw('], ')
->repr($this->getAttribute('method'))
->raw(', [')
;
$first = true;
foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
if (!$first) {
$compiler->raw(', ');
}
$first = false;
$compiler->subcompile($pair['value']);
}
$compiler
->raw('], ')
->repr($this->getTemplateLine())
->raw(', $context, $this->getSourceContext())');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class NameExpression extends AbstractExpression
{
private $specialVars = [
'_self' => '$this->getTemplateName()',
'_context' => '$context',
'_charset' => '$this->env->getCharset()',
];
public function __construct(string $name, int $lineno)
{
parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno);
}
public function compile(Compiler $compiler): void
{
$name = $this->getAttribute('name');
$compiler->addDebugInfo($this);
if ($this->getAttribute('is_defined_test')) {
if ($this->isSpecial()) {
$compiler->repr(true);
} elseif (\PHP_VERSION_ID >= 70400) {
$compiler
->raw('array_key_exists(')
->string($name)
->raw(', $context)')
;
} else {
$compiler
->raw('(isset($context[')
->string($name)
->raw(']) || array_key_exists(')
->string($name)
->raw(', $context))')
;
}
} elseif ($this->isSpecial()) {
$compiler->raw($this->specialVars[$name]);
} elseif ($this->getAttribute('always_defined')) {
$compiler
->raw('$context[')
->string($name)
->raw(']')
;
} else {
if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
$compiler
->raw('($context[')
->string($name)
->raw('] ?? null)')
;
} else {
$compiler
->raw('(isset($context[')
->string($name)
->raw(']) || array_key_exists(')
->string($name)
->raw(', $context) ? $context[')
->string($name)
->raw('] : (function () { throw new RuntimeError(\'Variable ')
->string($name)
->raw(' does not exist.\', ')
->repr($this->lineno)
->raw(', $this->source); })()')
->raw(')')
;
}
}
}
public function isSpecial()
{
return isset($this->specialVars[$this->getAttribute('name')]);
}
public function isSimple()
{
return !$this->isSpecial() && !$this->getAttribute('is_defined_test');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class ConstantExpression extends AbstractExpression
{
public function __construct($value, int $lineno)
{
parent::__construct([], ['value' => $value], $lineno);
}
public function compile(Compiler $compiler): void
{
$compiler->repr($this->getAttribute('value'));
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
/**
* Represents a parent node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ParentExpression extends AbstractExpression
{
public function __construct(string $name, int $lineno, string $tag = null)
{
parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
if ($this->getAttribute('output')) {
$compiler
->addDebugInfo($this)
->write('$this->displayParentBlock(')
->string($this->getAttribute('name'))
->raw(", \$context, \$blocks);\n")
;
} else {
$compiler
->raw('$this->renderParentBlock(')
->string($this->getAttribute('name'))
->raw(', $context, $blocks)')
;
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class ArrayExpression extends AbstractExpression
{
private $index;
public function __construct(array $elements, int $lineno)
{
parent::__construct($elements, [], $lineno);
$this->index = -1;
foreach ($this->getKeyValuePairs() as $pair) {
if ($pair['key'] instanceof ConstantExpression && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) {
$this->index = $pair['key']->getAttribute('value');
}
}
}
public function getKeyValuePairs(): array
{
$pairs = [];
foreach (array_chunk($this->nodes, 2) as $pair) {
$pairs[] = [
'key' => $pair[0],
'value' => $pair[1],
];
}
return $pairs;
}
public function hasElement(AbstractExpression $key): bool
{
foreach ($this->getKeyValuePairs() as $pair) {
// we compare the string representation of the keys
// to avoid comparing the line numbers which are not relevant here.
if ((string) $key === (string) $pair['key']) {
return true;
}
}
return false;
}
public function addElement(AbstractExpression $value, AbstractExpression $key = null): void
{
if (null === $key) {
$key = new ConstantExpression(++$this->index, $value->getTemplateLine());
}
array_push($this->nodes, $key, $value);
}
public function compile(Compiler $compiler): void
{
$compiler->raw('[');
$first = true;
foreach ($this->getKeyValuePairs() as $pair) {
if (!$first) {
$compiler->raw(', ');
}
$first = false;
$compiler
->subcompile($pair['key'])
->raw(' => ')
->subcompile($pair['value'])
;
}
$compiler->raw(']');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Node\Expression\TestExpression;
/**
* Checks if a number is even.
*
* {{ var is even }}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class EvenTest extends TestExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' % 2 == 0')
->raw(')')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\ArrayExpression;
use Twig\Node\Expression\BlockReferenceExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\TestExpression;
use Twig\Node\Node;
/**
* Checks if a variable is defined in the current context.
*
* {# defined works with variable names and variable attributes #}
* {% if foo is defined %}
* {# ... #}
* {% endif %}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DefinedTest extends TestExpression
{
public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
{
if ($node instanceof NameExpression) {
$node->setAttribute('is_defined_test', true);
} elseif ($node instanceof GetAttrExpression) {
$node->setAttribute('is_defined_test', true);
$this->changeIgnoreStrictCheck($node);
} elseif ($node instanceof BlockReferenceExpression) {
$node->setAttribute('is_defined_test', true);
} elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) {
$node->setAttribute('is_defined_test', true);
} elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) {
$node = new ConstantExpression(true, $node->getTemplateLine());
} elseif ($node instanceof MethodCallExpression) {
$node->setAttribute('is_defined_test', true);
} else {
throw new SyntaxError('The "defined" test only works with simple variables.', $lineno);
}
parent::__construct($node, $name, $arguments, $lineno);
}
private function changeIgnoreStrictCheck(GetAttrExpression $node)
{
$node->setAttribute('optimizable', false);
$node->setAttribute('ignore_strict_check', true);
if ($node->getNode('node') instanceof GetAttrExpression) {
$this->changeIgnoreStrictCheck($node->getNode('node'));
}
}
public function compile(Compiler $compiler): void
{
$compiler->subcompile($this->getNode('node'));
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Node\Expression\TestExpression;
/**
* Checks if a variable is divisible by a number.
*
* {% if loop.index is divisible by(3) %}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DivisiblebyTest extends TestExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('(0 == ')
->subcompile($this->getNode('node'))
->raw(' % ')
->subcompile($this->getNode('arguments')->getNode(0))
->raw(')')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Node\Expression\TestExpression;
/**
* Checks if a variable is the exact same value as a constant.
*
* {% if post.status is constant('Post::PUBLISHED') %}
* the status attribute is exactly the same as Post::PUBLISHED
* {% endif %}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ConstantTest extends TestExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' === constant(')
;
if ($this->getNode('arguments')->hasNode(1)) {
$compiler
->raw('get_class(')
->subcompile($this->getNode('arguments')->getNode(1))
->raw(')."::".')
;
}
$compiler
->subcompile($this->getNode('arguments')->getNode(0))
->raw('))')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Node\Expression\TestExpression;
/**
* Checks that a variable is null.
*
* {{ var is none }}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NullTest extends TestExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('(null === ')
->subcompile($this->getNode('node'))
->raw(')')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Node\Expression\TestExpression;
/**
* Checks if a number is odd.
*
* {{ var is odd }}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class OddTest extends TestExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' % 2 != 0')
->raw(')')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Test;
use Twig\Compiler;
use Twig\Node\Expression\TestExpression;
/**
* Checks if a variable is the same as another one (=== in PHP).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SameasTest extends TestExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('(')
->subcompile($this->getNode('node'))
->raw(' === ')
->subcompile($this->getNode('arguments')->getNode(0))
->raw(')')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class LessEqualBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler
->raw('(0 >= twig_compare(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw('))')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('<=');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class NotInBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('!twig_in_filter(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('not in');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class InBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('twig_in_filter(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('in');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class StartsWithBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
$left = $compiler->getVarName();
$right = $compiler->getVarName();
$compiler
->raw(sprintf('(is_string($%s = ', $left))
->subcompile($this->getNode('left'))
->raw(sprintf(') && is_string($%s = ', $right))
->subcompile($this->getNode('right'))
->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right))
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class BitwiseAndBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('&');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class ConcatBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('.');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class EqualBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler
->raw('(0 === twig_compare(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw('))')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('==');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class FloorDivBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
$compiler->raw('(int) floor(');
parent::compile($compiler);
$compiler->raw(')');
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('/');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class OrBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('||');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class DivBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('/');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class EndsWithBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
$left = $compiler->getVarName();
$right = $compiler->getVarName();
$compiler
->raw(sprintf('(is_string($%s = ', $left))
->subcompile($this->getNode('left'))
->raw(sprintf(') && is_string($%s = ', $right))
->subcompile($this->getNode('right'))
->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right))
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class ModBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('%');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class SpaceshipBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('<=>');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class PowerBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('**');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class SubBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('-');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class BitwiseOrBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('|');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class GreaterBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler
->raw('(1 === twig_compare(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw('))')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('>');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class MulBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('*');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class AndBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('&&');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class GreaterEqualBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler
->raw('(0 <= twig_compare(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw('))')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('>=');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Node;
abstract class AbstractBinary extends AbstractExpression
{
public function __construct(Node $left, Node $right, int $lineno)
{
parent::__construct(['left' => $left, 'right' => $right], [], $lineno);
}
public function compile(Compiler $compiler): void
{
$compiler
->raw('(')
->subcompile($this->getNode('left'))
->raw(' ')
;
$this->operator($compiler);
$compiler
->raw(' ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
abstract public function operator(Compiler $compiler): Compiler;
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class BitwiseXorBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('^');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class MatchesBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('preg_match(')
->subcompile($this->getNode('right'))
->raw(', ')
->subcompile($this->getNode('left'))
->raw(')')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class LessBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler
->raw('(-1 === twig_compare(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw('))')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('<');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class RangeBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('range(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw(')')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('..');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class NotEqualBinary extends AbstractBinary
{
public function compile(Compiler $compiler): void
{
if (\PHP_VERSION_ID >= 80000) {
parent::compile($compiler);
return;
}
$compiler
->raw('(0 !== twig_compare(')
->subcompile($this->getNode('left'))
->raw(', ')
->subcompile($this->getNode('right'))
->raw('))')
;
}
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('!=');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Binary;
use Twig\Compiler;
class AddBinary extends AbstractBinary
{
public function operator(Compiler $compiler): Compiler
{
return $compiler->raw('+');
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Node\Node;
/**
* Abstract class for all nodes that represents an expression.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractExpression extends Node
{
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Node\Node;
class FunctionExpression extends CallExpression
{
public function __construct(string $name, Node $arguments, int $lineno)
{
parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno);
}
public function compile(Compiler $compiler)
{
$name = $this->getAttribute('name');
$function = $compiler->getEnvironment()->getFunction($name);
$this->setAttribute('name', $name);
$this->setAttribute('type', 'function');
$this->setAttribute('needs_environment', $function->needsEnvironment());
$this->setAttribute('needs_context', $function->needsContext());
$this->setAttribute('arguments', $function->getArguments());
$callable = $function->getCallable();
if ('constant' === $name && $this->getAttribute('is_defined_test')) {
$callable = 'twig_constant_is_defined';
}
$this->setAttribute('callable', $callable);
$this->setAttribute('is_variadic', $function->isVariadic());
$this->compileCallable($compiler);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
class AssignNameExpression extends NameExpression
{
public function compile(Compiler $compiler): void
{
$compiler
->raw('$context[')
->string($this->getAttribute('name'))
->raw(']')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Node\Expression\Binary\AndBinary;
use Twig\Node\Expression\Test\DefinedTest;
use Twig\Node\Expression\Test\NullTest;
use Twig\Node\Expression\Unary\NotUnary;
use Twig\Node\Node;
class NullCoalesceExpression extends ConditionalExpression
{
public function __construct(Node $left, Node $right, int $lineno)
{
$test = new DefinedTest(clone $left, 'defined', new Node(), $left->getTemplateLine());
// for "block()", we don't need the null test as the return value is always a string
if (!$left instanceof BlockReferenceExpression) {
$test = new AndBinary(
$test,
new NotUnary(new NullTest($left, 'null', new Node(), $left->getTemplateLine()), $left->getTemplateLine()),
$left->getTemplateLine()
);
}
parent::__construct($test, $left, $right, $lineno);
}
public function compile(Compiler $compiler): void
{
/*
* This optimizes only one case. PHP 7 also supports more complex expressions
* that can return null. So, for instance, if log is defined, log("foo") ?? "..." works,
* but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced
* cases might be implemented as an optimizer node visitor, but has not been done
* as benefits are probably not worth the added complexity.
*/
if ($this->getNode('expr2') instanceof NameExpression) {
$this->getNode('expr2')->setAttribute('always_defined', true);
$compiler
->raw('((')
->subcompile($this->getNode('expr2'))
->raw(') ?? (')
->subcompile($this->getNode('expr3'))
->raw('))')
;
} else {
parent::compile($compiler);
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Node\Node;
class TestExpression extends CallExpression
{
public function __construct(Node $node, string $name, ?Node $arguments, int $lineno)
{
$nodes = ['node' => $node];
if (null !== $arguments) {
$nodes['arguments'] = $arguments;
}
parent::__construct($nodes, ['name' => $name], $lineno);
}
public function compile(Compiler $compiler): void
{
$name = $this->getAttribute('name');
$test = $compiler->getEnvironment()->getTest($name);
$this->setAttribute('name', $name);
$this->setAttribute('type', 'test');
$this->setAttribute('arguments', $test->getArguments());
$this->setAttribute('callable', $test->getCallable());
$this->setAttribute('is_variadic', $test->isVariadic());
$this->compileCallable($compiler);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Error\SyntaxError;
use Twig\Extension\ExtensionInterface;
use Twig\Node\Node;
abstract class CallExpression extends AbstractExpression
{
private $reflector;
protected function compileCallable(Compiler $compiler)
{
$callable = $this->getAttribute('callable');
$closingParenthesis = false;
$isArray = false;
if (\is_string($callable) && false === strpos($callable, '::')) {
$compiler->raw($callable);
} else {
list($r, $callable) = $this->reflectCallable($callable);
if ($r instanceof \ReflectionMethod && \is_string($callable[0])) {
if ($r->isStatic()) {
$compiler->raw(sprintf('%s::%s', $callable[0], $callable[1]));
} else {
$compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1]));
}
} elseif ($r instanceof \ReflectionMethod && $callable[0] instanceof ExtensionInterface) {
$class = \get_class($callable[0]);
if (!$compiler->getEnvironment()->hasExtension($class)) {
// Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error
$compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class));
} else {
$compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\')));
}
$compiler->raw(sprintf('->%s', $callable[1]));
} else {
$closingParenthesis = true;
$isArray = true;
$compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), ', ucfirst($this->getAttribute('type')), $this->getAttribute('name')));
}
}
$this->compileArguments($compiler, $isArray);
if ($closingParenthesis) {
$compiler->raw(')');
}
}
protected function compileArguments(Compiler $compiler, $isArray = false): void
{
$compiler->raw($isArray ? '[' : '(');
$first = true;
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
$compiler->raw('$this->env');
$first = false;
}
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->raw('$context');
$first = false;
}
if ($this->hasAttribute('arguments')) {
foreach ($this->getAttribute('arguments') as $argument) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->string($argument);
$first = false;
}
}
if ($this->hasNode('node')) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->subcompile($this->getNode('node'));
$first = false;
}
if ($this->hasNode('arguments')) {
$callable = $this->getAttribute('callable');
$arguments = $this->getArguments($callable, $this->getNode('arguments'));
foreach ($arguments as $node) {
if (!$first) {
$compiler->raw(', ');
}
$compiler->subcompile($node);
$first = false;
}
}
$compiler->raw($isArray ? ']' : ')');
}
protected function getArguments($callable, $arguments)
{
$callType = $this->getAttribute('type');
$callName = $this->getAttribute('name');
$parameters = [];
$named = false;
foreach ($arguments as $name => $node) {
if (!\is_int($name)) {
$named = true;
$name = $this->normalizeName($name);
} elseif ($named) {
throw new SyntaxError(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
}
$parameters[$name] = $node;
}
$isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic');
if (!$named && !$isVariadic) {
return $parameters;
}
if (!$callable) {
if ($named) {
$message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName);
} else {
$message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName);
}
throw new \LogicException($message);
}
list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic);
$arguments = [];
$names = [];
$missingArguments = [];
$optionalArguments = [];
$pos = 0;
foreach ($callableParameters as $callableParameter) {
$name = $this->normalizeName($callableParameter->name);
if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) {
if ('start' === $name) {
$name = 'low';
} elseif ('end' === $name) {
$name = 'high';
}
}
$names[] = $name;
if (\array_key_exists($name, $parameters)) {
if (\array_key_exists($pos, $parameters)) {
throw new SyntaxError(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
}
if (\count($missingArguments)) {
throw new SyntaxError(sprintf(
'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".',
$name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments)
), $this->getTemplateLine(), $this->getSourceContext());
}
$arguments = array_merge($arguments, $optionalArguments);
$arguments[] = $parameters[$name];
unset($parameters[$name]);
$optionalArguments = [];
} elseif (\array_key_exists($pos, $parameters)) {
$arguments = array_merge($arguments, $optionalArguments);
$arguments[] = $parameters[$pos];
unset($parameters[$pos]);
$optionalArguments = [];
++$pos;
} elseif ($callableParameter->isDefaultValueAvailable()) {
$optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1);
} elseif ($callableParameter->isOptional()) {
if (empty($parameters)) {
break;
} else {
$missingArguments[] = $name;
}
} else {
throw new SyntaxError(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext());
}
}
if ($isVariadic) {
$arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1);
foreach ($parameters as $key => $value) {
if (\is_int($key)) {
$arbitraryArguments->addElement($value);
} else {
$arbitraryArguments->addElement($value, new ConstantExpression($key, -1));
}
unset($parameters[$key]);
}
if ($arbitraryArguments->count()) {
$arguments = array_merge($arguments, $optionalArguments);
$arguments[] = $arbitraryArguments;
}
}
if (!empty($parameters)) {
$unknownParameter = null;
foreach ($parameters as $parameter) {
if ($parameter instanceof Node) {
$unknownParameter = $parameter;
break;
}
}
throw new SyntaxError(
sprintf(
'Unknown argument%s "%s" for %s "%s(%s)".',
\count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names)
),
$unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(),
$unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext()
);
}
return $arguments;
}
protected function normalizeName(string $name): string
{
return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name));
}
private function getCallableParameters($callable, bool $isVariadic): array
{
list($r) = $this->reflectCallable($callable);
if (null === $r) {
return [[], false];
}
$parameters = $r->getParameters();
if ($this->hasNode('node')) {
array_shift($parameters);
}
if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
array_shift($parameters);
}
if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
array_shift($parameters);
}
if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
foreach ($this->getAttribute('arguments') as $argument) {
array_shift($parameters);
}
}
$isPhpVariadic = false;
if ($isVariadic) {
$argument = end($parameters);
$isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName();
if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) {
array_pop($parameters);
} elseif ($argument && $argument->isVariadic()) {
array_pop($parameters);
$isPhpVariadic = true;
} else {
$callableName = $r->name;
if ($r instanceof \ReflectionMethod) {
$callableName = $r->getDeclaringClass()->name.'::'.$callableName;
}
throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name')));
}
}
return [$parameters, $isPhpVariadic];
}
private function reflectCallable($callable)
{
if (null !== $this->reflector) {
return $this->reflector;
}
if (\is_array($callable)) {
if (!method_exists($callable[0], $callable[1])) {
// __call()
return [null, []];
}
$r = new \ReflectionMethod($callable[0], $callable[1]);
} elseif (\is_object($callable) && !$callable instanceof \Closure) {
$r = new \ReflectionObject($callable);
$r = $r->getMethod('__invoke');
$callable = [$callable, '__invoke'];
} elseif (\is_string($callable) && false !== $pos = strpos($callable, '::')) {
$class = substr($callable, 0, $pos);
$method = substr($callable, $pos + 2);
if (!method_exists($class, $method)) {
// __staticCall()
return [null, []];
}
$r = new \ReflectionMethod($callable);
$callable = [$class, $method];
} else {
$r = new \ReflectionFunction($callable);
}
return $this->reflector = [$r, $callable];
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression\Filter;
use Twig\Compiler;
use Twig\Node\Expression\ConditionalExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\Test\DefinedTest;
use Twig\Node\Node;
/**
* Returns the value or the default value when it is undefined or empty.
*
* {{ var.foo|default('foo item on var is not defined') }}
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class DefaultFilter extends FilterExpression
{
public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null)
{
$default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine());
if ('default' === $filterName->getAttribute('value') && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) {
$test = new DefinedTest(clone $node, 'defined', new Node(), $node->getTemplateLine());
$false = \count($arguments) ? $arguments->getNode(0) : new ConstantExpression('', $node->getTemplateLine());
$node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine());
} else {
$node = $default;
}
parent::__construct($node, $filterName, $arguments, $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler->subcompile($this->getNode('node'));
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node\Expression;
use Twig\Compiler;
use Twig\Node\Node;
/**
* @internal
*/
final class InlinePrint extends AbstractExpression
{
public function __construct(Node $node, int $lineno)
{
parent::__construct(['node' => $node], [], $lineno);
}
public function compile(Compiler $compiler): void
{
$compiler
->raw('print (')
->subcompile($this->getNode('node'))
->raw(')')
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\Expression\ConstantExpression;
/**
* Represents a deprecated node.
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class DeprecatedNode extends Node
{
public function __construct(AbstractExpression $expr, int $lineno, string $tag = null)
{
parent::__construct(['expr' => $expr], [], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);
$expr = $this->getNode('expr');
if ($expr instanceof ConstantExpression) {
$compiler->write('@trigger_error(')
->subcompile($expr);
} else {
$varName = $compiler->getVarName();
$compiler->write(sprintf('$%s = ', $varName))
->subcompile($expr)
->raw(";\n")
->write(sprintf('@trigger_error($%s', $varName));
}
$compiler
->raw('.')
->string(sprintf(' ("%s" at line %d).', $this->getTemplateName(), $this->getTemplateLine()))
->raw(", E_USER_DEPRECATED);\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\ConstantExpression;
/**
* Represents a set node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SetNode extends Node implements NodeCaptureInterface
{
public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null)
{
parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag);
/*
* Optimizes the node when capture is used for a large block of text.
*
* {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo");
*/
if ($this->getAttribute('capture')) {
$this->setAttribute('safe', true);
$values = $this->getNode('values');
if ($values instanceof TextNode) {
$this->setNode('values', new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine()));
$this->setAttribute('capture', false);
}
}
}
public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);
if (\count($this->getNode('names')) > 1) {
$compiler->write('list(');
foreach ($this->getNode('names') as $idx => $node) {
if ($idx) {
$compiler->raw(', ');
}
$compiler->subcompile($node);
}
$compiler->raw(')');
} else {
if ($this->getAttribute('capture')) {
if ($compiler->getEnvironment()->isDebug()) {
$compiler->write("ob_start();\n");
} else {
$compiler->write("ob_start(function () { return ''; });\n");
}
$compiler
->subcompile($this->getNode('values'))
;
}
$compiler->subcompile($this->getNode('names'), false);
if ($this->getAttribute('capture')) {
$compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset())");
}
}
if (!$this->getAttribute('capture')) {
$compiler->raw(' = ');
if (\count($this->getNode('names')) > 1) {
$compiler->write('[');
foreach ($this->getNode('values') as $idx => $value) {
if ($idx) {
$compiler->raw(', ');
}
$compiler->subcompile($value);
}
$compiler->raw(']');
} else {
if ($this->getAttribute('safe')) {
$compiler
->raw("('' === \$tmp = ")
->subcompile($this->getNode('values'))
->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())")
;
} else {
$compiler->subcompile($this->getNode('values'));
}
}
}
$compiler->raw(";\n");
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
/**
* Represents a body node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class BodyNode extends Node
{
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Represents a nested "with" scope.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class WithNode extends Node
{
public function __construct(Node $body, ?Node $variables, bool $only, int $lineno, string $tag = null)
{
$nodes = ['body' => $body];
if (null !== $variables) {
$nodes['variables'] = $variables;
}
parent::__construct($nodes, ['only' => $only], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);
$parentContextName = $compiler->getVarName();
$compiler->write(sprintf("\$%s = \$context;\n", $parentContextName));
if ($this->hasNode('variables')) {
$node = $this->getNode('variables');
$varsName = $compiler->getVarName();
$compiler
->write(sprintf('$%s = ', $varsName))
->subcompile($node)
->raw(";\n")
->write(sprintf("if (!twig_test_iterable(\$%s)) {\n", $varsName))
->indent()
->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ")
->repr($node->getTemplateLine())
->raw(", \$this->getSourceContext());\n")
->outdent()
->write("}\n")
->write(sprintf("\$%s = twig_to_array(\$%s);\n", $varsName, $varsName))
;
if ($this->getAttribute('only')) {
$compiler->write("\$context = [];\n");
}
$compiler->write(sprintf("\$context = \$this->env->mergeGlobals(array_merge(\$context, \$%s));\n", $varsName));
}
$compiler
->subcompile($this->getNode('body'))
->write(sprintf("\$context = \$%s;\n", $parentContextName))
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class CheckSecurityNode extends Node
{
private $usedFilters;
private $usedTags;
private $usedFunctions;
public function __construct(array $usedFilters, array $usedTags, array $usedFunctions)
{
$this->usedFilters = $usedFilters;
$this->usedTags = $usedTags;
$this->usedFunctions = $usedFunctions;
parent::__construct();
}
public function compile(Compiler $compiler): void
{
$tags = $filters = $functions = [];
foreach (['tags', 'filters', 'functions'] as $type) {
foreach ($this->{'used'.ucfirst($type)} as $name => $node) {
if ($node instanceof Node) {
${$type}[$name] = $node->getTemplateLine();
} else {
${$type}[$node] = null;
}
}
}
$compiler
->write("\n")
->write("public function checkSecurity()\n")
->write("{\n")
->indent()
->write('static $tags = ')->repr(array_filter($tags))->raw(";\n")
->write('static $filters = ')->repr(array_filter($filters))->raw(";\n")
->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n")
->write("try {\n")
->indent()
->write("\$this->sandbox->checkSecurity(\n")
->indent()
->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n")
->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n")
->write(!$functions ? "[]\n" : "['".implode("', '", array_keys($functions))."']\n")
->outdent()
->write(");\n")
->outdent()
->write("} catch (SecurityError \$e) {\n")
->indent()
->write("\$e->setSourceContext(\$this->source);\n\n")
->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n")
->indent()
->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n")
->outdent()
->write("} elseif (\$e instanceof SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n")
->indent()
->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n")
->outdent()
->write("} elseif (\$e instanceof SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n")
->indent()
->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n")
->outdent()
->write("}\n\n")
->write("throw \$e;\n")
->outdent()
->write("}\n\n")
->outdent()
->write("}\n")
;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
use Twig\Node\Expression\AbstractExpression;
/**
* Represents an include node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class IncludeNode extends Node implements NodeOutputInterface
{
public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null)
{
$nodes = ['expr' => $expr];
if (null !== $variables) {
$nodes['variables'] = $variables;
}
parent::__construct($nodes, ['only' => (bool) $only, 'ignore_missing' => (bool) $ignoreMissing], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
$compiler->addDebugInfo($this);
if ($this->getAttribute('ignore_missing')) {
$template = $compiler->getVarName();
$compiler
->write(sprintf("$%s = null;\n", $template))
->write("try {\n")
->indent()
->write(sprintf('$%s = ', $template))
;
$this->addGetTemplate($compiler);
$compiler
->raw(";\n")
->outdent()
->write("} catch (LoaderError \$e) {\n")
->indent()
->write("// ignore missing template\n")
->outdent()
->write("}\n")
->write(sprintf("if ($%s) {\n", $template))
->indent()
->write(sprintf('$%s->display(', $template))
;
$this->addTemplateArguments($compiler);
$compiler
->raw(");\n")
->outdent()
->write("}\n")
;
} else {
$this->addGetTemplate($compiler);
$compiler->raw('->display(');
$this->addTemplateArguments($compiler);
$compiler->raw(");\n");
}
}
protected function addGetTemplate(Compiler $compiler)
{
$compiler
->write('$this->loadTemplate(')
->subcompile($this->getNode('expr'))
->raw(', ')
->repr($this->getTemplateName())
->raw(', ')
->repr($this->getTemplateLine())
->raw(')')
;
}
protected function addTemplateArguments(Compiler $compiler)
{
if (!$this->hasNode('variables')) {
$compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]');
} elseif (false === $this->getAttribute('only')) {
$compiler
->raw('twig_array_merge($context, ')
->subcompile($this->getNode('variables'))
->raw(')')
;
} else {
$compiler->raw('twig_to_array(');
$compiler->subcompile($this->getNode('variables'));
$compiler->raw(')');
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
use Twig\Compiler;
/**
* Internal node used by the for node.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ForLoopNode extends Node
{
public function __construct(int $lineno, string $tag = null)
{
parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag);
}
public function compile(Compiler $compiler): void
{
if ($this->getAttribute('else')) {
$compiler->write("\$context['_iterated'] = true;\n");
}
if ($this->getAttribute('with_loop')) {
$compiler
->write("++\$context['loop']['index0'];\n")
->write("++\$context['loop']['index'];\n")
->write("\$context['loop']['first'] = false;\n")
->write("if (isset(\$context['loop']['length'])) {\n")
->indent()
->write("--\$context['loop']['revindex0'];\n")
->write("--\$context['loop']['revindex'];\n")
->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n")
->outdent()
->write("}\n")
;
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Node;
/**
* Represents a displayable node in the AST.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface NodeOutputInterface
{
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\SyntaxError;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Lexer
{
private $tokens;
private $code;
private $cursor;
private $lineno;
private $end;
private $state;
private $states;
private $brackets;
private $env;
private $source;
private $options;
private $regexes;
private $position;
private $positions;
private $currentVarBlockLine;
const STATE_DATA = 0;
const STATE_BLOCK = 1;
const STATE_VAR = 2;
const STATE_STRING = 3;
const STATE_INTERPOLATION = 4;
const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A';
const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A';
const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As';
const REGEX_DQ_STRING_DELIM = '/"/A';
const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As';
const PUNCTUATION = '()[]{}?:.,|';
public function __construct(Environment $env, array $options = [])
{
$this->env = $env;
$this->options = array_merge([
'tag_comment' => ['{#', '#}'],
'tag_block' => ['{%', '%}'],
'tag_variable' => ['{{', '}}'],
'whitespace_trim' => '-',
'whitespace_line_trim' => '~',
'whitespace_line_chars' => ' \t\0\x0B',
'interpolation' => ['#{', '}'],
], $options);
// when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default
$this->regexes = [
// }}
'lex_var' => '{
\s*
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '#').'\s*'. // -}}\s*
'|'.
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \t\0\x0B]*
'|'.
preg_quote($this->options['tag_variable'][1], '#'). // }}
')
}Ax',
// %}
'lex_block' => '{
\s*
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*\n?'. // -%}\s*\n?
'|'.
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]*
'|'.
preg_quote($this->options['tag_block'][1], '#').'\n?'. // %}\n?
')
}Ax',
// {% endverbatim %}
'lex_raw_data' => '{'.
preg_quote($this->options['tag_block'][0], '#'). // {%
'('.
$this->options['whitespace_trim']. // -
'|'.
$this->options['whitespace_line_trim']. // ~
')?\s*endverbatim\s*'.
'(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}
'|'.
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]*
'|'.
preg_quote($this->options['tag_block'][1], '#'). // %}
')
}sx',
'operator' => $this->getOperatorRegex(),
// #}
'lex_comment' => '{
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n?
'|'.
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]*
'|'.
preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n?
')
}sx',
// verbatim %}
'lex_block_raw' => '{
\s*verbatim\s*
(?:'.
preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s*
'|'.
preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]*
'|'.
preg_quote($this->options['tag_block'][1], '#'). // %}
')
}Asx',
'lex_block_line' => '{\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '#').'}As',
// {{ or {% or {#
'lex_tokens_start' => '{
('.
preg_quote($this->options['tag_variable'][0], '#'). // {{
'|'.
preg_quote($this->options['tag_block'][0], '#'). // {%
'|'.
preg_quote($this->options['tag_comment'][0], '#'). // {#
')('.
preg_quote($this->options['whitespace_trim'], '#'). // -
'|'.
preg_quote($this->options['whitespace_line_trim'], '#'). // ~
')?
}sx',
'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0], '#').'\s*}A',
'interpolation_end' => '{\s*'.preg_quote($this->options['interpolation'][1], '#').'}A',
];
}
public function tokenize(Source $source): TokenStream
{
$this->source = $source;
$this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode());
$this->cursor = 0;
$this->lineno = 1;
$this->end = \strlen($this->code);
$this->tokens = [];
$this->state = self::STATE_DATA;
$this->states = [];
$this->brackets = [];
$this->position = -1;
// find all token starts in one go
preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, PREG_OFFSET_CAPTURE);
$this->positions = $matches;
while ($this->cursor < $this->end) {
// dispatch to the lexing functions depending
// on the current state
switch ($this->state) {
case self::STATE_DATA:
$this->lexData();
break;
case self::STATE_BLOCK:
$this->lexBlock();
break;
case self::STATE_VAR:
$this->lexVar();
break;
case self::STATE_STRING:
$this->lexString();
break;
case self::STATE_INTERPOLATION:
$this->lexInterpolation();
break;
}
}
$this->pushToken(/* Token::EOF_TYPE */ -1);
if (!empty($this->brackets)) {
list($expect, $lineno) = array_pop($this->brackets);
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
}
return new TokenStream($this->tokens, $this->source);
}
private function lexData(): void
{
// if no matches are left we return the rest of the template as simple text token
if ($this->position == \count($this->positions[0]) - 1) {
$this->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor));
$this->cursor = $this->end;
return;
}
// Find the first token after the current cursor
$position = $this->positions[0][++$this->position];
while ($position[1] < $this->cursor) {
if ($this->position == \count($this->positions[0]) - 1) {
return;
}
$position = $this->positions[0][++$this->position];
}
// push the template text first
$text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor);
// trim?
if (isset($this->positions[2][$this->position][0])) {
if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) {
// whitespace_trim detected ({%-, {{- or {#-)
$text = rtrim($text);
} elseif ($this->options['whitespace_line_trim'] === $this->positions[2][$this->position][0]) {
// whitespace_line_trim detected ({%~, {{~ or {#~)
// don't trim \r and \n
$text = rtrim($text, " \t\0\x0B");
}
}
$this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
$this->moveCursor($textContent.$position[0]);
switch ($this->positions[1][$this->position][0]) {
case $this->options['tag_comment'][0]:
$this->lexComment();
break;
case $this->options['tag_block'][0]:
// raw data?
if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lexRawData();
// {% line \d+ %}
} elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
$this->lineno = (int) $match[1];
} else {
$this->pushToken(/* Token::BLOCK_START_TYPE */ 1);
$this->pushState(self::STATE_BLOCK);
$this->currentVarBlockLine = $this->lineno;
}
break;
case $this->options['tag_variable'][0]:
$this->pushToken(/* Token::VAR_START_TYPE */ 2);
$this->pushState(self::STATE_VAR);
$this->currentVarBlockLine = $this->lineno;
break;
}
}
private function lexBlock(): void
{
if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(/* Token::BLOCK_END_TYPE */ 3);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
private function lexVar(): void
{
if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(/* Token::VAR_END_TYPE */ 4);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
private function lexExpression(): void
{
// whitespace
if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) {
$this->moveCursor($match[0]);
if ($this->cursor >= $this->end) {
throw new SyntaxError(sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source);
}
}
// arrow function
if ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) {
$this->pushToken(Token::ARROW_TYPE, '=>');
$this->moveCursor('=>');
}
// operators
elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) {
$this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0]));
$this->moveCursor($match[0]);
}
// names
elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]);
$this->moveCursor($match[0]);
}
// numbers
elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) {
$number = (float) $match[0]; // floats
if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) {
$number = (int) $match[0]; // integers lower than the maximum
}
$this->pushToken(/* Token::NUMBER_TYPE */ 6, $number);
$this->moveCursor($match[0]);
}
// punctuation
elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
// opening bracket
if (false !== strpos('([{', $this->code[$this->cursor])) {
$this->brackets[] = [$this->code[$this->cursor], $this->lineno];
}
// closing bracket
elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
if (empty($this->brackets)) {
throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
}
list($expect, $lineno) = array_pop($this->brackets);
if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) {
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
}
}
$this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]);
++$this->cursor;
}
// strings
elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) {
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1)));
$this->moveCursor($match[0]);
}
// opening double quoted string
elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
$this->brackets[] = ['"', $this->lineno];
$this->pushState(self::STATE_STRING);
$this->moveCursor($match[0]);
}
// unlexable
else {
throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
}
}
private function lexRawData(): void
{
if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source);
}
$text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
$this->moveCursor($text.$match[0][0]);
// trim?
if (isset($match[1][0])) {
if ($this->options['whitespace_trim'] === $match[1][0]) {
// whitespace_trim detected ({%-, {{- or {#-)
$text = rtrim($text);
} else {
// whitespace_line_trim detected ({%~, {{~ or {#~)
// don't trim \r and \n
$text = rtrim($text, " \t\0\x0B");
}
}
$this->pushToken(/* Token::TEXT_TYPE */ 0, $text);
}
private function lexComment(): void
{
if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source);
}
$this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]);
}
private function lexString(): void
{
if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {
$this->brackets[] = [$this->options['interpolation'][0], $this->lineno];
$this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10);
$this->moveCursor($match[0]);
$this->pushState(self::STATE_INTERPOLATION);
} elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) {
$this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0]));
$this->moveCursor($match[0]);
} elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) {
list($expect, $lineno) = array_pop($this->brackets);
if ('"' != $this->code[$this->cursor]) {
throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source);
}
$this->popState();
++$this->cursor;
} else {
// unlexable
throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source);
}
}
private function lexInterpolation(): void
{
$bracket = end($this->brackets);
if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) {
array_pop($this->brackets);
$this->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11);
$this->moveCursor($match[0]);
$this->popState();
} else {
$this->lexExpression();
}
}
private function pushToken($type, $value = ''): void
{
// do not push empty text tokens
if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) {
return;
}
$this->tokens[] = new Token($type, $value, $this->lineno);
}
private function moveCursor($text): void
{
$this->cursor += \strlen($text);
$this->lineno += substr_count($text, "\n");
}
private function getOperatorRegex(): string
{
$operators = array_merge(
['='],
array_keys($this->env->getUnaryOperators()),
array_keys($this->env->getBinaryOperators())
);
$operators = array_combine($operators, array_map('strlen', $operators));
arsort($operators);
$regex = [];
foreach ($operators as $operator => $length) {
// an operator that ends with a character must be followed by
// a whitespace, a parenthesis, an opening map [ or sequence {
$r = preg_quote($operator, '/');
if (ctype_alpha($operator[$length - 1])) {
$r .= '(?=[\s()\[{])';
}
// an operator that begins with a character must not have a dot or pipe before
if (ctype_alpha($operator[0])) {
$r = '(?<![\.\|])'.$r;
}
// an operator with a space can be any amount of whitespaces
$r = preg_replace('/\s+/', '\s+', $r);
$regex[] = $r;
}
return '/'.implode('|', $regex).'/A';
}
private function pushState($state): void
{
$this->states[] = $this->state;
$this->state = $state;
}
private function popState(): void
{
if (0 === \count($this->states)) {
throw new \LogicException('Cannot pop state without a previous state.');
}
$this->state = array_pop($this->states);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\RuntimeError;
use Twig\Extension\ExtensionInterface;
use Twig\Extension\GlobalsInterface;
use Twig\Extension\StagingExtension;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\TokenParser\TokenParserInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class ExtensionSet
{
private $extensions;
private $initialized = false;
private $runtimeInitialized = false;
private $staging;
private $parsers;
private $visitors;
private $filters;
private $tests;
private $functions;
private $unaryOperators;
private $binaryOperators;
private $globals;
private $functionCallbacks = [];
private $filterCallbacks = [];
private $parserCallbacks = [];
private $lastModified = 0;
public function __construct()
{
$this->staging = new StagingExtension();
}
public function initRuntime()
{
$this->runtimeInitialized = true;
}
public function hasExtension(string $class): bool
{
return isset($this->extensions[ltrim($class, '\\')]);
}
public function getExtension(string $class): ExtensionInterface
{
$class = ltrim($class, '\\');
if (!isset($this->extensions[$class])) {
throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class));
}
return $this->extensions[$class];
}
/**
* @param ExtensionInterface[] $extensions
*/
public function setExtensions(array $extensions): void
{
foreach ($extensions as $extension) {
$this->addExtension($extension);
}
}
/**
* @return ExtensionInterface[]
*/
public function getExtensions(): array
{
return $this->extensions;
}
public function getSignature(): string
{
return json_encode(array_keys($this->extensions));
}
public function isInitialized(): bool
{
return $this->initialized || $this->runtimeInitialized;
}
public function getLastModified(): int
{
if (0 !== $this->lastModified) {
return $this->lastModified;
}
foreach ($this->extensions as $extension) {
$r = new \ReflectionObject($extension);
if (is_file($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) {
$this->lastModified = $extensionTime;
}
}
return $this->lastModified;
}
public function addExtension(ExtensionInterface $extension): void
{
$class = \get_class($extension);
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class));
}
if (isset($this->extensions[$class])) {
throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class));
}
$this->extensions[$class] = $extension;
}
public function addFunction(TwigFunction $function): void
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName()));
}
$this->staging->addFunction($function);
}
/**
* @return TwigFunction[]
*/
public function getFunctions(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->functions;
}
public function getFunction(string $name): ?TwigFunction
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->functions[$name])) {
return $this->functions[$name];
}
foreach ($this->functions as $pattern => $function) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$function->setArguments($matches);
return $function;
}
}
foreach ($this->functionCallbacks as $callback) {
if (false !== $function = $callback($name)) {
return $function;
}
}
return null;
}
public function registerUndefinedFunctionCallback(callable $callable): void
{
$this->functionCallbacks[] = $callable;
}
public function addFilter(TwigFilter $filter): void
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName()));
}
$this->staging->addFilter($filter);
}
/**
* @return TwigFilter[]
*/
public function getFilters(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->filters;
}
public function getFilter(string $name): ?TwigFilter
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->filters[$name])) {
return $this->filters[$name];
}
foreach ($this->filters as $pattern => $filter) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$filter->setArguments($matches);
return $filter;
}
}
foreach ($this->filterCallbacks as $callback) {
if (false !== $filter = $callback($name)) {
return $filter;
}
}
return null;
}
public function registerUndefinedFilterCallback(callable $callable): void
{
$this->filterCallbacks[] = $callable;
}
public function addNodeVisitor(NodeVisitorInterface $visitor): void
{
if ($this->initialized) {
throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.');
}
$this->staging->addNodeVisitor($visitor);
}
/**
* @return NodeVisitorInterface[]
*/
public function getNodeVisitors(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->visitors;
}
public function addTokenParser(TokenParserInterface $parser): void
{
if ($this->initialized) {
throw new \LogicException('Unable to add a token parser as extensions have already been initialized.');
}
$this->staging->addTokenParser($parser);
}
/**
* @return TokenParserInterface[]
*/
public function getTokenParsers(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->parsers;
}
public function getTokenParser(string $name): ?TokenParserInterface
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->parsers[$name])) {
return $this->parsers[$name];
}
foreach ($this->parserCallbacks as $callback) {
if (false !== $parser = $callback($name)) {
return $parser;
}
}
return null;
}
public function registerUndefinedTokenParserCallback(callable $callable): void
{
$this->parserCallbacks[] = $callable;
}
public function getGlobals(): array
{
if (null !== $this->globals) {
return $this->globals;
}
$globals = [];
foreach ($this->extensions as $extension) {
if (!$extension instanceof GlobalsInterface) {
continue;
}
$extGlobals = $extension->getGlobals();
if (!\is_array($extGlobals)) {
throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension)));
}
$globals = array_merge($globals, $extGlobals);
}
if ($this->initialized) {
$this->globals = $globals;
}
return $globals;
}
public function addTest(TwigTest $test): void
{
if ($this->initialized) {
throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName()));
}
$this->staging->addTest($test);
}
/**
* @return TwigTest[]
*/
public function getTests(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->tests;
}
public function getTest(string $name): ?TwigTest
{
if (!$this->initialized) {
$this->initExtensions();
}
if (isset($this->tests[$name])) {
return $this->tests[$name];
}
foreach ($this->tests as $pattern => $test) {
$pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
if ($count) {
if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
array_shift($matches);
$test->setArguments($matches);
return $test;
}
}
}
return null;
}
public function getUnaryOperators(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->unaryOperators;
}
public function getBinaryOperators(): array
{
if (!$this->initialized) {
$this->initExtensions();
}
return $this->binaryOperators;
}
private function initExtensions(): void
{
$this->parsers = [];
$this->filters = [];
$this->functions = [];
$this->tests = [];
$this->visitors = [];
$this->unaryOperators = [];
$this->binaryOperators = [];
foreach ($this->extensions as $extension) {
$this->initExtension($extension);
}
$this->initExtension($this->staging);
// Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception
$this->initialized = true;
}
private function initExtension(ExtensionInterface $extension): void
{
// filters
foreach ($extension->getFilters() as $filter) {
$this->filters[$filter->getName()] = $filter;
}
// functions
foreach ($extension->getFunctions() as $function) {
$this->functions[$function->getName()] = $function;
}
// tests
foreach ($extension->getTests() as $test) {
$this->tests[$test->getName()] = $test;
}
// token parsers
foreach ($extension->getTokenParsers() as $parser) {
if (!$parser instanceof TokenParserInterface) {
throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.');
}
$this->parsers[$parser->getTag()] = $parser;
}
// node visitors
foreach ($extension->getNodeVisitors() as $visitor) {
$this->visitors[] = $visitor;
}
// operators
if ($operators = $extension->getOperators()) {
if (!\is_array($operators)) {
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators)));
}
if (2 !== \count($operators)) {
throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators)));
}
$this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
$this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Error;
use Twig\Source;
use Twig\Template;
/**
* Twig base exception.
*
* This exception class and its children must only be used when
* an error occurs during the loading of a template, when a syntax error
* is detected in a template, or when rendering a template. Other
* errors must use regular PHP exception classes (like when the template
* cache directory is not writable for instance).
*
* To help debugging template issues, this class tracks the original template
* name and line where the error occurred.
*
* Whenever possible, you must set these information (original template name
* and line number) yourself by passing them to the constructor. If some or all
* these information are not available from where you throw the exception, then
* this class will guess them automatically (when the line number is set to -1
* and/or the name is set to null). As this is a costly operation, this
* can be disabled by passing false for both the name and the line number
* when creating a new instance of this class.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Error extends \Exception
{
private $lineno;
private $name;
private $rawMessage;
private $sourcePath;
private $sourceCode;
/**
* Constructor.
*
* By default, automatic guessing is enabled.
*
* @param string $message The error message
* @param int $lineno The template line where the error occurred
* @param Source|null $source The source context where the error occurred
*/
public function __construct(string $message, int $lineno = -1, Source $source = null, \Exception $previous = null)
{
parent::__construct('', 0, $previous);
if (null === $source) {
$name = null;
} else {
$name = $source->getName();
$this->sourceCode = $source->getCode();
$this->sourcePath = $source->getPath();
}
$this->lineno = $lineno;
$this->name = $name;
$this->rawMessage = $message;
$this->updateRepr();
}
public function getRawMessage(): string
{
return $this->rawMessage;
}
public function getTemplateLine(): int
{
return $this->lineno;
}
public function setTemplateLine(int $lineno): void
{
$this->lineno = $lineno;
$this->updateRepr();
}
public function getSourceContext(): ?Source
{
return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null;
}
public function setSourceContext(Source $source = null): void
{
if (null === $source) {
$this->sourceCode = $this->name = $this->sourcePath = null;
} else {
$this->sourceCode = $source->getCode();
$this->name = $source->getName();
$this->sourcePath = $source->getPath();
}
$this->updateRepr();
}
public function guess(): void
{
$this->guessTemplateInfo();
$this->updateRepr();
}
public function appendMessage($rawMessage): void
{
$this->rawMessage .= $rawMessage;
$this->updateRepr();
}
private function updateRepr(): void
{
$this->message = $this->rawMessage;
if ($this->sourcePath && $this->lineno > 0) {
$this->file = $this->sourcePath;
$this->line = $this->lineno;
return;
}
$dot = false;
if ('.' === substr($this->message, -1)) {
$this->message = substr($this->message, 0, -1);
$dot = true;
}
$questionMark = false;
if ('?' === substr($this->message, -1)) {
$this->message = substr($this->message, 0, -1);
$questionMark = true;
}
if ($this->name) {
if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) {
$name = sprintf('"%s"', $this->name);
} else {
$name = json_encode($this->name);
}
$this->message .= sprintf(' in %s', $name);
}
if ($this->lineno && $this->lineno >= 0) {
$this->message .= sprintf(' at line %d', $this->lineno);
}
if ($dot) {
$this->message .= '.';
}
if ($questionMark) {
$this->message .= '?';
}
}
private function guessTemplateInfo(): void
{
$template = null;
$templateClass = null;
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS | DEBUG_BACKTRACE_PROVIDE_OBJECT);
foreach ($backtrace as $trace) {
if (isset($trace['object']) && $trace['object'] instanceof Template) {
$currentClass = \get_class($trace['object']);
$isEmbedContainer = 0 === strpos($templateClass, $currentClass);
if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) {
$template = $trace['object'];
$templateClass = \get_class($trace['object']);
}
}
}
// update template name
if (null !== $template && null === $this->name) {
$this->name = $template->getTemplateName();
}
// update template path if any
if (null !== $template && null === $this->sourcePath) {
$src = $template->getSourceContext();
$this->sourceCode = $src->getCode();
$this->sourcePath = $src->getPath();
}
if (null === $template || $this->lineno > -1) {
return;
}
$r = new \ReflectionObject($template);
$file = $r->getFileName();
$exceptions = [$e = $this];
while ($e = $e->getPrevious()) {
$exceptions[] = $e;
}
while ($e = array_pop($exceptions)) {
$traces = $e->getTrace();
array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]);
while ($trace = array_shift($traces)) {
if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
continue;
}
foreach ($template->getDebugInfo() as $codeLine => $templateLine) {
if ($codeLine <= $trace['line']) {
// update template line
$this->lineno = $templateLine;
return;
}
}
}
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Error;
/**
* Exception thrown when an error occurs at runtime.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class RuntimeError extends Error
{
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Error;
/**
* \Exception thrown when a syntax error occurs during lexing or parsing of a template.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SyntaxError extends Error
{
/**
* Tweaks the error message to include suggestions.
*
* @param string $name The original name of the item that does not exist
* @param array $items An array of possible items
*/
public function addSuggestions(string $name, array $items): void
{
$alternatives = [];
foreach ($items as $item) {
$lev = levenshtein($name, $item);
if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) {
$alternatives[$item] = $lev;
}
}
if (!$alternatives) {
return;
}
asort($alternatives);
$this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives))));
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Error;
/**
* Exception thrown when an error occurs during template loading.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class LoaderError extends Error
{
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Sandbox;
use Twig\Markup;
use Twig\Template;
/**
* Represents a security policy which need to be enforced when sandbox mode is enabled.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class SecurityPolicy implements SecurityPolicyInterface
{
private $allowedTags;
private $allowedFilters;
private $allowedMethods;
private $allowedProperties;
private $allowedFunctions;
public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = [])
{
$this->allowedTags = $allowedTags;
$this->allowedFilters = $allowedFilters;
$this->setAllowedMethods($allowedMethods);
$this->allowedProperties = $allowedProperties;
$this->allowedFunctions = $allowedFunctions;
}
public function setAllowedTags(array $tags): void
{
$this->allowedTags = $tags;
}
public function setAllowedFilters(array $filters): void
{
$this->allowedFilters = $filters;
}
public function setAllowedMethods(array $methods): void
{
$this->allowedMethods = [];
foreach ($methods as $class => $m) {
$this->allowedMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]);
}
}
public function setAllowedProperties(array $properties): void
{
$this->allowedProperties = $properties;
}
public function setAllowedFunctions(array $functions): void
{
$this->allowedFunctions = $functions;
}
public function checkSecurity($tags, $filters, $functions): void
{
foreach ($tags as $tag) {
if (!\in_array($tag, $this->allowedTags)) {
throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag);
}
}
foreach ($filters as $filter) {
if (!\in_array($filter, $this->allowedFilters)) {
throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter);
}
}
foreach ($functions as $function) {
if (!\in_array($function, $this->allowedFunctions)) {
throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function);
}
}
}
public function checkMethodAllowed($obj, $method): void
{
if ($obj instanceof Template || $obj instanceof Markup) {
return;
}
$allowed = false;
$method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
foreach ($this->allowedMethods as $class => $methods) {
if ($obj instanceof $class) {
$allowed = \in_array($method, $methods);
break;
}
}
if (!$allowed) {
$class = \get_class($obj);
throw new SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method);
}
}
public function checkPropertyAllowed($obj, $property): void
{
$allowed = false;
foreach ($this->allowedProperties as $class => $properties) {
if ($obj instanceof $class) {
$allowed = \in_array($property, \is_array($properties) ? $properties : [$properties]);
break;
}
}
if (!$allowed) {
$class = \get_class($obj);
throw new SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property);
}
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Sandbox;
/**
* Exception thrown when a not allowed filter is used in a template.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
final class SecurityNotAllowedFilterError extends SecurityError
{
private $filterName;
public function __construct(string $message, string $functionName)
{
parent::__construct($message);
$this->filterName = $functionName;
}
public function getFilterName(): string
{
return $this->filterName;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Sandbox;
/**
* Interface that all security policy classes must implements.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface SecurityPolicyInterface
{
/**
* @throws SecurityError
*/
public function checkSecurity($tags, $filters, $functions): void;
/**
* @throws SecurityNotAllowedMethodError
*/
public function checkMethodAllowed($obj, $method): void;
/**
* @throws SecurityNotAllowedPropertyError
*/
public function checkPropertyAllowed($obj, $method): void;
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Sandbox;
/**
* Exception thrown when a not allowed class method is used in a template.
*
* @author Kit Burton-Senior <mail@kitbs.com>
*/
final class SecurityNotAllowedMethodError extends SecurityError
{
private $className;
private $methodName;
public function __construct(string $message, string $className, string $methodName)
{
parent::__construct($message);
$this->className = $className;
$this->methodName = $methodName;
}
public function getClassName(): string
{
return $this->className;
}
public function getMethodName()
{
return $this->methodName;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Sandbox;
/**
* Exception thrown when a not allowed class property is used in a template.
*
* @author Kit Burton-Senior <mail@kitbs.com>
*/
final class SecurityNotAllowedPropertyError extends SecurityError
{
private $className;
private $propertyName;
public function __construct(string $message, string $className, string $propertyName)
{
parent::__construct($message);
$this->className = $className;
$this->propertyName = $propertyName;
}
public function getClassName(): string
{
return $this->className;
}
public function getPropertyName()
{
return $this->propertyName;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Sandbox;
use Twig\Error\Error;
/**
* Exception thrown when a security error occurs at runtime.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class SecurityError extends Error
{
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Sandbox;
/**
* Exception thrown when a not allowed tag is used in a template.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
final class SecurityNotAllowedTagError extends SecurityError
{
private $tagName;
public function __construct(string $message, string $tagName)
{
parent::__construct($message);
$this->tagName = $tagName;
}
public function getTagName(): string
{
return $this->tagName;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Sandbox;
/**
* Exception thrown when a not allowed function is used in a template.
*
* @author Martin Hasoň <martin.hason@gmail.com>
*/
final class SecurityNotAllowedFunctionError extends SecurityError
{
private $functionName;
public function __construct(string $message, string $functionName)
{
parent::__construct($message);
$this->functionName = $functionName;
}
public function getFunctionName(): string
{
return $this->functionName;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Cache\CacheInterface;
use Twig\Cache\FilesystemCache;
use Twig\Cache\NullCache;
use Twig\Error\Error;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Twig\Extension\CoreExtension;
use Twig\Extension\EscaperExtension;
use Twig\Extension\ExtensionInterface;
use Twig\Extension\OptimizerExtension;
use Twig\Loader\ArrayLoader;
use Twig\Loader\ChainLoader;
use Twig\Loader\LoaderInterface;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
use Twig\RuntimeLoader\RuntimeLoaderInterface;
use Twig\TokenParser\TokenParserInterface;
/**
* Stores the Twig configuration and renders templates.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class Environment
{
const VERSION = '3.3.0';
const VERSION_ID = 30300;
const MAJOR_VERSION = 3;
const MINOR_VERSION = 3;
const RELEASE_VERSION = 0;
const EXTRA_VERSION = '';
private $charset;
private $loader;
private $debug;
private $autoReload;
private $cache;
private $lexer;
private $parser;
private $compiler;
private $globals = [];
private $resolvedGlobals;
private $loadedTemplates;
private $strictVariables;
private $templateClassPrefix = '__TwigTemplate_';
private $originalCache;
private $extensionSet;
private $runtimeLoaders = [];
private $runtimes = [];
private $optionsHash;
/**
* Constructor.
*
* Available options:
*
* * debug: When set to true, it automatically set "auto_reload" to true as
* well (default to false).
*
* * charset: The charset used by the templates (default to UTF-8).
*
* * cache: An absolute path where to store the compiled templates,
* a \Twig\Cache\CacheInterface implementation,
* or false to disable compilation cache (default).
*
* * auto_reload: Whether to reload the template if the original source changed.
* If you don't provide the auto_reload option, it will be
* determined automatically based on the debug value.
*
* * strict_variables: Whether to ignore invalid variables in templates
* (default to false).
*
* * autoescape: Whether to enable auto-escaping (default to html):
* * false: disable auto-escaping
* * html, js: set the autoescaping to one of the supported strategies
* * name: set the autoescaping strategy based on the template name extension
* * PHP callback: a PHP callback that returns an escaping strategy based on the template "name"
*
* * optimizations: A flag that indicates which optimizations to apply
* (default to -1 which means that all optimizations are enabled;
* set it to 0 to disable).
*/
public function __construct(LoaderInterface $loader, $options = [])
{
$this->setLoader($loader);
$options = array_merge([
'debug' => false,
'charset' => 'UTF-8',
'strict_variables' => false,
'autoescape' => 'html',
'cache' => false,
'auto_reload' => null,
'optimizations' => -1,
], $options);
$this->debug = (bool) $options['debug'];
$this->setCharset($options['charset'] ?? 'UTF-8');
$this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
$this->strictVariables = (bool) $options['strict_variables'];
$this->setCache($options['cache']);
$this->extensionSet = new ExtensionSet();
$this->addExtension(new CoreExtension());
$this->addExtension(new EscaperExtension($options['autoescape']));
$this->addExtension(new OptimizerExtension($options['optimizations']));
}
/**
* Enables debugging mode.
*/
public function enableDebug()
{
$this->debug = true;
$this->updateOptionsHash();
}
/**
* Disables debugging mode.
*/
public function disableDebug()
{
$this->debug = false;
$this->updateOptionsHash();
}
/**
* Checks if debug mode is enabled.
*
* @return bool true if debug mode is enabled, false otherwise
*/
public function isDebug()
{
return $this->debug;
}
/**
* Enables the auto_reload option.
*/
public function enableAutoReload()
{
$this->autoReload = true;
}
/**
* Disables the auto_reload option.
*/
public function disableAutoReload()
{
$this->autoReload = false;
}
/**
* Checks if the auto_reload option is enabled.
*
* @return bool true if auto_reload is enabled, false otherwise
*/
public function isAutoReload()
{
return $this->autoReload;
}
/**
* Enables the strict_variables option.
*/
public function enableStrictVariables()
{
$this->strictVariables = true;
$this->updateOptionsHash();
}
/**
* Disables the strict_variables option.
*/
public function disableStrictVariables()
{
$this->strictVariables = false;
$this->updateOptionsHash();
}
/**
* Checks if the strict_variables option is enabled.
*
* @return bool true if strict_variables is enabled, false otherwise
*/
public function isStrictVariables()
{
return $this->strictVariables;
}
/**
* Gets the current cache implementation.
*
* @param bool $original Whether to return the original cache option or the real cache instance
*
* @return CacheInterface|string|false A Twig\Cache\CacheInterface implementation,
* an absolute path to the compiled templates,
* or false to disable cache
*/
public function getCache($original = true)
{
return $original ? $this->originalCache : $this->cache;
}
/**
* Sets the current cache implementation.
*
* @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation,
* an absolute path to the compiled templates,
* or false to disable cache
*/
public function setCache($cache)
{
if (\is_string($cache)) {
$this->originalCache = $cache;
$this->cache = new FilesystemCache($cache);
} elseif (false === $cache) {
$this->originalCache = $cache;
$this->cache = new NullCache();
} elseif ($cache instanceof CacheInterface) {
$this->originalCache = $this->cache = $cache;
} else {
throw new \LogicException(sprintf('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.'));
}
}
/**
* Gets the template class associated with the given string.
*
* The generated template class is based on the following parameters:
*
* * The cache key for the given template;
* * The currently enabled extensions;
* * Whether the Twig C extension is available or not;
* * PHP version;
* * Twig version;
* * Options with what environment was created.
*
* @param string $name The name for which to calculate the template class name
* @param int|null $index The index if it is an embedded template
*
* @internal
*/
public function getTemplateClass(string $name, int $index = null): string
{
$key = $this->getLoader()->getCacheKey($name).$this->optionsHash;
return $this->templateClassPrefix.hash('sha256', $key).(null === $index ? '' : '___'.$index);
}
/**
* Renders a template.
*
* @param string|TemplateWrapper $name The template name
*
* @throws LoaderError When the template cannot be found
* @throws SyntaxError When an error occurred during compilation
* @throws RuntimeError When an error occurred during rendering
*/
public function render($name, array $context = []): string
{
return $this->load($name)->render($context);
}
/**
* Displays a template.
*
* @param string|TemplateWrapper $name The template name
*
* @throws LoaderError When the template cannot be found
* @throws SyntaxError When an error occurred during compilation
* @throws RuntimeError When an error occurred during rendering
*/
public function display($name, array $context = []): void
{
$this->load($name)->display($context);
}
/**
* Loads a template.
*
* @param string|TemplateWrapper $name The template name
*
* @throws LoaderError When the template cannot be found
* @throws RuntimeError When a previously generated cache is corrupted
* @throws SyntaxError When an error occurred during compilation
*/
public function load($name): TemplateWrapper
{
if ($name instanceof TemplateWrapper) {
return $name;
}
return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name));
}
/**
* Loads a template internal representation.
*
* This method is for internal use only and should never be called
* directly.
*
* @param string $name The template name
* @param int $index The index if it is an embedded template
*
* @throws LoaderError When the template cannot be found
* @throws RuntimeError When a previously generated cache is corrupted
* @throws SyntaxError When an error occurred during compilation
*
* @internal
*/
public function loadTemplate(string $cls, string $name, int $index = null): Template
{
$mainCls = $cls;
if (null !== $index) {
$cls .= '___'.$index;
}
if (isset($this->loadedTemplates[$cls])) {
return $this->loadedTemplates[$cls];
}
if (!class_exists($cls, false)) {
$key = $this->cache->generateKey($name, $mainCls);
if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) {
$this->cache->load($key);
}
$source = null;
if (!class_exists($cls, false)) {
$source = $this->getLoader()->getSourceContext($name);
$content = $this->compileSource($source);
$this->cache->write($key, $content);
$this->cache->load($key);
if (!class_exists($mainCls, false)) {
/* Last line of defense if either $this->bcWriteCacheFile was used,
* $this->cache is implemented as a no-op or we have a race condition
* where the cache was cleared between the above calls to write to and load from
* the cache.
*/
eval('?>'.$content);
}
if (!class_exists($cls, false)) {
throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source);
}
}
}
$this->extensionSet->initRuntime();
return $this->loadedTemplates[$cls] = new $cls($this);
}
/**
* Creates a template from source.
*
* This method should not be used as a generic way to load templates.
*
* @param string $template The template source
* @param string $name An optional name of the template to be used in error messages
*
* @throws LoaderError When the template cannot be found
* @throws SyntaxError When an error occurred during compilation
*/
public function createTemplate(string $template, string $name = null): TemplateWrapper
{
$hash = hash('sha256', $template, false);
if (null !== $name) {
$name = sprintf('%s (string template %s)', $name, $hash);
} else {
$name = sprintf('__string_template__%s', $hash);
}
$loader = new ChainLoader([
new ArrayLoader([$name => $template]),
$current = $this->getLoader(),
]);
$this->setLoader($loader);
try {
return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name));
} finally {
$this->setLoader($current);
}
}
/**
* Returns true if the template is still fresh.
*
* Besides checking the loader for freshness information,
* this method also checks if the enabled extensions have
* not changed.
*
* @param int $time The last modification time of the cached template
*/
public function isTemplateFresh(string $name, int $time): bool
{
return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time);
}
/**
* Tries to load a template consecutively from an array.
*
* Similar to load() but it also accepts instances of \Twig\Template and
* \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded.
*
* @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively
*
* @throws LoaderError When none of the templates can be found
* @throws SyntaxError When an error occurred during compilation
*/
public function resolveTemplate($names): TemplateWrapper
{
if (!\is_array($names)) {
return $this->load($names);
}
foreach ($names as $name) {
try {
return $this->load($name);
} catch (LoaderError $e) {
}
}
throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
}
public function setLexer(Lexer $lexer)
{
$this->lexer = $lexer;
}
/**
* @throws SyntaxError When the code is syntactically wrong
*/
public function tokenize(Source $source): TokenStream
{
if (null === $this->lexer) {
$this->lexer = new Lexer($this);
}
return $this->lexer->tokenize($source);
}
public function setParser(Parser $parser)
{
$this->parser = $parser;
}
/**
* Converts a token stream to a node tree.
*
* @throws SyntaxError When the token stream is syntactically or semantically wrong
*/
public function parse(TokenStream $stream): ModuleNode
{
if (null === $this->parser) {
$this->parser = new Parser($this);
}
return $this->parser->parse($stream);
}
public function setCompiler(Compiler $compiler)
{
$this->compiler = $compiler;
}
/**
* Compiles a node and returns the PHP code.
*/
public function compile(Node $node): string
{
if (null === $this->compiler) {
$this->compiler = new Compiler($this);
}
return $this->compiler->compile($node)->getSource();
}
/**
* Compiles a template source code.
*
* @throws SyntaxError When there was an error during tokenizing, parsing or compiling
*/
public function compileSource(Source $source): string
{
try {
return $this->compile($this->parse($this->tokenize($source)));
} catch (Error $e) {
$e->setSourceContext($source);
throw $e;
} catch (\Exception $e) {
throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e);
}
}
public function setLoader(LoaderInterface $loader)
{
$this->loader = $loader;
}
public function getLoader(): LoaderInterface
{
return $this->loader;
}
public function setCharset(string $charset)
{
if ('UTF8' === $charset = strtoupper($charset)) {
// iconv on Windows requires "UTF-8" instead of "UTF8"
$charset = 'UTF-8';
}
$this->charset = $charset;
}
public function getCharset(): string
{
return $this->charset;
}
public function hasExtension(string $class): bool
{
return $this->extensionSet->hasExtension($class);
}
public function addRuntimeLoader(RuntimeLoaderInterface $loader)
{
$this->runtimeLoaders[] = $loader;
}
public function getExtension(string $class): ExtensionInterface
{
return $this->extensionSet->getExtension($class);
}
/**
* Returns the runtime implementation of a Twig element (filter/function/tag/test).
*
* @param string $class A runtime class name
*
* @return object The runtime implementation
*
* @throws RuntimeError When the template cannot be found
*/
public function getRuntime(string $class)
{
if (isset($this->runtimes[$class])) {
return $this->runtimes[$class];
}
foreach ($this->runtimeLoaders as $loader) {
if (null !== $runtime = $loader->load($class)) {
return $this->runtimes[$class] = $runtime;
}
}
throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class));
}
public function addExtension(ExtensionInterface $extension)
{
$this->extensionSet->addExtension($extension);
$this->updateOptionsHash();
}
/**
* @param ExtensionInterface[] $extensions An array of extensions
*/
public function setExtensions(array $extensions)
{
$this->extensionSet->setExtensions($extensions);
$this->updateOptionsHash();
}
/**
* @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on)
*/
public function getExtensions(): array
{
return $this->extensionSet->getExtensions();
}
public function addTokenParser(TokenParserInterface $parser)
{
$this->extensionSet->addTokenParser($parser);
}
/**
* @return TokenParserInterface[]
*
* @internal
*/
public function getTokenParsers(): array
{
return $this->extensionSet->getTokenParsers();
}
/**
* @internal
*/
public function getTokenParser(string $name): ?TokenParserInterface
{
return $this->extensionSet->getTokenParser($name);
}
public function registerUndefinedTokenParserCallback(callable $callable): void
{
$this->extensionSet->registerUndefinedTokenParserCallback($callable);
}
public function addNodeVisitor(NodeVisitorInterface $visitor)
{
$this->extensionSet->addNodeVisitor($visitor);
}
/**
* @return NodeVisitorInterface[]
*
* @internal
*/
public function getNodeVisitors(): array
{
return $this->extensionSet->getNodeVisitors();
}
public function addFilter(TwigFilter $filter)
{
$this->extensionSet->addFilter($filter);
}
/**
* @internal
*/
public function getFilter(string $name): ?TwigFilter
{
return $this->extensionSet->getFilter($name);
}
public function registerUndefinedFilterCallback(callable $callable): void
{
$this->extensionSet->registerUndefinedFilterCallback($callable);
}
/**
* Gets the registered Filters.
*
* Be warned that this method cannot return filters defined with registerUndefinedFilterCallback.
*
* @return TwigFilter[]
*
* @see registerUndefinedFilterCallback
*
* @internal
*/
public function getFilters(): array
{
return $this->extensionSet->getFilters();
}
public function addTest(TwigTest $test)
{
$this->extensionSet->addTest($test);
}
/**
* @return TwigTest[]
*
* @internal
*/
public function getTests(): array
{
return $this->extensionSet->getTests();
}
/**
* @internal
*/
public function getTest(string $name): ?TwigTest
{
return $this->extensionSet->getTest($name);
}
public function addFunction(TwigFunction $function)
{
$this->extensionSet->addFunction($function);
}
/**
* @internal
*/
public function getFunction(string $name): ?TwigFunction
{
return $this->extensionSet->getFunction($name);
}
public function registerUndefinedFunctionCallback(callable $callable): void
{
$this->extensionSet->registerUndefinedFunctionCallback($callable);
}
/**
* Gets registered functions.
*
* Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
*
* @return TwigFunction[]
*
* @see registerUndefinedFunctionCallback
*
* @internal
*/
public function getFunctions(): array
{
return $this->extensionSet->getFunctions();
}
/**
* Registers a Global.
*
* New globals can be added before compiling or rendering a template;
* but after, you can only update existing globals.
*
* @param mixed $value The global value
*/
public function addGlobal(string $name, $value)
{
if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) {
throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
}
if (null !== $this->resolvedGlobals) {
$this->resolvedGlobals[$name] = $value;
} else {
$this->globals[$name] = $value;
}
}
/**
* @internal
*/
public function getGlobals(): array
{
if ($this->extensionSet->isInitialized()) {
if (null === $this->resolvedGlobals) {
$this->resolvedGlobals = array_merge($this->extensionSet->getGlobals(), $this->globals);
}
return $this->resolvedGlobals;
}
return array_merge($this->extensionSet->getGlobals(), $this->globals);
}
public function mergeGlobals(array $context): array
{
// we don't use array_merge as the context being generally
// bigger than globals, this code is faster.
foreach ($this->getGlobals() as $key => $value) {
if (!\array_key_exists($key, $context)) {
$context[$key] = $value;
}
}
return $context;
}
/**
* @internal
*/
public function getUnaryOperators(): array
{
return $this->extensionSet->getUnaryOperators();
}
/**
* @internal
*/
public function getBinaryOperators(): array
{
return $this->extensionSet->getBinaryOperators();
}
private function updateOptionsHash(): void
{
$this->optionsHash = implode(':', [
$this->extensionSet->getSignature(),
PHP_MAJOR_VERSION,
PHP_MINOR_VERSION,
self::VERSION,
(int) $this->debug,
(int) $this->strictVariables,
]);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\Expression\BlockReferenceExpression;
use Twig\Node\Expression\ConditionalExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\ParentExpression;
use Twig\Node\Node;
/**
* @internal
*/
final class SafeAnalysisNodeVisitor implements NodeVisitorInterface
{
private $data = [];
private $safeVars = [];
public function setSafeVars(array $safeVars): void
{
$this->safeVars = $safeVars;
}
public function getSafe(Node $node)
{
$hash = spl_object_hash($node);
if (!isset($this->data[$hash])) {
return;
}
foreach ($this->data[$hash] as $bucket) {
if ($bucket['key'] !== $node) {
continue;
}
if (\in_array('html_attr', $bucket['value'])) {
$bucket['value'][] = 'html';
}
return $bucket['value'];
}
}
private function setSafe(Node $node, array $safe): void
{
$hash = spl_object_hash($node);
if (isset($this->data[$hash])) {
foreach ($this->data[$hash] as &$bucket) {
if ($bucket['key'] === $node) {
$bucket['value'] = $safe;
return;
}
}
}
$this->data[$hash][] = [
'key' => $node,
'value' => $safe,
];
}
public function enterNode(Node $node, Environment $env): Node
{
return $node;
}
public function leaveNode(Node $node, Environment $env): ?Node
{
if ($node instanceof ConstantExpression) {
// constants are marked safe for all
$this->setSafe($node, ['all']);
} elseif ($node instanceof BlockReferenceExpression) {
// blocks are safe by definition
$this->setSafe($node, ['all']);
} elseif ($node instanceof ParentExpression) {
// parent block is safe by definition
$this->setSafe($node, ['all']);
} elseif ($node instanceof ConditionalExpression) {
// intersect safeness of both operands
$safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3')));
$this->setSafe($node, $safe);
} elseif ($node instanceof FilterExpression) {
// filter expression is safe when the filter is safe
$name = $node->getNode('filter')->getAttribute('value');
$args = $node->getNode('arguments');
if ($filter = $env->getFilter($name)) {
$safe = $filter->getSafe($args);
if (null === $safe) {
$safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety());
}
$this->setSafe($node, $safe);
} else {
$this->setSafe($node, []);
}
} elseif ($node instanceof FunctionExpression) {
// function expression is safe when the function is safe
$name = $node->getAttribute('name');
$args = $node->getNode('arguments');
if ($function = $env->getFunction($name)) {
$this->setSafe($node, $function->getSafe($args));
} else {
$this->setSafe($node, []);
}
} elseif ($node instanceof MethodCallExpression) {
if ($node->getAttribute('safe')) {
$this->setSafe($node, ['all']);
} else {
$this->setSafe($node, []);
}
} elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) {
$name = $node->getNode('node')->getAttribute('name');
if (\in_array($name, $this->safeVars)) {
$this->setSafe($node, ['all']);
} else {
$this->setSafe($node, []);
}
} else {
$this->setSafe($node, []);
}
return $node;
}
private function intersectSafe(array $a = null, array $b = null): array
{
if (null === $a || null === $b) {
return [];
}
if (\in_array('all', $a)) {
return $b;
}
if (\in_array('all', $b)) {
return $a;
}
return array_intersect($a, $b);
}
public function getPriority(): int
{
return 0;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\CheckSecurityCallNode;
use Twig\Node\CheckSecurityNode;
use Twig\Node\CheckToStringNode;
use Twig\Node\Expression\Binary\ConcatBinary;
use Twig\Node\Expression\Binary\RangeBinary;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\Node\SetNode;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class SandboxNodeVisitor implements NodeVisitorInterface
{
private $inAModule = false;
private $tags;
private $filters;
private $functions;
private $needsToStringWrap = false;
public function enterNode(Node $node, Environment $env): Node
{
if ($node instanceof ModuleNode) {
$this->inAModule = true;
$this->tags = [];
$this->filters = [];
$this->functions = [];
return $node;
} elseif ($this->inAModule) {
// look for tags
if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) {
$this->tags[$node->getNodeTag()] = $node;
}
// look for filters
if ($node instanceof FilterExpression && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) {
$this->filters[$node->getNode('filter')->getAttribute('value')] = $node;
}
// look for functions
if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) {
$this->functions[$node->getAttribute('name')] = $node;
}
// the .. operator is equivalent to the range() function
if ($node instanceof RangeBinary && !isset($this->functions['range'])) {
$this->functions['range'] = $node;
}
if ($node instanceof PrintNode) {
$this->needsToStringWrap = true;
$this->wrapNode($node, 'expr');
}
if ($node instanceof SetNode && !$node->getAttribute('capture')) {
$this->needsToStringWrap = true;
}
// wrap outer nodes that can implicitly call __toString()
if ($this->needsToStringWrap) {
if ($node instanceof ConcatBinary) {
$this->wrapNode($node, 'left');
$this->wrapNode($node, 'right');
}
if ($node instanceof FilterExpression) {
$this->wrapNode($node, 'node');
$this->wrapArrayNode($node, 'arguments');
}
if ($node instanceof FunctionExpression) {
$this->wrapArrayNode($node, 'arguments');
}
}
}
return $node;
}
public function leaveNode(Node $node, Environment $env): ?Node
{
if ($node instanceof ModuleNode) {
$this->inAModule = false;
$node->setNode('constructor_end', new Node([new CheckSecurityCallNode(), $node->getNode('constructor_end')]));
$node->setNode('class_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')]));
} elseif ($this->inAModule) {
if ($node instanceof PrintNode || $node instanceof SetNode) {
$this->needsToStringWrap = false;
}
}
return $node;
}
private function wrapNode(Node $node, string $name): void
{
$expr = $node->getNode($name);
if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) {
$node->setNode($name, new CheckToStringNode($expr));
}
}
private function wrapArrayNode(Node $node, string $name): void
{
$args = $node->getNode($name);
foreach ($args as $name => $_) {
$this->wrapNode($args, $name);
}
}
public function getPriority(): int
{
return 0;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\BlockReferenceNode;
use Twig\Node\Expression\BlockReferenceExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\FunctionExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Expression\ParentExpression;
use Twig\Node\ForNode;
use Twig\Node\IncludeNode;
use Twig\Node\Node;
use Twig\Node\PrintNode;
/**
* Tries to optimize the AST.
*
* This visitor is always the last registered one.
*
* You can configure which optimizations you want to activate via the
* optimizer mode.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class OptimizerNodeVisitor implements NodeVisitorInterface
{
const OPTIMIZE_ALL = -1;
const OPTIMIZE_NONE = 0;
const OPTIMIZE_FOR = 2;
const OPTIMIZE_RAW_FILTER = 4;
private $loops = [];
private $loopsTargets = [];
private $optimizers;
/**
* @param int $optimizers The optimizer mode
*/
public function __construct(int $optimizers = -1)
{
if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER)) {
throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
}
$this->optimizers = $optimizers;
}
public function enterNode(Node $node, Environment $env): Node
{
if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
$this->enterOptimizeFor($node, $env);
}
return $node;
}
public function leaveNode(Node $node, Environment $env): ?Node
{
if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
$this->leaveOptimizeFor($node, $env);
}
if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
$node = $this->optimizeRawFilter($node, $env);
}
$node = $this->optimizePrintNode($node, $env);
return $node;
}
/**
* Optimizes print nodes.
*
* It replaces:
*
* * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
*/
private function optimizePrintNode(Node $node, Environment $env): Node
{
if (!$node instanceof PrintNode) {
return $node;
}
$exprNode = $node->getNode('expr');
if (
$exprNode instanceof BlockReferenceExpression ||
$exprNode instanceof ParentExpression
) {
$exprNode->setAttribute('output', true);
return $exprNode;
}
return $node;
}
/**
* Removes "raw" filters.
*/
private function optimizeRawFilter(Node $node, Environment $env): Node
{
if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) {
return $node->getNode('node');
}
return $node;
}
/**
* Optimizes "for" tag by removing the "loop" variable creation whenever possible.
*/
private function enterOptimizeFor(Node $node, Environment $env): void
{
if ($node instanceof ForNode) {
// disable the loop variable by default
$node->setAttribute('with_loop', false);
array_unshift($this->loops, $node);
array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name'));
array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name'));
} elseif (!$this->loops) {
// we are outside a loop
return;
}
// when do we need to add the loop variable back?
// the loop variable is referenced for the current loop
elseif ($node instanceof NameExpression && 'loop' === $node->getAttribute('name')) {
$node->setAttribute('always_defined', true);
$this->addLoopToCurrent();
}
// optimize access to loop targets
elseif ($node instanceof NameExpression && \in_array($node->getAttribute('name'), $this->loopsTargets)) {
$node->setAttribute('always_defined', true);
}
// block reference
elseif ($node instanceof BlockReferenceNode || $node instanceof BlockReferenceExpression) {
$this->addLoopToCurrent();
}
// include without the only attribute
elseif ($node instanceof IncludeNode && !$node->getAttribute('only')) {
$this->addLoopToAll();
}
// include function without the with_context=false parameter
elseif ($node instanceof FunctionExpression
&& 'include' === $node->getAttribute('name')
&& (!$node->getNode('arguments')->hasNode('with_context')
|| false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value')
)
) {
$this->addLoopToAll();
}
// the loop variable is referenced via an attribute
elseif ($node instanceof GetAttrExpression
&& (!$node->getNode('attribute') instanceof ConstantExpression
|| 'parent' === $node->getNode('attribute')->getAttribute('value')
)
&& (true === $this->loops[0]->getAttribute('with_loop')
|| ($node->getNode('node') instanceof NameExpression
&& 'loop' === $node->getNode('node')->getAttribute('name')
)
)
) {
$this->addLoopToAll();
}
}
/**
* Optimizes "for" tag by removing the "loop" variable creation whenever possible.
*/
private function leaveOptimizeFor(Node $node, Environment $env): void
{
if ($node instanceof ForNode) {
array_shift($this->loops);
array_shift($this->loopsTargets);
array_shift($this->loopsTargets);
}
}
private function addLoopToCurrent(): void
{
$this->loops[0]->setAttribute('with_loop', true);
}
private function addLoopToAll(): void
{
foreach ($this->loops as $loop) {
$loop->setAttribute('with_loop', true);
}
}
public function getPriority(): int
{
return 255;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\Node;
/**
* Interface for node visitor classes.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface NodeVisitorInterface
{
/**
* Called before child nodes are visited.
*
* @return Node The modified node
*/
public function enterNode(Node $node, Environment $env): Node;
/**
* Called after child nodes are visited.
*
* @return Node|null The modified node or null if the node must be removed
*/
public function leaveNode(Node $node, Environment $env): ?Node;
/**
* Returns the priority for this visitor.
*
* Priority should be between -10 and 10 (0 is the default).
*
* @return int The priority level
*/
public function getPriority();
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Extension\EscaperExtension;
use Twig\Node\AutoEscapeNode;
use Twig\Node\BlockNode;
use Twig\Node\BlockReferenceNode;
use Twig\Node\DoNode;
use Twig\Node\Expression\ConditionalExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Expression\InlinePrint;
use Twig\Node\ImportNode;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\NodeTraverser;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class EscaperNodeVisitor implements NodeVisitorInterface
{
private $statusStack = [];
private $blocks = [];
private $safeAnalysis;
private $traverser;
private $defaultStrategy = false;
private $safeVars = [];
public function __construct()
{
$this->safeAnalysis = new SafeAnalysisNodeVisitor();
}
public function enterNode(Node $node, Environment $env): Node
{
if ($node instanceof ModuleNode) {
if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) {
$this->defaultStrategy = $defaultStrategy;
}
$this->safeVars = [];
$this->blocks = [];
} elseif ($node instanceof AutoEscapeNode) {
$this->statusStack[] = $node->getAttribute('value');
} elseif ($node instanceof BlockNode) {
$this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
} elseif ($node instanceof ImportNode) {
$this->safeVars[] = $node->getNode('var')->getAttribute('name');
}
return $node;
}
public function leaveNode(Node $node, Environment $env): ?Node
{
if ($node instanceof ModuleNode) {
$this->defaultStrategy = false;
$this->safeVars = [];
$this->blocks = [];
} elseif ($node instanceof FilterExpression) {
return $this->preEscapeFilterNode($node, $env);
} elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) {
$expression = $node->getNode('expr');
if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) {
return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine());
}
return $this->escapePrintNode($node, $env, $type);
}
if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) {
array_pop($this->statusStack);
} elseif ($node instanceof BlockReferenceNode) {
$this->blocks[$node->getAttribute('name')] = $this->needEscaping($env);
}
return $node;
}
private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, string $type): bool
{
$expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env);
$expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env);
return $expr2Safe !== $expr3Safe;
}
private function unwrapConditional(ConditionalExpression $expression, Environment $env, string $type): ConditionalExpression
{
// convert "echo a ? b : c" to "a ? echo b : echo c" recursively
$expr2 = $expression->getNode('expr2');
if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) {
$expr2 = $this->unwrapConditional($expr2, $env, $type);
} else {
$expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type);
}
$expr3 = $expression->getNode('expr3');
if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr3, $env, $type)) {
$expr3 = $this->unwrapConditional($expr3, $env, $type);
} else {
$expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type);
}
return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine());
}
private function escapeInlinePrintNode(InlinePrint $node, Environment $env, string $type): Node
{
$expression = $node->getNode('node');
if ($this->isSafeFor($type, $expression, $env)) {
return $node;
}
return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine());
}
private function escapePrintNode(PrintNode $node, Environment $env, string $type): Node
{
if (false === $type) {
return $node;
}
$expression = $node->getNode('expr');
if ($this->isSafeFor($type, $expression, $env)) {
return $node;
}
$class = \get_class($node);
return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine());
}
private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression
{
$name = $filter->getNode('filter')->getAttribute('value');
$type = $env->getFilter($name)->getPreEscape();
if (null === $type) {
return $filter;
}
$node = $filter->getNode('node');
if ($this->isSafeFor($type, $node, $env)) {
return $filter;
}
$filter->setNode('node', $this->getEscaperFilter($type, $node));
return $filter;
}
private function isSafeFor(string $type, Node $expression, Environment $env): bool
{
$safe = $this->safeAnalysis->getSafe($expression);
if (null === $safe) {
if (null === $this->traverser) {
$this->traverser = new NodeTraverser($env, [$this->safeAnalysis]);
}
$this->safeAnalysis->setSafeVars($this->safeVars);
$this->traverser->traverse($expression);
$safe = $this->safeAnalysis->getSafe($expression);
}
return \in_array($type, $safe) || \in_array('all', $safe);
}
private function needEscaping(Environment $env)
{
if (\count($this->statusStack)) {
return $this->statusStack[\count($this->statusStack) - 1];
}
return $this->defaultStrategy ? $this->defaultStrategy : false;
}
private function getEscaperFilter(string $type, Node $node): FilterExpression
{
$line = $node->getTemplateLine();
$name = new ConstantExpression('escape', $line);
$args = new Node([new ConstantExpression((string) $type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]);
return new FilterExpression($node, $name, $args, $line);
}
public function getPriority(): int
{
return 0;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\Node;
/**
* Used to make node visitors compatible with Twig 1.x and 2.x.
*
* To be removed in Twig 3.1.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractNodeVisitor implements NodeVisitorInterface
{
final public function enterNode(Node $node, Environment $env): Node
{
return $this->doEnterNode($node, $env);
}
final public function leaveNode(Node $node, Environment $env): ?Node
{
return $this->doLeaveNode($node, $env);
}
/**
* Called before child nodes are visited.
*
* @return Node The modified node
*/
abstract protected function doEnterNode(Node $node, Environment $env);
/**
* Called after child nodes are visited.
*
* @return Node|null The modified node or null if the node must be removed
*/
abstract protected function doLeaveNode(Node $node, Environment $env);
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\NodeVisitor;
use Twig\Environment;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\GetAttrExpression;
use Twig\Node\Expression\MethodCallExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\ImportNode;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class MacroAutoImportNodeVisitor implements NodeVisitorInterface
{
private $inAModule = false;
private $hasMacroCalls = false;
public function enterNode(Node $node, Environment $env): Node
{
if ($node instanceof ModuleNode) {
$this->inAModule = true;
$this->hasMacroCalls = false;
}
return $node;
}
public function leaveNode(Node $node, Environment $env): Node
{
if ($node instanceof ModuleNode) {
$this->inAModule = false;
if ($this->hasMacroCalls) {
$node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, 'import', true));
}
} elseif ($this->inAModule) {
if (
$node instanceof GetAttrExpression &&
$node->getNode('node') instanceof NameExpression &&
'_self' === $node->getNode('node')->getAttribute('name') &&
$node->getNode('attribute') instanceof ConstantExpression
) {
$this->hasMacroCalls = true;
$name = $node->getNode('attribute')->getAttribute('value');
$node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine());
$node->setAttribute('safe', true);
}
}
return $node;
}
public function getPriority(): int
{
// we must be ran before auto-escaping
return -10;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Node\Node;
use Twig\NodeVisitor\NodeVisitorInterface;
/**
* A node traverser.
*
* It visits all nodes and their children and calls the given visitor for each.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class NodeTraverser
{
private $env;
private $visitors = [];
/**
* @param NodeVisitorInterface[] $visitors
*/
public function __construct(Environment $env, array $visitors = [])
{
$this->env = $env;
foreach ($visitors as $visitor) {
$this->addVisitor($visitor);
}
}
public function addVisitor(NodeVisitorInterface $visitor): void
{
$this->visitors[$visitor->getPriority()][] = $visitor;
}
/**
* Traverses a node and calls the registered visitors.
*/
public function traverse(Node $node): Node
{
ksort($this->visitors);
foreach ($this->visitors as $visitors) {
foreach ($visitors as $visitor) {
$node = $this->traverseForVisitor($visitor, $node);
}
}
return $node;
}
private function traverseForVisitor(NodeVisitorInterface $visitor, Node $node): ?Node
{
$node = $visitor->enterNode($node, $this->env);
foreach ($node as $k => $n) {
if (null !== $m = $this->traverseForVisitor($visitor, $n)) {
if ($m !== $n) {
$node->setNode($k, $m);
}
} else {
$node->removeNode($k);
}
}
return $visitor->leaveNode($node, $this->env);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Error\SyntaxError;
use Twig\Node\BlockNode;
use Twig\Node\BlockReferenceNode;
use Twig\Node\BodyNode;
use Twig\Node\Expression\AbstractExpression;
use Twig\Node\MacroNode;
use Twig\Node\ModuleNode;
use Twig\Node\Node;
use Twig\Node\NodeCaptureInterface;
use Twig\Node\NodeOutputInterface;
use Twig\Node\PrintNode;
use Twig\Node\TextNode;
use Twig\TokenParser\TokenParserInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Parser
{
private $stack = [];
private $stream;
private $parent;
private $visitors;
private $expressionParser;
private $blocks;
private $blockStack;
private $macros;
private $env;
private $importedSymbols;
private $traits;
private $embeddedTemplates = [];
private $varNameSalt = 0;
public function __construct(Environment $env)
{
$this->env = $env;
}
public function getVarName(): string
{
return sprintf('__internal_%s', hash('sha256', __METHOD__.$this->stream->getSourceContext()->getCode().$this->varNameSalt++));
}
public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode
{
$vars = get_object_vars($this);
unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames']);
$this->stack[] = $vars;
// node visitors
if (null === $this->visitors) {
$this->visitors = $this->env->getNodeVisitors();
}
if (null === $this->expressionParser) {
$this->expressionParser = new ExpressionParser($this, $this->env);
}
$this->stream = $stream;
$this->parent = null;
$this->blocks = [];
$this->macros = [];
$this->traits = [];
$this->blockStack = [];
$this->importedSymbols = [[]];
$this->embeddedTemplates = [];
$this->varNameSalt = 0;
try {
$body = $this->subparse($test, $dropNeedle);
if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) {
$body = new Node();
}
} catch (SyntaxError $e) {
if (!$e->getSourceContext()) {
$e->setSourceContext($this->stream->getSourceContext());
}
if (!$e->getTemplateLine()) {
$e->setTemplateLine($this->stream->getCurrent()->getLine());
}
throw $e;
}
$node = new ModuleNode(new BodyNode([$body]), $this->parent, new Node($this->blocks), new Node($this->macros), new Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext());
$traverser = new NodeTraverser($this->env, $this->visitors);
$node = $traverser->traverse($node);
// restore previous stack so previous parse() call can resume working
foreach (array_pop($this->stack) as $key => $val) {
$this->$key = $val;
}
return $node;
}
public function subparse($test, bool $dropNeedle = false): Node
{
$lineno = $this->getCurrentToken()->getLine();
$rv = [];
while (!$this->stream->isEOF()) {
switch ($this->getCurrentToken()->getType()) {
case /* Token::TEXT_TYPE */ 0:
$token = $this->stream->next();
$rv[] = new TextNode($token->getValue(), $token->getLine());
break;
case /* Token::VAR_START_TYPE */ 2:
$token = $this->stream->next();
$expr = $this->expressionParser->parseExpression();
$this->stream->expect(/* Token::VAR_END_TYPE */ 4);
$rv[] = new PrintNode($expr, $token->getLine());
break;
case /* Token::BLOCK_START_TYPE */ 1:
$this->stream->next();
$token = $this->getCurrentToken();
if (/* Token::NAME_TYPE */ 5 !== $token->getType()) {
throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext());
}
if (null !== $test && $test($token)) {
if ($dropNeedle) {
$this->stream->next();
}
if (1 === \count($rv)) {
return $rv[0];
}
return new Node($rv, [], $lineno);
}
if (!$subparser = $this->env->getTokenParser($token->getValue())) {
if (null !== $test) {
$e = new SyntaxError(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
if (\is_array($test) && isset($test[0]) && $test[0] instanceof TokenParserInterface) {
$e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno));
}
} else {
$e = new SyntaxError(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext());
$e->addSuggestions($token->getValue(), array_keys($this->env->getTokenParsers()));
}
throw $e;
}
$this->stream->next();
$subparser->setParser($this);
$node = $subparser->parse($token);
if (null !== $node) {
$rv[] = $node;
}
break;
default:
throw new SyntaxError('Lexer or parser ended up in unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext());
}
}
if (1 === \count($rv)) {
return $rv[0];
}
return new Node($rv, [], $lineno);
}
public function getBlockStack(): array
{
return $this->blockStack;
}
public function peekBlockStack()
{
return isset($this->blockStack[\count($this->blockStack) - 1]) ? $this->blockStack[\count($this->blockStack) - 1] : null;
}
public function popBlockStack(): void
{
array_pop($this->blockStack);
}
public function pushBlockStack($name): void
{
$this->blockStack[] = $name;
}
public function hasBlock(string $name): bool
{
return isset($this->blocks[$name]);
}
public function getBlock(string $name): Node
{
return $this->blocks[$name];
}
public function setBlock(string $name, BlockNode $value): void
{
$this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine());
}
public function hasMacro(string $name): bool
{
return isset($this->macros[$name]);
}
public function setMacro(string $name, MacroNode $node): void
{
$this->macros[$name] = $node;
}
public function addTrait($trait): void
{
$this->traits[] = $trait;
}
public function hasTraits(): bool
{
return \count($this->traits) > 0;
}
public function embedTemplate(ModuleNode $template)
{
$template->setIndex(mt_rand());
$this->embeddedTemplates[] = $template;
}
public function addImportedSymbol(string $type, string $alias, string $name = null, AbstractExpression $node = null): void
{
$this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node];
}
public function getImportedSymbol(string $type, string $alias)
{
// if the symbol does not exist in the current scope (0), try in the main/global scope (last index)
return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null);
}
public function isMainScope(): bool
{
return 1 === \count($this->importedSymbols);
}
public function pushLocalScope(): void
{
array_unshift($this->importedSymbols, []);
}
public function popLocalScope(): void
{
array_shift($this->importedSymbols);
}
public function getExpressionParser(): ExpressionParser
{
return $this->expressionParser;
}
public function getParent(): ?Node
{
return $this->parent;
}
public function setParent(?Node $parent): void
{
$this->parent = $parent;
}
public function getStream(): TokenStream
{
return $this->stream;
}
public function getCurrentToken(): Token
{
return $this->stream->getCurrent();
}
private function filterBodyNodes(Node $node, bool $nested = false): ?Node
{
// check that the body does not contain non-empty output nodes
if (
($node instanceof TextNode && !ctype_space($node->getAttribute('data')))
||
(!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface)
) {
if (false !== strpos((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) {
$t = substr($node->getAttribute('data'), 3);
if ('' === $t || ctype_space($t)) {
// bypass empty nodes starting with a BOM
return null;
}
}
throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext());
}
// bypass nodes that "capture" the output
if ($node instanceof NodeCaptureInterface) {
// a "block" tag in such a node will serve as a block definition AND be displayed in place as well
return $node;
}
// "block" tags that are not captured (see above) are only used for defining
// the content of the block. In such a case, nesting it does not work as
// expected as the definition is not part of the default template code flow.
if ($nested && $node instanceof BlockReferenceNode) {
throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext());
}
if ($node instanceof NodeOutputInterface) {
return null;
}
// here, $nested means "being at the root level of a child template"
// we need to discard the wrapping "Node" for the "body" node
$nested = $nested || Node::class !== \get_class($node);
foreach ($node as $k => $n) {
if (null !== $n && null === $this->filterBodyNodes($n, $nested)) {
$node->removeNode($k);
}
}
return $node;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Test;
use PHPUnit\Framework\TestCase;
use Twig\Environment;
use Twig\Error\Error;
use Twig\Extension\ExtensionInterface;
use Twig\Loader\ArrayLoader;
use Twig\RuntimeLoader\RuntimeLoaderInterface;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
/**
* Integration test helper.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Karma Dordrak <drak@zikula.org>
*/
abstract class IntegrationTestCase extends TestCase
{
/**
* @return string
*/
abstract protected function getFixturesDir();
/**
* @return RuntimeLoaderInterface[]
*/
protected function getRuntimeLoaders()
{
return [];
}
/**
* @return ExtensionInterface[]
*/
protected function getExtensions()
{
return [];
}
/**
* @return TwigFilter[]
*/
protected function getTwigFilters()
{
return [];
}
/**
* @return TwigFunction[]
*/
protected function getTwigFunctions()
{
return [];
}
/**
* @return TwigTest[]
*/
protected function getTwigTests()
{
return [];
}
/**
* @dataProvider getTests
*/
public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '')
{
$this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation);
}
/**
* @dataProvider getLegacyTests
* @group legacy
*/
public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '')
{
$this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation);
}
public function getTests($name, $legacyTests = false)
{
$fixturesDir = realpath($this->getFixturesDir());
$tests = [];
foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
if (!preg_match('/\.test$/', $file)) {
continue;
}
if ($legacyTests xor false !== strpos($file->getRealpath(), '.legacy.test')) {
continue;
}
$test = file_get_contents($file->getRealpath());
if (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) {
$message = $match[1];
$condition = $match[2];
$deprecation = $match[3];
$templates = self::parseTemplates($match[4]);
$exception = $match[6];
$outputs = [[null, $match[5], null, '']];
} elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) {
$message = $match[1];
$condition = $match[2];
$deprecation = $match[3];
$templates = self::parseTemplates($match[4]);
$exception = false;
preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER);
} else {
throw new \InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file)));
}
$tests[] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs, $deprecation];
}
if ($legacyTests && empty($tests)) {
// add a dummy test to avoid a PHPUnit message
return [['not', '-', '', [], '', []]];
}
return $tests;
}
public function getLegacyTests()
{
return $this->getTests('testLegacyIntegration', true);
}
protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '')
{
if (!$outputs) {
$this->markTestSkipped('no tests to run');
}
if ($condition) {
eval('$ret = '.$condition.';');
if (!$ret) {
$this->markTestSkipped($condition);
}
}
$loader = new ArrayLoader($templates);
foreach ($outputs as $i => $match) {
$config = array_merge([
'cache' => false,
'strict_variables' => true,
], $match[2] ? eval($match[2].';') : []);
$twig = new Environment($loader, $config);
$twig->addGlobal('global', 'global');
foreach ($this->getRuntimeLoaders() as $runtimeLoader) {
$twig->addRuntimeLoader($runtimeLoader);
}
foreach ($this->getExtensions() as $extension) {
$twig->addExtension($extension);
}
foreach ($this->getTwigFilters() as $filter) {
$twig->addFilter($filter);
}
foreach ($this->getTwigTests() as $test) {
$twig->addTest($test);
}
foreach ($this->getTwigFunctions() as $function) {
$twig->addFunction($function);
}
// avoid using the same PHP class name for different cases
$p = new \ReflectionProperty($twig, 'templateClassPrefix');
$p->setAccessible(true);
$p->setValue($twig, '__TwigTemplate_'.hash('sha256', uniqid(mt_rand(), true), false).'_');
$deprecations = [];
try {
$prevHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) {
if (E_USER_DEPRECATED === $type) {
$deprecations[] = $msg;
return true;
}
return $prevHandler ? $prevHandler($type, $msg, $file, $line, $context) : false;
});
$template = $twig->load('index.twig');
} catch (\Exception $e) {
if (false !== $exception) {
$message = $e->getMessage();
$this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $message)));
$last = substr($message, \strlen($message) - 1);
$this->assertTrue('.' === $last || '?' === $last, 'Exception message must end with a dot or a question mark.');
return;
}
throw new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e);
} finally {
restore_error_handler();
}
$this->assertSame($deprecation, implode("\n", $deprecations));
try {
$output = trim($template->render(eval($match[1].';')), "\n ");
} catch (\Exception $e) {
if (false !== $exception) {
$this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $e->getMessage())));
return;
}
$e = new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e);
$output = trim(sprintf('%s: %s', \get_class($e), $e->getMessage()));
}
if (false !== $exception) {
list($class) = explode(':', $exception);
$constraintClass = class_exists('PHPUnit\Framework\Constraint\Exception') ? 'PHPUnit\Framework\Constraint\Exception' : 'PHPUnit_Framework_Constraint_Exception';
$this->assertThat(null, new $constraintClass($class));
}
$expected = trim($match[3], "\n ");
if ($expected !== $output) {
printf("Compiled templates that failed on case %d:\n", $i + 1);
foreach (array_keys($templates) as $name) {
echo "Template: $name\n";
echo $twig->compile($twig->parse($twig->tokenize($twig->getLoader()->getSourceContext($name))));
}
}
$this->assertEquals($expected, $output, $message.' (in '.$file.')');
}
}
protected static function parseTemplates($test)
{
$templates = [];
preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
$templates[($match[1] ?: 'index.twig')] = $match[2];
}
return $templates;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Test;
use PHPUnit\Framework\TestCase;
use Twig\Compiler;
use Twig\Environment;
use Twig\Loader\ArrayLoader;
use Twig\Node\Node;
abstract class NodeTestCase extends TestCase
{
abstract public function getTests();
/**
* @dataProvider getTests
*/
public function testCompile($node, $source, $environment = null, $isPattern = false)
{
$this->assertNodeCompilation($source, $node, $environment, $isPattern);
}
public function assertNodeCompilation($source, Node $node, Environment $environment = null, $isPattern = false)
{
$compiler = $this->getCompiler($environment);
$compiler->compile($node);
if ($isPattern) {
$this->assertStringMatchesFormat($source, trim($compiler->getSource()));
} else {
$this->assertEquals($source, trim($compiler->getSource()));
}
}
protected function getCompiler(Environment $environment = null)
{
return new Compiler(null === $environment ? $this->getEnvironment() : $environment);
}
protected function getEnvironment()
{
return new Environment(new ArrayLoader([]));
}
protected function getVariableGetter($name, $line = false)
{
$line = $line > 0 ? "// line {$line}\n" : '';
return sprintf('%s($context["%s"] ?? null)', $line, $name);
}
protected function getAttributeGetter()
{
return 'twig_get_attribute($this->env, $this->source, ';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\Node;
use Twig\Node\WithNode;
use Twig\Token;
/**
* Creates a nested scope.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @internal
*/
final class WithTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$stream = $this->parser->getStream();
$variables = null;
$only = false;
if (!$stream->test(/* Token::BLOCK_END_TYPE */ 3)) {
$variables = $this->parser->getExpressionParser()->parseExpression();
$only = (bool) $stream->nextIf(/* Token::NAME_TYPE */ 5, 'only');
}
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$body = $this->parser->subparse([$this, 'decideWithEnd'], true);
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
return new WithNode($body, $variables, $only, $token->getLine(), $this->getTag());
}
public function decideWithEnd(Token $token): bool
{
return $token->test('endwith');
}
public function getTag(): string
{
return 'with';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Node;
use Twig\Token;
/**
* Imports blocks defined in another template into the current template.
*
* {% extends "base.html" %}
*
* {% use "blocks.html" %}
*
* {% block title %}{% endblock %}
* {% block content %}{% endblock %}
*
* @see https://twig.symfony.com/doc/templates.html#horizontal-reuse for details.
*
* @internal
*/
final class UseTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$template = $this->parser->getExpressionParser()->parseExpression();
$stream = $this->parser->getStream();
if (!$template instanceof ConstantExpression) {
throw new SyntaxError('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
$targets = [];
if ($stream->nextIf('with')) {
do {
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
$alias = $name;
if ($stream->nextIf('as')) {
$alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
}
$targets[$name] = new ConstantExpression($alias, -1);
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
} while (true);
}
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)]));
return new Node();
}
public function getTag(): string
{
return 'use';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Parser;
/**
* Base class for all token parsers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
abstract class AbstractTokenParser implements TokenParserInterface
{
/**
* @var Parser
*/
protected $parser;
public function setParser(Parser $parser): void
{
$this->parser = $parser;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\EmbedNode;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Expression\NameExpression;
use Twig\Node\Node;
use Twig\Token;
/**
* Embeds a template.
*
* @internal
*/
final class EmbedTokenParser extends IncludeTokenParser
{
public function parse(Token $token): Node
{
$stream = $this->parser->getStream();
$parent = $this->parser->getExpressionParser()->parseExpression();
list($variables, $only, $ignoreMissing) = $this->parseArguments();
$parentToken = $fakeParentToken = new Token(/* Token::STRING_TYPE */ 7, '__parent__', $token->getLine());
if ($parent instanceof ConstantExpression) {
$parentToken = new Token(/* Token::STRING_TYPE */ 7, $parent->getAttribute('value'), $token->getLine());
} elseif ($parent instanceof NameExpression) {
$parentToken = new Token(/* Token::NAME_TYPE */ 5, $parent->getAttribute('name'), $token->getLine());
}
// inject a fake parent to make the parent() function work
$stream->injectTokens([
new Token(/* Token::BLOCK_START_TYPE */ 1, '', $token->getLine()),
new Token(/* Token::NAME_TYPE */ 5, 'extends', $token->getLine()),
$parentToken,
new Token(/* Token::BLOCK_END_TYPE */ 3, '', $token->getLine()),
]);
$module = $this->parser->parse($stream, [$this, 'decideBlockEnd'], true);
// override the parent with the correct one
if ($fakeParentToken === $parentToken) {
$module->setNode('parent', $parent);
}
$this->parser->embedTemplate($module);
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
}
public function decideBlockEnd(Token $token): bool
{
return $token->test('endembed');
}
public function getTag(): string
{
return 'embed';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\DoNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Evaluates an expression, discarding the returned value.
*
* @internal
*/
final class DoTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$expr = $this->parser->getExpressionParser()->parseExpression();
$this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3);
return new DoNode($expr, $token->getLine(), $this->getTag());
}
public function getTag(): string
{
return 'do';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\IncludeNode;
use Twig\Node\Node;
use Twig\Node\SandboxNode;
use Twig\Node\TextNode;
use Twig\Token;
/**
* Marks a section of a template as untrusted code that must be evaluated in the sandbox mode.
*
* {% sandbox %}
* {% include 'user.html' %}
* {% endsandbox %}
*
* @see https://twig.symfony.com/doc/api.html#sandbox-extension for details
*
* @internal
*/
final class SandboxTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$stream = $this->parser->getStream();
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$body = $this->parser->subparse([$this, 'decideBlockEnd'], true);
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
// in a sandbox tag, only include tags are allowed
if (!$body instanceof IncludeNode) {
foreach ($body as $node) {
if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) {
continue;
}
if (!$node instanceof IncludeNode) {
throw new SyntaxError('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext());
}
}
}
return new SandboxNode($body, $token->getLine(), $this->getTag());
}
public function decideBlockEnd(Token $token): bool
{
return $token->test('endsandbox');
}
public function getTag(): string
{
return 'sandbox';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\AutoEscapeNode;
use Twig\Node\Expression\ConstantExpression;
use Twig\Node\Node;
use Twig\Token;
/**
* Marks a section of a template to be escaped or not.
*
* @internal
*/
final class AutoEscapeTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
if ($stream->test(/* Token::BLOCK_END_TYPE */ 3)) {
$value = 'html';
} else {
$expr = $this->parser->getExpressionParser()->parseExpression();
if (!$expr instanceof ConstantExpression) {
throw new SyntaxError('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
$value = $expr->getAttribute('value');
}
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$body = $this->parser->subparse([$this, 'decideBlockEnd'], true);
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
return new AutoEscapeNode($value, $body, $lineno, $this->getTag());
}
public function decideBlockEnd(Token $token): bool
{
return $token->test('endautoescape');
}
public function getTag(): string
{
return 'autoescape';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Node\ForNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Loops over each item of a sequence.
*
* <ul>
* {% for user in users %}
* <li>{{ user.username|e }}</li>
* {% endfor %}
* </ul>
*
* @internal
*/
final class ForTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$targets = $this->parser->getExpressionParser()->parseAssignmentExpression();
$stream->expect(/* Token::OPERATOR_TYPE */ 8, 'in');
$seq = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$body = $this->parser->subparse([$this, 'decideForFork']);
if ('else' == $stream->next()->getValue()) {
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$else = $this->parser->subparse([$this, 'decideForEnd'], true);
} else {
$else = null;
}
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
if (\count($targets) > 1) {
$keyTarget = $targets->getNode(0);
$keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine());
$valueTarget = $targets->getNode(1);
$valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine());
} else {
$keyTarget = new AssignNameExpression('_key', $lineno);
$valueTarget = $targets->getNode(0);
$valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine());
}
return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno, $this->getTag());
}
public function decideForFork(Token $token): bool
{
return $token->test(['else', 'endfor']);
}
public function decideForEnd(Token $token): bool
{
return $token->test('endfor');
}
public function getTag(): string
{
return 'for';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\DeprecatedNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Deprecates a section of a template.
*
* {% deprecated 'The "base.twig" template is deprecated, use "layout.twig" instead.' %}
* {% extends 'layout.html.twig' %}
*
* @author Yonel Ceruto <yonelceruto@gmail.com>
*
* @internal
*/
final class DeprecatedTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$expr = $this->parser->getExpressionParser()->parseExpression();
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
return new DeprecatedNode($expr, $token->getLine(), $this->getTag());
}
public function getTag(): string
{
return 'deprecated';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Node\ImportNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Imports macros.
*
* {% from 'forms.html' import forms %}
*
* @internal
*/
final class FromTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$macro = $this->parser->getExpressionParser()->parseExpression();
$stream = $this->parser->getStream();
$stream->expect(/* Token::NAME_TYPE */ 5, 'import');
$targets = [];
do {
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
$alias = $name;
if ($stream->nextIf('as')) {
$alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
}
$targets[$name] = $alias;
if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) {
break;
}
} while (true);
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$var = new AssignNameExpression($this->parser->getVarName(), $token->getLine());
$node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope());
foreach ($targets as $name => $alias) {
$this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var);
}
return $node;
}
public function getTag(): string
{
return 'from';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\BlockNode;
use Twig\Node\BlockReferenceNode;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\Token;
/**
* Marks a section of a template as being reusable.
*
* {% block head %}
* <link rel="stylesheet" href="style.css" />
* <title>{% block title %}{% endblock %} - My Webpage</title>
* {% endblock %}
*
* @internal
*/
final class BlockTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
if ($this->parser->hasBlock($name)) {
throw new SyntaxError(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
$this->parser->setBlock($name, $block = new BlockNode($name, new Node([]), $lineno));
$this->parser->pushLocalScope();
$this->parser->pushBlockStack($name);
if ($stream->nextIf(/* Token::BLOCK_END_TYPE */ 3)) {
$body = $this->parser->subparse([$this, 'decideBlockEnd'], true);
if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) {
$value = $token->getValue();
if ($value != $name) {
throw new SyntaxError(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
}
} else {
$body = new Node([
new PrintNode($this->parser->getExpressionParser()->parseExpression(), $lineno),
]);
}
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$block->setNode('body', $body);
$this->parser->popBlockStack();
$this->parser->popLocalScope();
return new BlockReferenceNode($name, $lineno, $this->getTag());
}
public function decideBlockEnd(Token $token): bool
{
return $token->test('endblock');
}
public function getTag(): string
{
return 'block';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\Node;
use Twig\Token;
/**
* Extends a template by another one.
*
* {% extends "base.html" %}
*
* @internal
*/
final class ExtendsTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$stream = $this->parser->getStream();
if ($this->parser->peekBlockStack()) {
throw new SyntaxError('Cannot use "extend" in a block.', $token->getLine(), $stream->getSourceContext());
} elseif (!$this->parser->isMainScope()) {
throw new SyntaxError('Cannot use "extend" in a macro.', $token->getLine(), $stream->getSourceContext());
}
if (null !== $this->parser->getParent()) {
throw new SyntaxError('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext());
}
$this->parser->setParent($this->parser->getExpressionParser()->parseExpression());
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
return new Node();
}
public function getTag(): string
{
return 'extends';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\IfNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Tests a condition.
*
* {% if users %}
* <ul>
* {% for user in users %}
* <li>{{ user.username|e }}</li>
* {% endfor %}
* </ul>
* {% endif %}
*
* @internal
*/
final class IfTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$lineno = $token->getLine();
$expr = $this->parser->getExpressionParser()->parseExpression();
$stream = $this->parser->getStream();
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$body = $this->parser->subparse([$this, 'decideIfFork']);
$tests = [$expr, $body];
$else = null;
$end = false;
while (!$end) {
switch ($stream->next()->getValue()) {
case 'else':
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$else = $this->parser->subparse([$this, 'decideIfEnd']);
break;
case 'elseif':
$expr = $this->parser->getExpressionParser()->parseExpression();
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$body = $this->parser->subparse([$this, 'decideIfFork']);
$tests[] = $expr;
$tests[] = $body;
break;
case 'endif':
$end = true;
break;
default:
throw new SyntaxError(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
}
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
return new IfNode(new Node($tests), $else, $lineno, $this->getTag());
}
public function decideIfFork(Token $token): bool
{
return $token->test(['elseif', 'else', 'endif']);
}
public function decideIfEnd(Token $token): bool
{
return $token->test(['endif']);
}
public function getTag(): string
{
return 'if';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\BodyNode;
use Twig\Node\MacroNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Defines a macro.
*
* {% macro input(name, value, type, size) %}
* <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
* {% endmacro %}
*
* @internal
*/
final class MacroTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue();
$arguments = $this->parser->getExpressionParser()->parseArguments(true, true);
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$this->parser->pushLocalScope();
$body = $this->parser->subparse([$this, 'decideBlockEnd'], true);
if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) {
$value = $token->getValue();
if ($value != $name) {
throw new SyntaxError(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
}
$this->parser->popLocalScope();
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $this->getTag()));
return new Node();
}
public function decideBlockEnd(Token $token): bool
{
return $token->test('endmacro');
}
public function getTag(): string
{
return 'macro';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\FlushNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Flushes the output to the client.
*
* @see flush()
*
* @internal
*/
final class FlushTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3);
return new FlushNode($token->getLine(), $this->getTag());
}
public function getTag(): string
{
return 'flush';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\Node;
use Twig\Node\SetNode;
use Twig\Token;
/**
* Defines a variable.
*
* {% set foo = 'foo' %}
* {% set foo = [1, 2] %}
* {% set foo = {'foo': 'bar'} %}
* {% set foo = 'foo' ~ 'bar' %}
* {% set foo, bar = 'foo', 'bar' %}
* {% set foo %}Some content{% endset %}
*
* @internal
*/
final class SetTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$lineno = $token->getLine();
$stream = $this->parser->getStream();
$names = $this->parser->getExpressionParser()->parseAssignmentExpression();
$capture = false;
if ($stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) {
$values = $this->parser->getExpressionParser()->parseMultitargetExpression();
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
if (\count($names) !== \count($values)) {
throw new SyntaxError('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
} else {
$capture = true;
if (\count($names) > 1) {
throw new SyntaxError('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext());
}
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
$values = $this->parser->subparse([$this, 'decideBlockEnd'], true);
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
}
return new SetNode($capture, $names, $values, $lineno, $this->getTag());
}
public function decideBlockEnd(Token $token): bool
{
return $token->test('endset');
}
public function getTag(): string
{
return 'set';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Error\SyntaxError;
use Twig\Node\Node;
use Twig\Parser;
use Twig\Token;
/**
* Interface implemented by token parsers.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface TokenParserInterface
{
/**
* Sets the parser associated with this token parser.
*/
public function setParser(Parser $parser): void;
/**
* Parses a token and returns a node.
*
* @return Node
*
* @throws SyntaxError
*/
public function parse(Token $token);
/**
* Gets the tag name associated with this token parser.
*
* @return string
*/
public function getTag();
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\Expression\TempNameExpression;
use Twig\Node\Node;
use Twig\Node\PrintNode;
use Twig\Node\SetNode;
use Twig\Token;
/**
* Applies filters on a section of a template.
*
* {% apply upper %}
* This text becomes uppercase
* {% endapply %}
*
* @internal
*/
final class ApplyTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$lineno = $token->getLine();
$name = $this->parser->getVarName();
$ref = new TempNameExpression($name, $lineno);
$ref->setAttribute('always_defined', true);
$filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag());
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
$body = $this->parser->subparse([$this, 'decideApplyEnd'], true);
$this->parser->getStream()->expect(Token::BLOCK_END_TYPE);
return new Node([
new SetNode(true, $ref, $body, $lineno, $this->getTag()),
new PrintNode($filter, $lineno, $this->getTag()),
]);
}
public function decideApplyEnd(Token $token): bool
{
return $token->test('endapply');
}
public function getTag(): string
{
return 'apply';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
* (c) Armin Ronacher
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\IncludeNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Includes a template.
*
* {% include 'header.html' %}
* Body
* {% include 'footer.html' %}
*
* @internal
*/
class IncludeTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$expr = $this->parser->getExpressionParser()->parseExpression();
list($variables, $only, $ignoreMissing) = $this->parseArguments();
return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
}
protected function parseArguments()
{
$stream = $this->parser->getStream();
$ignoreMissing = false;
if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'ignore')) {
$stream->expect(/* Token::NAME_TYPE */ 5, 'missing');
$ignoreMissing = true;
}
$variables = null;
if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'with')) {
$variables = $this->parser->getExpressionParser()->parseExpression();
}
$only = false;
if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'only')) {
$only = true;
}
$stream->expect(/* Token::BLOCK_END_TYPE */ 3);
return [$variables, $only, $ignoreMissing];
}
public function getTag(): string
{
return 'include';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\TokenParser;
use Twig\Node\Expression\AssignNameExpression;
use Twig\Node\ImportNode;
use Twig\Node\Node;
use Twig\Token;
/**
* Imports macros.
*
* {% import 'forms.html' as forms %}
*
* @internal
*/
final class ImportTokenParser extends AbstractTokenParser
{
public function parse(Token $token): Node
{
$macro = $this->parser->getExpressionParser()->parseExpression();
$this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5, 'as');
$var = new AssignNameExpression($this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5)->getValue(), $token->getLine());
$this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3);
$this->parser->addImportedSymbol('template', $var->getAttribute('name'));
return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope());
}
public function getTag(): string
{
return 'import';
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\RuntimeLoader;
use Psr\Container\ContainerInterface;
/**
* Lazily loads Twig runtime implementations from a PSR-11 container.
*
* Note that the runtime services MUST use their class names as identifiers.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class ContainerRuntimeLoader implements RuntimeLoaderInterface
{
private $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function load(string $class)
{
return $this->container->has($class) ? $this->container->get($class) : null;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\RuntimeLoader;
/**
* Lazy loads the runtime implementations for a Twig element.
*
* @author Robin Chalas <robin.chalas@gmail.com>
*/
class FactoryRuntimeLoader implements RuntimeLoaderInterface
{
private $map;
/**
* @param array $map An array where keys are class names and values factory callables
*/
public function __construct(array $map = [])
{
$this->map = $map;
}
public function load(string $class)
{
if (!isset($this->map[$class])) {
return null;
}
$runtimeFactory = $this->map[$class];
return $runtimeFactory();
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\RuntimeLoader;
/**
* Creates runtime implementations for Twig elements (filters/functions/tests).
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface RuntimeLoaderInterface
{
/**
* Creates the runtime implementation of a Twig element (filter/function/test).
*
* @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class
*/
public function load(string $class);
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Cache;
/**
* Interface implemented by cache classes.
*
* It is highly recommended to always store templates on the filesystem to
* benefit from the PHP opcode cache. This interface is mostly useful if you
* need to implement a custom strategy for storing templates on the filesystem.
*
* @author Andrew Tch <andrew@noop.lv>
*/
interface CacheInterface
{
/**
* Generates a cache key for the given template class name.
*/
public function generateKey(string $name, string $className): string;
/**
* Writes the compiled template to cache.
*
* @param string $content The template representation as a PHP class
*/
public function write(string $key, string $content): void;
/**
* Loads a template from the cache.
*/
public function load(string $key): void;
/**
* Returns the modification timestamp of a key.
*/
public function getTimestamp(string $key): int;
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Cache;
/**
* Implements a no-cache strategy.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
final class NullCache implements CacheInterface
{
public function generateKey(string $name, string $className): string
{
return '';
}
public function write(string $key, string $content): void
{
}
public function load(string $key): void
{
}
public function getTimestamp(string $key): int
{
return 0;
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Cache;
/**
* Implements a cache on the filesystem.
*
* @author Andrew Tch <andrew@noop.lv>
*/
class FilesystemCache implements CacheInterface
{
const FORCE_BYTECODE_INVALIDATION = 1;
private $directory;
private $options;
public function __construct(string $directory, int $options = 0)
{
$this->directory = rtrim($directory, '\/').'/';
$this->options = $options;
}
public function generateKey(string $name, string $className): string
{
$hash = hash('sha256', $className);
return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php';
}
public function load(string $key): void
{
if (is_file($key)) {
@include_once $key;
}
}
public function write(string $key, string $content): void
{
$dir = \dirname($key);
if (!is_dir($dir)) {
if (false === @mkdir($dir, 0777, true)) {
clearstatcache(true, $dir);
if (!is_dir($dir)) {
throw new \RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir));
}
}
} elseif (!is_writable($dir)) {
throw new \RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir));
}
$tmpFile = tempnam($dir, basename($key));
if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) {
@chmod($key, 0666 & ~umask());
if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) {
// Compile cached file into bytecode cache
if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
@opcache_invalidate($key, true);
} elseif (\function_exists('apc_compile_file')) {
apc_compile_file($key);
}
}
return;
}
throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $key));
}
public function getTimestamp(string $key): int
{
if (!is_file($key)) {
return 0;
}
return (int) @filemtime($key);
}
}
<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig;
use Twig\Node\Expression\FilterExpression;
use Twig\Node\Node;
/**
* Represents a template filter.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see https://twig.symfony.com/doc/templates.html#filters
*/
final class TwigFilter
{
private $name;
private $callable;
private $options;
private $arguments = [];
/**
* @param callable|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation.
*/
public function __construct(string $name, $callable = null, array $options = [])
{
$this->name = $name;
$this->callable = $callable;
$this->options = array_merge([
'needs_environment' => false,
'needs_context' => false,
'is_variadic' => false,
'is_safe' => null,
'is_safe_callback' => null,
'pre_escape' => null,
'preserves_safety' => null,
'node_class' => FilterExpression::class,
'deprecated' => false,
'alternative' => null,
], $options);
}
public function getName(): string
{
return $this->name;
}
/**
* Returns the callable to execute for this filter.
*
* @return callable|null
*/
public function getCallable()
{
return $this->callable;
}
public function getNodeClass(): string
{
return $this->options['node_class'];
}
public function setArguments(array $arguments): void
{
$this->arguments = $arguments;
}
public function getArguments(): array
{
return $this->arguments;
}
public function needsEnvironment(): bool
{
return $this->options['needs_environment'];
}
public function needsContext(): bool
{
return $this->options['needs_context'];
}
public function getSafe(Node $filterArgs): ?array
{
if (null !== $this->options['is_safe']) {
return $this->options['is_safe'];
}
if (null !== $this->options['is_safe_callback']) {
return $this->options['is_safe_callback']($filterArgs);
}
return null;
}
public function getPreservesSafety(): ?array
{
return $this->options['preserves_safety'];
}
public function getPreEscape(): ?string
{
return $this->options['pre_escape'];
}
public function isVariadic(): bool
{
return $this->options['is_variadic'];
}
public function isDeprecated(): bool
{
return (bool) $this->options['deprecated'];
}
public function getDeprecatedVersion(): string
{
return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated'];
}
public function getAlternative(): ?string
{
return $this->options['alternative'];
}
}
Copyright (c) 2009-2021 by the Twig Team.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Twig nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (c) 2010-2017 Fabien Potencier
Copyright (c) 2020 William Desportes
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.
J¤Ü—f>é ¼j\`ŸÌ”,Hù—3Vlµïz¬²óYGBMB