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.
Files changed (119) hide show
  1. package/README.md +1 -1
  2. package/bin/onejs-build.js +32 -0
  3. package/package.json +11 -3
  4. package/scripts/README-template-compiler.md +133 -0
  5. package/scripts/README.md +61 -0
  6. package/scripts/__pycache__/build.cpython-314.pyc +0 -0
  7. package/scripts/__pycache__/compile.cpython-313.pyc +0 -0
  8. package/scripts/__pycache__/compile.cpython-314.pyc +0 -0
  9. package/scripts/build.py +573 -0
  10. package/scripts/check-system-errors.php +214 -0
  11. package/scripts/compile.py +101 -0
  12. package/scripts/compiler/README_CONFIG.md +196 -0
  13. package/scripts/compiler/__init__.py +18 -0
  14. package/scripts/compiler/__pycache__/__init__.cpython-313.pyc +0 -0
  15. package/scripts/compiler/__pycache__/__init__.cpython-314.pyc +0 -0
  16. package/scripts/compiler/__pycache__/binding_directive_service.cpython-314.pyc +0 -0
  17. package/scripts/compiler/__pycache__/class_binding_handler.cpython-314.pyc +0 -0
  18. package/scripts/compiler/__pycache__/compiler_utils.cpython-313.pyc +0 -0
  19. package/scripts/compiler/__pycache__/compiler_utils.cpython-314.pyc +0 -0
  20. package/scripts/compiler/__pycache__/conditional_handlers.cpython-313.pyc +0 -0
  21. package/scripts/compiler/__pycache__/conditional_handlers.cpython-314.pyc +0 -0
  22. package/scripts/compiler/__pycache__/config.cpython-313.pyc +0 -0
  23. package/scripts/compiler/__pycache__/config.cpython-314.pyc +0 -0
  24. package/scripts/compiler/__pycache__/declaration_tracker.cpython-314.pyc +0 -0
  25. package/scripts/compiler/__pycache__/directive_processors.cpython-313.pyc +0 -0
  26. package/scripts/compiler/__pycache__/directive_processors.cpython-314.pyc +0 -0
  27. package/scripts/compiler/__pycache__/echo_processor.cpython-314.pyc +0 -0
  28. package/scripts/compiler/__pycache__/event_directive_processor.cpython-313.pyc +0 -0
  29. package/scripts/compiler/__pycache__/event_directive_processor.cpython-314.pyc +0 -0
  30. package/scripts/compiler/__pycache__/function_generators.cpython-313.pyc +0 -0
  31. package/scripts/compiler/__pycache__/function_generators.cpython-314.pyc +0 -0
  32. package/scripts/compiler/__pycache__/loop_handlers.cpython-313.pyc +0 -0
  33. package/scripts/compiler/__pycache__/loop_handlers.cpython-314.pyc +0 -0
  34. package/scripts/compiler/__pycache__/main_compiler.cpython-313.pyc +0 -0
  35. package/scripts/compiler/__pycache__/main_compiler.cpython-314.pyc +0 -0
  36. package/scripts/compiler/__pycache__/parsers.cpython-313.pyc +0 -0
  37. package/scripts/compiler/__pycache__/parsers.cpython-314.pyc +0 -0
  38. package/scripts/compiler/__pycache__/php_converter.cpython-313.pyc +0 -0
  39. package/scripts/compiler/__pycache__/php_converter.cpython-314.pyc +0 -0
  40. package/scripts/compiler/__pycache__/php_js_converter.cpython-313.pyc +0 -0
  41. package/scripts/compiler/__pycache__/php_js_converter.cpython-314.pyc +0 -0
  42. package/scripts/compiler/__pycache__/register_parser.cpython-313.pyc +0 -0
  43. package/scripts/compiler/__pycache__/register_parser.cpython-314.pyc +0 -0
  44. package/scripts/compiler/__pycache__/section_handlers.cpython-313.pyc +0 -0
  45. package/scripts/compiler/__pycache__/section_handlers.cpython-314.pyc +0 -0
  46. package/scripts/compiler/__pycache__/show_directive_handler.cpython-314.pyc +0 -0
  47. package/scripts/compiler/__pycache__/style_directive_handler.cpython-314.pyc +0 -0
  48. package/scripts/compiler/__pycache__/template_analyzer.cpython-313.pyc +0 -0
  49. package/scripts/compiler/__pycache__/template_analyzer.cpython-314.pyc +0 -0
  50. package/scripts/compiler/__pycache__/template_processor.cpython-313.pyc +0 -0
  51. package/scripts/compiler/__pycache__/template_processor.cpython-314.pyc +0 -0
  52. package/scripts/compiler/__pycache__/template_processors.cpython-313.pyc +0 -0
  53. package/scripts/compiler/__pycache__/template_processors.cpython-314.pyc +0 -0
  54. package/scripts/compiler/__pycache__/utils.cpython-313.pyc +0 -0
  55. package/scripts/compiler/__pycache__/utils.cpython-314.pyc +0 -0
  56. package/scripts/compiler/__pycache__/wrapper_parser.cpython-313.pyc +0 -0
  57. package/scripts/compiler/__pycache__/wrapper_parser.cpython-314.pyc +0 -0
  58. package/scripts/compiler/binding_directive_service.py +103 -0
  59. package/scripts/compiler/class_binding_handler.py +347 -0
  60. package/scripts/compiler/cli.py +34 -0
  61. package/scripts/compiler/code_generator.py +141 -0
  62. package/scripts/compiler/compiler.config.json +36 -0
  63. package/scripts/compiler/compiler_utils.py +55 -0
  64. package/scripts/compiler/conditional_handlers.py +252 -0
  65. package/scripts/compiler/config.py +107 -0
  66. package/scripts/compiler/declaration_tracker.py +420 -0
  67. package/scripts/compiler/directive_processors.py +603 -0
  68. package/scripts/compiler/echo_processor.py +667 -0
  69. package/scripts/compiler/event_directive_processor.py +1099 -0
  70. package/scripts/compiler/fetch_parser.py +49 -0
  71. package/scripts/compiler/function_generators.py +310 -0
  72. package/scripts/compiler/loop_handlers.py +224 -0
  73. package/scripts/compiler/main_compiler.py +1763 -0
  74. package/scripts/compiler/parsers.py +1418 -0
  75. package/scripts/compiler/php_converter.py +470 -0
  76. package/scripts/compiler/php_js_converter.py +603 -0
  77. package/scripts/compiler/register_parser.py +480 -0
  78. package/scripts/compiler/section_handlers.py +122 -0
  79. package/scripts/compiler/show_directive_handler.py +85 -0
  80. package/scripts/compiler/style_directive_handler.py +169 -0
  81. package/scripts/compiler/template_analyzer.py +162 -0
  82. package/scripts/compiler/template_processor.py +1167 -0
  83. package/scripts/compiler/template_processors.py +1557 -0
  84. package/scripts/compiler/test_compiler.py +69 -0
  85. package/scripts/compiler/utils.py +54 -0
  86. package/scripts/compiler/variables_analyzer.py +135 -0
  87. package/scripts/compiler/view_identifier_generator.py +278 -0
  88. package/scripts/compiler/wrapper_parser.py +78 -0
  89. package/scripts/dev-context.js +311 -0
  90. package/scripts/dev.js +109 -0
  91. package/scripts/generate-assets-order.js +200 -0
  92. package/scripts/migrate-namespace.php +146 -0
  93. package/scripts/node/MIGRATION.md +190 -0
  94. package/scripts/node/README.md +269 -0
  95. package/scripts/node/build.js +208 -0
  96. package/scripts/node/compiler/compiler-utils.js +38 -0
  97. package/scripts/node/compiler/conditional-handlers.js +45 -0
  98. package/scripts/node/compiler/config.js +178 -0
  99. package/scripts/node/compiler/directive-processors.js +51 -0
  100. package/scripts/node/compiler/event-directive-processor.js +182 -0
  101. package/scripts/node/compiler/function-generators.js +239 -0
  102. package/scripts/node/compiler/loop-handlers.js +45 -0
  103. package/scripts/node/compiler/main-compiler.js +236 -0
  104. package/scripts/node/compiler/parsers.js +358 -0
  105. package/scripts/node/compiler/php-converter.js +227 -0
  106. package/scripts/node/compiler/register-parser.js +32 -0
  107. package/scripts/node/compiler/section-handlers.js +46 -0
  108. package/scripts/node/compiler/template-analyzer.js +50 -0
  109. package/scripts/node/compiler/template-processor.js +371 -0
  110. package/scripts/node/compiler/template-processors.js +219 -0
  111. package/scripts/node/compiler/utils.js +203 -0
  112. package/scripts/node/compiler/wrapper-parser.js +25 -0
  113. package/scripts/node/package.json +24 -0
  114. package/scripts/node/test-compiler.js +52 -0
  115. package/scripts/node-run.cjs +28 -0
  116. package/scripts/standardize-directories.php +92 -0
  117. package/templates/view.module.js +2 -0
  118. package/templates/view.tpl-raw.js +13 -0
  119. 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