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,480 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Parser cho directive @register với format mới
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from config import JS_FUNCTION_PREFIX
|
|
7
|
+
|
|
8
|
+
class RegisterParser:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.register_content = ""
|
|
11
|
+
self.scripts = []
|
|
12
|
+
self.styles = []
|
|
13
|
+
self.userDefined = {}
|
|
14
|
+
self.setup_content = [] # Store setup script content for file top
|
|
15
|
+
|
|
16
|
+
def reset(self):
|
|
17
|
+
self.register_content = ""
|
|
18
|
+
self.scripts = []
|
|
19
|
+
self.styles = []
|
|
20
|
+
self.userDefined = {}
|
|
21
|
+
self.setup_content = [] # Reset setup content
|
|
22
|
+
# Reset merged content to prevent data leakage
|
|
23
|
+
if hasattr(self, '_merged_content'):
|
|
24
|
+
delattr(self, '_merged_content')
|
|
25
|
+
|
|
26
|
+
def parse_register_content(self, content, view_name=None):
|
|
27
|
+
self.register_content = content
|
|
28
|
+
self.scripts = []
|
|
29
|
+
self.styles = []
|
|
30
|
+
self.userDefined = {}
|
|
31
|
+
# Reset merged content to prevent data leakage
|
|
32
|
+
if hasattr(self, '_merged_content'):
|
|
33
|
+
delattr(self, '_merged_content')
|
|
34
|
+
|
|
35
|
+
self._parse_scripts(content)
|
|
36
|
+
self._parse_styles(content)
|
|
37
|
+
|
|
38
|
+
return self.get_all_data()
|
|
39
|
+
|
|
40
|
+
def _parse_scripts(self, content):
|
|
41
|
+
processed_exports = set()
|
|
42
|
+
processed_scripts = set() # Track processed scripts to avoid duplicates
|
|
43
|
+
|
|
44
|
+
# Find all script tags with attributes - parse once
|
|
45
|
+
all_script_matches = re.finditer(r'<script([^>]*?)>(.*?)</script>', content, re.DOTALL)
|
|
46
|
+
|
|
47
|
+
for match in all_script_matches:
|
|
48
|
+
attrs_string = match.group(1)
|
|
49
|
+
script_content = match.group(2).strip()
|
|
50
|
+
full_match = match.group(0)
|
|
51
|
+
|
|
52
|
+
# Skip if already processed
|
|
53
|
+
if full_match in processed_scripts:
|
|
54
|
+
continue
|
|
55
|
+
processed_scripts.add(full_match)
|
|
56
|
+
|
|
57
|
+
# Check if it's external script (has src)
|
|
58
|
+
if 'src=' in attrs_string:
|
|
59
|
+
# External script - handle both regular URLs and Blade syntax
|
|
60
|
+
src_match = re.search(r'src=(["\'])([^"\']*?(?:\{\{[^}]*\}\}[^"\']*?)*[^"\']*?)\1', attrs_string)
|
|
61
|
+
if src_match:
|
|
62
|
+
src = src_match.group(2)
|
|
63
|
+
script_obj = {
|
|
64
|
+
'type': 'src',
|
|
65
|
+
'src': src
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Parse attributes (exclude src to avoid duplication)
|
|
69
|
+
attributes = self._parse_attributes(attrs_string, exclude_attrs=['src'])
|
|
70
|
+
if attributes.get('id'):
|
|
71
|
+
script_obj['id'] = attributes['id']
|
|
72
|
+
if attributes.get('className'):
|
|
73
|
+
script_obj['className'] = attributes['className']
|
|
74
|
+
if attributes.get('attributes'):
|
|
75
|
+
script_obj['attributes'] = attributes['attributes']
|
|
76
|
+
|
|
77
|
+
self.scripts.append(script_obj)
|
|
78
|
+
else:
|
|
79
|
+
# Inline script
|
|
80
|
+
if not script_content:
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
# Parse attributes
|
|
84
|
+
script_obj = {
|
|
85
|
+
'type': 'code',
|
|
86
|
+
'content': script_content
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# Add attributes
|
|
90
|
+
attributes = self._parse_attributes(attrs_string)
|
|
91
|
+
if attributes.get('id'):
|
|
92
|
+
script_obj['id'] = attributes['id']
|
|
93
|
+
if attributes.get('className'):
|
|
94
|
+
script_obj['className'] = attributes['className']
|
|
95
|
+
if attributes.get('attributes'):
|
|
96
|
+
script_obj['attributes'] = attributes['attributes']
|
|
97
|
+
|
|
98
|
+
# Check if this is a setup script (has setup, view, or component attribute)
|
|
99
|
+
is_setup_script = self._is_setup_script(attributes)
|
|
100
|
+
|
|
101
|
+
export_match = self._find_export_object(script_content)
|
|
102
|
+
remaining_content = script_content
|
|
103
|
+
|
|
104
|
+
if export_match:
|
|
105
|
+
obj_content = export_match.group(1)
|
|
106
|
+
obj_hash = hash(obj_content)
|
|
107
|
+
|
|
108
|
+
if obj_hash not in processed_exports:
|
|
109
|
+
processed_exports.add(obj_hash)
|
|
110
|
+
self._extract_to_user_defined(obj_content)
|
|
111
|
+
|
|
112
|
+
# Remove export statement from script content
|
|
113
|
+
remaining_content = self._remove_export_from_script(script_content, is_setup_script)
|
|
114
|
+
|
|
115
|
+
# For setup scripts, store remaining content for file top (after removing export)
|
|
116
|
+
if is_setup_script:
|
|
117
|
+
# Store the remaining content (everything except export but keep imports) for file top
|
|
118
|
+
remaining_without_export = self._remove_only_export_from_script(script_content)
|
|
119
|
+
if remaining_without_export.strip():
|
|
120
|
+
self.setup_content.append(remaining_without_export.strip())
|
|
121
|
+
# For regular scripts, only add if there's remaining content after removing export
|
|
122
|
+
elif remaining_content.strip():
|
|
123
|
+
script_obj['content'] = remaining_content
|
|
124
|
+
self.scripts.append(script_obj)
|
|
125
|
+
|
|
126
|
+
def _parse_styles(self, content):
|
|
127
|
+
# Parse inline styles with attributes
|
|
128
|
+
style_matches = re.finditer(r'<style([^>]*?)>(.*?)</style>', content, re.DOTALL)
|
|
129
|
+
|
|
130
|
+
for match in style_matches:
|
|
131
|
+
attrs_string = match.group(1)
|
|
132
|
+
css_content = match.group(2).strip()
|
|
133
|
+
|
|
134
|
+
if css_content:
|
|
135
|
+
style_obj = {
|
|
136
|
+
'type': 'code',
|
|
137
|
+
'content': css_content
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Parse attributes
|
|
141
|
+
attributes = self._parse_attributes(attrs_string)
|
|
142
|
+
if attributes.get('id'):
|
|
143
|
+
style_obj['id'] = attributes['id']
|
|
144
|
+
if attributes.get('className'):
|
|
145
|
+
style_obj['className'] = attributes['className']
|
|
146
|
+
if attributes.get('attributes'):
|
|
147
|
+
style_obj['attributes'] = attributes['attributes']
|
|
148
|
+
|
|
149
|
+
self.styles.append(style_obj)
|
|
150
|
+
|
|
151
|
+
# Parse external stylesheets with attributes
|
|
152
|
+
# Use a more flexible pattern that handles Blade syntax better
|
|
153
|
+
link_pattern = r'<link([^>]*?)rel=["\']stylesheet["\']([^>]*?)>'
|
|
154
|
+
link_matches = re.finditer(link_pattern, content)
|
|
155
|
+
|
|
156
|
+
for match in link_matches:
|
|
157
|
+
full_tag = match.group(0)
|
|
158
|
+
attrs_combined = match.group(1) + match.group(2)
|
|
159
|
+
|
|
160
|
+
# Extract href - handle both regular URLs and Blade syntax
|
|
161
|
+
href_match = re.search(r'href=(["\'])([^"\']*?(?:\{\{[^}]*\}\}[^"\']*?)*[^"\']*?)\1', full_tag)
|
|
162
|
+
if href_match:
|
|
163
|
+
href = href_match.group(2)
|
|
164
|
+
|
|
165
|
+
style_obj = {
|
|
166
|
+
'type': 'href',
|
|
167
|
+
'href': href
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# Parse attributes (exclude href and rel to avoid duplication)
|
|
171
|
+
attributes = self._parse_attributes(attrs_combined, exclude_attrs=['href', 'rel'])
|
|
172
|
+
if attributes.get('id'):
|
|
173
|
+
style_obj['id'] = attributes['id']
|
|
174
|
+
if attributes.get('className'):
|
|
175
|
+
style_obj['className'] = attributes['className']
|
|
176
|
+
if attributes.get('attributes'):
|
|
177
|
+
style_obj['attributes'] = attributes['attributes']
|
|
178
|
+
|
|
179
|
+
self.styles.append(style_obj)
|
|
180
|
+
|
|
181
|
+
def _parse_attributes(self, attrs_string, exclude_attrs=None):
|
|
182
|
+
"""Parse HTML attributes from string and extract id, class, and other attributes"""
|
|
183
|
+
if exclude_attrs is None:
|
|
184
|
+
exclude_attrs = []
|
|
185
|
+
|
|
186
|
+
result = {
|
|
187
|
+
'id': '',
|
|
188
|
+
'className': '',
|
|
189
|
+
'attributes': {}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if not attrs_string or not attrs_string.strip():
|
|
193
|
+
return result
|
|
194
|
+
|
|
195
|
+
# Parse attributes using regex
|
|
196
|
+
attr_pattern = r'(\w+(?:-\w+)*)(?:=(["\'])([^"\']*?)\2|(?==|\s|$))'
|
|
197
|
+
matches = re.findall(attr_pattern, attrs_string)
|
|
198
|
+
|
|
199
|
+
for match in matches:
|
|
200
|
+
attr_name = match[0].strip()
|
|
201
|
+
attr_value = match[2] if len(match) > 2 else None
|
|
202
|
+
|
|
203
|
+
# Skip excluded attributes
|
|
204
|
+
if attr_name in exclude_attrs:
|
|
205
|
+
continue
|
|
206
|
+
|
|
207
|
+
if attr_name == 'id':
|
|
208
|
+
result['id'] = attr_value or ''
|
|
209
|
+
elif attr_name == 'class':
|
|
210
|
+
result['className'] = attr_value or ''
|
|
211
|
+
elif attr_name in ['script', 'view', 'component', 'setup', 'defer', 'async']:
|
|
212
|
+
# Boolean attributes - if present without value, set to true
|
|
213
|
+
if attr_value is None or attr_value == '':
|
|
214
|
+
result['attributes'][attr_name] = True
|
|
215
|
+
else:
|
|
216
|
+
result['attributes'][attr_name] = attr_value
|
|
217
|
+
else:
|
|
218
|
+
# Other attributes
|
|
219
|
+
if attr_value is None or attr_value == '':
|
|
220
|
+
# Boolean attribute
|
|
221
|
+
result['attributes'][attr_name] = True
|
|
222
|
+
else:
|
|
223
|
+
result['attributes'][attr_name] = attr_value
|
|
224
|
+
|
|
225
|
+
# Clean up empty attributes
|
|
226
|
+
if not result['attributes']:
|
|
227
|
+
del result['attributes']
|
|
228
|
+
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
def _remove_export_from_script(self, script_content, is_setup_script=False):
|
|
232
|
+
"""Remove export statement from script content and return remaining content"""
|
|
233
|
+
# Remove export statement first
|
|
234
|
+
export_pattern = r'export\s+(?:default\s+)?(\{)'
|
|
235
|
+
match = re.search(export_pattern, script_content, re.DOTALL)
|
|
236
|
+
|
|
237
|
+
remaining_content = script_content
|
|
238
|
+
|
|
239
|
+
if match:
|
|
240
|
+
# Find the full export statement including trailing semicolon/newline
|
|
241
|
+
export_start = match.start()
|
|
242
|
+
start_pos = match.start(1)
|
|
243
|
+
brace_count = 1
|
|
244
|
+
end_pos = start_pos + 1
|
|
245
|
+
|
|
246
|
+
# Find matching closing brace
|
|
247
|
+
while end_pos < len(script_content) and brace_count > 0:
|
|
248
|
+
char = script_content[end_pos]
|
|
249
|
+
if char == '{':
|
|
250
|
+
brace_count += 1
|
|
251
|
+
elif char == '}':
|
|
252
|
+
brace_count -= 1
|
|
253
|
+
end_pos += 1
|
|
254
|
+
|
|
255
|
+
if brace_count == 0:
|
|
256
|
+
# Include trailing semicolon if present
|
|
257
|
+
while end_pos < len(script_content) and script_content[end_pos] in [';', ' ', '\t']:
|
|
258
|
+
end_pos += 1
|
|
259
|
+
|
|
260
|
+
# Include trailing newlines
|
|
261
|
+
while end_pos < len(script_content) and script_content[end_pos] in ['\n', '\r']:
|
|
262
|
+
end_pos += 1
|
|
263
|
+
|
|
264
|
+
# Remove the export statement
|
|
265
|
+
remaining_content = script_content[:export_start] + script_content[end_pos:]
|
|
266
|
+
|
|
267
|
+
# For setup scripts, return empty string (all remaining content goes to file top)
|
|
268
|
+
# For regular scripts, remove import statements only (these will be handled at file level)
|
|
269
|
+
if is_setup_script:
|
|
270
|
+
return ""
|
|
271
|
+
else:
|
|
272
|
+
# Remove all import statements (these will be handled at file level)
|
|
273
|
+
import_pattern = r'import\s+.*?(?:from\s+["\'][^"\']*["\']|["\'][^"\']*["\'])\s*;?\s*\n?'
|
|
274
|
+
remaining_content = re.sub(import_pattern, '', remaining_content, flags=re.MULTILINE)
|
|
275
|
+
|
|
276
|
+
return remaining_content.strip()
|
|
277
|
+
|
|
278
|
+
def _remove_only_export_from_script(self, script_content):
|
|
279
|
+
"""Remove only export statement from script content, keep everything else including imports"""
|
|
280
|
+
# Remove export statement only
|
|
281
|
+
export_pattern = r'export\s+(?:default\s+)?(\{)'
|
|
282
|
+
match = re.search(export_pattern, script_content, re.DOTALL)
|
|
283
|
+
|
|
284
|
+
if not match:
|
|
285
|
+
return script_content
|
|
286
|
+
|
|
287
|
+
# Find the full export statement including trailing semicolon/newline
|
|
288
|
+
export_start = match.start()
|
|
289
|
+
start_pos = match.start(1)
|
|
290
|
+
brace_count = 1
|
|
291
|
+
end_pos = start_pos + 1
|
|
292
|
+
|
|
293
|
+
# Find matching closing brace
|
|
294
|
+
while end_pos < len(script_content) and brace_count > 0:
|
|
295
|
+
char = script_content[end_pos]
|
|
296
|
+
if char == '{':
|
|
297
|
+
brace_count += 1
|
|
298
|
+
elif char == '}':
|
|
299
|
+
brace_count -= 1
|
|
300
|
+
end_pos += 1
|
|
301
|
+
|
|
302
|
+
if brace_count == 0:
|
|
303
|
+
# Include trailing semicolon if present
|
|
304
|
+
while end_pos < len(script_content) and script_content[end_pos] in [';', ' ', '\t']:
|
|
305
|
+
end_pos += 1
|
|
306
|
+
|
|
307
|
+
# Include trailing newlines
|
|
308
|
+
while end_pos < len(script_content) and script_content[end_pos] in ['\n', '\r']:
|
|
309
|
+
end_pos += 1
|
|
310
|
+
|
|
311
|
+
# Remove the export statement
|
|
312
|
+
remaining_content = script_content[:export_start] + script_content[end_pos:]
|
|
313
|
+
return remaining_content.strip()
|
|
314
|
+
|
|
315
|
+
return script_content
|
|
316
|
+
|
|
317
|
+
def _find_export_object(self, script_content):
|
|
318
|
+
"""Find and extract export object with proper brace matching"""
|
|
319
|
+
# Look for export or export default
|
|
320
|
+
export_pattern = r'export\s+(?:default\s+)?(\{)'
|
|
321
|
+
match = re.search(export_pattern, script_content, re.DOTALL)
|
|
322
|
+
|
|
323
|
+
if not match:
|
|
324
|
+
return None
|
|
325
|
+
|
|
326
|
+
start_pos = match.start(1)
|
|
327
|
+
brace_count = 1
|
|
328
|
+
end_pos = start_pos + 1
|
|
329
|
+
|
|
330
|
+
# Find matching closing brace
|
|
331
|
+
while end_pos < len(script_content) and brace_count > 0:
|
|
332
|
+
char = script_content[end_pos]
|
|
333
|
+
if char == '{':
|
|
334
|
+
brace_count += 1
|
|
335
|
+
elif char == '}':
|
|
336
|
+
brace_count -= 1
|
|
337
|
+
end_pos += 1
|
|
338
|
+
|
|
339
|
+
if brace_count == 0:
|
|
340
|
+
obj_content = script_content[start_pos:end_pos]
|
|
341
|
+
|
|
342
|
+
class MockMatch:
|
|
343
|
+
def group(self, n):
|
|
344
|
+
return obj_content
|
|
345
|
+
|
|
346
|
+
return MockMatch()
|
|
347
|
+
|
|
348
|
+
return None
|
|
349
|
+
|
|
350
|
+
def _is_setup_script(self, attributes):
|
|
351
|
+
"""Check if script has setup, view, or component attributes"""
|
|
352
|
+
if not attributes or 'attributes' not in attributes:
|
|
353
|
+
return False
|
|
354
|
+
|
|
355
|
+
script_attributes = attributes['attributes']
|
|
356
|
+
setup_attrs = ['setup', 'view', 'component']
|
|
357
|
+
|
|
358
|
+
# Check if any setup attribute exists
|
|
359
|
+
return any(attr in script_attributes for attr in setup_attrs)
|
|
360
|
+
|
|
361
|
+
def _extract_to_user_defined(self, obj_content):
|
|
362
|
+
"""Just store the raw object content - bê nguyên vào userDefined"""
|
|
363
|
+
obj_content = obj_content.strip()
|
|
364
|
+
|
|
365
|
+
# Chỉ cần store raw object content - main_compiler sẽ sử dụng trực tiếp
|
|
366
|
+
# Vì có thể có nhiều exports, ta merge vào một object
|
|
367
|
+
if obj_content.startswith('{') and obj_content.endswith('}'):
|
|
368
|
+
# Remove outer braces và merge content
|
|
369
|
+
inner_content = obj_content[1:-1].strip()
|
|
370
|
+
if inner_content:
|
|
371
|
+
# Nếu đã có userDefined content, merge với dấu phẩy
|
|
372
|
+
if hasattr(self, '_merged_content'):
|
|
373
|
+
self._merged_content += ",\n " + inner_content
|
|
374
|
+
else:
|
|
375
|
+
self._merged_content = inner_content
|
|
376
|
+
|
|
377
|
+
def get_scripts(self):
|
|
378
|
+
return self.scripts
|
|
379
|
+
|
|
380
|
+
def get_styles(self):
|
|
381
|
+
return self.styles
|
|
382
|
+
|
|
383
|
+
def get_user_defined(self):
|
|
384
|
+
return self.userDefined
|
|
385
|
+
|
|
386
|
+
def get_all_data(self):
|
|
387
|
+
# Get merged lifecycle object
|
|
388
|
+
lifecycle_obj = self.get_lifecycle_obj()
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
'scripts': self.scripts,
|
|
392
|
+
'styles': self.styles,
|
|
393
|
+
'userDefined': lifecycle_obj, # Raw object string
|
|
394
|
+
# Backward compatibility
|
|
395
|
+
'lifecycle': lifecycle_obj, # main_compiler expects 'lifecycle'
|
|
396
|
+
'setup': self.get_setup_script(),
|
|
397
|
+
'setupContent': self.get_setup_content(), # Content for file top
|
|
398
|
+
'sections': {},
|
|
399
|
+
'css': {
|
|
400
|
+
'inline': self.get_inline_css(),
|
|
401
|
+
'external': self.get_external_css()
|
|
402
|
+
},
|
|
403
|
+
'resources': self.get_resources()
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
# Backward compatibility methods
|
|
407
|
+
def get_lifecycle_obj(self):
|
|
408
|
+
# Return merged object content như format cũ
|
|
409
|
+
if hasattr(self, '_merged_content') and self._merged_content:
|
|
410
|
+
return "{\n " + self._merged_content + "\n}"
|
|
411
|
+
return self.userDefined if isinstance(self.userDefined, str) else "{}"
|
|
412
|
+
|
|
413
|
+
def get_setup_script(self):
|
|
414
|
+
code_scripts = [s['content'] for s in self.scripts if s['type'] == 'code']
|
|
415
|
+
return '\n\n'.join(code_scripts) if code_scripts else ""
|
|
416
|
+
|
|
417
|
+
def get_setup_content(self):
|
|
418
|
+
"""Get setup script content that should go to file top"""
|
|
419
|
+
return '\n\n'.join(self.setup_content) if self.setup_content else ""
|
|
420
|
+
|
|
421
|
+
def get_section_scripts(self):
|
|
422
|
+
return {}
|
|
423
|
+
|
|
424
|
+
def get_all_scripts(self, view_name=None):
|
|
425
|
+
return {
|
|
426
|
+
'lifecycle': self.userDefined,
|
|
427
|
+
'setup': self.get_setup_script(),
|
|
428
|
+
'scripts': self.scripts
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
def get_inline_css(self):
|
|
432
|
+
inline_css = [s['content'] for s in self.styles if s['type'] == 'code']
|
|
433
|
+
return '\n'.join(inline_css) if inline_css else ""
|
|
434
|
+
|
|
435
|
+
def get_external_css(self):
|
|
436
|
+
return [s['href'] for s in self.styles if s['type'] == 'href']
|
|
437
|
+
|
|
438
|
+
def get_resources(self):
|
|
439
|
+
resources = []
|
|
440
|
+
|
|
441
|
+
for script in self.scripts:
|
|
442
|
+
if script['type'] == 'src':
|
|
443
|
+
attrs = {'src': script['src']}
|
|
444
|
+
|
|
445
|
+
# Add other attributes
|
|
446
|
+
if 'attributes' in script:
|
|
447
|
+
attrs.update(script['attributes'])
|
|
448
|
+
if 'id' in script and script['id']:
|
|
449
|
+
attrs['id'] = script['id']
|
|
450
|
+
if 'className' in script and script['className']:
|
|
451
|
+
attrs['class'] = script['className']
|
|
452
|
+
|
|
453
|
+
resources.append({
|
|
454
|
+
'tag': 'script',
|
|
455
|
+
'uuid': f"script-{len(resources)}",
|
|
456
|
+
'attrs': attrs
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
for style in self.styles:
|
|
460
|
+
if style['type'] == 'href':
|
|
461
|
+
attrs = {
|
|
462
|
+
'rel': 'stylesheet',
|
|
463
|
+
'href': style['href']
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
# Add other attributes
|
|
467
|
+
if 'attributes' in style:
|
|
468
|
+
attrs.update(style['attributes'])
|
|
469
|
+
if 'id' in style and style['id']:
|
|
470
|
+
attrs['id'] = style['id']
|
|
471
|
+
if 'className' in style and style['className']:
|
|
472
|
+
attrs['class'] = style['className']
|
|
473
|
+
|
|
474
|
+
resources.append({
|
|
475
|
+
'tag': 'link',
|
|
476
|
+
'uuid': f"link-{len(resources)}",
|
|
477
|
+
'attrs': attrs
|
|
478
|
+
})
|
|
479
|
+
|
|
480
|
+
return resources
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Handlers cho các section directives (@section, @endsection, @yield, etc.)
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from config import JS_FUNCTION_PREFIX
|
|
6
|
+
from php_converter import php_to_js
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
class SectionHandlers:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
def process_section_directive(self, line, stack, output, sections):
|
|
14
|
+
"""Process @section directive"""
|
|
15
|
+
# Two parameter version
|
|
16
|
+
match_two = re.match(r'@section\s*\(\s*[\'"]([^\'"]*)[\'"]\s*,\s*(.*?)\s*\)', line, re.DOTALL)
|
|
17
|
+
if match_two and ',' in line:
|
|
18
|
+
section_name = match_two.group(1)
|
|
19
|
+
section_value_raw = match_two.group(2).strip()
|
|
20
|
+
|
|
21
|
+
# Handle string literals directly without php_to_js to avoid corruption
|
|
22
|
+
if ((section_value_raw.startswith("'") and section_value_raw.endswith("'")) or
|
|
23
|
+
(section_value_raw.startswith('"') and section_value_raw.endswith('"'))):
|
|
24
|
+
# Direct string literal - fix escaping
|
|
25
|
+
section_value = self._ensure_proper_escaping(section_value_raw)
|
|
26
|
+
else:
|
|
27
|
+
# Other expressions - use php_to_js
|
|
28
|
+
section_value = php_to_js(section_value_raw) if section_value_raw else "''"
|
|
29
|
+
section_value = self._ensure_proper_escaping(section_value)
|
|
30
|
+
|
|
31
|
+
result = '${' + JS_FUNCTION_PREFIX + '.section(\'' + section_name + '\', ' + section_value + ', \'string\')}'
|
|
32
|
+
output.append(result)
|
|
33
|
+
sections.append(result)
|
|
34
|
+
return True
|
|
35
|
+
|
|
36
|
+
# Single parameter version
|
|
37
|
+
match_one = re.match(r'@section\s*\(\s*[\'"]([^\'"]*)[\'"]|([^)]*)\s*\)', line)
|
|
38
|
+
if match_one:
|
|
39
|
+
section_name = match_one.group(1) or php_to_js(match_one.group(2))
|
|
40
|
+
stack.append(('section', section_name, len(output)))
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
def process_endsection_directive(self, stack, output, sections):
|
|
46
|
+
"""Process @endsection directive"""
|
|
47
|
+
if stack and stack[-1][0] == 'section':
|
|
48
|
+
_, section_name, start_idx = stack.pop()
|
|
49
|
+
# Filter out boolean values and join strings
|
|
50
|
+
section_content = '\n'.join([str(item) for item in output[start_idx:] if isinstance(item, str)])
|
|
51
|
+
output[:] = output[:start_idx] # Modify output in place
|
|
52
|
+
|
|
53
|
+
# Determine section type based on content
|
|
54
|
+
section_type = 'html' if self._contains_html_tags(section_content) else 'string'
|
|
55
|
+
|
|
56
|
+
section_line = '${' + JS_FUNCTION_PREFIX + '.section(\'' + section_name + '\', `' + section_content + '`, \'' + section_type + '\')}'
|
|
57
|
+
output.append(section_line)
|
|
58
|
+
sections.append(section_line)
|
|
59
|
+
return True
|
|
60
|
+
|
|
61
|
+
def process_block_directive(self, line, stack, output, sections):
|
|
62
|
+
"""Process @block directive - similar to @section"""
|
|
63
|
+
# Two parameter version: @block('name', attributes)
|
|
64
|
+
match_two = re.match(r'@block\s*\(\s*[\'"]([^\'"]*)[\'"]\s*,\s*(.*?)\s*\)', line)
|
|
65
|
+
if match_two and ',' in line:
|
|
66
|
+
block_name = match_two.group(1)
|
|
67
|
+
block_attributes = match_two.group(2)
|
|
68
|
+
block_attributes_js = php_to_js(block_attributes) if block_attributes else "{}"
|
|
69
|
+
result = '${this.__block(\'' + block_name + '\', ' + block_attributes_js + ', `'
|
|
70
|
+
stack.append(('block', block_name, len(output), block_attributes_js))
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
# Single parameter version: @block('name')
|
|
74
|
+
match_one = re.match(r'@block\s*\(\s*[\'"]([^\'"]*)[\'"]|([^)]*)\s*\)', line)
|
|
75
|
+
if match_one:
|
|
76
|
+
block_name = match_one.group(1) or php_to_js(match_one.group(2))
|
|
77
|
+
result = '${this.__block(\'' + block_name + '\', {}, `'
|
|
78
|
+
stack.append(('block', block_name, len(output), '{}'))
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def process_endblock_directive(self, stack, output, sections):
|
|
84
|
+
"""Process @endblock/@endBlock directive - similar to @endsection"""
|
|
85
|
+
if stack and stack[-1][0] == 'block':
|
|
86
|
+
_, block_name, start_idx, block_attributes = stack.pop()
|
|
87
|
+
# Filter out boolean values and join strings
|
|
88
|
+
block_content = '\n'.join([str(item) for item in output[start_idx:] if isinstance(item, str)])
|
|
89
|
+
output[:] = output[:start_idx] # Modify output in place
|
|
90
|
+
|
|
91
|
+
# Complete the block call
|
|
92
|
+
block_line = '${this.__block(\'' + block_name + '\', ' + block_attributes + ', `' + block_content + '`)}'
|
|
93
|
+
output.append(block_line)
|
|
94
|
+
sections.append(block_line)
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
def _contains_html_tags(self, content):
|
|
98
|
+
"""Check if content contains HTML tags"""
|
|
99
|
+
# Simple check for HTML tags
|
|
100
|
+
html_pattern = r'<[a-zA-Z][^>]*>'
|
|
101
|
+
return bool(re.search(html_pattern, content))
|
|
102
|
+
|
|
103
|
+
def _ensure_proper_escaping(self, section_value):
|
|
104
|
+
"""Ensure section value is properly escaped for JavaScript string literals"""
|
|
105
|
+
# If it's already a string literal (starts and ends with quotes), fix escaping
|
|
106
|
+
if ((section_value.startswith("'") and section_value.endswith("'")) or
|
|
107
|
+
(section_value.startswith('"') and section_value.endswith('"'))):
|
|
108
|
+
# Extract content and re-escape properly
|
|
109
|
+
quote_char = section_value[0]
|
|
110
|
+
content = section_value[1:-1] # Remove quotes
|
|
111
|
+
|
|
112
|
+
# Escape single quotes in content
|
|
113
|
+
if quote_char == "'":
|
|
114
|
+
content = content.replace("\\'", "'") # Unescape first
|
|
115
|
+
content = content.replace("'", "\\'") # Re-escape properly
|
|
116
|
+
else:
|
|
117
|
+
content = content.replace('\\"', '"') # Unescape first
|
|
118
|
+
content = content.replace('"', '\\"') # Re-escape properly
|
|
119
|
+
|
|
120
|
+
return quote_char + content + quote_char
|
|
121
|
+
|
|
122
|
+
return section_value
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Show Directive Handler - Xử lý @show directive
|
|
3
|
+
Similar to Vue's v-show - toggles element visibility with display property
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import re
|
|
7
|
+
from php_js_converter import php_to_js_advanced
|
|
8
|
+
|
|
9
|
+
class ShowDirectiveHandler:
|
|
10
|
+
def __init__(self, state_variables=None):
|
|
11
|
+
self.state_variables = state_variables or set()
|
|
12
|
+
|
|
13
|
+
def process_show_directive(self, content):
|
|
14
|
+
"""
|
|
15
|
+
Process @show directive
|
|
16
|
+
@show($isVisible) -> style="${this.__showBinding(['isVisible'], isVisible)}"
|
|
17
|
+
|
|
18
|
+
Similar to v-show in Vue - element always exists in DOM, just toggles display
|
|
19
|
+
|
|
20
|
+
Note: Supports multi-line directives within HTML tag attributes
|
|
21
|
+
"""
|
|
22
|
+
result = content
|
|
23
|
+
|
|
24
|
+
# Process all @show directives globally (supports multi-line)
|
|
25
|
+
while True:
|
|
26
|
+
match = re.search(r'@show\s*\(', result)
|
|
27
|
+
if not match:
|
|
28
|
+
break
|
|
29
|
+
|
|
30
|
+
# Find matching closing parenthesis (can span multiple lines)
|
|
31
|
+
start_pos = match.end() - 1
|
|
32
|
+
paren_count = 0
|
|
33
|
+
i = start_pos
|
|
34
|
+
|
|
35
|
+
while i < len(result):
|
|
36
|
+
if result[i] == '(':
|
|
37
|
+
paren_count += 1
|
|
38
|
+
elif result[i] == ')':
|
|
39
|
+
paren_count -= 1
|
|
40
|
+
if paren_count == 0:
|
|
41
|
+
# Found complete directive
|
|
42
|
+
expression = result[start_pos + 1:i].strip()
|
|
43
|
+
replacement = self._generate_show_output(expression)
|
|
44
|
+
result = result[:match.start()] + replacement + result[i + 1:]
|
|
45
|
+
break
|
|
46
|
+
i += 1
|
|
47
|
+
else:
|
|
48
|
+
# No matching closing paren found - skip this one
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
def _generate_show_output(self, expression):
|
|
54
|
+
"""
|
|
55
|
+
Generate reactive show binding output
|
|
56
|
+
|
|
57
|
+
Input: $isVisible
|
|
58
|
+
Output: style="${this.__showBinding(['isVisible'], isVisible)}"
|
|
59
|
+
"""
|
|
60
|
+
# Convert PHP expression to JS
|
|
61
|
+
js_expression = php_to_js_advanced(expression)
|
|
62
|
+
|
|
63
|
+
# Extract state variables
|
|
64
|
+
state_vars = self._extract_state_variables(expression)
|
|
65
|
+
|
|
66
|
+
if state_vars:
|
|
67
|
+
watch_keys = list(state_vars)
|
|
68
|
+
return f'style="${{this.__showBinding({watch_keys}, {js_expression})}}"'
|
|
69
|
+
else:
|
|
70
|
+
# No state vars, but still use binding for consistency
|
|
71
|
+
return f'style="${{this.__showBinding([], {js_expression})}}"'
|
|
72
|
+
|
|
73
|
+
def _extract_state_variables(self, expression):
|
|
74
|
+
"""
|
|
75
|
+
Extract state variable names from expression
|
|
76
|
+
"""
|
|
77
|
+
state_vars = set()
|
|
78
|
+
|
|
79
|
+
# Find all PHP variables ($varName)
|
|
80
|
+
var_matches = re.findall(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', expression)
|
|
81
|
+
for var_name in var_matches:
|
|
82
|
+
if var_name in self.state_variables:
|
|
83
|
+
state_vars.add(var_name)
|
|
84
|
+
|
|
85
|
+
return state_vars
|