onelaraveljs 1.0.0 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/onejs-build.js +32 -0
- package/index.js +3 -1
- package/package.json +11 -3
- package/scripts/README-template-compiler.md +133 -0
- package/scripts/README.md +61 -0
- package/scripts/__pycache__/build.cpython-314.pyc +0 -0
- package/scripts/__pycache__/compile.cpython-313.pyc +0 -0
- package/scripts/__pycache__/compile.cpython-314.pyc +0 -0
- package/scripts/build.py +574 -0
- package/scripts/check-system-errors.php +214 -0
- package/scripts/compile.py +101 -0
- package/scripts/compiler/README_CONFIG.md +196 -0
- package/scripts/compiler/__init__.py +18 -0
- package/scripts/compiler/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/__init__.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/binding_directive_service.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/class_binding_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/compiler_utils.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/compiler_utils.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/conditional_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/conditional_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/config.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/config.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/declaration_tracker.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/directive_processors.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/directive_processors.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/echo_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/event_directive_processor.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/event_directive_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/function_generators.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/function_generators.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/loop_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/loop_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/main_compiler.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/main_compiler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/parsers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/parsers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/php_converter.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/php_converter.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/php_js_converter.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/php_js_converter.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/register_parser.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/register_parser.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/section_handlers.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/section_handlers.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/show_directive_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/style_directive_handler.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_analyzer.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_analyzer.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processor.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processor.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processors.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/template_processors.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/utils.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/utils.cpython-314.pyc +0 -0
- package/scripts/compiler/__pycache__/wrapper_parser.cpython-313.pyc +0 -0
- package/scripts/compiler/__pycache__/wrapper_parser.cpython-314.pyc +0 -0
- package/scripts/compiler/binding_directive_service.py +103 -0
- package/scripts/compiler/class_binding_handler.py +347 -0
- package/scripts/compiler/cli.py +34 -0
- package/scripts/compiler/code_generator.py +141 -0
- package/scripts/compiler/compiler.config.json +36 -0
- package/scripts/compiler/compiler_utils.py +55 -0
- package/scripts/compiler/conditional_handlers.py +252 -0
- package/scripts/compiler/config.py +107 -0
- package/scripts/compiler/declaration_tracker.py +420 -0
- package/scripts/compiler/directive_processors.py +603 -0
- package/scripts/compiler/echo_processor.py +667 -0
- package/scripts/compiler/event_directive_processor.py +1099 -0
- package/scripts/compiler/fetch_parser.py +49 -0
- package/scripts/compiler/function_generators.py +310 -0
- package/scripts/compiler/loop_handlers.py +224 -0
- package/scripts/compiler/main_compiler.py +1763 -0
- package/scripts/compiler/parsers.py +1418 -0
- package/scripts/compiler/php_converter.py +470 -0
- package/scripts/compiler/php_js_converter.py +603 -0
- package/scripts/compiler/register_parser.py +480 -0
- package/scripts/compiler/section_handlers.py +122 -0
- package/scripts/compiler/show_directive_handler.py +85 -0
- package/scripts/compiler/style_directive_handler.py +169 -0
- package/scripts/compiler/template_analyzer.py +162 -0
- package/scripts/compiler/template_processor.py +1167 -0
- package/scripts/compiler/template_processors.py +1557 -0
- package/scripts/compiler/test_compiler.py +69 -0
- package/scripts/compiler/utils.py +54 -0
- package/scripts/compiler/variables_analyzer.py +135 -0
- package/scripts/compiler/view_identifier_generator.py +278 -0
- package/scripts/compiler/wrapper_parser.py +78 -0
- package/scripts/dev-context.js +311 -0
- package/scripts/dev.js +109 -0
- package/scripts/generate-assets-order.js +208 -0
- package/scripts/migrate-namespace.php +146 -0
- package/scripts/node/MIGRATION.md +190 -0
- package/scripts/node/README.md +269 -0
- package/scripts/node/build.js +208 -0
- package/scripts/node/compiler/compiler-utils.js +38 -0
- package/scripts/node/compiler/conditional-handlers.js +45 -0
- package/scripts/node/compiler/config.js +178 -0
- package/scripts/node/compiler/directive-processors.js +51 -0
- package/scripts/node/compiler/event-directive-processor.js +182 -0
- package/scripts/node/compiler/function-generators.js +239 -0
- package/scripts/node/compiler/loop-handlers.js +45 -0
- package/scripts/node/compiler/main-compiler.js +236 -0
- package/scripts/node/compiler/parsers.js +358 -0
- package/scripts/node/compiler/php-converter.js +227 -0
- package/scripts/node/compiler/register-parser.js +32 -0
- package/scripts/node/compiler/section-handlers.js +46 -0
- package/scripts/node/compiler/template-analyzer.js +50 -0
- package/scripts/node/compiler/template-processor.js +371 -0
- package/scripts/node/compiler/template-processors.js +219 -0
- package/scripts/node/compiler/utils.js +203 -0
- package/scripts/node/compiler/wrapper-parser.js +25 -0
- package/scripts/node/package.json +24 -0
- package/scripts/node/test-compiler.js +52 -0
- package/scripts/node-run.cjs +28 -0
- package/scripts/standardize-directories.php +92 -0
- package/src/core/ViewManager.js +4 -4
- package/templates/view.module.js +2 -0
- package/templates/view.tpl-raw.js +13 -0
- package/templates/wraper.js +71 -0
|
@@ -0,0 +1,1167 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Template processor chính xử lý template content
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from utils import extract_balanced_parentheses
|
|
7
|
+
from conditional_handlers import ConditionalHandlers
|
|
8
|
+
from loop_handlers import LoopHandlers
|
|
9
|
+
from section_handlers import SectionHandlers
|
|
10
|
+
from template_processors import TemplateProcessors
|
|
11
|
+
from directive_processors import DirectiveProcessor
|
|
12
|
+
from event_directive_processor import EventDirectiveProcessor
|
|
13
|
+
from echo_processor import EchoProcessor
|
|
14
|
+
from class_binding_handler import ClassBindingHandler
|
|
15
|
+
|
|
16
|
+
class TemplateProcessor:
|
|
17
|
+
def __init__(self, usestate_variables=None):
|
|
18
|
+
self.state_variables = usestate_variables or set()
|
|
19
|
+
self.watch_counter = 0 # Counter for generating unique watch IDs
|
|
20
|
+
self.conditional_handlers = ConditionalHandlers(self.state_variables, self)
|
|
21
|
+
self.loop_handlers = LoopHandlers(self.state_variables, self)
|
|
22
|
+
self.section_handlers = SectionHandlers()
|
|
23
|
+
self.template_processors = TemplateProcessors()
|
|
24
|
+
self.directive_processors = DirectiveProcessor()
|
|
25
|
+
self.event_processor = EventDirectiveProcessor(self.state_variables)
|
|
26
|
+
self.echo_processor = EchoProcessor(self.state_variables)
|
|
27
|
+
self.class_binding_handler = ClassBindingHandler(self.state_variables)
|
|
28
|
+
|
|
29
|
+
def _is_attribute_directive(self, line):
|
|
30
|
+
"""
|
|
31
|
+
Detect if directive is in attribute context (inside tag attributes)
|
|
32
|
+
Example: <div @if(condition)> - attribute directive (return True)
|
|
33
|
+
Example: <div>@if(condition) - block directive (return False)
|
|
34
|
+
"""
|
|
35
|
+
# Find position of directive
|
|
36
|
+
directive_patterns = [r'@if\b', r'@foreach\b', r'@for\b', r'@while\b', r'@switch\b']
|
|
37
|
+
directive_pos = -1
|
|
38
|
+
for pattern in directive_patterns:
|
|
39
|
+
match = re.search(pattern, line)
|
|
40
|
+
if match:
|
|
41
|
+
directive_pos = match.start()
|
|
42
|
+
break
|
|
43
|
+
|
|
44
|
+
if directive_pos == -1:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
# Find the LAST opening < before the directive
|
|
48
|
+
before_directive = line[:directive_pos]
|
|
49
|
+
last_open_tag = before_directive.rfind('<')
|
|
50
|
+
|
|
51
|
+
if last_open_tag == -1:
|
|
52
|
+
return False
|
|
53
|
+
|
|
54
|
+
# Check if there's a closing > between last_open_tag and directive
|
|
55
|
+
between = line[last_open_tag:directive_pos]
|
|
56
|
+
if '>' in between:
|
|
57
|
+
# There's a tag closing between last < and directive
|
|
58
|
+
# So directive is NOT in attributes
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
# Directive is between < and (potential) >, so it's inline
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
def _process_inline_directive(self, line):
|
|
65
|
+
"""
|
|
66
|
+
Process inline directives in HTML attributes
|
|
67
|
+
Example: <div @if($count > 0) data-test="active" @endif>
|
|
68
|
+
Becomes: <div ${this.__execute(() => { if(count > 0){ return `data-test="active"`; } return ''; })}>
|
|
69
|
+
"""
|
|
70
|
+
from php_converter import php_to_js
|
|
71
|
+
|
|
72
|
+
# Pattern for @if...@endif inline
|
|
73
|
+
if_pattern = r'@if\s*\((.*?)\)\s*(.*?)\s*@endif'
|
|
74
|
+
def replace_inline_if(match):
|
|
75
|
+
condition_php = match.group(1).strip()
|
|
76
|
+
content = match.group(2).strip()
|
|
77
|
+
condition_js = php_to_js(condition_php)
|
|
78
|
+
|
|
79
|
+
# Compile to expression without __watch (inline directives are not reactive)
|
|
80
|
+
return f"${{this.__execute(() => {{ if({condition_js}){{ return `{content}`; }} return ''; }})}}"
|
|
81
|
+
|
|
82
|
+
line = re.sub(if_pattern, replace_inline_if, line)
|
|
83
|
+
|
|
84
|
+
# Pattern for @foreach...@endforeach inline
|
|
85
|
+
# Match: @foreach($items as $item) content @endforeach
|
|
86
|
+
# Also match: @foreach($key => $value as $item) content @endforeach
|
|
87
|
+
foreach_pattern = r'@foreach\s*\(\s*\\?[$](\w+)\s+as\s+(?:\\?[$](\w+)\s*=>\s*)?\\?[$](\w+)\s*\)\s*(.*?)@endforeach'
|
|
88
|
+
def replace_inline_foreach(match):
|
|
89
|
+
array_var = match.group(1) # items
|
|
90
|
+
key_var = match.group(2) # optional key
|
|
91
|
+
value_var = match.group(3) # item
|
|
92
|
+
content = match.group(4).strip()
|
|
93
|
+
|
|
94
|
+
# Convert PHP variables to JS
|
|
95
|
+
array_js = array_var # No need for php_to_js for simple variable names
|
|
96
|
+
|
|
97
|
+
# Note: Content may have {{ }} expressions - keep them for later echo processing
|
|
98
|
+
# For now, simple join with space
|
|
99
|
+
if key_var:
|
|
100
|
+
return f"${{this.__foreach({array_js}, ({value_var}, {key_var}) => `{content}`).join(' ')}}"
|
|
101
|
+
else:
|
|
102
|
+
return f"${{this.__foreach({array_js}, ({value_var}) => `{content}`).join(' ')}}"
|
|
103
|
+
|
|
104
|
+
line = re.sub(foreach_pattern, replace_inline_foreach, line, flags=re.DOTALL)
|
|
105
|
+
|
|
106
|
+
# Pattern for @for...@endfor inline
|
|
107
|
+
# Match: @for($i = 0; $i < 3; $i++) content @endfor
|
|
108
|
+
for_pattern = r'@for\s*\(\s*\\?[$](\w+)\s*=\s*([^;]+);\s*\\?[$]\1\s*([<>=!]+)\s*([^;]+);\s*\\?[$]\1\s*\+\+\s*\)\s*(.*?)@endfor'
|
|
109
|
+
def replace_inline_for(match):
|
|
110
|
+
var_name = match.group(1)
|
|
111
|
+
start_value_php = match.group(2).strip()
|
|
112
|
+
operator = match.group(3)
|
|
113
|
+
end_value_php = match.group(4).strip()
|
|
114
|
+
content = match.group(5).strip()
|
|
115
|
+
|
|
116
|
+
start_js = php_to_js(start_value_php)
|
|
117
|
+
end_js = php_to_js(end_value_php)
|
|
118
|
+
|
|
119
|
+
# Generate for loop with string concatenation
|
|
120
|
+
return f"${{this.__execute(() => {{ let __output = ''; for(let {var_name} = {start_js}; {var_name} {operator} {end_js}; {var_name}++) {{ __output += `{content}`; }} return __output; }})}}"
|
|
121
|
+
|
|
122
|
+
line = re.sub(for_pattern, replace_inline_for, line, flags=re.DOTALL)
|
|
123
|
+
|
|
124
|
+
# Pattern for @while...@endwhile inline
|
|
125
|
+
# Match: @while($condition) content @endwhile
|
|
126
|
+
while_pattern = r'@while\s*\((.*?)\)\s*(.*?)@endwhile'
|
|
127
|
+
def replace_inline_while(match):
|
|
128
|
+
condition_php = match.group(1).strip()
|
|
129
|
+
content = match.group(2).strip()
|
|
130
|
+
condition_js = php_to_js(condition_php)
|
|
131
|
+
|
|
132
|
+
# Generate while loop (careful with infinite loops!)
|
|
133
|
+
return f"${{this.__execute(() => {{ let __output = ''; let __iterations = 0; while({condition_js} && __iterations < 1000) {{ __output += `{content}`; __iterations++; }} return __output; }})}}"
|
|
134
|
+
|
|
135
|
+
line = re.sub(while_pattern, replace_inline_while, line, flags=re.DOTALL)
|
|
136
|
+
|
|
137
|
+
# Pattern for @switch...@endswitch inline (complex with @case/@break/@default)
|
|
138
|
+
# Match: @switch($var) @case(val1) content1 @break @case(val2) content2 @break @default contentD @endswitch
|
|
139
|
+
switch_pattern = r'@switch\s*\((.*?)\)\s*(.*?)@endswitch'
|
|
140
|
+
def replace_inline_switch(match):
|
|
141
|
+
switch_var_php = match.group(1).strip()
|
|
142
|
+
switch_content = match.group(2)
|
|
143
|
+
switch_var_js = php_to_js(switch_var_php)
|
|
144
|
+
|
|
145
|
+
# Parse @case/@default blocks
|
|
146
|
+
result = f"${{this.__execute(() => {{ let __output = ''; switch({switch_var_js}) {{"
|
|
147
|
+
|
|
148
|
+
# Find all @case directives
|
|
149
|
+
case_pattern = r'@case\s*\((.*?)\)\s*(.*?)(?:@break|@case|@default|$)'
|
|
150
|
+
for case_match in re.finditer(case_pattern, switch_content):
|
|
151
|
+
case_value_php = case_match.group(1).strip()
|
|
152
|
+
case_content = case_match.group(2).strip()
|
|
153
|
+
case_value_js = php_to_js(case_value_php)
|
|
154
|
+
result += f" case {case_value_js}: __output = `{case_content}`; break;"
|
|
155
|
+
|
|
156
|
+
# Find @default
|
|
157
|
+
default_pattern = r'@default\s*(.*?)(?:@endswitch|$)'
|
|
158
|
+
default_match = re.search(default_pattern, switch_content)
|
|
159
|
+
if default_match:
|
|
160
|
+
default_content = default_match.group(1).strip()
|
|
161
|
+
result += f" default: __output = `{default_content}`;"
|
|
162
|
+
|
|
163
|
+
# Close switch and function
|
|
164
|
+
result += " } return __output; })}}"
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
line = re.sub(switch_pattern, replace_inline_switch, line, flags=re.DOTALL)
|
|
168
|
+
|
|
169
|
+
return line
|
|
170
|
+
|
|
171
|
+
def process_template(self, blade_code):
|
|
172
|
+
"""Process template content and extract sections"""
|
|
173
|
+
# NOTE: @verbatim blocks are already processed in main_compiler.py before this step
|
|
174
|
+
# They are already replaced with placeholders (__VERBATIM_BLOCK_N__)
|
|
175
|
+
# So we don't need to process them again here
|
|
176
|
+
|
|
177
|
+
# Remove page/document directives - these are only for PHP compilation, not JS
|
|
178
|
+
# @pageStart, @pageEnd, @pageOpen, @pageClose, @docStart, @docEnd
|
|
179
|
+
blade_code = self._remove_page_directives(blade_code)
|
|
180
|
+
|
|
181
|
+
# Remove already processed directives
|
|
182
|
+
blade_code = re.sub(r'@extends\s*\([^)]*\)', '', blade_code, flags=re.DOTALL)
|
|
183
|
+
blade_code = re.sub(r'@vars\s*\([^)]*\)', '', blade_code, flags=re.DOTALL)
|
|
184
|
+
|
|
185
|
+
# Remove @let directives with balanced parentheses
|
|
186
|
+
let_pattern = r'@let\s*\('
|
|
187
|
+
while re.search(let_pattern, blade_code):
|
|
188
|
+
match = re.search(let_pattern, blade_code)
|
|
189
|
+
if match:
|
|
190
|
+
start_pos = match.end() - 1
|
|
191
|
+
content, end_pos = extract_balanced_parentheses(blade_code, start_pos)
|
|
192
|
+
if content is not None:
|
|
193
|
+
blade_code = blade_code[:match.start()] + blade_code[start_pos + len(content) + 2:]
|
|
194
|
+
else:
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
# Remove @const directives with balanced parentheses
|
|
198
|
+
const_pattern = r'@const\s*\('
|
|
199
|
+
while re.search(const_pattern, blade_code):
|
|
200
|
+
match = re.search(const_pattern, blade_code)
|
|
201
|
+
if match:
|
|
202
|
+
start_pos = match.end() - 1
|
|
203
|
+
content, end_pos = extract_balanced_parentheses(blade_code, start_pos)
|
|
204
|
+
if content is not None:
|
|
205
|
+
blade_code = blade_code[:match.start()] + blade_code[start_pos + len(content) + 2:]
|
|
206
|
+
else:
|
|
207
|
+
break
|
|
208
|
+
|
|
209
|
+
# Remove @useState directives with balanced parentheses
|
|
210
|
+
usestate_pattern = r'@useState\s*\('
|
|
211
|
+
while re.search(usestate_pattern, blade_code):
|
|
212
|
+
match = re.search(usestate_pattern, blade_code)
|
|
213
|
+
if match:
|
|
214
|
+
start_pos = match.end() - 1
|
|
215
|
+
content, end_pos = extract_balanced_parentheses(blade_code, start_pos)
|
|
216
|
+
if content is not None:
|
|
217
|
+
blade_code = blade_code[:match.start()] + blade_code[start_pos + len(content) + 2:]
|
|
218
|
+
else:
|
|
219
|
+
break
|
|
220
|
+
# Remove @fetch directive with balanced parentheses
|
|
221
|
+
fetch_pattern = r'@fetch\s*\('
|
|
222
|
+
while re.search(fetch_pattern, blade_code):
|
|
223
|
+
match = re.search(fetch_pattern, blade_code)
|
|
224
|
+
if match:
|
|
225
|
+
start_pos = match.end() - 1
|
|
226
|
+
fetch_content, end_pos = extract_balanced_parentheses(blade_code, start_pos)
|
|
227
|
+
if fetch_content is not None:
|
|
228
|
+
blade_code = blade_code[:match.start()] + blade_code[start_pos + len(fetch_content) + 2:]
|
|
229
|
+
else:
|
|
230
|
+
break
|
|
231
|
+
blade_code = re.sub(r'@await\s*\([^)]*\)', '', blade_code, flags=re.DOTALL)
|
|
232
|
+
|
|
233
|
+
# Process @include directives (multiline support) BEFORE processing line by line
|
|
234
|
+
blade_code = self._process_multiline_include_directives(blade_code)
|
|
235
|
+
|
|
236
|
+
blade_code = re.sub(r'@oninit.*?@endoninit', '', blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
237
|
+
# Remove @register directive - với hoặc không có parameters
|
|
238
|
+
blade_code = re.sub(r'@register\s*(?:\([^)]*\))?.*?@endregister', '', blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
239
|
+
|
|
240
|
+
# Remove @setup directive (alias of @register) - với hoặc không có parameters
|
|
241
|
+
blade_code = re.sub(r'@setup\s*(?:\([^)]*\))?.*?@endsetup', '', blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
242
|
+
|
|
243
|
+
# Remove @script directive (xử lý như @register)
|
|
244
|
+
blade_code = re.sub(r'@script\s*(?:\([^)]*\))?.*?@endscript', '', blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
245
|
+
|
|
246
|
+
# Process inline directives in HTML attributes (before echo processing)
|
|
247
|
+
# Example: <div @if($count > 0) data-test="active" @endif>
|
|
248
|
+
lines = blade_code.splitlines()
|
|
249
|
+
processed_lines = []
|
|
250
|
+
for line in lines:
|
|
251
|
+
if self._is_attribute_directive(line):
|
|
252
|
+
line = self._process_inline_directive(line)
|
|
253
|
+
processed_lines.append(line)
|
|
254
|
+
blade_code = '\n'.join(processed_lines)
|
|
255
|
+
|
|
256
|
+
# Process echo expressions {{ }} and {!! !!} with intelligent context-aware handling
|
|
257
|
+
blade_code = self.echo_processor.process_echo_expressions(blade_code)
|
|
258
|
+
|
|
259
|
+
lines = blade_code.splitlines()
|
|
260
|
+
output = []
|
|
261
|
+
sections = []
|
|
262
|
+
stack = []
|
|
263
|
+
skip_until = None
|
|
264
|
+
remove_directive_markers = False
|
|
265
|
+
in_pre_tag = False # Track if we're inside <pre> tags
|
|
266
|
+
|
|
267
|
+
i = 0
|
|
268
|
+
while i < len(lines):
|
|
269
|
+
original_line = lines[i]
|
|
270
|
+
|
|
271
|
+
# Check if we're entering or leaving <pre> tags
|
|
272
|
+
if '<pre>' in original_line or '<pre ' in original_line:
|
|
273
|
+
in_pre_tag = True
|
|
274
|
+
if '</pre>' in original_line:
|
|
275
|
+
in_pre_tag = False
|
|
276
|
+
|
|
277
|
+
# Preserve whitespace inside <pre> tags
|
|
278
|
+
if in_pre_tag:
|
|
279
|
+
line = original_line.rstrip() # Only remove trailing whitespace
|
|
280
|
+
else:
|
|
281
|
+
line = original_line.strip() # Normal processing
|
|
282
|
+
|
|
283
|
+
if not line:
|
|
284
|
+
# Don't add empty lines in loops
|
|
285
|
+
if not (stack and stack[-1][0] in ['for', 'while']):
|
|
286
|
+
output.append('')
|
|
287
|
+
i += 1
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# Handle skip modes
|
|
291
|
+
if skip_until:
|
|
292
|
+
if skip_until == '@endserverside':
|
|
293
|
+
# Check for all endserverside aliases
|
|
294
|
+
endserverside_aliases = [
|
|
295
|
+
'@endserverside', '@endServerSide', '@endSSR', '@endSsr',
|
|
296
|
+
'@EndSSR', '@EndSsr', '@endssr'
|
|
297
|
+
]
|
|
298
|
+
if any(line.startswith(alias) for alias in endserverside_aliases):
|
|
299
|
+
skip_until = None
|
|
300
|
+
remove_directive_markers = False
|
|
301
|
+
# Skip the end directive line
|
|
302
|
+
i += 1
|
|
303
|
+
continue
|
|
304
|
+
elif skip_until == '@endclientside':
|
|
305
|
+
# Check for all endclientside aliases
|
|
306
|
+
endclientside_aliases = [
|
|
307
|
+
'@endclientside', '@endClientSide', '@endcsr', '@endCSR',
|
|
308
|
+
'@endCsr', '@endusecsr', '@endUseCSR', '@endUseCsr'
|
|
309
|
+
]
|
|
310
|
+
if any(line.startswith(alias) for alias in endclientside_aliases):
|
|
311
|
+
skip_until = None
|
|
312
|
+
remove_directive_markers = False
|
|
313
|
+
# Skip the end directive line
|
|
314
|
+
i += 1
|
|
315
|
+
continue
|
|
316
|
+
elif remove_directive_markers:
|
|
317
|
+
# Process line normally but remove directive markers
|
|
318
|
+
processed_line = self.template_processors.process_template_line(line)
|
|
319
|
+
output.append(processed_line)
|
|
320
|
+
else:
|
|
321
|
+
# Original logic for other skip modes
|
|
322
|
+
if line.startswith(skip_until):
|
|
323
|
+
skip_until = None
|
|
324
|
+
remove_directive_markers = False
|
|
325
|
+
# Skip the end directive line
|
|
326
|
+
i += 1
|
|
327
|
+
continue
|
|
328
|
+
i += 1
|
|
329
|
+
continue
|
|
330
|
+
|
|
331
|
+
# Check for multiline @view/@template directives
|
|
332
|
+
if self._is_incomplete_view_directive(line):
|
|
333
|
+
# Join with next lines until complete
|
|
334
|
+
complete_line, lines_joined = self._join_multiline_view_directive(lines, i)
|
|
335
|
+
if complete_line:
|
|
336
|
+
# Process through template processor (not directive processor)
|
|
337
|
+
processed = self.template_processors.process_template_line(complete_line)
|
|
338
|
+
if processed:
|
|
339
|
+
output.append(processed)
|
|
340
|
+
# Skip the lines that were joined
|
|
341
|
+
i += lines_joined
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
# Check for multiline event directives
|
|
345
|
+
if self._is_incomplete_event_directive(line):
|
|
346
|
+
# Join with next lines until complete
|
|
347
|
+
complete_line, lines_joined = self._join_multiline_event_directive(lines, i)
|
|
348
|
+
if complete_line:
|
|
349
|
+
processed = self._process_line_directives(complete_line, stack, output, sections)
|
|
350
|
+
if processed:
|
|
351
|
+
output.append(processed)
|
|
352
|
+
# Skip the lines that were joined
|
|
353
|
+
i += lines_joined
|
|
354
|
+
continue
|
|
355
|
+
|
|
356
|
+
# Process directives
|
|
357
|
+
processed = self._process_line_directives(line, stack, output, sections)
|
|
358
|
+
if processed:
|
|
359
|
+
if processed == 'skip_until_@endserverside':
|
|
360
|
+
skip_until = '@endserverside'
|
|
361
|
+
# Skip the @ssr line itself
|
|
362
|
+
i += 1
|
|
363
|
+
continue
|
|
364
|
+
elif processed == 'remove_directive_markers_until_@endclientside':
|
|
365
|
+
skip_until = '@endclientside'
|
|
366
|
+
remove_directive_markers = True
|
|
367
|
+
# Skip the @csr line itself
|
|
368
|
+
i += 1
|
|
369
|
+
continue
|
|
370
|
+
elif processed is not True:
|
|
371
|
+
if stack and stack[-1][0] in ['for', 'while']:
|
|
372
|
+
# Inside a loop, we need to append to the loop's output variable
|
|
373
|
+
loop_type = stack[-1][0]
|
|
374
|
+
output_var = f"__{loop_type}OutputContent__"
|
|
375
|
+
|
|
376
|
+
# Check if the last item in output is an incomplete template string for this loop
|
|
377
|
+
if output and isinstance(output[-1], str) and output[-1].startswith(output_var) and not output[-1].endswith('`;'):
|
|
378
|
+
# Append to the existing template string
|
|
379
|
+
output[-1] = output[-1].rstrip('`') + '\n' + processed + '`;'
|
|
380
|
+
else:
|
|
381
|
+
# Start a new template string
|
|
382
|
+
output.append(f"{output_var} += `{processed}`;")
|
|
383
|
+
else:
|
|
384
|
+
output.append(processed)
|
|
385
|
+
i += 1
|
|
386
|
+
continue
|
|
387
|
+
|
|
388
|
+
# Check if we're inside a php block first
|
|
389
|
+
if stack and stack[-1][0] == 'php':
|
|
390
|
+
# Convert PHP to JavaScript
|
|
391
|
+
if line.strip():
|
|
392
|
+
from php_converter import php_to_js
|
|
393
|
+
js_code = php_to_js(line)
|
|
394
|
+
processed_line = f" {js_code}"
|
|
395
|
+
else:
|
|
396
|
+
# Skip empty lines in php blocks
|
|
397
|
+
i += 1
|
|
398
|
+
continue
|
|
399
|
+
else:
|
|
400
|
+
# Process regular content
|
|
401
|
+
processed_line = self.template_processors.process_template_line(line)
|
|
402
|
+
|
|
403
|
+
# Check if we're inside a loop that needs output variable
|
|
404
|
+
if stack and stack[-1][0] in ['for', 'while']:
|
|
405
|
+
# Skip empty lines in loops completely
|
|
406
|
+
if not processed_line.strip():
|
|
407
|
+
i += 1
|
|
408
|
+
continue
|
|
409
|
+
|
|
410
|
+
# Get loop info first
|
|
411
|
+
loop_type = stack[-1][0]
|
|
412
|
+
output_var = f"__{loop_type}OutputContent__"
|
|
413
|
+
|
|
414
|
+
# Handle @endwhile/@endfor in loops
|
|
415
|
+
if line.startswith('@endwhile') or line.startswith('@endfor'):
|
|
416
|
+
# Process the end directive
|
|
417
|
+
processed = self._process_line_directives(line, stack, output, sections)
|
|
418
|
+
if processed:
|
|
419
|
+
output.append(processed)
|
|
420
|
+
i += 1
|
|
421
|
+
continue
|
|
422
|
+
|
|
423
|
+
# Handle @php ... @endphp as a pair directive in loops
|
|
424
|
+
if line.startswith('@php'):
|
|
425
|
+
# Find the matching @endphp
|
|
426
|
+
php_content = []
|
|
427
|
+
j = i + 1
|
|
428
|
+
while j < len(lines) and not lines[j].strip().startswith('@endphp'):
|
|
429
|
+
if lines[j].strip():
|
|
430
|
+
php_content.append(lines[j].strip())
|
|
431
|
+
j += 1
|
|
432
|
+
|
|
433
|
+
if j < len(lines) and lines[j].strip().startswith('@endphp'):
|
|
434
|
+
# Found matching @endphp, process the entire php block
|
|
435
|
+
if php_content:
|
|
436
|
+
from php_converter import php_to_js
|
|
437
|
+
js_code = '\n'.join([php_to_js(php_line) for php_line in php_content])
|
|
438
|
+
processed_line = f"${{APP.View.execute(() => {{\n {js_code}\n}})}}"
|
|
439
|
+
else:
|
|
440
|
+
processed_line = ""
|
|
441
|
+
|
|
442
|
+
# Skip to after @endphp
|
|
443
|
+
i = j
|
|
444
|
+
else:
|
|
445
|
+
# No matching @endphp found, treat as regular content
|
|
446
|
+
processed_line = self.template_processors.process_template_line(line)
|
|
447
|
+
else:
|
|
448
|
+
# Regular content
|
|
449
|
+
processed_line = self.template_processors.process_template_line(line)
|
|
450
|
+
|
|
451
|
+
# Now append processed_line to the loop's output variable
|
|
452
|
+
# Check if the last item in output is an incomplete template string for this loop
|
|
453
|
+
if output and isinstance(output[-1], str) and output[-1].startswith(output_var) and not output[-1].endswith('`;'):
|
|
454
|
+
# Append to the existing template string
|
|
455
|
+
output[-1] = output[-1].rstrip('`') + '\n' + processed_line + '`;'
|
|
456
|
+
else:
|
|
457
|
+
# Start a new template string
|
|
458
|
+
output.append(f"{output_var} += `{processed_line}`;")
|
|
459
|
+
|
|
460
|
+
i += 1
|
|
461
|
+
continue
|
|
462
|
+
|
|
463
|
+
output.append(processed_line)
|
|
464
|
+
|
|
465
|
+
i += 1
|
|
466
|
+
|
|
467
|
+
# Filter out boolean values and join strings
|
|
468
|
+
template_content = '\n'.join([str(item) for item in output if isinstance(item, str)])
|
|
469
|
+
|
|
470
|
+
# Process @class directive AFTER all other directives have been processed
|
|
471
|
+
template_content = self.class_binding_handler.process_class_directive(template_content)
|
|
472
|
+
|
|
473
|
+
# Replace __INCLUDE_WATCH_PLACEHOLDER__ with actual watch IDs
|
|
474
|
+
# This ensures @include directives processed early get proper sequential watch IDs
|
|
475
|
+
while '__INCLUDE_WATCH_PLACEHOLDER__' in template_content:
|
|
476
|
+
self.watch_counter += 1
|
|
477
|
+
# Replace placeholder (which is already quoted) with watch ID including __VIEW_ID__
|
|
478
|
+
template_content = template_content.replace("'__INCLUDE_WATCH_PLACEHOLDER__'",
|
|
479
|
+
f"`${{__VIEW_ID__}}-watch-{self.watch_counter}`", 1)
|
|
480
|
+
|
|
481
|
+
# NOTE: Verbatim blocks restoration is handled in main_compiler.py
|
|
482
|
+
# after all processing is complete, so we don't restore them here
|
|
483
|
+
|
|
484
|
+
return template_content, sections
|
|
485
|
+
|
|
486
|
+
def _process_event_directives(self, line):
|
|
487
|
+
"""Process event directives (@click, @change, @submit, etc.) and aliases (@onClick, @onChange...)"""
|
|
488
|
+
# List of event types to check - comprehensive DOM events
|
|
489
|
+
event_types = [
|
|
490
|
+
# Mouse Events
|
|
491
|
+
'click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove',
|
|
492
|
+
'mouseenter', 'mouseleave', 'wheel', 'auxclick',
|
|
493
|
+
|
|
494
|
+
# Keyboard Events
|
|
495
|
+
'keydown', 'keyup', 'keypress',
|
|
496
|
+
|
|
497
|
+
# Form Events
|
|
498
|
+
'input', 'change', 'submit', 'reset', 'invalid', 'search',
|
|
499
|
+
|
|
500
|
+
# Focus Events
|
|
501
|
+
'focus', 'blur', 'focusin', 'focusout',
|
|
502
|
+
|
|
503
|
+
# Selection Events
|
|
504
|
+
'select', 'selectstart', 'selectionchange',
|
|
505
|
+
|
|
506
|
+
# Touch Events
|
|
507
|
+
'touchstart', 'touchmove', 'touchend', 'touchcancel',
|
|
508
|
+
|
|
509
|
+
# Drag & Drop Events
|
|
510
|
+
'dragstart', 'drag', 'dragend', 'dragenter', 'dragleave', 'dragover', 'drop',
|
|
511
|
+
|
|
512
|
+
# Media Events
|
|
513
|
+
'play', 'pause', 'ended', 'loadstart', 'loadeddata', 'loadedmetadata', 'canplay',
|
|
514
|
+
'canplaythrough', 'waiting', 'seeking', 'seeked', 'ratechange', 'durationchange',
|
|
515
|
+
'volumechange', 'suspend', 'stalled', 'progress', 'emptied', 'encrypted', 'wakeup',
|
|
516
|
+
|
|
517
|
+
# Window Events
|
|
518
|
+
'load', 'unload', 'beforeunload', 'resize', 'scroll', 'orientationchange',
|
|
519
|
+
'visibilitychange', 'pagehide', 'pageshow', 'popstate', 'hashchange', 'online', 'offline',
|
|
520
|
+
|
|
521
|
+
# Document Events
|
|
522
|
+
'DOMContentLoaded', 'readystatechange',
|
|
523
|
+
|
|
524
|
+
# Error Events
|
|
525
|
+
'error', 'abort',
|
|
526
|
+
|
|
527
|
+
# Context Menu
|
|
528
|
+
'contextmenu',
|
|
529
|
+
|
|
530
|
+
# Animation Events
|
|
531
|
+
'animationstart', 'animationend', 'animationiteration',
|
|
532
|
+
|
|
533
|
+
# Transition Events
|
|
534
|
+
'transitionstart', 'transitionend', 'transitionrun', 'transitioncancel',
|
|
535
|
+
|
|
536
|
+
# Pointer Events (Modern browsers)
|
|
537
|
+
'pointerdown', 'pointerup', 'pointermove', 'pointerover', 'pointerout',
|
|
538
|
+
'pointerenter', 'pointerleave', 'pointercancel', 'gotpointercapture', 'lostpointercapture',
|
|
539
|
+
|
|
540
|
+
# Fullscreen Events
|
|
541
|
+
'fullscreenchange', 'fullscreenerror',
|
|
542
|
+
|
|
543
|
+
# Clipboard Events
|
|
544
|
+
'copy', 'cut', 'paste',
|
|
545
|
+
|
|
546
|
+
# Gamepad Events
|
|
547
|
+
'gamepadconnected', 'gamepaddisconnected',
|
|
548
|
+
|
|
549
|
+
# Battery Events
|
|
550
|
+
'batterychargingchange', 'batterylevelchange',
|
|
551
|
+
|
|
552
|
+
# Device Orientation Events
|
|
553
|
+
'deviceorientation', 'devicemotion', 'devicelight', 'deviceproximity',
|
|
554
|
+
|
|
555
|
+
# WebGL Events
|
|
556
|
+
'webglcontextlost', 'webglcontextrestored'
|
|
557
|
+
]
|
|
558
|
+
|
|
559
|
+
result = line
|
|
560
|
+
changed = False
|
|
561
|
+
|
|
562
|
+
# Process all event directives in the line
|
|
563
|
+
for event_type in event_types:
|
|
564
|
+
# Check for @eventType(...) or @onEventType(...) pattern with balanced parentheses
|
|
565
|
+
pattern = rf'@(?:on)?{event_type}\s*\('
|
|
566
|
+
|
|
567
|
+
# Loop to handle multiple occurrences of the same event type
|
|
568
|
+
while True:
|
|
569
|
+
match = re.search(pattern, result, re.IGNORECASE)
|
|
570
|
+
if not match:
|
|
571
|
+
break
|
|
572
|
+
|
|
573
|
+
# Extract content within balanced parentheses
|
|
574
|
+
open_paren_pos = match.end() - 1 # Position of opening parenthesis
|
|
575
|
+
content = self._extract_balanced_content(result, open_paren_pos)
|
|
576
|
+
|
|
577
|
+
if content is not None:
|
|
578
|
+
# Process directive
|
|
579
|
+
event_config = self.event_processor.process_event_directive(event_type, content)
|
|
580
|
+
|
|
581
|
+
# Replace @eventType(...) or @onEventType(...) with event config
|
|
582
|
+
directive_start = match.start()
|
|
583
|
+
# Calculate end position: open_paren_pos + 1 (len of '(') + len(content) + 1 (len of ')')
|
|
584
|
+
directive_end = open_paren_pos + 1 + len(content) + 1
|
|
585
|
+
|
|
586
|
+
result = result[:directive_start] + event_config + result[directive_end:]
|
|
587
|
+
changed = True
|
|
588
|
+
else:
|
|
589
|
+
# If content is None, it means unbalanced parentheses or other issue
|
|
590
|
+
# Break loop to avoid infinite loop
|
|
591
|
+
break
|
|
592
|
+
|
|
593
|
+
return result if changed else None
|
|
594
|
+
|
|
595
|
+
def _is_incomplete_event_directive(self, line):
|
|
596
|
+
"""Check if line contains an incomplete event directive"""
|
|
597
|
+
event_types = [
|
|
598
|
+
'click', 'change', 'submit', 'focus', 'blur', 'input', 'keydown', 'keyup', 'keypress',
|
|
599
|
+
'mousedown', 'mouseup', 'mouseover', 'mouseout', 'mousemove', 'mouseenter', 'mouseleave',
|
|
600
|
+
'dblclick', 'contextmenu', 'wheel', 'scroll', 'resize', 'load', 'unload', 'beforeunload',
|
|
601
|
+
'error', 'abort', 'select', 'selectstart', 'selectionchange'
|
|
602
|
+
]
|
|
603
|
+
|
|
604
|
+
for event_type in event_types:
|
|
605
|
+
# Check for both @event(...) and @onEvent(...)
|
|
606
|
+
pattern = rf'@(?:on)?{event_type}\s*\('
|
|
607
|
+
if re.search(pattern, line, re.IGNORECASE):
|
|
608
|
+
# Check if parentheses are balanced
|
|
609
|
+
paren_count = 0
|
|
610
|
+
in_quotes = False
|
|
611
|
+
quote_char = ''
|
|
612
|
+
|
|
613
|
+
for char in line:
|
|
614
|
+
if (char == '"' or char == "'") and not in_quotes:
|
|
615
|
+
in_quotes = True
|
|
616
|
+
quote_char = char
|
|
617
|
+
elif char == quote_char and in_quotes:
|
|
618
|
+
in_quotes = False
|
|
619
|
+
quote_char = ''
|
|
620
|
+
elif not in_quotes:
|
|
621
|
+
if char == '(':
|
|
622
|
+
paren_count += 1
|
|
623
|
+
elif char == ')':
|
|
624
|
+
paren_count -= 1
|
|
625
|
+
|
|
626
|
+
# If parentheses are not balanced, it's incomplete
|
|
627
|
+
if paren_count > 0:
|
|
628
|
+
return True
|
|
629
|
+
|
|
630
|
+
return False
|
|
631
|
+
|
|
632
|
+
def _join_multiline_event_directive(self, lines, start_index):
|
|
633
|
+
"""Join multiline event directive into a single line"""
|
|
634
|
+
result = lines[start_index].strip()
|
|
635
|
+
lines_joined = 1 # Start with 1 (the first line)
|
|
636
|
+
|
|
637
|
+
for i in range(start_index + 1, len(lines)):
|
|
638
|
+
line = lines[i].strip()
|
|
639
|
+
result += ' ' + line
|
|
640
|
+
lines_joined += 1
|
|
641
|
+
|
|
642
|
+
# Check if directive is now complete
|
|
643
|
+
if self._is_event_directive_complete(result):
|
|
644
|
+
return result, lines_joined
|
|
645
|
+
|
|
646
|
+
return result, lines_joined
|
|
647
|
+
|
|
648
|
+
def _is_event_directive_complete(self, line):
|
|
649
|
+
"""Check if event directive is complete (balanced parentheses)"""
|
|
650
|
+
paren_count = 0
|
|
651
|
+
in_quotes = False
|
|
652
|
+
quote_char = ''
|
|
653
|
+
|
|
654
|
+
for char in line:
|
|
655
|
+
if (char == '"' or char == "'") and not in_quotes:
|
|
656
|
+
in_quotes = True
|
|
657
|
+
quote_char = char
|
|
658
|
+
elif char == quote_char and in_quotes:
|
|
659
|
+
in_quotes = False
|
|
660
|
+
quote_char = ''
|
|
661
|
+
elif not in_quotes:
|
|
662
|
+
if char == '(':
|
|
663
|
+
paren_count += 1
|
|
664
|
+
elif char == ')':
|
|
665
|
+
paren_count -= 1
|
|
666
|
+
|
|
667
|
+
return paren_count == 0
|
|
668
|
+
|
|
669
|
+
def _is_incomplete_view_directive(self, line):
|
|
670
|
+
"""Check if line contains an incomplete @view/@template directive"""
|
|
671
|
+
line_stripped = line.strip().lower()
|
|
672
|
+
|
|
673
|
+
# Check if line starts with @view or @template
|
|
674
|
+
if line_stripped.startswith('@view(') or line_stripped.startswith('@template('):
|
|
675
|
+
# Check if directive is complete
|
|
676
|
+
return not self._is_event_directive_complete(line)
|
|
677
|
+
|
|
678
|
+
return False
|
|
679
|
+
|
|
680
|
+
def _join_multiline_view_directive(self, lines, start_index):
|
|
681
|
+
"""Join multiline @view/@template directive into a single line"""
|
|
682
|
+
result = lines[start_index].strip()
|
|
683
|
+
lines_joined = 1 # Start with 1 (the first line)
|
|
684
|
+
|
|
685
|
+
for i in range(start_index + 1, len(lines)):
|
|
686
|
+
line = lines[i].strip()
|
|
687
|
+
result += ' ' + line
|
|
688
|
+
lines_joined += 1
|
|
689
|
+
|
|
690
|
+
# Check if directive is now complete
|
|
691
|
+
if self._is_event_directive_complete(result):
|
|
692
|
+
return result, lines_joined
|
|
693
|
+
|
|
694
|
+
return result, lines_joined
|
|
695
|
+
|
|
696
|
+
def _extract_balanced_content(self, line, start_pos):
|
|
697
|
+
"""Extract content within balanced parentheses"""
|
|
698
|
+
paren_count = 0
|
|
699
|
+
in_quotes = False
|
|
700
|
+
quote_char = ''
|
|
701
|
+
|
|
702
|
+
for i in range(start_pos, len(line)):
|
|
703
|
+
char = line[i]
|
|
704
|
+
|
|
705
|
+
if (char == '"' or char == "'") and not in_quotes:
|
|
706
|
+
in_quotes = True
|
|
707
|
+
quote_char = char
|
|
708
|
+
elif char == quote_char and in_quotes:
|
|
709
|
+
# Check if this is an escaped quote
|
|
710
|
+
if i > 0 and line[i - 1] == '\\':
|
|
711
|
+
continue
|
|
712
|
+
in_quotes = False
|
|
713
|
+
quote_char = ''
|
|
714
|
+
elif not in_quotes:
|
|
715
|
+
if char == '(':
|
|
716
|
+
paren_count += 1
|
|
717
|
+
elif char == ')':
|
|
718
|
+
paren_count -= 1
|
|
719
|
+
if paren_count == 0:
|
|
720
|
+
# Found matching closing parenthesis
|
|
721
|
+
return line[start_pos + 1:i]
|
|
722
|
+
|
|
723
|
+
return None
|
|
724
|
+
|
|
725
|
+
def _process_line_directives(self, line, stack, output, sections):
|
|
726
|
+
"""Process Blade directives in a line"""
|
|
727
|
+
|
|
728
|
+
# Handle @class directive (BEFORE event directives to allow @class in same tag)
|
|
729
|
+
if '@class' in line:
|
|
730
|
+
line = self.class_binding_handler.process_class_directive(line)
|
|
731
|
+
|
|
732
|
+
# Handle event directives (@click, @change, @submit, etc.)
|
|
733
|
+
result = self._process_event_directives(line)
|
|
734
|
+
if result:
|
|
735
|
+
# Process {{ $var }} after event directives
|
|
736
|
+
result = self.template_processors.process_template_line(result)
|
|
737
|
+
return result
|
|
738
|
+
|
|
739
|
+
# Handle @serverside/@serverSide
|
|
740
|
+
result = self.template_processors.process_serverside_directive(line)
|
|
741
|
+
if result:
|
|
742
|
+
return result
|
|
743
|
+
|
|
744
|
+
# Handle @clientside
|
|
745
|
+
result = self.template_processors.process_clientside_directive(line)
|
|
746
|
+
if result:
|
|
747
|
+
return result
|
|
748
|
+
|
|
749
|
+
# Handle @auth/@guest
|
|
750
|
+
result = self.directive_processors.process_auth_directive(line)
|
|
751
|
+
if result:
|
|
752
|
+
return result
|
|
753
|
+
|
|
754
|
+
# Handle @endauth/@endguest
|
|
755
|
+
result = self.directive_processors.process_endauth_directive(line)
|
|
756
|
+
if result:
|
|
757
|
+
return result
|
|
758
|
+
|
|
759
|
+
# Handle @can/@cannot
|
|
760
|
+
result = self.directive_processors.process_can_directive(line)
|
|
761
|
+
if result:
|
|
762
|
+
return result
|
|
763
|
+
|
|
764
|
+
# Handle @endcan/@endcannot
|
|
765
|
+
result = self.directive_processors.process_endcan_directive(line)
|
|
766
|
+
if result:
|
|
767
|
+
return result
|
|
768
|
+
|
|
769
|
+
# Handle @csrf
|
|
770
|
+
result = self.directive_processors.process_csrf_directive(line)
|
|
771
|
+
if result:
|
|
772
|
+
return result
|
|
773
|
+
|
|
774
|
+
# Handle @method
|
|
775
|
+
result = self.directive_processors.process_method_directive(line)
|
|
776
|
+
if result:
|
|
777
|
+
return result
|
|
778
|
+
|
|
779
|
+
# Handle @error
|
|
780
|
+
result = self.directive_processors.process_error_directive(line)
|
|
781
|
+
if result:
|
|
782
|
+
return result
|
|
783
|
+
|
|
784
|
+
# Handle @enderror
|
|
785
|
+
result = self.directive_processors.process_enderror_directive(line)
|
|
786
|
+
if result:
|
|
787
|
+
return result
|
|
788
|
+
|
|
789
|
+
# Handle @hasSection
|
|
790
|
+
result = self.directive_processors.process_hassection_directive(line)
|
|
791
|
+
if result:
|
|
792
|
+
return result
|
|
793
|
+
|
|
794
|
+
# Handle @endhassection
|
|
795
|
+
result = self.directive_processors.process_endhassection_directive(line)
|
|
796
|
+
if result:
|
|
797
|
+
return result
|
|
798
|
+
|
|
799
|
+
# Handle @empty
|
|
800
|
+
result = self.directive_processors.process_empty_directive(line, stack, output)
|
|
801
|
+
if result:
|
|
802
|
+
return result
|
|
803
|
+
|
|
804
|
+
# Handle @isset
|
|
805
|
+
result = self.directive_processors.process_isset_directive(line, stack, output)
|
|
806
|
+
if result:
|
|
807
|
+
return result
|
|
808
|
+
|
|
809
|
+
# Handle @unless
|
|
810
|
+
result = self.directive_processors.process_unless_directive(line)
|
|
811
|
+
if result:
|
|
812
|
+
return result
|
|
813
|
+
|
|
814
|
+
# Handle @endunless
|
|
815
|
+
result = self.directive_processors.process_endunless_directive(line)
|
|
816
|
+
if result:
|
|
817
|
+
return result
|
|
818
|
+
|
|
819
|
+
# Handle @endempty
|
|
820
|
+
if line.startswith('@endempty'):
|
|
821
|
+
result = self.directive_processors.process_endempty_directive(stack, output)
|
|
822
|
+
if result:
|
|
823
|
+
return result
|
|
824
|
+
|
|
825
|
+
# Handle @endisset
|
|
826
|
+
if line.startswith('@endisset'):
|
|
827
|
+
result = self.directive_processors.process_endisset_directive(stack, output)
|
|
828
|
+
if result:
|
|
829
|
+
return result
|
|
830
|
+
|
|
831
|
+
# Handle @php
|
|
832
|
+
if line.startswith('@php'):
|
|
833
|
+
# Check if we're inside a loop
|
|
834
|
+
if stack and stack[-1][0] in ['for', 'while']:
|
|
835
|
+
# For @php inside loops, don't process as directive, let it be handled as content
|
|
836
|
+
pass
|
|
837
|
+
else:
|
|
838
|
+
result = self.directive_processors.process_php_directive(line, stack, output)
|
|
839
|
+
if result:
|
|
840
|
+
return result
|
|
841
|
+
|
|
842
|
+
# Handle @endphp
|
|
843
|
+
if line.startswith('@endphp'):
|
|
844
|
+
# Check if we're inside a loop
|
|
845
|
+
if stack and stack[-1][0] in ['for', 'while']:
|
|
846
|
+
# For @endphp inside loops, don't process as directive, let it be handled as content
|
|
847
|
+
pass
|
|
848
|
+
else:
|
|
849
|
+
result = self.directive_processors.process_endphp_directive(stack, output)
|
|
850
|
+
if result:
|
|
851
|
+
return result
|
|
852
|
+
|
|
853
|
+
# Handle @json
|
|
854
|
+
result = self.directive_processors.process_json_directive(line)
|
|
855
|
+
if result:
|
|
856
|
+
return result
|
|
857
|
+
|
|
858
|
+
# Handle @lang
|
|
859
|
+
result = self.directive_processors.process_lang_directive(line)
|
|
860
|
+
if result:
|
|
861
|
+
return result
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
# Handle @choice
|
|
865
|
+
result = self.directive_processors.process_choice_directive(line)
|
|
866
|
+
if result:
|
|
867
|
+
return result
|
|
868
|
+
|
|
869
|
+
# Handle @exec
|
|
870
|
+
result = self.directive_processors.process_exec_directive(line)
|
|
871
|
+
if result:
|
|
872
|
+
return result
|
|
873
|
+
|
|
874
|
+
# Handle @out
|
|
875
|
+
result = self.directive_processors.process_out_directive(line)
|
|
876
|
+
if result:
|
|
877
|
+
return result
|
|
878
|
+
|
|
879
|
+
# Handle @section
|
|
880
|
+
if line.startswith('@section'):
|
|
881
|
+
return self.section_handlers.process_section_directive(line, stack, output, sections)
|
|
882
|
+
|
|
883
|
+
if line.startswith('@endsection'):
|
|
884
|
+
return self.section_handlers.process_endsection_directive(stack, output, sections)
|
|
885
|
+
|
|
886
|
+
# Handle @block
|
|
887
|
+
if line.startswith('@block'):
|
|
888
|
+
return self.section_handlers.process_block_directive(line, stack, output, sections)
|
|
889
|
+
|
|
890
|
+
if line.startswith('@endblock') or line.startswith('@endBlock'):
|
|
891
|
+
return self.section_handlers.process_endblock_directive(stack, output, sections)
|
|
892
|
+
|
|
893
|
+
# Handle @if/@endif
|
|
894
|
+
if line.startswith('@if'):
|
|
895
|
+
is_attr = self._is_attribute_directive(line)
|
|
896
|
+
return self.conditional_handlers.process_if_directive(line, stack, output, is_attr)
|
|
897
|
+
|
|
898
|
+
if line.startswith('@elseif'):
|
|
899
|
+
return self.conditional_handlers.process_elseif_directive(line, stack, output)
|
|
900
|
+
|
|
901
|
+
if line.startswith('@else'):
|
|
902
|
+
return self.conditional_handlers.process_else_directive(line, stack, output)
|
|
903
|
+
|
|
904
|
+
if line.startswith('@endif'):
|
|
905
|
+
return self.conditional_handlers.process_endif_directive(stack, output)
|
|
906
|
+
|
|
907
|
+
# Handle @foreach
|
|
908
|
+
if line.startswith('@foreach'):
|
|
909
|
+
is_attr = self._is_attribute_directive(line)
|
|
910
|
+
return self.loop_handlers.process_foreach_directive(line, stack, output, is_attr)
|
|
911
|
+
|
|
912
|
+
if line.startswith('@endforeach'):
|
|
913
|
+
return self.loop_handlers.process_endforeach_directive(stack, output)
|
|
914
|
+
|
|
915
|
+
# Handle @for
|
|
916
|
+
if line.startswith('@for'):
|
|
917
|
+
is_attr = self._is_attribute_directive(line)
|
|
918
|
+
return self.loop_handlers.process_for_directive(line, stack, output, is_attr)
|
|
919
|
+
|
|
920
|
+
if line.startswith('@endfor'):
|
|
921
|
+
return self.loop_handlers.process_endfor_directive(stack, output)
|
|
922
|
+
|
|
923
|
+
# Handle @while
|
|
924
|
+
if line.startswith('@while'):
|
|
925
|
+
is_attr = self._is_attribute_directive(line)
|
|
926
|
+
return self.loop_handlers.process_while_directive(line, stack, output, is_attr)
|
|
927
|
+
|
|
928
|
+
if line.startswith('@endwhile'):
|
|
929
|
+
return self.loop_handlers.process_endwhile_directive(stack, output)
|
|
930
|
+
|
|
931
|
+
# Handle @switch
|
|
932
|
+
if line.startswith('@switch'):
|
|
933
|
+
is_attr = self._is_attribute_directive(line)
|
|
934
|
+
return self.conditional_handlers.process_switch_directive(line, stack, output, is_attr)
|
|
935
|
+
|
|
936
|
+
if line.startswith('@case'):
|
|
937
|
+
return self.conditional_handlers.process_case_directive(line, stack, output)
|
|
938
|
+
|
|
939
|
+
if line.startswith('@default'):
|
|
940
|
+
return self.conditional_handlers.process_default_directive(line, stack, output)
|
|
941
|
+
|
|
942
|
+
if line.startswith('@break'):
|
|
943
|
+
return self.conditional_handlers.process_break_directive(line, stack, output)
|
|
944
|
+
|
|
945
|
+
if line.startswith('@endswitch'):
|
|
946
|
+
return self.conditional_handlers.process_endswitch_directive(stack, output)
|
|
947
|
+
|
|
948
|
+
# Handle @register
|
|
949
|
+
if line.startswith('@register'):
|
|
950
|
+
result = self.directive_processors.process_register_directive(line, stack, output)
|
|
951
|
+
if result:
|
|
952
|
+
return result
|
|
953
|
+
|
|
954
|
+
# Handle @endregister
|
|
955
|
+
if line.startswith('@endregister'):
|
|
956
|
+
result = self.directive_processors.process_endregister_directive(stack, output)
|
|
957
|
+
if result:
|
|
958
|
+
return result
|
|
959
|
+
|
|
960
|
+
# Handle @setup (alias của @register)
|
|
961
|
+
if line.startswith('@setup'):
|
|
962
|
+
result = self.directive_processors.process_register_directive(line, stack, output)
|
|
963
|
+
if result:
|
|
964
|
+
return result
|
|
965
|
+
|
|
966
|
+
# Handle @endsetup (alias của @endregister)
|
|
967
|
+
if line.startswith('@endsetup'):
|
|
968
|
+
result = self.directive_processors.process_endregister_directive(stack, output)
|
|
969
|
+
if result:
|
|
970
|
+
return result
|
|
971
|
+
|
|
972
|
+
# Handle @script (xử lý như @register)
|
|
973
|
+
if line.startswith('@script'):
|
|
974
|
+
result = self.directive_processors.process_register_directive(line, stack, output)
|
|
975
|
+
if result:
|
|
976
|
+
return result
|
|
977
|
+
|
|
978
|
+
# Handle @endscript (xử lý như @endregister)
|
|
979
|
+
if line.startswith('@endscript'):
|
|
980
|
+
result = self.directive_processors.process_endregister_directive(stack, output)
|
|
981
|
+
if result:
|
|
982
|
+
return result
|
|
983
|
+
|
|
984
|
+
# Handle @wrapper and @wrap (NOT @view - that's handled as @template style)
|
|
985
|
+
line_lower = line.lower()
|
|
986
|
+
if line_lower.startswith('@wrapper') or line_lower.startswith('@wrap'):
|
|
987
|
+
result = self.directive_processors.process_wrapper_directive(line, stack, output)
|
|
988
|
+
if result:
|
|
989
|
+
return result
|
|
990
|
+
|
|
991
|
+
# Handle @endwrapper and @endwrap (NOT @endview - that's handled with @template style)
|
|
992
|
+
if line_lower.startswith('@endwrapper') or line_lower.startswith('@endwrap'):
|
|
993
|
+
result = self.directive_processors.process_endwrapper_directive(stack, output)
|
|
994
|
+
if result:
|
|
995
|
+
return result
|
|
996
|
+
|
|
997
|
+
return False
|
|
998
|
+
|
|
999
|
+
def _process_multiline_include_directives(self, blade_code):
|
|
1000
|
+
"""Process multiline @include directives before line-by-line processing"""
|
|
1001
|
+
from config import APP_VIEW_NAMESPACE
|
|
1002
|
+
|
|
1003
|
+
# Handle @include directive with string literals and variables (multiline arrays/objects)
|
|
1004
|
+
def replace_include_directive(match):
|
|
1005
|
+
view_name = match.group(1).strip()
|
|
1006
|
+
variables = match.group(2).strip() if match.group(2) else '{}'
|
|
1007
|
+
|
|
1008
|
+
# Don't use convert_php_array_to_json as it evals PHP which fails with undefined vars
|
|
1009
|
+
# Instead, just convert PHP array syntax to JS object syntax
|
|
1010
|
+
variables_js = variables
|
|
1011
|
+
|
|
1012
|
+
# Extract state variables used in the include data
|
|
1013
|
+
state_vars_used = set()
|
|
1014
|
+
# Find all $variable references in the variables data
|
|
1015
|
+
var_matches = re.findall(r'\$(\w+)', variables)
|
|
1016
|
+
for var_name in var_matches:
|
|
1017
|
+
if var_name in self.state_variables:
|
|
1018
|
+
state_vars_used.add(var_name)
|
|
1019
|
+
|
|
1020
|
+
# Convert PHP array ['key' => $value] to JS {key: value}
|
|
1021
|
+
# Handle array syntax
|
|
1022
|
+
if variables_js.startswith('[') and variables_js.endswith(']'):
|
|
1023
|
+
variables_js = variables_js[1:-1].strip() # Remove [ ]
|
|
1024
|
+
|
|
1025
|
+
# Convert 'key' => $value patterns to "key": value
|
|
1026
|
+
variables_js = re.sub(r"'([^']+)'\s*=>\s*\$(\w+)", r'"\1": \2', variables_js)
|
|
1027
|
+
variables_js = re.sub(r'"([^"]+)"\s*=>\s*\$(\w+)', r'"\1": \2', variables_js)
|
|
1028
|
+
|
|
1029
|
+
# Wrap in { }
|
|
1030
|
+
variables_js = '{' + variables_js + '}'
|
|
1031
|
+
|
|
1032
|
+
include_call = APP_VIEW_NAMESPACE + ".renderView(this.__include('" + view_name + "', " + variables_js + "))"
|
|
1033
|
+
|
|
1034
|
+
# If state variables are used, wrap with __watch
|
|
1035
|
+
# Use placeholder for watch ID that will be replaced later with actual counter
|
|
1036
|
+
if state_vars_used:
|
|
1037
|
+
watch_keys = list(state_vars_used)
|
|
1038
|
+
return "${this.__watch('__INCLUDE_WATCH_PLACEHOLDER__', " + str(watch_keys) + ", () => " + include_call + ")}"
|
|
1039
|
+
|
|
1040
|
+
return "${" + include_call + "}"
|
|
1041
|
+
|
|
1042
|
+
# Handle @include directive with PHP expressions and variables (multiline)
|
|
1043
|
+
def replace_include_php_directive(match):
|
|
1044
|
+
view_expr = match.group(1).strip()
|
|
1045
|
+
variables = match.group(2).strip() if match.group(2) else '{}'
|
|
1046
|
+
|
|
1047
|
+
# Extract state variables used in the include data
|
|
1048
|
+
state_vars_used = set()
|
|
1049
|
+
# Find all $variable references in the variables data
|
|
1050
|
+
var_matches = re.findall(r'\$(\w+)', variables)
|
|
1051
|
+
for var_name in var_matches:
|
|
1052
|
+
if var_name in self.state_variables:
|
|
1053
|
+
state_vars_used.add(var_name)
|
|
1054
|
+
|
|
1055
|
+
# Don't eval PHP - just parse syntax
|
|
1056
|
+
variables_js = variables
|
|
1057
|
+
|
|
1058
|
+
# Convert PHP array to JS object
|
|
1059
|
+
if variables_js.startswith('[') and variables_js.endswith(']'):
|
|
1060
|
+
variables_js = variables_js[1:-1].strip()
|
|
1061
|
+
# Convert 'key' => $value to "key": value
|
|
1062
|
+
variables_js = re.sub(r"'([^']+)'\s*=>\s*\$(\w+)", r'"\1": \2', variables_js)
|
|
1063
|
+
variables_js = re.sub(r'"([^"]+)"\s*=>\s*\$(\w+)', r'"\1": \2', variables_js)
|
|
1064
|
+
variables_js = '{' + variables_js + '}'
|
|
1065
|
+
else:
|
|
1066
|
+
# Remove $ from simple variables
|
|
1067
|
+
variables_js = re.sub(r'\$(\w+)', r'\1', variables_js)
|
|
1068
|
+
|
|
1069
|
+
# Convert PHP expression to JavaScript
|
|
1070
|
+
from php_converter import php_to_js
|
|
1071
|
+
view_expr_js = php_to_js(view_expr)
|
|
1072
|
+
|
|
1073
|
+
include_call = APP_VIEW_NAMESPACE + ".renderView(this.__include(" + view_expr_js + ", " + variables_js + "))"
|
|
1074
|
+
|
|
1075
|
+
# If state variables are used, wrap with __watch
|
|
1076
|
+
# Use placeholder for watch ID that will be replaced later with actual counter
|
|
1077
|
+
if state_vars_used:
|
|
1078
|
+
watch_keys = list(state_vars_used)
|
|
1079
|
+
return "${this.__watch('__INCLUDE_WATCH_PLACEHOLDER__', " + str(watch_keys) + ", () => " + include_call + ")}"
|
|
1080
|
+
|
|
1081
|
+
return "${" + include_call + "}"
|
|
1082
|
+
|
|
1083
|
+
# Process multiline @include directives with proper patterns
|
|
1084
|
+
# Handle @include with PHP expressions (must be before string literal patterns)
|
|
1085
|
+
blade_code = re.sub(r'@include\s*\(\s*([^,\'"][^)]*?)\s*,\s*(\[[^\]]*\]|\{[^\}]*\}|[^)]*)\s*\)', replace_include_php_directive, blade_code, flags=re.MULTILINE | re.DOTALL)
|
|
1086
|
+
|
|
1087
|
+
# Handle @include with string literals (multiline arrays/objects)
|
|
1088
|
+
blade_code = re.sub(r'@include\s*\(\s*[\'"]([^\'"]*)[\'"]\s*,\s*(\[[^\]]*\]|\{[^\}]*\}|[^)]*)\s*\)', replace_include_directive, blade_code, flags=re.MULTILINE | re.DOTALL)
|
|
1089
|
+
|
|
1090
|
+
# Handle @include without variables
|
|
1091
|
+
def replace_include_no_vars_directive(match):
|
|
1092
|
+
view_expr = match.group(1).strip()
|
|
1093
|
+
from php_converter import php_to_js
|
|
1094
|
+
view_expr_js = php_to_js(view_expr)
|
|
1095
|
+
return "${" + APP_VIEW_NAMESPACE + ".renderView(this.__include(" + view_expr_js + "))}"
|
|
1096
|
+
|
|
1097
|
+
blade_code = re.sub(r'@include\s*\(\s*([^,\'"][^)]*?)\s*\)', replace_include_no_vars_directive, blade_code)
|
|
1098
|
+
blade_code = re.sub(r'@include\s*\(\s*[\'"]([^\'"]*)[\'"]\s*\)', r'${' + APP_VIEW_NAMESPACE + r'.renderView(this.__include("\1", {}))}', blade_code)
|
|
1099
|
+
|
|
1100
|
+
return blade_code
|
|
1101
|
+
|
|
1102
|
+
def _process_verbatim_blocks(self, blade_code):
|
|
1103
|
+
"""Process @verbatim...@endverbatim blocks to preserve their content"""
|
|
1104
|
+
# Store verbatim blocks and replace them with placeholders
|
|
1105
|
+
self.verbatim_blocks = {}
|
|
1106
|
+
verbatim_counter = 0
|
|
1107
|
+
|
|
1108
|
+
# Find all @verbatim...@endverbatim blocks
|
|
1109
|
+
verbatim_pattern = r'@verbatim\s*(.*?)\s*@endverbatim'
|
|
1110
|
+
|
|
1111
|
+
def replace_verbatim_block(match):
|
|
1112
|
+
nonlocal verbatim_counter
|
|
1113
|
+
# Extract the content between @verbatim and @endverbatim
|
|
1114
|
+
content = match.group(1)
|
|
1115
|
+
# Store content with a unique placeholder
|
|
1116
|
+
placeholder = f"__VERBATIM_BLOCK_{verbatim_counter}__"
|
|
1117
|
+
self.verbatim_blocks[placeholder] = content
|
|
1118
|
+
verbatim_counter += 1
|
|
1119
|
+
return placeholder
|
|
1120
|
+
|
|
1121
|
+
# Replace all @verbatim blocks with placeholders
|
|
1122
|
+
blade_code = re.sub(verbatim_pattern, replace_verbatim_block, blade_code, flags=re.DOTALL)
|
|
1123
|
+
|
|
1124
|
+
return blade_code
|
|
1125
|
+
|
|
1126
|
+
def _restore_verbatim_blocks(self, processed_content):
|
|
1127
|
+
"""Restore verbatim blocks from placeholders"""
|
|
1128
|
+
if hasattr(self, 'verbatim_blocks'):
|
|
1129
|
+
for placeholder, content in self.verbatim_blocks.items():
|
|
1130
|
+
processed_content = processed_content.replace(placeholder, content)
|
|
1131
|
+
return processed_content
|
|
1132
|
+
|
|
1133
|
+
def _remove_page_directives(self, blade_code):
|
|
1134
|
+
"""
|
|
1135
|
+
Remove page/document directives from Blade code.
|
|
1136
|
+
These directives are only for PHP compilation (server-side),
|
|
1137
|
+
and should be completely removed when compiling to JavaScript (client-side).
|
|
1138
|
+
|
|
1139
|
+
Directives to remove:
|
|
1140
|
+
- @pageStart, @pageOpen, @docStart
|
|
1141
|
+
- @pageEnd, @pageClose, @docEnd
|
|
1142
|
+
"""
|
|
1143
|
+
# Remove all page/document directives (case-insensitive)
|
|
1144
|
+
# These directives don't take parameters, so we just match the directive name
|
|
1145
|
+
# Pattern matches: @directiveName followed by word boundary, optional whitespace, and optional newline
|
|
1146
|
+
directive_patterns = [
|
|
1147
|
+
r'@pageStart\b',
|
|
1148
|
+
r'@pageOpen\b',
|
|
1149
|
+
r'@pageEnd\b',
|
|
1150
|
+
r'@pageClose\b',
|
|
1151
|
+
r'@docStart\b',
|
|
1152
|
+
r'@docEnd\b',
|
|
1153
|
+
]
|
|
1154
|
+
|
|
1155
|
+
for pattern in directive_patterns:
|
|
1156
|
+
# Remove directive with optional whitespace and newline after it
|
|
1157
|
+
# This handles cases like:
|
|
1158
|
+
# @pageStart
|
|
1159
|
+
# @pageStart\n
|
|
1160
|
+
# @pageStart \n
|
|
1161
|
+
blade_code = re.sub(pattern + r'\s*\n?', '', blade_code, flags=re.IGNORECASE | re.MULTILINE)
|
|
1162
|
+
# Also handle directive at end of line (standalone on a line)
|
|
1163
|
+
blade_code = re.sub(r'^\s*' + pattern + r'\s*$', '', blade_code, flags=re.IGNORECASE | re.MULTILINE)
|
|
1164
|
+
|
|
1165
|
+
return blade_code
|
|
1166
|
+
|
|
1167
|
+
|