33namespace Phug \Formatter ;
44
55use Phug \Formatter ;
6+ use Phug \Formatter \Element \CodeElement ;
67use Phug \Formatter \Element \MarkupElement ;
78use Phug \Formatter \Format \XhtmlFormat ;
89
910abstract class AbstractTwigFormat extends XhtmlFormat
1011{
12+ protected $ codeBlocks = [];
13+ protected $ phpMode = true ;
14+ protected $ statements = [
15+ 'if ' ,
16+ 'else ' ,
17+ 'elseif ' ,
18+ 'for ' ,
19+ 'autoescape ' ,
20+ 'block ' ,
21+ 'do ' ,
22+ 'embed ' ,
23+ 'extends ' ,
24+ 'filter ' ,
25+ 'flush ' ,
26+ 'from ' ,
27+ 'import ' ,
28+ 'include ' ,
29+ 'macro ' ,
30+ 'sandbox ' ,
31+ 'set ' ,
32+ 'spaceless ' ,
33+ 'use ' ,
34+ 'verbatim ' ,
35+ 'with ' ,
36+ ];
37+
1138 public function __construct (Formatter $ formatter = null )
1239 {
1340 parent ::__construct ($ formatter );
1441
15- $ nestedCodes = [];
16- $ codeBlocks = [] ;
42+ $ this -> codeBlocks = [];
43+ $ this -> phpMode = true ;
1744 $ this
1845 ->setOptionsRecursive ([
1946 'php_token_handlers ' => [
@@ -27,48 +54,18 @@ public function __construct(Formatter $formatter = null)
2754 'string_attribute ' => '%s ' ,
2855 'expression_in_text ' => '%s ' ,
2956 'html_expression_escape ' => '%s | e ' ,
30- 'php_handle_code ' => function ($ input ) use (&$ formatter , &$ nestedCodes , &$ codeBlocks ) {
31- $ pugModuleName = '$ ' .$ formatter ->getOption ('dependencies_storage ' );
32- if ($ this ->mustBeHandleWithPhp ($ input , $ pugModuleName )) {
33- $ input = preg_replace_callback (
34- '/\{\[block:(\d+)\]\}/ ' ,
35- function ($ match ) use (&$ codeBlocks ) {
36- return ' ?> ' .$ codeBlocks [intval ($ match [1 ])].'<?php ' ;
37- },
38- $ input
39- );
40-
41- return "<?php $ input ?> " ;
42- }
43-
44- list ($ statement , $ input ) = explode (' ' , $ input , 2 );
45- $ statement = $ statement === 'each ' ? 'for ' : $ statement ;
46- $ input = $ statement .' ' .$ input ;
47- $ hasBlocks = false ;
48- $ input = preg_replace_callback (
49- '/\{\[block:(\d+)\]\}/ ' ,
50- function ($ match ) use (&$ codeBlocks , &$ hasBlocks ) {
51- $ hasBlocks = true ;
52-
53- return ' %} ' .$ codeBlocks [intval ($ match [1 ])].'{% ' ;
54- },
55- $ input
56- );
57- if ($ hasBlocks ) {
58- $ input .= 'end ' .$ statement ;
59- }
60-
61- return "{% $ input %} " ;
57+ 'php_handle_code ' => function ($ input ) {
58+ return $ this ->replaceTwigBlocks ($ input , true );
6259 },
6360 'php_nested_html ' => '%s ' ,
64- 'php_block_code ' => function ($ input ) use (& $ codeBlocks ) {
65- $ id = count ($ codeBlocks );
66- $ codeBlocks [] = $ input ;
61+ 'php_block_code ' => function ($ input ) {
62+ $ id = count ($ this -> codeBlocks );
63+ $ this -> codeBlocks [] = $ input ;
6764
6865 return '{[block: ' .$ id .']} ' ;
6966 },
70- 'php_display_code ' => function ($ input ) use (& $ formatter ) {
71- $ pugModuleName = '$ ' .$ formatter ->getOption ('dependencies_storage ' );
67+ 'php_display_code ' => function ($ input ) {
68+ $ pugModuleName = '$ ' .$ this -> formatter ->getOption ('dependencies_storage ' );
7269 if ($ this ->mustBeHandleWithPhp ($ input , $ pugModuleName )) {
7370 return "<?= $ input ?> " ;
7471 }
@@ -100,4 +97,119 @@ protected function formatAttributes(MarkupElement $element)
10097
10198 return $ code ;
10299 }
100+
101+ protected function deduplicatePhpTags ($ commentPattern , &$ content , &$ childContent )
102+ {
103+ // @codeCoverageIgnoreStart
104+ $ content = preg_replace ('/ \\s \\?>$/ ' , '' , $ content );
105+ $ childContent = preg_replace ('/^< \\?(?:php)? \\s/ ' , '' , $ childContent );
106+ if ($ commentPattern &&
107+ ($ pos = mb_strpos ($ childContent , $ commentPattern )) !== false && (
108+ ($ end = mb_strpos ($ childContent , '?> ' )) === false ||
109+ $ pos < $ end
110+ ) &&
111+ preg_match ('/ \\} \\s*$/ ' , $ content )
112+ ) {
113+ $ content = preg_replace (
114+ '/ \\} \\s*$/ ' ,
115+ preg_replace ('/ \\?>< \\?php(?:php)?(\s+ \\?>< \\?php(?:php)?)*/ ' , '\\\\0 ' , $ childContent , 1 ),
116+ $ content
117+ );
118+ $ childContent = '' ;
119+ }
120+ // @codeCoverageIgnoreEnd
121+ }
122+
123+ protected function formatTwigChildElement ($ child , $ previous , &$ content , $ commentPattern )
124+ {
125+ $ childContent = $ this ->formatter ->format ($ child );
126+
127+ if ($ child instanceof CodeElement &&
128+ $ previous instanceof CodeElement &&
129+ $ previous ->isCodeBlock ()
130+ ) {
131+ $ this ->deduplicatePhpTags ($ commentPattern , $ content , $ childContent );
132+ }
133+
134+ if (preg_match ('/^\{% else/ ' , $ childContent )) {
135+ $ content = preg_replace ('/\{% end(if)+ %\}$/ ' , '' , $ content );
136+ }
137+
138+ return $ this ->replaceTwigBlocks ($ childContent , false );
139+ }
140+
141+ protected function formatElementChildren (ElementInterface $ element , $ indentStep = 1 )
142+ {
143+ $ indentLevel = $ this ->formatter ->getLevel ();
144+ $ this ->formatter ->setLevel ($ indentLevel + $ indentStep );
145+ $ content = '' ;
146+ $ previous = null ;
147+ $ commentPattern = $ this ->getOption ('debug ' );
148+ foreach ($ this ->getChildrenIterator ($ element ) as $ child ) {
149+ if ($ child instanceof ElementInterface) {
150+ $ content .= $ this ->formatTwigChildElement ($ child , $ previous , $ content , $ commentPattern );
151+ $ previous = $ child ;
152+ }
153+ }
154+ $ this ->formatter ->setLevel ($ indentLevel );
155+
156+ return $ content ;
157+ }
158+
159+ protected function replaceTwigPhpBlocks ($ input , $ wrap )
160+ {
161+ $ input = $ this ->restoreBlockSubstitutes ($ input );
162+ $ this ->phpMode = true ;
163+
164+ return $ wrap ? "<?php $ input ?> " : $ input ;
165+ }
166+
167+ protected function extractStatement (&$ input )
168+ {
169+ $ statement = $ input ;
170+ $ parts = explode (' ' , $ input , 2 );
171+ if (count ($ parts ) === 2 ) {
172+ list ($ statement , $ input ) = $ parts ;
173+ $ statement = $ statement === 'each ' ? 'for ' : $ statement ;
174+ $ input = "$ statement $ input " ;
175+ }
176+
177+ return $ statement ;
178+ }
179+
180+ protected function restoreBlockSubstitutes ($ input , $ pattern = ' ?>%s<?php ' , &$ hasBlocks = null )
181+ {
182+ return preg_replace_callback (
183+ '/\{\[block:(\d+)\]\}/ ' ,
184+ function ($ match ) use ($ pattern , &$ hasBlocks ) {
185+ $ hasBlocks = true ;
186+
187+ return sprintf ($ pattern , $ this ->codeBlocks [intval ($ match [1 ])]);
188+ },
189+ $ input
190+ );
191+ }
192+
193+ protected function replaceTwigTemplateBlocks ($ input , $ wrap )
194+ {
195+ $ this ->phpMode = false ;
196+ $ statement = $ this ->extractStatement ($ input );
197+ $ hasBlocks = false ;
198+ $ input = $ this ->restoreBlockSubstitutes ($ input , ' %%}%s{%% ' , $ hasBlocks );
199+ if ($ hasBlocks ) {
200+ $ statement = preg_replace ('/^([^ \\{]+) \\{.*$/ ' , '$1 ' , $ statement );
201+ if (in_array ($ statement , $ this ->statements )) {
202+ $ input .= 'end ' .preg_replace ('/^else/ ' , 'if ' , $ statement );
203+ }
204+ }
205+
206+ return $ wrap && trim ($ input ) !== '' ? "{% $ input %} " : $ input ;
207+ }
208+
209+ protected function replaceTwigBlocks ($ input , $ wrap )
210+ {
211+ return $ this ->mustBeHandleWithPhp ($ input , '$ ' .$ this ->formatter ->getOption ('dependencies_storage ' ))
212+ ? $ this ->replaceTwigPhpBlocks ($ input , $ wrap )
213+ : $ this ->replaceTwigTemplateBlocks ($ input , $ wrap );
214+ }
103215}
0 commit comments