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,603 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PHP to JavaScript Converter - Advanced version for complex data structures
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import List, Dict, Any, Tuple
|
|
7
|
+
|
|
8
|
+
class PHPToJSConverter:
|
|
9
|
+
"""Advanced PHP to JavaScript converter for complex data structures"""
|
|
10
|
+
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.js_function_prefix = "App.View"
|
|
13
|
+
|
|
14
|
+
def convert_php_expression_to_js(self, expr: str) -> str:
|
|
15
|
+
"""Convert PHP expression to JavaScript"""
|
|
16
|
+
if not expr or not expr.strip():
|
|
17
|
+
return "''"
|
|
18
|
+
|
|
19
|
+
expr = expr.strip()
|
|
20
|
+
|
|
21
|
+
# Protect ++ and -- operators
|
|
22
|
+
expr = expr.replace('++', '__INC_OPERATOR__')
|
|
23
|
+
expr = expr.replace('--', '__DEC_OPERATOR__')
|
|
24
|
+
|
|
25
|
+
# Step 1: Handle string concatenation (. to +) FIRST - but only for actual concatenation
|
|
26
|
+
# Skip function calls like route('api.users') and object access like now()->format()
|
|
27
|
+
# Also skip nested function calls like json_encode(event('view.rendered'))
|
|
28
|
+
if '.' in expr and ('$' in expr or '"' in expr or "'" in expr) and not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*\(', expr.strip()):
|
|
29
|
+
# Skip comparison operators like ===, ==, !=, !==, <, >, <=, >=
|
|
30
|
+
if re.search(r'[=!<>]=?', expr):
|
|
31
|
+
pass # Skip string concatenation for comparison operators
|
|
32
|
+
# Skip ternary operators like condition ? value1 : value2
|
|
33
|
+
elif re.search(r'\?.*:', expr):
|
|
34
|
+
pass # Skip string concatenation for ternary operators
|
|
35
|
+
# Check if this is object access pattern (var->method) - skip if so
|
|
36
|
+
# Pattern: anything->anything (not just $var->method)
|
|
37
|
+
elif not re.search(r'[a-zA-Z_][a-zA-Z0-9_]*->[a-zA-Z_][a-zA-Z0-9_]*', expr):
|
|
38
|
+
expr = self._handle_string_concatenation(expr)
|
|
39
|
+
# Restore ++ and -- operators
|
|
40
|
+
expr = expr.replace('__INC_OPERATOR__', '++')
|
|
41
|
+
expr = expr.replace('__DEC_OPERATOR__', '--')
|
|
42
|
+
return expr
|
|
43
|
+
|
|
44
|
+
# Step 2: Convert object property access (-> to .) AFTER string concatenation
|
|
45
|
+
expr = re.sub(r'->', '.', expr)
|
|
46
|
+
|
|
47
|
+
# Step 3: Handle PHP string concatenation with + operator
|
|
48
|
+
# Convert patterns like (+'string'+) to ('string')
|
|
49
|
+
expr = re.sub(r'\(\+([\'"][^\'\"]*[\'\"])\+\)', r'(\1)', expr)
|
|
50
|
+
|
|
51
|
+
# Fix patterns like config(+'app.debug'+) to config('app.debug')
|
|
52
|
+
expr = re.sub(r'(\w+)\(\+([\'"][^\'\"]*[\'\"])\+\)', r'\1(\2)', expr)
|
|
53
|
+
|
|
54
|
+
# Step 4: Handle patterns like +??+'string' FIRST
|
|
55
|
+
expr = re.sub(r'\+\?\?\+([\'"][^\'\"]*[\'\"])', r'??+\1', expr)
|
|
56
|
+
|
|
57
|
+
# Step 5: Handle PHP null coalescing operator ??
|
|
58
|
+
# Fix patterns like +??+ to ?? (remove extra +)
|
|
59
|
+
expr = re.sub(r'\+\?\?\+', '??', expr)
|
|
60
|
+
expr = re.sub(r'\?\?\+\+', '??', expr)
|
|
61
|
+
|
|
62
|
+
# Step 6: Handle patterns like +'string'+ (without parentheses) - but only for actual concatenation
|
|
63
|
+
# Don't convert function calls like route('api.users') -> route(+'api.users'+)
|
|
64
|
+
# Only convert when there's actual concatenation with variables
|
|
65
|
+
# Check if this is a function call pattern (function_name('string'))
|
|
66
|
+
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*\)$', expr.strip()):
|
|
67
|
+
# Skip comparison operators like ===, ==, !=, !==, <, >, <=, >=
|
|
68
|
+
if not re.search(r'[=!<>]=?', expr):
|
|
69
|
+
# Skip ternary operators like condition ? value1 : value2
|
|
70
|
+
if not re.search(r'\?.*:', expr):
|
|
71
|
+
if '.' in expr and ('$' in expr or '+' in expr):
|
|
72
|
+
expr = re.sub(r'\+([\'"][^\'\"]*[\'\"])\+', r'+\1+', expr)
|
|
73
|
+
|
|
74
|
+
# Step 7: Handle double + operators
|
|
75
|
+
expr = re.sub(r'\+\+', '+', expr)
|
|
76
|
+
|
|
77
|
+
# Step 8: Fix ternary operators with + characters
|
|
78
|
+
# Fix patterns like condition ?+'value'+:'value' to condition ? 'value' : 'value'
|
|
79
|
+
expr = re.sub(r'\?\s*\+([\'"][^\'\"]*[\'\"])\+\s*:', r'? \1 :', expr)
|
|
80
|
+
expr = re.sub(r':\s*\+([\'"][^\'\"]*[\'\"])\+', r': \1', expr)
|
|
81
|
+
|
|
82
|
+
# Step 9: Fix remaining + characters in function calls and expressions
|
|
83
|
+
# Fix patterns like config(+'app.debug'+) to config('app.debug')
|
|
84
|
+
expr = re.sub(r'(\w+)\(\+([\'"][^\'\"]*[\'\"])\+\)', r'\1(\2)', expr)
|
|
85
|
+
|
|
86
|
+
# Fix nested function calls like json_encode(event(+'view.rendered'+))
|
|
87
|
+
# Recursively fix until no more patterns found
|
|
88
|
+
max_iterations = 10
|
|
89
|
+
for _ in range(max_iterations):
|
|
90
|
+
old_expr = expr
|
|
91
|
+
# Fix patterns like func(+'string'+) - match any characters after the closing )
|
|
92
|
+
expr = re.sub(r'(\w+)\(\+([\'"][^\'\"]*[\'\"])\+\)', r'\1(\2)', expr)
|
|
93
|
+
# Also fix patterns with extra characters after like func(+'string'+))
|
|
94
|
+
expr = re.sub(r'\(\+([\'"][^\'\"]*[\'\"])\+\)', r'(\1)', expr)
|
|
95
|
+
if old_expr == expr:
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
# Fix patterns like +'string'+ to 'string' (standalone)
|
|
99
|
+
expr = re.sub(r'\+([\'"][^\'\"]*[\'\"])\+', r'\1', expr)
|
|
100
|
+
|
|
101
|
+
# Remove PHP variable prefix, but preserve function calls
|
|
102
|
+
# Only remove $ from variable names, not from function calls
|
|
103
|
+
expr = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'\1', expr)
|
|
104
|
+
|
|
105
|
+
# Remove PHP (array) cast - not needed in JavaScript
|
|
106
|
+
expr = re.sub(r'\(array\)\s+', '', expr)
|
|
107
|
+
|
|
108
|
+
# Handle complex array/object structures first
|
|
109
|
+
expr = self._convert_complex_structures(expr)
|
|
110
|
+
|
|
111
|
+
# Handle string concatenation
|
|
112
|
+
expr = self._handle_string_concatenation(expr)
|
|
113
|
+
|
|
114
|
+
# Add function prefixes
|
|
115
|
+
expr = self._add_function_prefixes(expr)
|
|
116
|
+
|
|
117
|
+
# Restore ++ and -- operators
|
|
118
|
+
expr = expr.replace('__INC_OPERATOR__', '++')
|
|
119
|
+
expr = expr.replace('__DEC_OPERATOR__', '--')
|
|
120
|
+
|
|
121
|
+
return expr
|
|
122
|
+
|
|
123
|
+
def _convert_complex_structures(self, expr: str) -> str:
|
|
124
|
+
"""Convert complex PHP structures (arrays, objects) to JavaScript"""
|
|
125
|
+
|
|
126
|
+
# Process from innermost brackets outward
|
|
127
|
+
max_iterations = 10 # Prevent infinite loops
|
|
128
|
+
iteration = 0
|
|
129
|
+
|
|
130
|
+
while '[' in expr and ']' in expr and iteration < max_iterations:
|
|
131
|
+
iteration += 1
|
|
132
|
+
|
|
133
|
+
# Find the innermost bracket pair
|
|
134
|
+
start_pos = -1
|
|
135
|
+
end_pos = -1
|
|
136
|
+
bracket_count = 0
|
|
137
|
+
|
|
138
|
+
for i, char in enumerate(expr):
|
|
139
|
+
if char == '[':
|
|
140
|
+
if start_pos == -1:
|
|
141
|
+
start_pos = i
|
|
142
|
+
bracket_count += 1
|
|
143
|
+
elif char == ']':
|
|
144
|
+
bracket_count -= 1
|
|
145
|
+
if bracket_count == 0:
|
|
146
|
+
end_pos = i
|
|
147
|
+
break
|
|
148
|
+
|
|
149
|
+
if start_pos != -1 and end_pos != -1:
|
|
150
|
+
array_content = expr[start_pos + 1:end_pos].strip()
|
|
151
|
+
|
|
152
|
+
# Check if this is array access vs array literal
|
|
153
|
+
if self._is_array_access(array_content):
|
|
154
|
+
# Skip this bracket pair
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
# Convert array elements
|
|
158
|
+
elements = self._parse_array_elements(array_content)
|
|
159
|
+
js_elements = []
|
|
160
|
+
|
|
161
|
+
# Check if this is a mixed array (has both key=>value pairs and simple values)
|
|
162
|
+
has_key_value_pairs = any('=>' in elem for elem in elements)
|
|
163
|
+
has_simple_values = any('=>' not in elem for elem in elements)
|
|
164
|
+
is_mixed_array = has_key_value_pairs and has_simple_values
|
|
165
|
+
|
|
166
|
+
# Also check if this is an object array (only key=>value pairs)
|
|
167
|
+
is_object_array = has_key_value_pairs and not has_simple_values
|
|
168
|
+
|
|
169
|
+
if is_mixed_array:
|
|
170
|
+
# For mixed arrays, convert each element individually
|
|
171
|
+
for element in elements:
|
|
172
|
+
if '=>' in element:
|
|
173
|
+
# Key-value pair - check if it's a nested array
|
|
174
|
+
if element.startswith('[') and element.endswith(']'):
|
|
175
|
+
# This is a nested array with key-value pairs
|
|
176
|
+
nested_content = element[1:-1].strip()
|
|
177
|
+
nested_elements = self._parse_array_elements(nested_content)
|
|
178
|
+
|
|
179
|
+
# Check if all elements are key-value pairs
|
|
180
|
+
all_key_value = all('=>' in elem for elem in nested_elements)
|
|
181
|
+
|
|
182
|
+
if all_key_value:
|
|
183
|
+
# Convert to object
|
|
184
|
+
nested_object_parts = []
|
|
185
|
+
for nested_elem in nested_elements:
|
|
186
|
+
nested_js_element = self._convert_array_element(nested_elem)
|
|
187
|
+
nested_object_parts.append(nested_js_element)
|
|
188
|
+
js_elements.append('{' + ', '.join(nested_object_parts) + '}')
|
|
189
|
+
else:
|
|
190
|
+
# Keep as array
|
|
191
|
+
nested_js_elements = []
|
|
192
|
+
for nested_elem in nested_elements:
|
|
193
|
+
if '=>' in nested_elem:
|
|
194
|
+
# Key-value pair in nested array
|
|
195
|
+
nested_js_element = self._convert_array_element(nested_elem)
|
|
196
|
+
nested_js_elements.append('{' + nested_js_element + '}')
|
|
197
|
+
else:
|
|
198
|
+
# Simple value in nested array
|
|
199
|
+
nested_js_element = self._convert_value(nested_elem)
|
|
200
|
+
nested_js_elements.append(nested_js_element)
|
|
201
|
+
js_elements.append('[' + ', '.join(nested_js_elements) + ']')
|
|
202
|
+
else:
|
|
203
|
+
# Direct key-value pair
|
|
204
|
+
js_element = self._convert_array_element(element)
|
|
205
|
+
js_elements.append('{' + js_element + '}')
|
|
206
|
+
else:
|
|
207
|
+
# Simple value
|
|
208
|
+
js_element = self._convert_value(element)
|
|
209
|
+
js_elements.append(js_element)
|
|
210
|
+
elif is_object_array:
|
|
211
|
+
# For object arrays, check if we have nested arrays
|
|
212
|
+
has_nested_arrays = any(element.startswith('[') and element.endswith(']') for element in elements)
|
|
213
|
+
|
|
214
|
+
if has_nested_arrays:
|
|
215
|
+
# This is an object array with nested arrays - keep as array
|
|
216
|
+
for element in elements:
|
|
217
|
+
if '=>' in element:
|
|
218
|
+
# Key-value pair - check if it's a nested array
|
|
219
|
+
if element.startswith('[') and element.endswith(']'):
|
|
220
|
+
# This is a nested array with key-value pairs
|
|
221
|
+
nested_content = element[1:-1].strip()
|
|
222
|
+
nested_elements = self._parse_array_elements(nested_content)
|
|
223
|
+
|
|
224
|
+
# Check if all elements are key-value pairs
|
|
225
|
+
all_key_value = all('=>' in elem for elem in nested_elements)
|
|
226
|
+
|
|
227
|
+
if all_key_value:
|
|
228
|
+
# Convert to object
|
|
229
|
+
nested_object_parts = []
|
|
230
|
+
for nested_elem in nested_elements:
|
|
231
|
+
nested_js_element = self._convert_array_element(nested_elem)
|
|
232
|
+
nested_object_parts.append(nested_js_element)
|
|
233
|
+
js_elements.append('{' + ', '.join(nested_object_parts) + '}')
|
|
234
|
+
else:
|
|
235
|
+
# Keep as array
|
|
236
|
+
nested_js_elements = []
|
|
237
|
+
for nested_elem in nested_elements:
|
|
238
|
+
if '=>' in nested_elem:
|
|
239
|
+
# Key-value pair in nested array
|
|
240
|
+
nested_js_element = self._convert_array_element(nested_elem)
|
|
241
|
+
nested_js_elements.append('{' + nested_js_element + '}')
|
|
242
|
+
else:
|
|
243
|
+
# Simple value in nested array
|
|
244
|
+
nested_js_element = self._convert_value(nested_elem)
|
|
245
|
+
nested_js_elements.append(nested_js_element)
|
|
246
|
+
js_elements.append('[' + ', '.join(nested_js_elements) + ']')
|
|
247
|
+
else:
|
|
248
|
+
# Direct key-value pair
|
|
249
|
+
js_element = self._convert_array_element(element)
|
|
250
|
+
js_elements.append('{' + js_element + '}')
|
|
251
|
+
else:
|
|
252
|
+
# Simple value
|
|
253
|
+
js_element = self._convert_value(element)
|
|
254
|
+
js_elements.append(js_element)
|
|
255
|
+
else:
|
|
256
|
+
# Regular object array - combine all key-value pairs into single object
|
|
257
|
+
object_parts = []
|
|
258
|
+
for element in elements:
|
|
259
|
+
if '=>' in element:
|
|
260
|
+
# Key-value pair
|
|
261
|
+
js_element = self._convert_array_element(element)
|
|
262
|
+
object_parts.append(js_element)
|
|
263
|
+
# Combine into single object
|
|
264
|
+
new_content = '{' + ', '.join(object_parts) + '}'
|
|
265
|
+
expr = expr[:start_pos] + new_content + expr[end_pos + 1:]
|
|
266
|
+
continue
|
|
267
|
+
else:
|
|
268
|
+
# Regular array processing
|
|
269
|
+
for element in elements:
|
|
270
|
+
js_element = self._convert_array_element(element)
|
|
271
|
+
js_elements.append(js_element)
|
|
272
|
+
|
|
273
|
+
# Replace the bracket content
|
|
274
|
+
new_content = '[' + ', '.join(js_elements) + ']'
|
|
275
|
+
expr = expr[:start_pos] + new_content + expr[end_pos + 1:]
|
|
276
|
+
else:
|
|
277
|
+
break
|
|
278
|
+
|
|
279
|
+
return expr
|
|
280
|
+
|
|
281
|
+
def _is_array_access(self, content: str) -> bool:
|
|
282
|
+
"""Check if this is array access (e.g., ['key']) vs array literal"""
|
|
283
|
+
# Simple heuristic: if it's a single quoted string or number, it's array access
|
|
284
|
+
content = content.strip()
|
|
285
|
+
if (content.startswith("'") and content.endswith("'") and content.count("'") == 2) or \
|
|
286
|
+
(content.startswith('"') and content.endswith('"') and content.count('"') == 2) or \
|
|
287
|
+
content.isdigit() or content.replace('.', '').isdigit():
|
|
288
|
+
return True
|
|
289
|
+
return False
|
|
290
|
+
|
|
291
|
+
def _parse_array_elements(self, content: str) -> List[str]:
|
|
292
|
+
"""Parse array elements, handling nested structures"""
|
|
293
|
+
elements = []
|
|
294
|
+
current_element = ''
|
|
295
|
+
paren_count = 0
|
|
296
|
+
bracket_count = 0
|
|
297
|
+
in_quotes = False
|
|
298
|
+
quote_char = ''
|
|
299
|
+
i = 0
|
|
300
|
+
|
|
301
|
+
while i < len(content):
|
|
302
|
+
char = content[i]
|
|
303
|
+
|
|
304
|
+
if (char == '"' or char == "'") and not in_quotes:
|
|
305
|
+
in_quotes = True
|
|
306
|
+
quote_char = char
|
|
307
|
+
current_element += char
|
|
308
|
+
elif char == quote_char and in_quotes:
|
|
309
|
+
# Check if this is an escaped quote
|
|
310
|
+
if i > 0 and content[i - 1] == '\\':
|
|
311
|
+
current_element += char
|
|
312
|
+
else:
|
|
313
|
+
in_quotes = False
|
|
314
|
+
quote_char = ''
|
|
315
|
+
current_element += char
|
|
316
|
+
elif not in_quotes:
|
|
317
|
+
if char == '(':
|
|
318
|
+
paren_count += 1
|
|
319
|
+
current_element += char
|
|
320
|
+
elif char == ')':
|
|
321
|
+
paren_count -= 1
|
|
322
|
+
current_element += char
|
|
323
|
+
elif char == '[':
|
|
324
|
+
bracket_count += 1
|
|
325
|
+
current_element += char
|
|
326
|
+
elif char == ']':
|
|
327
|
+
bracket_count -= 1
|
|
328
|
+
current_element += char
|
|
329
|
+
elif char == ',' and paren_count == 0 and bracket_count == 0:
|
|
330
|
+
# This is a top-level comma
|
|
331
|
+
elements.append(current_element.strip())
|
|
332
|
+
current_element = ''
|
|
333
|
+
else:
|
|
334
|
+
current_element += char
|
|
335
|
+
else:
|
|
336
|
+
current_element += char
|
|
337
|
+
|
|
338
|
+
i += 1
|
|
339
|
+
|
|
340
|
+
# Add the last element
|
|
341
|
+
if current_element.strip():
|
|
342
|
+
elements.append(current_element.strip())
|
|
343
|
+
|
|
344
|
+
return elements
|
|
345
|
+
|
|
346
|
+
def _convert_array_element(self, element: str) -> str:
|
|
347
|
+
"""Convert a single array element to JavaScript"""
|
|
348
|
+
element = element.strip()
|
|
349
|
+
|
|
350
|
+
# Check if it's a key => value pair
|
|
351
|
+
if '=>' in element:
|
|
352
|
+
key_value_parts = self._split_key_value(element)
|
|
353
|
+
if len(key_value_parts) == 2:
|
|
354
|
+
key, value = key_value_parts
|
|
355
|
+
js_key = self._convert_key(key)
|
|
356
|
+
js_value = self._convert_value(value)
|
|
357
|
+
return f'"{js_key}": {js_value}'
|
|
358
|
+
|
|
359
|
+
# It's a simple value
|
|
360
|
+
return self._convert_value(element)
|
|
361
|
+
|
|
362
|
+
def _split_key_value(self, element: str) -> List[str]:
|
|
363
|
+
"""Split key => value pair, handling nested structures"""
|
|
364
|
+
# Find the first => that's not inside quotes or nested structures
|
|
365
|
+
paren_count = 0
|
|
366
|
+
bracket_count = 0
|
|
367
|
+
in_quotes = False
|
|
368
|
+
quote_char = ''
|
|
369
|
+
|
|
370
|
+
for i, char in enumerate(element):
|
|
371
|
+
if (char == '"' or char == "'") and not in_quotes:
|
|
372
|
+
in_quotes = True
|
|
373
|
+
quote_char = char
|
|
374
|
+
elif char == quote_char and in_quotes:
|
|
375
|
+
if i > 0 and element[i - 1] == '\\':
|
|
376
|
+
continue
|
|
377
|
+
in_quotes = False
|
|
378
|
+
quote_char = ''
|
|
379
|
+
elif not in_quotes:
|
|
380
|
+
if char == '(':
|
|
381
|
+
paren_count += 1
|
|
382
|
+
elif char == ')':
|
|
383
|
+
paren_count -= 1
|
|
384
|
+
elif char == '[':
|
|
385
|
+
bracket_count += 1
|
|
386
|
+
elif char == ']':
|
|
387
|
+
bracket_count -= 1
|
|
388
|
+
elif char == '=' and i + 1 < len(element) and element[i + 1] == '>' and paren_count == 0 and bracket_count == 0:
|
|
389
|
+
key = element[:i].strip()
|
|
390
|
+
value = element[i + 2:].strip()
|
|
391
|
+
return [key, value]
|
|
392
|
+
|
|
393
|
+
return [element]
|
|
394
|
+
|
|
395
|
+
def _convert_key(self, key: str) -> str:
|
|
396
|
+
"""Convert array key to JavaScript key"""
|
|
397
|
+
key = key.strip()
|
|
398
|
+
|
|
399
|
+
# Remove quotes if present
|
|
400
|
+
if (key.startswith("'") and key.endswith("'")) or (key.startswith('"') and key.endswith('"')):
|
|
401
|
+
key = key[1:-1]
|
|
402
|
+
|
|
403
|
+
return key
|
|
404
|
+
|
|
405
|
+
def _convert_value(self, value: str) -> str:
|
|
406
|
+
"""Convert array value to JavaScript value"""
|
|
407
|
+
value = value.strip()
|
|
408
|
+
|
|
409
|
+
# Handle different value types
|
|
410
|
+
if value.startswith("'") and value.endswith("'"):
|
|
411
|
+
# String value
|
|
412
|
+
return f'"{value[1:-1]}"'
|
|
413
|
+
elif value.startswith('"') and value.endswith('"'):
|
|
414
|
+
# String value
|
|
415
|
+
return value
|
|
416
|
+
elif value.isdigit() or value.replace('.', '').isdigit():
|
|
417
|
+
# Numeric value
|
|
418
|
+
return value
|
|
419
|
+
elif value.lower() in ['true', 'false', 'null']:
|
|
420
|
+
# Boolean/null values
|
|
421
|
+
return value.lower()
|
|
422
|
+
elif value.startswith('[') and value.endswith(']'):
|
|
423
|
+
# Nested array - recursive conversion
|
|
424
|
+
return self._convert_complex_structures(value)
|
|
425
|
+
elif value.startswith('"') or value.startswith("'"):
|
|
426
|
+
# Quoted string
|
|
427
|
+
return value
|
|
428
|
+
elif value.isalnum() or value.replace('_', '').isalnum():
|
|
429
|
+
# Simple identifier (variable name)
|
|
430
|
+
return value
|
|
431
|
+
else:
|
|
432
|
+
# Variable or expression
|
|
433
|
+
return self._convert_simple_expression(value)
|
|
434
|
+
|
|
435
|
+
def _handle_string_concatenation(self, expr: str) -> str:
|
|
436
|
+
"""Handle string concatenation (. to +) while preserving object property access"""
|
|
437
|
+
|
|
438
|
+
# Handle string concatenation with variables and string literals
|
|
439
|
+
# But skip function calls like route('api.users') and object method calls like now().format()
|
|
440
|
+
# Also skip nested function calls like json_encode(event('view.rendered'))
|
|
441
|
+
if '.' in expr and ('$' in expr or '"' in expr or "'" in expr) and not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*\s*\(', expr.strip()):
|
|
442
|
+
# Skip array literals like ['name' => 'John', 'email' => 'john@example.com']
|
|
443
|
+
if expr.strip().startswith('[') and expr.strip().endswith(']'):
|
|
444
|
+
return expr
|
|
445
|
+
# Skip object literals like {"name": "John", "email": "john@example.com"}
|
|
446
|
+
if expr.strip().startswith('{') and expr.strip().endswith('}'):
|
|
447
|
+
return expr
|
|
448
|
+
# Skip null coalescing operator like __VIEW_NAME__ ?? 'layouts.base'
|
|
449
|
+
if '??' in expr:
|
|
450
|
+
return expr
|
|
451
|
+
# Skip comparison operators like __VIEW_PATH__ === 'layouts.base'
|
|
452
|
+
# But check outside string literals to avoid matching < in '<strong>'
|
|
453
|
+
expr_without_strings = re.sub(r"'[^']*'", '', expr)
|
|
454
|
+
expr_without_strings = re.sub(r'"[^"]*"', '', expr_without_strings)
|
|
455
|
+
if re.search(r'[=!<>]=?|==|!=|<=|>=|===|!==', expr_without_strings):
|
|
456
|
+
return expr
|
|
457
|
+
# Skip ternary operators like condition ? value1 : value2
|
|
458
|
+
if re.search(r'\?.*:', expr):
|
|
459
|
+
return expr
|
|
460
|
+
# Also skip object method calls like now().format() or App.Helper.now().format()
|
|
461
|
+
# Pattern: method().method() or object.method().method()
|
|
462
|
+
if re.search(r'[a-zA-Z_][a-zA-Z0-9_]*\s*\([^)]*\)\s*\.\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\(', expr):
|
|
463
|
+
return expr
|
|
464
|
+
# Protect string literals FIRST before processing
|
|
465
|
+
string_literals = []
|
|
466
|
+
def protect_string_literal(match):
|
|
467
|
+
pattern = match.group(0)
|
|
468
|
+
placeholder = f"__STR_LIT_{len(string_literals)}__"
|
|
469
|
+
string_literals.append(pattern)
|
|
470
|
+
return placeholder
|
|
471
|
+
|
|
472
|
+
# Protect single-quoted strings
|
|
473
|
+
expr_protected = re.sub(r"'[^']*'", protect_string_literal, expr)
|
|
474
|
+
# Protect double-quoted strings
|
|
475
|
+
expr_protected = re.sub(r'"[^"]*"', protect_string_literal, expr_protected)
|
|
476
|
+
|
|
477
|
+
# Use regex to properly split string concatenation
|
|
478
|
+
# Pattern to match: variable, string literal placeholder, or other tokens (excluding . and spaces)
|
|
479
|
+
pattern = r'(\$[a-zA-Z_][a-zA-Z0-9_]*|__STR_LIT_\d+__|[^\s\.]+)'
|
|
480
|
+
parts = re.findall(pattern, expr_protected)
|
|
481
|
+
|
|
482
|
+
js_parts = []
|
|
483
|
+
for part in parts:
|
|
484
|
+
if not part or part == '.':
|
|
485
|
+
# Skip empty strings and dots
|
|
486
|
+
continue
|
|
487
|
+
if part.startswith('$'):
|
|
488
|
+
var_name = part[1:]
|
|
489
|
+
js_parts.append(var_name)
|
|
490
|
+
elif part.startswith('__STR_LIT_'):
|
|
491
|
+
# Restore string literal
|
|
492
|
+
idx = int(re.search(r'\d+', part).group())
|
|
493
|
+
literal = string_literals[idx]
|
|
494
|
+
if literal.startswith("'"):
|
|
495
|
+
js_parts.append(literal)
|
|
496
|
+
elif literal.startswith('"'):
|
|
497
|
+
inner = literal[1:-1]
|
|
498
|
+
inner = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', inner)
|
|
499
|
+
js_parts.append(f"`{inner}`")
|
|
500
|
+
else:
|
|
501
|
+
# Other tokens (shouldn't normally reach here)
|
|
502
|
+
js_parts.append(part)
|
|
503
|
+
|
|
504
|
+
return '+'.join(js_parts)
|
|
505
|
+
|
|
506
|
+
# Protect string literals FIRST before processing object access
|
|
507
|
+
string_literals = []
|
|
508
|
+
def protect_string_literal(match):
|
|
509
|
+
pattern = match.group(0)
|
|
510
|
+
placeholder = f"__STR_LIT_{len(string_literals)}__"
|
|
511
|
+
string_literals.append(pattern)
|
|
512
|
+
return placeholder
|
|
513
|
+
|
|
514
|
+
# Protect single-quoted strings
|
|
515
|
+
expr = re.sub(r"'[^']*'", protect_string_literal, expr)
|
|
516
|
+
# Protect double-quoted strings
|
|
517
|
+
expr = re.sub(r'"[^"]*"', protect_string_literal, expr)
|
|
518
|
+
|
|
519
|
+
# Protect object property access patterns
|
|
520
|
+
object_access_patterns = []
|
|
521
|
+
def protect_object_access(match):
|
|
522
|
+
pattern = match.group(0)
|
|
523
|
+
placeholder = f"__OBJ_ACCESS_{len(object_access_patterns)}__"
|
|
524
|
+
object_access_patterns.append(pattern)
|
|
525
|
+
return placeholder
|
|
526
|
+
|
|
527
|
+
# Protect object property access patterns (word.word)
|
|
528
|
+
expr = re.sub(r'\b\w+\.\w+\b', protect_object_access, expr)
|
|
529
|
+
|
|
530
|
+
# Convert remaining . to + for concatenation
|
|
531
|
+
expr = re.sub(r'\s+\.\s+', ' + ', expr)
|
|
532
|
+
expr = re.sub(r'\.\s+', ' + ', expr)
|
|
533
|
+
expr = re.sub(r'\s+\.', ' + ', expr)
|
|
534
|
+
|
|
535
|
+
# Restore object property access patterns
|
|
536
|
+
for i, pattern in enumerate(object_access_patterns):
|
|
537
|
+
expr = expr.replace(f"__OBJ_ACCESS_{i}__", pattern)
|
|
538
|
+
|
|
539
|
+
# Restore string literals LAST
|
|
540
|
+
for i, literal in enumerate(string_literals):
|
|
541
|
+
expr = expr.replace(f"__STR_LIT_{i}__", literal)
|
|
542
|
+
|
|
543
|
+
return expr
|
|
544
|
+
|
|
545
|
+
def _convert_simple_expression(self, expr: str) -> str:
|
|
546
|
+
"""Convert simple PHP expression to JavaScript"""
|
|
547
|
+
expr = expr.strip()
|
|
548
|
+
|
|
549
|
+
# Convert object property access
|
|
550
|
+
expr = re.sub(r'->', '.', expr)
|
|
551
|
+
|
|
552
|
+
# Handle string concatenation
|
|
553
|
+
expr = self._handle_string_concatenation(expr)
|
|
554
|
+
|
|
555
|
+
# Add function prefixes
|
|
556
|
+
expr = self._add_function_prefixes(expr)
|
|
557
|
+
|
|
558
|
+
return expr
|
|
559
|
+
|
|
560
|
+
def _add_function_prefixes(self, expr: str) -> str:
|
|
561
|
+
"""Add App.View. or App.Helper. prefix to PHP functions based on ViewConfig"""
|
|
562
|
+
from config import ViewConfig, APP_VIEW_NAMESPACE, APP_HELPER_NAMESPACE
|
|
563
|
+
|
|
564
|
+
# Get all known functions (View + Helper)
|
|
565
|
+
all_functions = [
|
|
566
|
+
'count', 'min', 'max', 'abs', 'ceil', 'floor', 'round', 'sqrt',
|
|
567
|
+
'strlen', 'substr', 'trim', 'ltrim', 'rtrim', 'strtolower', 'strtoupper',
|
|
568
|
+
'isset', 'empty', 'is_null', 'is_array', 'is_string', 'is_numeric',
|
|
569
|
+
'array_key_exists', 'in_array', 'array_merge', 'array_push', 'array_pop',
|
|
570
|
+
'json_encode', 'json_decode', 'md5', 'sha1', 'base64_encode', 'base64_decode',
|
|
571
|
+
'now', 'today', 'date', 'time', 'strtotime', 'mktime',
|
|
572
|
+
'diffInDays', 'diffInHours', 'diffInMinutes', 'diffInSeconds',
|
|
573
|
+
'addDays', 'subDays', 'addHours', 'subHours', 'addMinutes', 'subMinutes',
|
|
574
|
+
'format', 'parse', 'createFromFormat',
|
|
575
|
+
'env', 'config', 'auth', 'request', 'response', 'session', 'cache',
|
|
576
|
+
'view', 'redirect', 'route', 'url', 'asset', 'mix',
|
|
577
|
+
'collect', 'dd', 'dump', 'logger', 'abort', 'old', 'slug',
|
|
578
|
+
# Additional functions that might be used
|
|
579
|
+
'ucfirst', 'lcfirst', 'str_replace', 'explode', 'implode', 'array_unique',
|
|
580
|
+
'formatDate', 'formatNumber', 'formatCurrency', 'truncate', 'number_format',
|
|
581
|
+
'updateTitle', 'updateDescription', 'updateKeywords',
|
|
582
|
+
'getUrlParams', 'buildUrl', 'isInViewport', 'scrollTo', 'copyToClipboard',
|
|
583
|
+
'getDeviceType', 'isMobile', 'isTablet', 'isDesktop'
|
|
584
|
+
]
|
|
585
|
+
|
|
586
|
+
for func in all_functions:
|
|
587
|
+
pattern = r'(?<![\w.])(' + re.escape(func) + r')\s*\('
|
|
588
|
+
# Use ViewConfig to determine correct prefix
|
|
589
|
+
if ViewConfig.is_view_function(func):
|
|
590
|
+
replacement = f'{APP_VIEW_NAMESPACE}.\\1('
|
|
591
|
+
else:
|
|
592
|
+
replacement = f'{APP_HELPER_NAMESPACE}.\\1('
|
|
593
|
+
expr = re.sub(pattern, replacement, expr)
|
|
594
|
+
|
|
595
|
+
return expr
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
# Global instance for backward compatibility
|
|
599
|
+
_php_js_converter = PHPToJSConverter()
|
|
600
|
+
|
|
601
|
+
def php_to_js_advanced(expr: str) -> str:
|
|
602
|
+
"""Advanced PHP to JavaScript conversion function"""
|
|
603
|
+
return _php_js_converter.convert_php_expression_to_js(expr)
|