onelaraveljs 1.0.0 → 1.1.1
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/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 +573 -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 +200 -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/templates/view.module.js +2 -0
- package/templates/view.tpl-raw.js +13 -0
- package/templates/wraper.js +71 -0
|
@@ -0,0 +1,1418 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parsers cho các directive (@extends, @vars, @fetch, @onInit)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
import json
|
|
7
|
+
from utils import extract_balanced_parentheses
|
|
8
|
+
from php_converter import php_to_js, convert_php_array_to_json, convert_php_array_with_php_r
|
|
9
|
+
|
|
10
|
+
class DirectiveParsers:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
def _remove_script_tags(self, blade_code):
|
|
15
|
+
"""Loại bỏ JavaScript code trong <script> tags để tránh xử lý useState trong JS"""
|
|
16
|
+
# Loại bỏ tất cả content trong <script> tags
|
|
17
|
+
filtered_code = re.sub(r'<script[^>]*>.*?</script>', '', blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
18
|
+
return filtered_code
|
|
19
|
+
|
|
20
|
+
def _remove_verbatim_blocks(self, blade_code):
|
|
21
|
+
"""Loại bỏ @verbatim...@endverbatim blocks để tránh xử lý directives bên trong"""
|
|
22
|
+
# Loại bỏ tất cả content trong @verbatim blocks
|
|
23
|
+
filtered_code = re.sub(r'@verbatim\s*.*?\s*@endverbatim', '', blade_code, flags=re.DOTALL | re.IGNORECASE)
|
|
24
|
+
return filtered_code
|
|
25
|
+
|
|
26
|
+
def parse_extends(self, blade_code):
|
|
27
|
+
"""Parse @extends directive"""
|
|
28
|
+
extends_match = re.search(r'@extends\s*\(\s*([^)]+)\s*\)', blade_code, re.DOTALL)
|
|
29
|
+
if not extends_match:
|
|
30
|
+
return None, None, None
|
|
31
|
+
|
|
32
|
+
extends_content = extends_match.group(1).strip()
|
|
33
|
+
|
|
34
|
+
# Check if there's a comma (indicating data parameter)
|
|
35
|
+
comma_pos = extends_content.find(',')
|
|
36
|
+
if comma_pos != -1:
|
|
37
|
+
view_expr = extends_content[:comma_pos].strip()
|
|
38
|
+
data_expr = extends_content[comma_pos + 1:].strip()
|
|
39
|
+
extends_data = self._convert_extends_data(data_expr)
|
|
40
|
+
else:
|
|
41
|
+
view_expr = extends_content
|
|
42
|
+
extends_data = None
|
|
43
|
+
|
|
44
|
+
# Check if it's a simple string literal (no variables or function calls)
|
|
45
|
+
if self._is_simple_string_literal(view_expr):
|
|
46
|
+
extended_view = view_expr[1:-1] # Remove quotes
|
|
47
|
+
extends_expression = None
|
|
48
|
+
else:
|
|
49
|
+
# Complex expression - convert to JavaScript
|
|
50
|
+
extends_expression = self._convert_extends_expression(view_expr)
|
|
51
|
+
extended_view = None
|
|
52
|
+
|
|
53
|
+
return extended_view, extends_expression, extends_data
|
|
54
|
+
|
|
55
|
+
def parse_vars(self, blade_code):
|
|
56
|
+
"""Parse @vars directive - improved to handle complex arrays like Event directive"""
|
|
57
|
+
# Loại bỏ @verbatim blocks để tránh parse directives bên trong
|
|
58
|
+
blade_code = self._remove_verbatim_blocks(blade_code)
|
|
59
|
+
vars_match = re.search(r'@vars\s*\(\s*(.*?)\s*\)', blade_code, re.DOTALL)
|
|
60
|
+
if not vars_match:
|
|
61
|
+
return ''
|
|
62
|
+
|
|
63
|
+
vars_content = vars_match.group(1)
|
|
64
|
+
var_parts = []
|
|
65
|
+
|
|
66
|
+
# Special handling for object destructuring syntax {var1, var2}
|
|
67
|
+
if vars_content.strip().startswith('{') and vars_content.strip().endswith('}'):
|
|
68
|
+
# Extract content inside braces
|
|
69
|
+
inner_content = vars_content.strip()[1:-1]
|
|
70
|
+
# Split by comma at level 0
|
|
71
|
+
parts = self._split_vars_content_correct(inner_content)
|
|
72
|
+
else:
|
|
73
|
+
# Use improved splitting logic (same as Event directive)
|
|
74
|
+
parts = self._split_vars_content_correct(vars_content)
|
|
75
|
+
|
|
76
|
+
for var in parts:
|
|
77
|
+
var = var.strip()
|
|
78
|
+
if '=' in var:
|
|
79
|
+
equals_pos = self._find_first_equals(var)
|
|
80
|
+
if equals_pos != -1:
|
|
81
|
+
var_name = var[:equals_pos].strip().lstrip('$')
|
|
82
|
+
var_value = var[equals_pos + 1:].strip()
|
|
83
|
+
# Convert PHP array syntax to JavaScript
|
|
84
|
+
var_value = self._convert_php_to_js(var_value)
|
|
85
|
+
var_parts.append(f"{var_name} = {var_value}")
|
|
86
|
+
else:
|
|
87
|
+
var_name = var.strip().lstrip('$')
|
|
88
|
+
var_parts.append(var_name)
|
|
89
|
+
else:
|
|
90
|
+
var_name = var.strip().lstrip('$')
|
|
91
|
+
var_parts.append(var_name)
|
|
92
|
+
|
|
93
|
+
return "let {" + ', '.join(var_parts) + "} = $$$DATA$$$ || {};"
|
|
94
|
+
|
|
95
|
+
def parse_let_directives(self, blade_code):
|
|
96
|
+
"""Parse @let directives - chỉ xử lý Blade directives, không xử lý JavaScript code"""
|
|
97
|
+
# Loại bỏ JavaScript code trong <script> tags trước khi parse
|
|
98
|
+
blade_code_filtered = self._remove_script_tags(blade_code)
|
|
99
|
+
# Loại bỏ @verbatim blocks để tránh parse directives bên trong
|
|
100
|
+
blade_code_filtered = self._remove_verbatim_blocks(blade_code_filtered)
|
|
101
|
+
|
|
102
|
+
# Sử dụng balanced parentheses để parse đúng
|
|
103
|
+
let_matches = []
|
|
104
|
+
pattern = r'@let\s*\('
|
|
105
|
+
for match in re.finditer(pattern, blade_code_filtered):
|
|
106
|
+
start_pos = match.end() - 1
|
|
107
|
+
content, _ = extract_balanced_parentheses(blade_code_filtered, start_pos)
|
|
108
|
+
if content is not None and content.strip():
|
|
109
|
+
let_matches.append(content.strip())
|
|
110
|
+
|
|
111
|
+
if not let_matches:
|
|
112
|
+
return ''
|
|
113
|
+
|
|
114
|
+
let_declarations = []
|
|
115
|
+
for expression in let_matches:
|
|
116
|
+
# Split expression by comma để xử lý mixed declarations
|
|
117
|
+
parts = self._split_assignments(expression)
|
|
118
|
+
|
|
119
|
+
for part in parts:
|
|
120
|
+
part = part.strip()
|
|
121
|
+
if not part:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Loại bỏ dấu $ ở đầu part nếu có
|
|
125
|
+
part = re.sub(r'^\s*\$\s*', '', part)
|
|
126
|
+
|
|
127
|
+
# Xử lý destructuring syntax đặc biệt
|
|
128
|
+
if '[' in part and ']' in part and '=' in part:
|
|
129
|
+
# Đây là destructuring assignment
|
|
130
|
+
js_expression = self._convert_destructuring_assignment(part)
|
|
131
|
+
if js_expression:
|
|
132
|
+
# Thay const bằng let
|
|
133
|
+
js_expression = js_expression.replace('const ', 'let ')
|
|
134
|
+
let_declarations.append(js_expression)
|
|
135
|
+
continue
|
|
136
|
+
elif '{' in part and '}' in part and '=' in part:
|
|
137
|
+
# Object destructuring
|
|
138
|
+
js_expression = self._convert_destructuring_assignment(part)
|
|
139
|
+
if js_expression:
|
|
140
|
+
js_expression = js_expression.replace('const ', 'let ')
|
|
141
|
+
let_declarations.append(js_expression)
|
|
142
|
+
continue
|
|
143
|
+
|
|
144
|
+
# Convert PHP to JavaScript với xử lý mảng bằng php -r
|
|
145
|
+
js_expression = self._convert_php_expression_with_arrays(part)
|
|
146
|
+
|
|
147
|
+
# Loại bỏ dấu $ từ biến (nếu có) - cải thiện regex
|
|
148
|
+
js_expression = re.sub(r'\$(\w+)', r'\1', js_expression)
|
|
149
|
+
|
|
150
|
+
# Loại bỏ dấu $ ở đầu assignment nếu có
|
|
151
|
+
if '=' in js_expression:
|
|
152
|
+
parts = js_expression.split('=', 1)
|
|
153
|
+
if len(parts) == 2:
|
|
154
|
+
left_part = parts[0].strip()
|
|
155
|
+
right_part = parts[1].strip()
|
|
156
|
+
# Loại bỏ $ ở đầu left part
|
|
157
|
+
left_part = re.sub(r'^\s*\$\s*', '', left_part)
|
|
158
|
+
js_expression = f'{left_part} = {right_part}'
|
|
159
|
+
|
|
160
|
+
# Thêm prefix 'let ' và dấu ; nếu chưa có
|
|
161
|
+
if not js_expression.startswith('let '):
|
|
162
|
+
js_expression = 'let ' + js_expression
|
|
163
|
+
if not js_expression.endswith(';'):
|
|
164
|
+
js_expression += ';'
|
|
165
|
+
let_declarations.append(js_expression)
|
|
166
|
+
|
|
167
|
+
return '\n'.join(let_declarations)
|
|
168
|
+
|
|
169
|
+
def _convert_php_expression_with_arrays(self, expression):
|
|
170
|
+
"""Convert PHP expression to JavaScript with array handling using php -r"""
|
|
171
|
+
# Sử dụng regex đơn giản để tìm và replace arrays
|
|
172
|
+
def replace_array(match):
|
|
173
|
+
array_expr = match.group(0)
|
|
174
|
+
# Sử dụng php -r để convert array
|
|
175
|
+
json_result = convert_php_array_with_php_r(array_expr)
|
|
176
|
+
if json_result is not None:
|
|
177
|
+
return json_result
|
|
178
|
+
# Fallback to old method
|
|
179
|
+
return self._convert_php_array_legacy(array_expr)
|
|
180
|
+
|
|
181
|
+
# Sử dụng php -r trực tiếp cho toàn bộ expression nếu có array
|
|
182
|
+
import re
|
|
183
|
+
if '[' in expression and ']' in expression:
|
|
184
|
+
# Thử sử dụng php -r cho toàn bộ expression
|
|
185
|
+
json_result = convert_php_array_with_php_r(expression)
|
|
186
|
+
if json_result is not None:
|
|
187
|
+
expression = json_result
|
|
188
|
+
else:
|
|
189
|
+
# Fallback to convert_php_array_to_json
|
|
190
|
+
expression = convert_php_array_to_json(expression)
|
|
191
|
+
|
|
192
|
+
# Convert remaining PHP to JavaScript
|
|
193
|
+
result = php_to_js(expression)
|
|
194
|
+
|
|
195
|
+
# Loại bỏ dấu $ từ biến (nếu có)
|
|
196
|
+
result = re.sub(r'\$(\w+)', r'\1', result)
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
|
|
200
|
+
def _split_assignments(self, expression):
|
|
201
|
+
"""Split expression by comma, respecting parentheses and brackets"""
|
|
202
|
+
assignments = []
|
|
203
|
+
current = ''
|
|
204
|
+
balance = 0
|
|
205
|
+
in_string = False
|
|
206
|
+
string_char = ''
|
|
207
|
+
|
|
208
|
+
for char in expression:
|
|
209
|
+
if char in ['"', "'"]:
|
|
210
|
+
if in_string and char == string_char:
|
|
211
|
+
in_string = False
|
|
212
|
+
elif not in_string:
|
|
213
|
+
in_string = True
|
|
214
|
+
string_char = char
|
|
215
|
+
elif not in_string:
|
|
216
|
+
if char in ['(', '[', '{']:
|
|
217
|
+
balance += 1
|
|
218
|
+
elif char in [')', ']', '}']:
|
|
219
|
+
balance -= 1
|
|
220
|
+
elif char == ',' and balance == 0:
|
|
221
|
+
assignments.append(current.strip())
|
|
222
|
+
current = ''
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
current += char
|
|
226
|
+
|
|
227
|
+
if current.strip():
|
|
228
|
+
assignments.append(current.strip())
|
|
229
|
+
|
|
230
|
+
return assignments
|
|
231
|
+
|
|
232
|
+
def parse_const_directives(self, blade_code):
|
|
233
|
+
"""Parse @const directives - chỉ xử lý Blade directives, không xử lý JavaScript code"""
|
|
234
|
+
# Loại bỏ JavaScript code trong <script> tags trước khi parse
|
|
235
|
+
blade_code_filtered = self._remove_script_tags(blade_code)
|
|
236
|
+
# Loại bỏ @verbatim blocks để tránh parse directives bên trong
|
|
237
|
+
blade_code_filtered = self._remove_verbatim_blocks(blade_code_filtered)
|
|
238
|
+
|
|
239
|
+
# Sử dụng balanced parentheses để parse đúng
|
|
240
|
+
const_matches = []
|
|
241
|
+
pattern = r'@const\s*\('
|
|
242
|
+
for match in re.finditer(pattern, blade_code_filtered):
|
|
243
|
+
start_pos = match.end() - 1
|
|
244
|
+
content, _ = extract_balanced_parentheses(blade_code_filtered, start_pos)
|
|
245
|
+
if content is not None and content.strip():
|
|
246
|
+
const_matches.append(content.strip())
|
|
247
|
+
|
|
248
|
+
if not const_matches:
|
|
249
|
+
return ''
|
|
250
|
+
|
|
251
|
+
const_declarations = []
|
|
252
|
+
for expression in const_matches:
|
|
253
|
+
# Xử lý destructuring syntax đặc biệt
|
|
254
|
+
if '[' in expression and ']' in expression and '=' in expression:
|
|
255
|
+
# Đây là destructuring assignment
|
|
256
|
+
js_expression = self._convert_destructuring_assignment(expression)
|
|
257
|
+
if js_expression:
|
|
258
|
+
const_declarations.append(js_expression)
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
# Convert PHP to JavaScript với xử lý mảng bằng php -r
|
|
262
|
+
js_expression = self._convert_php_expression_with_arrays(expression)
|
|
263
|
+
|
|
264
|
+
# Split by comma to handle multiple assignments
|
|
265
|
+
assignments = self._split_assignments(js_expression)
|
|
266
|
+
|
|
267
|
+
for assignment in assignments:
|
|
268
|
+
assignment = assignment.strip()
|
|
269
|
+
if assignment:
|
|
270
|
+
# Thêm prefix 'const ' và dấu ; nếu chưa có
|
|
271
|
+
if not assignment.startswith('const '):
|
|
272
|
+
assignment = 'const ' + assignment
|
|
273
|
+
if not assignment.endswith(';'):
|
|
274
|
+
assignment += ';'
|
|
275
|
+
const_declarations.append(assignment)
|
|
276
|
+
|
|
277
|
+
return '\n'.join(const_declarations)
|
|
278
|
+
|
|
279
|
+
def _convert_destructuring_assignment(self, expression):
|
|
280
|
+
"""Convert destructuring assignment like [a, b] = something() or {a, b} = something()"""
|
|
281
|
+
# Tìm dấu = đầu tiên
|
|
282
|
+
equals_pos = expression.find('=')
|
|
283
|
+
if equals_pos == -1:
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
# Lấy phần bên trái (destructuring pattern) và bên phải (value)
|
|
287
|
+
left_part = expression[:equals_pos].strip()
|
|
288
|
+
right_part = expression[equals_pos + 1:].strip()
|
|
289
|
+
|
|
290
|
+
# Xử lý left part: [$userState, $setUserState] -> [userState, setUserState]
|
|
291
|
+
if left_part.startswith('[') and left_part.endswith(']'):
|
|
292
|
+
# Array destructuring
|
|
293
|
+
inner_content = left_part[1:-1].strip()
|
|
294
|
+
# Split by comma và loại bỏ $ từ mỗi biến
|
|
295
|
+
variables = []
|
|
296
|
+
for var in inner_content.split(','):
|
|
297
|
+
var = var.strip().lstrip('$')
|
|
298
|
+
variables.append(var)
|
|
299
|
+
|
|
300
|
+
left_part_js = '[' + ', '.join(variables) + ']'
|
|
301
|
+
elif left_part.startswith('{') and left_part.endswith('}'):
|
|
302
|
+
# Object destructuring
|
|
303
|
+
inner_content = left_part[1:-1].strip()
|
|
304
|
+
# Split by comma và loại bỏ $ từ mỗi biến
|
|
305
|
+
variables = []
|
|
306
|
+
for var in inner_content.split(','):
|
|
307
|
+
var = var.strip().lstrip('$')
|
|
308
|
+
variables.append(var)
|
|
309
|
+
|
|
310
|
+
left_part_js = '{' + ', '.join(variables) + '}'
|
|
311
|
+
else:
|
|
312
|
+
left_part_js = left_part
|
|
313
|
+
|
|
314
|
+
# Convert right part với xử lý mảng
|
|
315
|
+
right_part_js = self._convert_php_expression_with_arrays(right_part)
|
|
316
|
+
|
|
317
|
+
return f'const {left_part_js} = {right_part_js};'
|
|
318
|
+
|
|
319
|
+
def parse_usestate_directives(self, blade_code):
|
|
320
|
+
"""Parse @useState directives - chỉ xử lý Blade directives, không xử lý JavaScript code"""
|
|
321
|
+
# Loại bỏ JavaScript code trong <script> tags trước khi parse
|
|
322
|
+
blade_code_filtered = self._remove_script_tags(blade_code)
|
|
323
|
+
# Loại bỏ @verbatim blocks để tránh parse directives bên trong
|
|
324
|
+
blade_code_filtered = self._remove_verbatim_blocks(blade_code_filtered)
|
|
325
|
+
|
|
326
|
+
usestate_matches = re.findall(r'@useState\s*\(\s*(.*?)\s*\)', blade_code_filtered, re.DOTALL)
|
|
327
|
+
if not usestate_matches:
|
|
328
|
+
return ''
|
|
329
|
+
|
|
330
|
+
usestate_declarations = []
|
|
331
|
+
for match in usestate_matches:
|
|
332
|
+
expression = match.strip()
|
|
333
|
+
# Parse 3 parameters: value, stateName, setStateName
|
|
334
|
+
params = self._parse_usestate_params(expression)
|
|
335
|
+
if len(params) >= 2:
|
|
336
|
+
value = params[0]
|
|
337
|
+
state_name = params[1] if len(params) > 1 else 'state'
|
|
338
|
+
set_state_name = params[2] if len(params) > 2 else 'setState'
|
|
339
|
+
# Convert PHP to JavaScript với xử lý mảng bằng php -r
|
|
340
|
+
value_js = self._convert_php_expression_with_arrays(value)
|
|
341
|
+
state_name_js = php_to_js(state_name)
|
|
342
|
+
set_state_name_js = php_to_js(set_state_name)
|
|
343
|
+
usestate_declarations.append(f'const [{state_name_js}, {set_state_name_js}] = useState({value_js});')
|
|
344
|
+
|
|
345
|
+
return '\n'.join(usestate_declarations)
|
|
346
|
+
|
|
347
|
+
def _parse_usestate_params(self, expression):
|
|
348
|
+
"""Parse @useState parameters with proper array handling"""
|
|
349
|
+
params = []
|
|
350
|
+
current = ''
|
|
351
|
+
paren_count = 0
|
|
352
|
+
bracket_count = 0
|
|
353
|
+
in_quotes = False
|
|
354
|
+
quote_char = ''
|
|
355
|
+
i = 0
|
|
356
|
+
|
|
357
|
+
while i < len(expression):
|
|
358
|
+
char = expression[i]
|
|
359
|
+
|
|
360
|
+
if not in_quotes:
|
|
361
|
+
if char in ['"', "'"]:
|
|
362
|
+
in_quotes = True
|
|
363
|
+
quote_char = char
|
|
364
|
+
current += char
|
|
365
|
+
elif char == '(':
|
|
366
|
+
paren_count += 1
|
|
367
|
+
current += char
|
|
368
|
+
elif char == ')':
|
|
369
|
+
paren_count -= 1
|
|
370
|
+
current += char
|
|
371
|
+
elif char == '[':
|
|
372
|
+
bracket_count += 1
|
|
373
|
+
current += char
|
|
374
|
+
elif char == ']':
|
|
375
|
+
bracket_count -= 1
|
|
376
|
+
current += char
|
|
377
|
+
elif char == ',' and paren_count == 0 and bracket_count == 0:
|
|
378
|
+
params.append(current.strip())
|
|
379
|
+
current = ''
|
|
380
|
+
else:
|
|
381
|
+
current += char
|
|
382
|
+
else:
|
|
383
|
+
current += char
|
|
384
|
+
if char == quote_char:
|
|
385
|
+
# Check if it's escaped
|
|
386
|
+
escape_count = 0
|
|
387
|
+
j = i - 1
|
|
388
|
+
while j >= 0 and expression[j] == '\\':
|
|
389
|
+
escape_count += 1
|
|
390
|
+
j -= 1
|
|
391
|
+
|
|
392
|
+
# If even number of backslashes, quote is not escaped
|
|
393
|
+
if escape_count % 2 == 0:
|
|
394
|
+
in_quotes = False
|
|
395
|
+
|
|
396
|
+
i += 1
|
|
397
|
+
|
|
398
|
+
if current.strip():
|
|
399
|
+
params.append(current.strip())
|
|
400
|
+
|
|
401
|
+
return params
|
|
402
|
+
|
|
403
|
+
def _split_fetch_parameters(self, content):
|
|
404
|
+
"""Split fetch parameters by comma, respecting parentheses, brackets and quotes"""
|
|
405
|
+
params = []
|
|
406
|
+
current = ''
|
|
407
|
+
paren_count = 0
|
|
408
|
+
bracket_count = 0
|
|
409
|
+
in_quotes = False
|
|
410
|
+
quote_char = ''
|
|
411
|
+
|
|
412
|
+
i = 0
|
|
413
|
+
while i < len(content):
|
|
414
|
+
char = content[i]
|
|
415
|
+
|
|
416
|
+
if not in_quotes:
|
|
417
|
+
if char in ['"', "'"]:
|
|
418
|
+
in_quotes = True
|
|
419
|
+
quote_char = char
|
|
420
|
+
current += char
|
|
421
|
+
elif char == '(':
|
|
422
|
+
paren_count += 1
|
|
423
|
+
current += char
|
|
424
|
+
elif char == ')':
|
|
425
|
+
paren_count -= 1
|
|
426
|
+
current += char
|
|
427
|
+
elif char == '[':
|
|
428
|
+
bracket_count += 1
|
|
429
|
+
current += char
|
|
430
|
+
elif char == ']':
|
|
431
|
+
bracket_count -= 1
|
|
432
|
+
current += char
|
|
433
|
+
elif char == ',' and paren_count == 0 and bracket_count == 0:
|
|
434
|
+
# Comma at level 0 - parameter separator
|
|
435
|
+
params.append(current.strip())
|
|
436
|
+
current = ''
|
|
437
|
+
i += 1
|
|
438
|
+
continue
|
|
439
|
+
else:
|
|
440
|
+
current += char
|
|
441
|
+
else:
|
|
442
|
+
current += char
|
|
443
|
+
if char == quote_char and (i == 0 or content[i-1] != '\\'):
|
|
444
|
+
in_quotes = False
|
|
445
|
+
quote_char = ''
|
|
446
|
+
|
|
447
|
+
i += 1
|
|
448
|
+
|
|
449
|
+
# Add last parameter
|
|
450
|
+
if current.strip():
|
|
451
|
+
params.append(current.strip())
|
|
452
|
+
|
|
453
|
+
return params
|
|
454
|
+
|
|
455
|
+
def _parse_php_array_to_js_object(self, php_array_str):
|
|
456
|
+
"""Parse PHP array string to JavaScript object"""
|
|
457
|
+
try:
|
|
458
|
+
# Convert PHP array syntax to JavaScript object syntax
|
|
459
|
+
js_str = php_array_str
|
|
460
|
+
# Remove $ prefix from variables
|
|
461
|
+
js_str = re.sub(r'(?<!")\$(\w+)(?!")', r'\1', js_str)
|
|
462
|
+
# Convert array syntax to object syntax
|
|
463
|
+
js_str = re.sub(r'\[', '{', js_str)
|
|
464
|
+
js_str = re.sub(r'\]', '}', js_str)
|
|
465
|
+
js_str = re.sub(r'\s*=>\s*', ': ', js_str)
|
|
466
|
+
js_str = re.sub(r'\s+\.\s+', ' + ', js_str)
|
|
467
|
+
|
|
468
|
+
# Handle single quotes to double quotes for JSON
|
|
469
|
+
js_str = re.sub(r"'([^']*)'", r'"\1"', js_str)
|
|
470
|
+
|
|
471
|
+
# Parse the JavaScript object
|
|
472
|
+
import json
|
|
473
|
+
return json.loads(js_str)
|
|
474
|
+
except Exception as e:
|
|
475
|
+
print(f"Error parsing {php_array_str}: {e}")
|
|
476
|
+
return {}
|
|
477
|
+
|
|
478
|
+
def parse_fetch(self, blade_code):
|
|
479
|
+
"""Parse @fetch directive - supports @fetch(url, data, headers) and @fetch([config])"""
|
|
480
|
+
fetch_match = re.search(r'@fetch\s*\(', blade_code)
|
|
481
|
+
if not fetch_match:
|
|
482
|
+
return None
|
|
483
|
+
|
|
484
|
+
start_pos = fetch_match.end() - 1
|
|
485
|
+
fetch_content, _ = extract_balanced_parentheses(blade_code, start_pos)
|
|
486
|
+
if fetch_content:
|
|
487
|
+
fetch_content = fetch_content.strip()
|
|
488
|
+
|
|
489
|
+
# Check if it's multiple parameters: @fetch(url, data, headers)
|
|
490
|
+
if ',' in fetch_content and not fetch_content.startswith('['):
|
|
491
|
+
# Split by comma that's not inside parentheses or quotes
|
|
492
|
+
parts = self._split_fetch_parameters(fetch_content)
|
|
493
|
+
|
|
494
|
+
if len(parts) >= 1:
|
|
495
|
+
# Parse URL (first parameter)
|
|
496
|
+
url_part = parts[0].strip()
|
|
497
|
+
if url_part.startswith("'") and url_part.endswith("'"):
|
|
498
|
+
url = f"`{url_part[1:-1]}`"
|
|
499
|
+
elif url_part.startswith('"') and url_part.endswith('"'):
|
|
500
|
+
url = f"`{url_part[1:-1]}`"
|
|
501
|
+
elif '(' in url_part and ')' in url_part:
|
|
502
|
+
# Function call like route('api.posts')
|
|
503
|
+
js_url = php_to_js(url_part)
|
|
504
|
+
url = f"`${{{js_url}}}`"
|
|
505
|
+
else:
|
|
506
|
+
url = f"`${{{url_part}}}`"
|
|
507
|
+
|
|
508
|
+
# Parse data (second parameter, optional)
|
|
509
|
+
data = {}
|
|
510
|
+
if len(parts) >= 2:
|
|
511
|
+
data_part = parts[1].strip()
|
|
512
|
+
if data_part.startswith('[') and data_part.endswith(']'):
|
|
513
|
+
data = self._parse_php_array_to_js_object(data_part)
|
|
514
|
+
|
|
515
|
+
# Parse headers (third parameter, optional)
|
|
516
|
+
headers = {}
|
|
517
|
+
if len(parts) >= 3:
|
|
518
|
+
headers_part = parts[2].strip()
|
|
519
|
+
if headers_part.startswith('[') and headers_part.endswith(']'):
|
|
520
|
+
headers = self._parse_php_array_to_js_object(headers_part)
|
|
521
|
+
|
|
522
|
+
return {
|
|
523
|
+
'url': url,
|
|
524
|
+
'method': 'GET',
|
|
525
|
+
'data': data,
|
|
526
|
+
'headers': headers
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
# Simple URL fetch
|
|
530
|
+
elif fetch_content.startswith("'") and fetch_content.endswith("'"):
|
|
531
|
+
url_content = fetch_content[1:-1]
|
|
532
|
+
return {'url': f"`{url_content}`", 'method': 'GET'}
|
|
533
|
+
elif fetch_content.startswith('"') and fetch_content.endswith('"'):
|
|
534
|
+
url_content = fetch_content[1:-1]
|
|
535
|
+
return {'url': f"`{url_content}`", 'method': 'GET'}
|
|
536
|
+
|
|
537
|
+
# Function call fetch (e.g., route('web.users'))
|
|
538
|
+
elif '(' in fetch_content and ')' in fetch_content and not fetch_content.startswith('['):
|
|
539
|
+
js_url = php_to_js(fetch_content)
|
|
540
|
+
return {'url': f"`${{{js_url}}}`", 'method': 'GET'}
|
|
541
|
+
|
|
542
|
+
# Array configuration fetch
|
|
543
|
+
elif fetch_content.startswith('[') and fetch_content.endswith(']'):
|
|
544
|
+
try:
|
|
545
|
+
# Handle multiple array parameters (e.g., [config], [data], [headers])
|
|
546
|
+
if ',' in fetch_content and fetch_content.count('[') > 1:
|
|
547
|
+
# Split by comma that's not inside brackets
|
|
548
|
+
parts = self._split_fetch_parameters(fetch_content)
|
|
549
|
+
|
|
550
|
+
# Parse first part as main config
|
|
551
|
+
config = {}
|
|
552
|
+
if parts:
|
|
553
|
+
config = self._parse_php_array_to_js_object(parts[0])
|
|
554
|
+
|
|
555
|
+
# Parse additional data parameter (second parameter)
|
|
556
|
+
if len(parts) >= 2:
|
|
557
|
+
additional_data = self._parse_php_array_to_js_object(parts[1])
|
|
558
|
+
if 'data' in config:
|
|
559
|
+
config['data'].update(additional_data)
|
|
560
|
+
else:
|
|
561
|
+
config['data'] = additional_data
|
|
562
|
+
|
|
563
|
+
# Parse additional headers parameter (third parameter)
|
|
564
|
+
if len(parts) >= 3:
|
|
565
|
+
additional_headers = self._parse_php_array_to_js_object(parts[2])
|
|
566
|
+
if 'headers' in config:
|
|
567
|
+
config['headers'].update(additional_headers)
|
|
568
|
+
else:
|
|
569
|
+
config['headers'] = additional_headers
|
|
570
|
+
|
|
571
|
+
# Ensure required fields
|
|
572
|
+
if 'url' not in config:
|
|
573
|
+
config['url'] = ''
|
|
574
|
+
if 'method' not in config:
|
|
575
|
+
config['method'] = 'GET'
|
|
576
|
+
if 'data' not in config:
|
|
577
|
+
config['data'] = {}
|
|
578
|
+
if 'headers' not in config:
|
|
579
|
+
config['headers'] = {}
|
|
580
|
+
|
|
581
|
+
# Convert values to template strings if needed
|
|
582
|
+
if isinstance(config['url'], str) and not config['url'].startswith('`'):
|
|
583
|
+
config['url'] = f"`{config['url']}`"
|
|
584
|
+
|
|
585
|
+
return config
|
|
586
|
+
else:
|
|
587
|
+
# Single array parameter
|
|
588
|
+
config = self._parse_php_array_to_js_object(fetch_content)
|
|
589
|
+
|
|
590
|
+
# Ensure required fields
|
|
591
|
+
if 'url' not in config:
|
|
592
|
+
config['url'] = ''
|
|
593
|
+
if 'method' not in config:
|
|
594
|
+
config['method'] = 'GET'
|
|
595
|
+
if 'data' not in config:
|
|
596
|
+
config['data'] = {}
|
|
597
|
+
if 'headers' not in config:
|
|
598
|
+
config['headers'] = {}
|
|
599
|
+
|
|
600
|
+
# Convert values to template strings if needed
|
|
601
|
+
if isinstance(config['url'], str) and not config['url'].startswith('`'):
|
|
602
|
+
config['url'] = f"`{config['url']}`"
|
|
603
|
+
|
|
604
|
+
return config
|
|
605
|
+
except Exception as e:
|
|
606
|
+
# Fallback to simple URL if parsing fails
|
|
607
|
+
print(f"Fetch parsing error: {e}")
|
|
608
|
+
return {'url': "`" + fetch_content + "`", 'method': 'GET'}
|
|
609
|
+
|
|
610
|
+
return None
|
|
611
|
+
|
|
612
|
+
def parse_init(self, blade_code):
|
|
613
|
+
"""Parse @onInit directives"""
|
|
614
|
+
init_functions = []
|
|
615
|
+
css_content = []
|
|
616
|
+
|
|
617
|
+
# Find all @onInit blocks - support both @onInit(...) and @onInit...@endonInit formats
|
|
618
|
+
# Find @onInit blocks by finding @onInit( and matching the closing )
|
|
619
|
+
init_matches = []
|
|
620
|
+
start_pattern = re.compile(r'@oninit\s*\(', re.IGNORECASE)
|
|
621
|
+
|
|
622
|
+
for match in start_pattern.finditer(blade_code):
|
|
623
|
+
start_pos = match.end()
|
|
624
|
+
# Find the matching closing parenthesis
|
|
625
|
+
paren_count = 1
|
|
626
|
+
pos = start_pos
|
|
627
|
+
|
|
628
|
+
while pos < len(blade_code) and paren_count > 0:
|
|
629
|
+
if blade_code[pos] == '(':
|
|
630
|
+
paren_count += 1
|
|
631
|
+
elif blade_code[pos] == ')':
|
|
632
|
+
paren_count -= 1
|
|
633
|
+
pos += 1
|
|
634
|
+
|
|
635
|
+
if paren_count == 0:
|
|
636
|
+
# Found matching closing parenthesis
|
|
637
|
+
content = blade_code[start_pos:pos-1]
|
|
638
|
+
# Create a mock match object
|
|
639
|
+
class MockMatch:
|
|
640
|
+
def __init__(self, content):
|
|
641
|
+
self._content = content
|
|
642
|
+
def group(self, n):
|
|
643
|
+
if n == 1:
|
|
644
|
+
return self._content
|
|
645
|
+
return None
|
|
646
|
+
|
|
647
|
+
init_matches.append(MockMatch(content))
|
|
648
|
+
|
|
649
|
+
for match in init_matches:
|
|
650
|
+
init_content = match.group(1).strip()
|
|
651
|
+
|
|
652
|
+
# Extract script content from <script> tags
|
|
653
|
+
script_content = []
|
|
654
|
+
in_script = False
|
|
655
|
+
|
|
656
|
+
# Extract CSS content from <style> tags
|
|
657
|
+
style_matches = re.finditer(r'<style[^>]*>(.*?)</style>', init_content, re.DOTALL | re.IGNORECASE)
|
|
658
|
+
for style_match in style_matches:
|
|
659
|
+
css_content.append(style_match.group(1).strip())
|
|
660
|
+
|
|
661
|
+
# Check if content has <script> tags
|
|
662
|
+
has_script_tags = '<script' in init_content.lower()
|
|
663
|
+
|
|
664
|
+
if has_script_tags:
|
|
665
|
+
# Process content with <script> tags
|
|
666
|
+
for line in init_content.split('\n'):
|
|
667
|
+
if '<script' in line.lower():
|
|
668
|
+
in_script = True
|
|
669
|
+
script_start = line.lower().find('<script')
|
|
670
|
+
script_tag_end = line.find('>', script_start)
|
|
671
|
+
if script_tag_end != -1:
|
|
672
|
+
script_content.append(line[script_tag_end + 1:])
|
|
673
|
+
elif '</script>' in line.lower():
|
|
674
|
+
in_script = False
|
|
675
|
+
script_end = line.lower().find('</script>')
|
|
676
|
+
if script_end > 0:
|
|
677
|
+
script_content.append(line[:script_end])
|
|
678
|
+
elif in_script:
|
|
679
|
+
script_content.append(line)
|
|
680
|
+
else:
|
|
681
|
+
# Process content directly without <script> tags
|
|
682
|
+
script_content.append(init_content)
|
|
683
|
+
|
|
684
|
+
if script_content:
|
|
685
|
+
init_code = '\n'.join(script_content).strip()
|
|
686
|
+
if init_code:
|
|
687
|
+
init_functions.append(init_code)
|
|
688
|
+
|
|
689
|
+
return init_functions, css_content
|
|
690
|
+
|
|
691
|
+
def _convert_extends_data(self, data_expr):
|
|
692
|
+
"""Convert extends data expression"""
|
|
693
|
+
extends_data = convert_php_array_to_json(data_expr)
|
|
694
|
+
extends_data = re.sub(r'(?<!")\$(\w+)(?!")', r'\1', extends_data)
|
|
695
|
+
extends_data = re.sub(r'\[', '{', extends_data)
|
|
696
|
+
extends_data = re.sub(r'\]', '}', extends_data)
|
|
697
|
+
extends_data = re.sub(r'\s*=>\s*', ': ', extends_data)
|
|
698
|
+
extends_data = re.sub(r'\s+\.\s+', ' + ', extends_data)
|
|
699
|
+
return extends_data
|
|
700
|
+
|
|
701
|
+
def _split_vars_content(self, content):
|
|
702
|
+
"""Split vars content respecting parentheses and brackets"""
|
|
703
|
+
parts = []
|
|
704
|
+
current = ''
|
|
705
|
+
paren_count = 0
|
|
706
|
+
bracket_count = 0
|
|
707
|
+
in_quotes = False
|
|
708
|
+
quote_char = ''
|
|
709
|
+
i = 0
|
|
710
|
+
|
|
711
|
+
while i < len(content):
|
|
712
|
+
char = content[i]
|
|
713
|
+
|
|
714
|
+
if (char == '"' or char == "'") and not in_quotes:
|
|
715
|
+
in_quotes = True
|
|
716
|
+
quote_char = char
|
|
717
|
+
elif char == quote_char and in_quotes:
|
|
718
|
+
in_quotes = False
|
|
719
|
+
quote_char = ''
|
|
720
|
+
elif not in_quotes:
|
|
721
|
+
if char == '(':
|
|
722
|
+
paren_count += 1
|
|
723
|
+
elif char == ')':
|
|
724
|
+
paren_count -= 1
|
|
725
|
+
elif char == '[':
|
|
726
|
+
bracket_count += 1
|
|
727
|
+
elif char == ']':
|
|
728
|
+
bracket_count -= 1
|
|
729
|
+
elif char == ',' and paren_count == 0 and bracket_count == 0:
|
|
730
|
+
parts.append(current.strip())
|
|
731
|
+
current = ''
|
|
732
|
+
i += 1
|
|
733
|
+
continue
|
|
734
|
+
|
|
735
|
+
current += char
|
|
736
|
+
i += 1
|
|
737
|
+
|
|
738
|
+
if current.strip():
|
|
739
|
+
parts.append(current.strip())
|
|
740
|
+
|
|
741
|
+
return parts
|
|
742
|
+
|
|
743
|
+
def _split_vars_content_improved(self, content):
|
|
744
|
+
"""
|
|
745
|
+
Split vars content with improved logic (same as Event directive)
|
|
746
|
+
Handles complex nested arrays and objects properly
|
|
747
|
+
"""
|
|
748
|
+
parts = []
|
|
749
|
+
current = ''
|
|
750
|
+
paren_count = 0
|
|
751
|
+
bracket_count = 0
|
|
752
|
+
in_quotes = False
|
|
753
|
+
quote_char = ''
|
|
754
|
+
|
|
755
|
+
i = 0
|
|
756
|
+
while i < len(content):
|
|
757
|
+
char = content[i]
|
|
758
|
+
|
|
759
|
+
if not in_quotes:
|
|
760
|
+
if char in ['"', "'"]:
|
|
761
|
+
in_quotes = True
|
|
762
|
+
quote_char = char
|
|
763
|
+
current += char
|
|
764
|
+
elif char == '(':
|
|
765
|
+
paren_count += 1
|
|
766
|
+
current += char
|
|
767
|
+
elif char == ')':
|
|
768
|
+
paren_count -= 1
|
|
769
|
+
current += char
|
|
770
|
+
elif char == '[':
|
|
771
|
+
bracket_count += 1
|
|
772
|
+
current += char
|
|
773
|
+
elif char == ']':
|
|
774
|
+
bracket_count -= 1
|
|
775
|
+
current += char
|
|
776
|
+
elif char == ',' and paren_count == 0 and bracket_count == 0:
|
|
777
|
+
# Comma at level 0 - variable separator
|
|
778
|
+
parts.append(current.strip())
|
|
779
|
+
current = ''
|
|
780
|
+
i += 1
|
|
781
|
+
continue
|
|
782
|
+
else:
|
|
783
|
+
current += char
|
|
784
|
+
else:
|
|
785
|
+
current += char
|
|
786
|
+
if char == quote_char and (i == 0 or content[i-1] != '\\'):
|
|
787
|
+
in_quotes = False
|
|
788
|
+
quote_char = ''
|
|
789
|
+
|
|
790
|
+
i += 1
|
|
791
|
+
|
|
792
|
+
# Add last variable
|
|
793
|
+
if current.strip():
|
|
794
|
+
parts.append(current.strip())
|
|
795
|
+
|
|
796
|
+
return parts
|
|
797
|
+
|
|
798
|
+
def _split_vars_content_fixed(self, content):
|
|
799
|
+
"""
|
|
800
|
+
Split vars content with fixed logic - properly handles nested arrays
|
|
801
|
+
"""
|
|
802
|
+
parts = []
|
|
803
|
+
current = ''
|
|
804
|
+
paren_count = 0
|
|
805
|
+
bracket_count = 0
|
|
806
|
+
in_quotes = False
|
|
807
|
+
quote_char = ''
|
|
808
|
+
|
|
809
|
+
i = 0
|
|
810
|
+
while i < len(content):
|
|
811
|
+
char = content[i]
|
|
812
|
+
|
|
813
|
+
if not in_quotes:
|
|
814
|
+
if char in ['"', "'"]:
|
|
815
|
+
in_quotes = True
|
|
816
|
+
quote_char = char
|
|
817
|
+
current += char
|
|
818
|
+
elif char == '(':
|
|
819
|
+
paren_count += 1
|
|
820
|
+
current += char
|
|
821
|
+
elif char == ')':
|
|
822
|
+
paren_count -= 1
|
|
823
|
+
current += char
|
|
824
|
+
elif char == '[':
|
|
825
|
+
bracket_count += 1
|
|
826
|
+
current += char
|
|
827
|
+
elif char == ']':
|
|
828
|
+
bracket_count -= 1
|
|
829
|
+
current += char
|
|
830
|
+
elif char == ',' and paren_count == 0 and bracket_count == 0:
|
|
831
|
+
# Comma at level 0 - variable separator
|
|
832
|
+
parts.append(current.strip())
|
|
833
|
+
current = ''
|
|
834
|
+
i += 1
|
|
835
|
+
continue
|
|
836
|
+
else:
|
|
837
|
+
current += char
|
|
838
|
+
else:
|
|
839
|
+
current += char
|
|
840
|
+
if char == quote_char and (i == 0 or content[i-1] != '\\'):
|
|
841
|
+
in_quotes = False
|
|
842
|
+
quote_char = ''
|
|
843
|
+
|
|
844
|
+
i += 1
|
|
845
|
+
|
|
846
|
+
# Add last variable
|
|
847
|
+
if current.strip():
|
|
848
|
+
parts.append(current.strip())
|
|
849
|
+
|
|
850
|
+
return parts
|
|
851
|
+
|
|
852
|
+
def _split_vars_content_correct(self, content):
|
|
853
|
+
"""
|
|
854
|
+
Split vars content with correct logic - handles nested arrays properly
|
|
855
|
+
"""
|
|
856
|
+
parts = []
|
|
857
|
+
current = ''
|
|
858
|
+
paren_count = 0
|
|
859
|
+
bracket_count = 0
|
|
860
|
+
brace_count = 0
|
|
861
|
+
in_quotes = False
|
|
862
|
+
quote_char = ''
|
|
863
|
+
|
|
864
|
+
i = 0
|
|
865
|
+
while i < len(content):
|
|
866
|
+
char = content[i]
|
|
867
|
+
|
|
868
|
+
if not in_quotes:
|
|
869
|
+
if char in ['"', "'"]:
|
|
870
|
+
in_quotes = True
|
|
871
|
+
quote_char = char
|
|
872
|
+
current += char
|
|
873
|
+
elif char == '(':
|
|
874
|
+
paren_count += 1
|
|
875
|
+
current += char
|
|
876
|
+
elif char == ')':
|
|
877
|
+
paren_count -= 1
|
|
878
|
+
current += char
|
|
879
|
+
elif char == '[':
|
|
880
|
+
bracket_count += 1
|
|
881
|
+
current += char
|
|
882
|
+
elif char == ']':
|
|
883
|
+
bracket_count -= 1
|
|
884
|
+
current += char
|
|
885
|
+
elif char == '{':
|
|
886
|
+
brace_count += 1
|
|
887
|
+
current += char
|
|
888
|
+
elif char == '}':
|
|
889
|
+
brace_count -= 1
|
|
890
|
+
current += char
|
|
891
|
+
elif char == ',' and paren_count == 0 and bracket_count == 0 and brace_count == 0:
|
|
892
|
+
# Comma at level 0 - variable separator
|
|
893
|
+
parts.append(current.strip())
|
|
894
|
+
current = ''
|
|
895
|
+
i += 1
|
|
896
|
+
continue
|
|
897
|
+
else:
|
|
898
|
+
current += char
|
|
899
|
+
else:
|
|
900
|
+
current += char
|
|
901
|
+
if char == quote_char and (i == 0 or content[i-1] != '\\'):
|
|
902
|
+
in_quotes = False
|
|
903
|
+
quote_char = ''
|
|
904
|
+
|
|
905
|
+
i += 1
|
|
906
|
+
|
|
907
|
+
# Add last variable
|
|
908
|
+
if current.strip():
|
|
909
|
+
parts.append(current.strip())
|
|
910
|
+
|
|
911
|
+
return parts
|
|
912
|
+
|
|
913
|
+
def _convert_php_to_js(self, php_value):
|
|
914
|
+
"""Convert PHP array syntax to JavaScript object/array syntax using php -r"""
|
|
915
|
+
php_value = php_value.strip()
|
|
916
|
+
|
|
917
|
+
# Handle string literals
|
|
918
|
+
if (php_value.startswith("'") and php_value.endswith("'")) or \
|
|
919
|
+
(php_value.startswith('"') and php_value.endswith('"')):
|
|
920
|
+
return php_value
|
|
921
|
+
|
|
922
|
+
# Handle numbers
|
|
923
|
+
if php_value.isdigit() or (php_value.startswith('-') and php_value[1:].isdigit()):
|
|
924
|
+
return php_value
|
|
925
|
+
|
|
926
|
+
# Handle booleans
|
|
927
|
+
if php_value.lower() in ['true', 'false']:
|
|
928
|
+
return php_value.lower()
|
|
929
|
+
|
|
930
|
+
# Handle null
|
|
931
|
+
if php_value.lower() == 'null':
|
|
932
|
+
return 'null'
|
|
933
|
+
|
|
934
|
+
# Handle arrays - sử dụng php -r để convert
|
|
935
|
+
if php_value.startswith('[') and php_value.endswith(']'):
|
|
936
|
+
# Sử dụng php -r để convert array
|
|
937
|
+
json_result = convert_php_array_with_php_r(php_value)
|
|
938
|
+
if json_result is not None:
|
|
939
|
+
return json_result
|
|
940
|
+
|
|
941
|
+
# Fallback to old method if php -r fails
|
|
942
|
+
return self._convert_php_array_legacy(php_value)
|
|
943
|
+
|
|
944
|
+
# Default: return as is
|
|
945
|
+
return php_value
|
|
946
|
+
|
|
947
|
+
def _convert_php_array_legacy(self, php_value):
|
|
948
|
+
"""Legacy method for converting PHP arrays (fallback)"""
|
|
949
|
+
# Remove outer brackets
|
|
950
|
+
inner_content = php_value[1:-1].strip()
|
|
951
|
+
if not inner_content:
|
|
952
|
+
return '[]' # Empty array, not object
|
|
953
|
+
|
|
954
|
+
# Check if this is an associative array (has =>) or indexed array
|
|
955
|
+
has_arrow_operator = '=>' in inner_content
|
|
956
|
+
|
|
957
|
+
if has_arrow_operator:
|
|
958
|
+
# Associative array - convert to JavaScript object
|
|
959
|
+
js_content = inner_content
|
|
960
|
+
# Replace => with :
|
|
961
|
+
js_content = re.sub(r'\s*=>\s*', ': ', js_content)
|
|
962
|
+
# Replace single quotes with double quotes for keys
|
|
963
|
+
js_content = re.sub(r"'([^']+)'\s*:", r'"\1":', js_content)
|
|
964
|
+
# Replace single quotes with double quotes for string values
|
|
965
|
+
js_content = re.sub(r":\s*'([^']+)'", r': "\1"', js_content)
|
|
966
|
+
|
|
967
|
+
return '{' + js_content + '}'
|
|
968
|
+
else:
|
|
969
|
+
# Indexed array - keep as JavaScript array
|
|
970
|
+
js_content = inner_content
|
|
971
|
+
# Replace single quotes with double quotes for string values
|
|
972
|
+
js_content = re.sub(r"'([^']+)'", r'"\1"', js_content)
|
|
973
|
+
|
|
974
|
+
return '[' + js_content + ']'
|
|
975
|
+
|
|
976
|
+
def _find_first_equals(self, var):
|
|
977
|
+
"""Find first = that's not inside quotes or brackets"""
|
|
978
|
+
paren_count = 0
|
|
979
|
+
bracket_count = 0
|
|
980
|
+
in_quotes = False
|
|
981
|
+
quote_char = ''
|
|
982
|
+
|
|
983
|
+
for i, char in enumerate(var):
|
|
984
|
+
if (char == '"' or char == "'") and not in_quotes:
|
|
985
|
+
in_quotes = True
|
|
986
|
+
quote_char = char
|
|
987
|
+
elif char == quote_char and in_quotes:
|
|
988
|
+
in_quotes = False
|
|
989
|
+
quote_char = ''
|
|
990
|
+
elif not in_quotes:
|
|
991
|
+
if char == '(':
|
|
992
|
+
paren_count += 1
|
|
993
|
+
elif char == ')':
|
|
994
|
+
paren_count -= 1
|
|
995
|
+
elif char == '[':
|
|
996
|
+
bracket_count += 1
|
|
997
|
+
elif char == ']':
|
|
998
|
+
bracket_count -= 1
|
|
999
|
+
elif char == '=' and paren_count == 0 and bracket_count == 0:
|
|
1000
|
+
return i
|
|
1001
|
+
|
|
1002
|
+
return -1
|
|
1003
|
+
|
|
1004
|
+
def parse_register(self, blade_code):
|
|
1005
|
+
"""Parse @register directive hoặc aliases (@setup, @script)
|
|
1006
|
+
|
|
1007
|
+
NOTE: This method should NOT find @register inside @verbatim blocks
|
|
1008
|
+
because @verbatim blocks are already replaced with placeholders in main_compiler.py
|
|
1009
|
+
before this method is called. This ensures @verbatim has absolute priority.
|
|
1010
|
+
"""
|
|
1011
|
+
# Skip verbatim placeholders to ensure we don't process anything inside them
|
|
1012
|
+
# (Even though they should already be replaced, this is a safety check)
|
|
1013
|
+
blade_code_filtered = re.sub(r'__VERBATIM_BLOCK_\d+__', '', blade_code)
|
|
1014
|
+
|
|
1015
|
+
# Tìm @register trước (có thể có hoặc không có parameters)
|
|
1016
|
+
register_match = re.search(r'@register(?:\s*\([^)]*\))?(.*?)@endregister', blade_code_filtered, re.DOTALL | re.IGNORECASE)
|
|
1017
|
+
if register_match:
|
|
1018
|
+
return register_match.group(1).strip()
|
|
1019
|
+
|
|
1020
|
+
# Tìm @setup (alias của @register, có thể có hoặc không có parameters)
|
|
1021
|
+
setup_match = re.search(r'@setup(?:\s*\([^)]*\))?(.*?)@endsetup', blade_code_filtered, re.DOTALL | re.IGNORECASE)
|
|
1022
|
+
if setup_match:
|
|
1023
|
+
return setup_match.group(1).strip()
|
|
1024
|
+
|
|
1025
|
+
# Tìm @script (xử lý như @register)
|
|
1026
|
+
script_match = re.search(r'@script\s*(?:\([^)]*\))?(.*?)@endscript', blade_code_filtered, re.DOTALL | re.IGNORECASE)
|
|
1027
|
+
if script_match:
|
|
1028
|
+
return script_match.group(1).strip()
|
|
1029
|
+
|
|
1030
|
+
return None
|
|
1031
|
+
|
|
1032
|
+
def parse_view_type(self, blade_code):
|
|
1033
|
+
"""Parse @viewType/@viewtype directive"""
|
|
1034
|
+
# Match both @viewType and @viewtype (case insensitive)
|
|
1035
|
+
viewtype_match = re.search(r'@viewtype\s*\(', blade_code, re.IGNORECASE)
|
|
1036
|
+
if not viewtype_match:
|
|
1037
|
+
return None
|
|
1038
|
+
|
|
1039
|
+
start_pos = viewtype_match.end() - 1
|
|
1040
|
+
viewtype_content, _ = extract_balanced_parentheses(blade_code, start_pos)
|
|
1041
|
+
if not viewtype_content:
|
|
1042
|
+
return None
|
|
1043
|
+
|
|
1044
|
+
viewtype_content = viewtype_content.strip()
|
|
1045
|
+
|
|
1046
|
+
# Extract the parameter value
|
|
1047
|
+
param_value = self._extract_viewtype_parameter(viewtype_content)
|
|
1048
|
+
if not param_value:
|
|
1049
|
+
return None
|
|
1050
|
+
|
|
1051
|
+
# Normalize and determine view type
|
|
1052
|
+
view_type = self._normalize_view_type(param_value)
|
|
1053
|
+
|
|
1054
|
+
return {
|
|
1055
|
+
'viewType': view_type,
|
|
1056
|
+
'originalValue': param_value
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
def _extract_viewtype_parameter(self, content):
|
|
1060
|
+
"""Extract parameter value from @viewType directive"""
|
|
1061
|
+
content = content.strip()
|
|
1062
|
+
|
|
1063
|
+
# Handle string literals (single or double quotes)
|
|
1064
|
+
if (content.startswith("'") and content.endswith("'")) or (content.startswith('"') and content.endswith('"')):
|
|
1065
|
+
return content[1:-1] # Remove quotes
|
|
1066
|
+
|
|
1067
|
+
# Handle variables or expressions - convert to string
|
|
1068
|
+
if content.startswith('$'):
|
|
1069
|
+
# PHP variable - convert to JS
|
|
1070
|
+
return php_to_js(content)
|
|
1071
|
+
|
|
1072
|
+
# Handle function calls
|
|
1073
|
+
if '(' in content and ')' in content:
|
|
1074
|
+
return php_to_js(content)
|
|
1075
|
+
|
|
1076
|
+
# Handle arrays or other complex expressions
|
|
1077
|
+
if content.startswith('[') or content.startswith('{'):
|
|
1078
|
+
try:
|
|
1079
|
+
# Try to parse as JSON-like structure
|
|
1080
|
+
parsed = json.loads(content)
|
|
1081
|
+
if isinstance(parsed, (list, dict)):
|
|
1082
|
+
return str(parsed)
|
|
1083
|
+
except:
|
|
1084
|
+
pass
|
|
1085
|
+
|
|
1086
|
+
# Handle numbers, booleans, etc.
|
|
1087
|
+
if content.lower() in ['true', 'false', 'null']:
|
|
1088
|
+
return content.lower()
|
|
1089
|
+
|
|
1090
|
+
# Try to parse as number
|
|
1091
|
+
try:
|
|
1092
|
+
float(content)
|
|
1093
|
+
return content
|
|
1094
|
+
except:
|
|
1095
|
+
pass
|
|
1096
|
+
|
|
1097
|
+
# Default: treat as string
|
|
1098
|
+
return content
|
|
1099
|
+
|
|
1100
|
+
def _normalize_view_type(self, value):
|
|
1101
|
+
"""Normalize view type value to standard types"""
|
|
1102
|
+
if not value:
|
|
1103
|
+
return 'view'
|
|
1104
|
+
|
|
1105
|
+
# Convert to lowercase string for comparison
|
|
1106
|
+
value_str = str(value).lower().strip()
|
|
1107
|
+
|
|
1108
|
+
# HTML Document types
|
|
1109
|
+
html_doc_types = [
|
|
1110
|
+
'html', 'document', 'html-document', 'htmldocument',
|
|
1111
|
+
'fullpage', 'finalhtml', 'webpage'
|
|
1112
|
+
]
|
|
1113
|
+
if value_str in html_doc_types:
|
|
1114
|
+
return 'html-document'
|
|
1115
|
+
|
|
1116
|
+
# Layout types
|
|
1117
|
+
layout_types = [
|
|
1118
|
+
'layout', 'view-layout', 'view/layout'
|
|
1119
|
+
]
|
|
1120
|
+
if value_str in layout_types:
|
|
1121
|
+
return 'layout'
|
|
1122
|
+
|
|
1123
|
+
# View types
|
|
1124
|
+
view_types = [
|
|
1125
|
+
'view', 'page', 'viewpage', 'view-page', 'view/page'
|
|
1126
|
+
]
|
|
1127
|
+
if value_str in view_types:
|
|
1128
|
+
return 'view'
|
|
1129
|
+
|
|
1130
|
+
# Template types
|
|
1131
|
+
template_types = [
|
|
1132
|
+
'template', 'view-template', 'temp', 'tpl', 'viewtpl', 'view/template'
|
|
1133
|
+
]
|
|
1134
|
+
if value_str in template_types:
|
|
1135
|
+
return 'template'
|
|
1136
|
+
|
|
1137
|
+
# Component types
|
|
1138
|
+
component_types = [
|
|
1139
|
+
'component', 'compunent' # Handle typo
|
|
1140
|
+
]
|
|
1141
|
+
if value_str in component_types:
|
|
1142
|
+
return 'component'
|
|
1143
|
+
|
|
1144
|
+
# Default fallback
|
|
1145
|
+
return 'view'
|
|
1146
|
+
|
|
1147
|
+
def _validate_block_directives(self, blade_code):
|
|
1148
|
+
"""Validate that @block directives have matching @endblock/@endBlock directives"""
|
|
1149
|
+
# Remove @verbatim blocks to avoid checking directives inside them
|
|
1150
|
+
blade_code_filtered = self._remove_verbatim_blocks(blade_code)
|
|
1151
|
+
|
|
1152
|
+
# Check for nested blocks and validate order
|
|
1153
|
+
# We'll use a simple stack-based approach to check nesting
|
|
1154
|
+
lines = blade_code_filtered.split('\n')
|
|
1155
|
+
stack = []
|
|
1156
|
+
|
|
1157
|
+
for line_num, line in enumerate(lines, 1):
|
|
1158
|
+
# Check for @block
|
|
1159
|
+
block_match = re.search(r'@block\s*\(', line, re.IGNORECASE)
|
|
1160
|
+
if block_match:
|
|
1161
|
+
# Extract block name if possible
|
|
1162
|
+
block_name_match = re.search(r'@block\s*\(\s*[\'"]([^\'"]*)[\'"]', line, re.IGNORECASE)
|
|
1163
|
+
block_name = block_name_match.group(1) if block_name_match else f"block_{len(stack)}"
|
|
1164
|
+
stack.append(('block', block_name, line_num))
|
|
1165
|
+
|
|
1166
|
+
# Check for @endblock/@endBlock
|
|
1167
|
+
endblock_match = re.search(r'@endblock\b|@endBlock\b', line, re.IGNORECASE)
|
|
1168
|
+
if endblock_match:
|
|
1169
|
+
if not stack:
|
|
1170
|
+
error_msg = f"Lỗi tại dòng {line_num}: Tìm thấy @endblock/@endBlock nhưng không có @block tương ứng"
|
|
1171
|
+
raise ValueError(error_msg)
|
|
1172
|
+
stack.pop()
|
|
1173
|
+
|
|
1174
|
+
# Check if there are unclosed blocks
|
|
1175
|
+
if stack:
|
|
1176
|
+
unclosed_blocks = [f"'{name}' (dòng {line_num})" for _, name, line_num in stack]
|
|
1177
|
+
error_msg = f"Lỗi: Có {len(stack)} block chưa được đóng: {', '.join(unclosed_blocks)}"
|
|
1178
|
+
raise ValueError(error_msg)
|
|
1179
|
+
|
|
1180
|
+
return True
|
|
1181
|
+
|
|
1182
|
+
def parse_block_directives(self, blade_code):
|
|
1183
|
+
"""Parse @block directive - now handled as section in template processor"""
|
|
1184
|
+
# Validate block directives before processing
|
|
1185
|
+
self._validate_block_directives(blade_code)
|
|
1186
|
+
# @block is now handled in template_processor.py as a section
|
|
1187
|
+
return blade_code
|
|
1188
|
+
|
|
1189
|
+
def parse_endblock_directives(self, blade_code):
|
|
1190
|
+
"""Parse @endblock/@endBlock directive - now handled as section in template processor"""
|
|
1191
|
+
# Validation is done in parse_block_directives
|
|
1192
|
+
# @endblock is now handled in template_processor.py as a section
|
|
1193
|
+
return blade_code
|
|
1194
|
+
|
|
1195
|
+
def parse_useblock_directives(self, blade_code):
|
|
1196
|
+
"""Parse @useBlock/@useblock directive with optional defaultValue"""
|
|
1197
|
+
# Pattern to match @useBlock/@useblock with optional defaultValue
|
|
1198
|
+
pattern = r'@(useBlock|useblock)\s*\(\s*([^,)]+)(?:\s*,\s*([^)]+))?\s*\)'
|
|
1199
|
+
|
|
1200
|
+
def replace_useblock(match):
|
|
1201
|
+
name_expr = match.group(2).strip()
|
|
1202
|
+
default_expr = match.group(3).strip() if match.group(3) else None
|
|
1203
|
+
|
|
1204
|
+
# Convert name to JavaScript
|
|
1205
|
+
js_name = self._convert_php_to_js(name_expr)
|
|
1206
|
+
# Remove $ prefix from variables
|
|
1207
|
+
js_name = re.sub(r'\$(\w+)', r'\1', js_name)
|
|
1208
|
+
|
|
1209
|
+
# Convert defaultValue to JavaScript if provided
|
|
1210
|
+
if default_expr:
|
|
1211
|
+
js_default = self._convert_php_to_js(default_expr)
|
|
1212
|
+
# Remove $ prefix from variables
|
|
1213
|
+
js_default = re.sub(r'\$(\w+)', r'\1', js_default)
|
|
1214
|
+
return f"${{this.useBlock({js_name}, {js_default})}}"
|
|
1215
|
+
else:
|
|
1216
|
+
return f"${{this.useBlock({js_name})}}"
|
|
1217
|
+
|
|
1218
|
+
return re.sub(pattern, replace_useblock, blade_code)
|
|
1219
|
+
|
|
1220
|
+
def parse_onblock_directives(self, blade_code):
|
|
1221
|
+
"""Parse @onBlock/@onblock/@onBlockChange directives with subscribeBlock"""
|
|
1222
|
+
# Pattern to match @onBlock/@onblock/@onBlockChange with parameters
|
|
1223
|
+
pattern = r'@(onBlock|onblock|onBlockChange)\s*\(\s*([^)]+)\s*\)'
|
|
1224
|
+
|
|
1225
|
+
def replace_onblock(match):
|
|
1226
|
+
directive = match.group(1)
|
|
1227
|
+
params_expr = match.group(2).strip()
|
|
1228
|
+
|
|
1229
|
+
# Parse parameters - could be string, variable, or array
|
|
1230
|
+
if params_expr.startswith('[') and params_expr.endswith(']'):
|
|
1231
|
+
# Array syntax: ['#children' => 'document.body', 'title' => 'block-title']
|
|
1232
|
+
# Convert PHP array to JavaScript object
|
|
1233
|
+
from php_converter import convert_php_array_to_json
|
|
1234
|
+
js_params = convert_php_array_to_json(params_expr)
|
|
1235
|
+
# Format with proper spacing
|
|
1236
|
+
js_params = re.sub(r'":', r'": ', js_params)
|
|
1237
|
+
js_params = re.sub(r',"', r', "', js_params)
|
|
1238
|
+
return f"${{this.subscribeBlock({js_params})}}"
|
|
1239
|
+
else:
|
|
1240
|
+
# Single parameter: 'title' or $blockName
|
|
1241
|
+
js_param = self._convert_php_to_js(params_expr)
|
|
1242
|
+
# Remove $ prefix from variables
|
|
1243
|
+
js_param = re.sub(r'\$(\w+)', r'\1', js_param)
|
|
1244
|
+
return f"${{this.subscribeBlock({js_param})}}"
|
|
1245
|
+
|
|
1246
|
+
return re.sub(pattern, replace_onblock, blade_code)
|
|
1247
|
+
|
|
1248
|
+
def _parse_block_expression(self, expression):
|
|
1249
|
+
"""Parse block expression to extract name and attributes"""
|
|
1250
|
+
# Find first comma outside quotes and brackets
|
|
1251
|
+
comma_pos = self._find_outer_comma(expression)
|
|
1252
|
+
|
|
1253
|
+
if comma_pos == -1:
|
|
1254
|
+
# Only block name, no attributes
|
|
1255
|
+
name = expression.strip()
|
|
1256
|
+
return {'name': self._convert_php_to_js(name), 'attributes': None}
|
|
1257
|
+
|
|
1258
|
+
# Split name and attributes
|
|
1259
|
+
name_part = expression[:comma_pos].strip()
|
|
1260
|
+
attributes_part = expression[comma_pos + 1:].strip()
|
|
1261
|
+
|
|
1262
|
+
name = self._convert_php_to_js(name_part)
|
|
1263
|
+
attributes = self._convert_block_attributes(attributes_part)
|
|
1264
|
+
|
|
1265
|
+
return {'name': name, 'attributes': attributes}
|
|
1266
|
+
|
|
1267
|
+
def _convert_block_attributes(self, attributes_expr):
|
|
1268
|
+
"""Convert block attributes to JavaScript"""
|
|
1269
|
+
attributes_expr = attributes_expr.strip()
|
|
1270
|
+
|
|
1271
|
+
# Check if it's array syntax
|
|
1272
|
+
if attributes_expr.startswith('[') and attributes_expr.endswith(']'):
|
|
1273
|
+
# Convert PHP array to JavaScript object
|
|
1274
|
+
return convert_php_array_to_json(attributes_expr)
|
|
1275
|
+
else:
|
|
1276
|
+
# Convert PHP expression to JavaScript
|
|
1277
|
+
return self._convert_php_to_js(attributes_expr)
|
|
1278
|
+
|
|
1279
|
+
def _find_outer_comma(self, expression):
|
|
1280
|
+
"""Find first comma outside quotes and brackets"""
|
|
1281
|
+
in_quotes = False
|
|
1282
|
+
quote_char = None
|
|
1283
|
+
bracket_count = 0
|
|
1284
|
+
paren_count = 0
|
|
1285
|
+
|
|
1286
|
+
for i, char in enumerate(expression):
|
|
1287
|
+
if not in_quotes:
|
|
1288
|
+
if char in ['"', "'"]:
|
|
1289
|
+
in_quotes = True
|
|
1290
|
+
quote_char = char
|
|
1291
|
+
elif char == '[':
|
|
1292
|
+
bracket_count += 1
|
|
1293
|
+
elif char == ']':
|
|
1294
|
+
bracket_count -= 1
|
|
1295
|
+
elif char == '(':
|
|
1296
|
+
paren_count += 1
|
|
1297
|
+
elif char == ')':
|
|
1298
|
+
paren_count -= 1
|
|
1299
|
+
elif char == ',' and bracket_count == 0 and paren_count == 0:
|
|
1300
|
+
return i
|
|
1301
|
+
else:
|
|
1302
|
+
if char == quote_char and (i == 0 or expression[i-1] != '\\'):
|
|
1303
|
+
in_quotes = False
|
|
1304
|
+
quote_char = None
|
|
1305
|
+
|
|
1306
|
+
return -1
|
|
1307
|
+
|
|
1308
|
+
def _is_simple_string_literal(self, view_expr):
|
|
1309
|
+
"""Check if view_expr is a simple string literal without variables or function calls"""
|
|
1310
|
+
view_expr = view_expr.strip()
|
|
1311
|
+
|
|
1312
|
+
# Must start and end with quotes
|
|
1313
|
+
if not ((view_expr.startswith('"') and view_expr.endswith('"')) or
|
|
1314
|
+
(view_expr.startswith("'") and view_expr.endswith("'"))):
|
|
1315
|
+
return False
|
|
1316
|
+
|
|
1317
|
+
# Check for variables ($) or function calls (parentheses)
|
|
1318
|
+
if '$' in view_expr or '(' in view_expr:
|
|
1319
|
+
return False
|
|
1320
|
+
|
|
1321
|
+
return True
|
|
1322
|
+
|
|
1323
|
+
def _convert_extends_expression(self, view_expr):
|
|
1324
|
+
"""Convert @extends expression to JavaScript"""
|
|
1325
|
+
view_expr = view_expr.strip()
|
|
1326
|
+
|
|
1327
|
+
# Handle string with variables: "theme.layout" -> `theme+'.layout'`
|
|
1328
|
+
if view_expr.startswith('"') and view_expr.endswith('"'):
|
|
1329
|
+
inner_content = view_expr[1:-1]
|
|
1330
|
+
# Convert PHP string concatenation to JavaScript
|
|
1331
|
+
js_expr = self._convert_php_string_concat(inner_content)
|
|
1332
|
+
return f"`{js_expr}`"
|
|
1333
|
+
|
|
1334
|
+
# Handle single quotes: 'theme.layout' -> `theme+'.layout'`
|
|
1335
|
+
elif view_expr.startswith("'") and view_expr.endswith("'"):
|
|
1336
|
+
inner_content = view_expr[1:-1]
|
|
1337
|
+
js_expr = self._convert_php_string_concat(inner_content)
|
|
1338
|
+
return f"`{js_expr}`"
|
|
1339
|
+
|
|
1340
|
+
# Handle function calls first: test('.def')
|
|
1341
|
+
elif '(' in view_expr and ')' in view_expr:
|
|
1342
|
+
# Function call - use direct conversion
|
|
1343
|
+
return php_to_js(view_expr)
|
|
1344
|
+
|
|
1345
|
+
# Handle variables and concatenation: $theme.'.layout', $abc.'.def'.$Abxy
|
|
1346
|
+
elif '.' in view_expr and ('$' in view_expr or '"' in view_expr or "'" in view_expr):
|
|
1347
|
+
# Has concatenation - convert to template literal with View.execute
|
|
1348
|
+
js_expr = self._convert_php_string_concat(view_expr)
|
|
1349
|
+
return f"View.execute(() => `{js_expr}`)"
|
|
1350
|
+
|
|
1351
|
+
# Handle simple variables: $theme
|
|
1352
|
+
else:
|
|
1353
|
+
# Check if it contains undefined variables (starts with $ and not in declared vars)
|
|
1354
|
+
if view_expr.startswith('$') and not self._is_variable_declared(view_expr):
|
|
1355
|
+
return f"View.execute(() => {php_to_js(view_expr)})"
|
|
1356
|
+
else:
|
|
1357
|
+
return php_to_js(view_expr)
|
|
1358
|
+
|
|
1359
|
+
def _convert_php_string_concat(self, content):
|
|
1360
|
+
"""Convert PHP string concatenation to JavaScript template literal"""
|
|
1361
|
+
# Use regex to find all parts separated by . outside quotes
|
|
1362
|
+
import re
|
|
1363
|
+
|
|
1364
|
+
# Pattern to match: variable, string literal, or function call
|
|
1365
|
+
pattern = r'(\$[a-zA-Z_][a-zA-Z0-9_]*|"[^"]*"|\'[^\']*\'|[a-zA-Z_][a-zA-Z0-9_]*\([^)]*\))'
|
|
1366
|
+
parts = re.findall(pattern, content)
|
|
1367
|
+
|
|
1368
|
+
if not parts:
|
|
1369
|
+
# Fallback: try to split by . and process each part
|
|
1370
|
+
parts = content.split('.')
|
|
1371
|
+
|
|
1372
|
+
# Convert parts to JavaScript
|
|
1373
|
+
js_parts = []
|
|
1374
|
+
for part in parts:
|
|
1375
|
+
part = part.strip()
|
|
1376
|
+
if part.startswith('"') and part.endswith('"'):
|
|
1377
|
+
# String literal
|
|
1378
|
+
js_parts.append(f'"{part[1:-1]}"')
|
|
1379
|
+
elif part.startswith("'") and part.endswith("'"):
|
|
1380
|
+
# String literal
|
|
1381
|
+
js_parts.append(f"'{part[1:-1]}'")
|
|
1382
|
+
elif part.startswith('$'):
|
|
1383
|
+
# Variable
|
|
1384
|
+
var_name = part[1:]
|
|
1385
|
+
js_parts.append(f"{var_name}")
|
|
1386
|
+
elif '(' in part and ')' in part:
|
|
1387
|
+
# Function call
|
|
1388
|
+
js_parts.append(f"{php_to_js(part)}")
|
|
1389
|
+
else:
|
|
1390
|
+
# Other expression
|
|
1391
|
+
js_parts.append(f"{php_to_js(part)}")
|
|
1392
|
+
|
|
1393
|
+
# Wrap the entire expression in template literal
|
|
1394
|
+
return f"${{{'+'.join(js_parts)}}}"
|
|
1395
|
+
|
|
1396
|
+
def _is_variable_declared(self, view_expr):
|
|
1397
|
+
"""Check if a variable is declared in the template"""
|
|
1398
|
+
# Extract variable name from $variable
|
|
1399
|
+
if view_expr.startswith('$'):
|
|
1400
|
+
var_name = view_expr[1:].split('.')[0] # Get first part before any dots
|
|
1401
|
+
|
|
1402
|
+
# Check if variable is declared in @vars, @let, @const, or @useState
|
|
1403
|
+
# This is a simple check - in a real implementation, you'd need to track
|
|
1404
|
+
# all declared variables from the template
|
|
1405
|
+
declared_vars = self._get_declared_variables()
|
|
1406
|
+
return var_name in declared_vars
|
|
1407
|
+
|
|
1408
|
+
return True # Non-variable expressions are considered "declared"
|
|
1409
|
+
|
|
1410
|
+
def _get_declared_variables(self):
|
|
1411
|
+
"""Get list of declared variables from the template"""
|
|
1412
|
+
# This is a simplified version - in practice, you'd need to track
|
|
1413
|
+
# variables declared in @vars, @let, @const, @useState directives
|
|
1414
|
+
# For now, return common variables that are typically declared
|
|
1415
|
+
return {
|
|
1416
|
+
'user', 'userState', 'setUserState', 'isEditModalOpen', 'setIsEditModalOpen',
|
|
1417
|
+
'posts', 'setPosts', 'loading', 'setLoading', 'data', 'config'
|
|
1418
|
+
}
|