quantum-framework 0.9.0__py3-none-any.whl
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.
- quantum/__init__.py +1 -0
- quantum/cli/__init__.py +2 -0
- quantum/cli/commands/__init__.py +15 -0
- quantum/cli/commands/build.py +417 -0
- quantum/cli/commands/dev.py +185 -0
- quantum/cli/commands/docs.py +329 -0
- quantum/cli/commands/lint.py +523 -0
- quantum/cli/commands/migrate.py +527 -0
- quantum/cli/commands/new.py +622 -0
- quantum/cli/commands/serve.py +190 -0
- quantum/cli/commands/test.py +193 -0
- quantum/cli/deploy.py +810 -0
- quantum/cli/hot_reload.py +951 -0
- quantum/cli/jobs.py +356 -0
- quantum/cli/mq.py +582 -0
- quantum/cli/pkg.py +390 -0
- quantum/cli/runner.py +547 -0
- quantum/cli/server_process.py +159 -0
- quantum/cli/utils.py +334 -0
- quantum/compiler/__init__.py +30 -0
- quantum/compiler/base_generator.py +367 -0
- quantum/compiler/cli.py +295 -0
- quantum/compiler/expression_transformer.py +444 -0
- quantum/compiler/javascript/__init__.py +10 -0
- quantum/compiler/javascript/generator.py +659 -0
- quantum/compiler/optimizer.py +270 -0
- quantum/compiler/python/__init__.py +10 -0
- quantum/compiler/python/generator.py +883 -0
- quantum/compiler/python/runtime.py +863 -0
- quantum/compiler/transpiler.py +330 -0
- quantum/core/__init__.py +3 -0
- quantum/core/ast_nodes.py +2611 -0
- quantum/core/expression_diagnostics.py +87 -0
- quantum/core/expression_stdlib.py +148 -0
- quantum/core/expressions.py +591 -0
- quantum/core/features/agents/src/__init__.py +30 -0
- quantum/core/features/agents/src/ast_node.py +540 -0
- quantum/core/features/conditionals/src/__init__.py +8 -0
- quantum/core/features/conditionals/src/ast_node.py +68 -0
- quantum/core/features/data_fetching/src/__init__.py +21 -0
- quantum/core/features/data_fetching/src/ast_node.py +312 -0
- quantum/core/features/data_fetching/src/desktop_adapter.py +351 -0
- quantum/core/features/data_fetching/src/html_adapter.py +474 -0
- quantum/core/features/data_fetching/src/parser.py +225 -0
- quantum/core/features/data_import/src/__init__.py +0 -0
- quantum/core/features/data_import/src/ast_node.py +291 -0
- quantum/core/features/data_import/src/runtime.py +538 -0
- quantum/core/features/dump/src/__init__.py +12 -0
- quantum/core/features/dump/src/ast_node.py +106 -0
- quantum/core/features/dump/src/parser.py +61 -0
- quantum/core/features/dump/src/runtime.py +246 -0
- quantum/core/features/functions/src/__init__.py +8 -0
- quantum/core/features/functions/src/ast_node.py +149 -0
- quantum/core/features/game_engine_2d/src/__init__.py +19 -0
- quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
- quantum/core/features/game_engine_2d/src/parser.py +983 -0
- quantum/core/features/invocation/src/__init__.py +0 -0
- quantum/core/features/invocation/src/ast_node.py +146 -0
- quantum/core/features/invocation/src/runtime.py +327 -0
- quantum/core/features/knowledge_base/src/__init__.py +6 -0
- quantum/core/features/knowledge_base/src/ast_node.py +113 -0
- quantum/core/features/knowledge_base/src/parser.py +82 -0
- quantum/core/features/logging/src/__init__.py +12 -0
- quantum/core/features/logging/src/ast_node.py +111 -0
- quantum/core/features/logging/src/parser.py +50 -0
- quantum/core/features/logging/src/runtime.py +190 -0
- quantum/core/features/loops/src/__init__.py +8 -0
- quantum/core/features/loops/src/ast_node.py +60 -0
- quantum/core/features/query/src/__init__.py +0 -0
- quantum/core/features/query/src/database_service.py +322 -0
- quantum/core/features/query/src/query_validators.py +20 -0
- quantum/core/features/state_management/src/__init__.py +11 -0
- quantum/core/features/state_management/src/ast_node.py +228 -0
- quantum/core/features/terminal_engine/src/__init__.py +21 -0
- quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
- quantum/core/features/terminal_engine/src/parser.py +361 -0
- quantum/core/features/testing_engine/src/__init__.py +41 -0
- quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
- quantum/core/features/testing_engine/src/parser.py +604 -0
- quantum/core/features/theming/src/__init__.py +48 -0
- quantum/core/features/theming/src/ast_node.py +137 -0
- quantum/core/features/theming/src/presets.py +405 -0
- quantum/core/features/ui_engine/src/__init__.py +20 -0
- quantum/core/features/ui_engine/src/ast_nodes.py +1854 -0
- quantum/core/features/ui_engine/src/parser.py +1106 -0
- quantum/core/features/websocket/src/__init__.py +24 -0
- quantum/core/features/websocket/src/ast_node.py +247 -0
- quantum/core/html_compat.py +299 -0
- quantum/core/parser.py +1235 -0
- quantum/core/parser_registry.py +213 -0
- quantum/core/parsers/__init__.py +76 -0
- quantum/core/parsers/ai/__init__.py +12 -0
- quantum/core/parsers/ai/agent_parser.py +106 -0
- quantum/core/parsers/ai/knowledge_parser.py +86 -0
- quantum/core/parsers/ai/llm_parser.py +78 -0
- quantum/core/parsers/ai/team_parser.py +88 -0
- quantum/core/parsers/base.py +322 -0
- quantum/core/parsers/composition/__init__.py +10 -0
- quantum/core/parsers/composition/import_parser.py +50 -0
- quantum/core/parsers/composition/slot_parser.py +48 -0
- quantum/core/parsers/control_flow/__init__.py +11 -0
- quantum/core/parsers/control_flow/if_parser.py +68 -0
- quantum/core/parsers/control_flow/loop_parser.py +105 -0
- quantum/core/parsers/control_flow/set_parser.py +89 -0
- quantum/core/parsers/data/__init__.py +12 -0
- quantum/core/parsers/data/data_parser.py +217 -0
- quantum/core/parsers/data/invoke_parser.py +116 -0
- quantum/core/parsers/data/query_parser.py +188 -0
- quantum/core/parsers/data/transaction_parser.py +77 -0
- quantum/core/parsers/events/__init__.py +9 -0
- quantum/core/parsers/events/dispatch_event_parser.py +44 -0
- quantum/core/parsers/forms/__init__.py +11 -0
- quantum/core/parsers/forms/action_parser.py +54 -0
- quantum/core/parsers/forms/flash_parser.py +34 -0
- quantum/core/parsers/forms/redirect_parser.py +33 -0
- quantum/core/parsers/functions/__init__.py +11 -0
- quantum/core/parsers/functions/function_parser.py +137 -0
- quantum/core/parsers/functions/param_parser.py +66 -0
- quantum/core/parsers/functions/return_parser.py +31 -0
- quantum/core/parsers/html/__init__.py +10 -0
- quantum/core/parsers/html/component_call_parser.py +130 -0
- quantum/core/parsers/html/html_parser.py +115 -0
- quantum/core/parsers/jobs/__init__.py +11 -0
- quantum/core/parsers/jobs/job_parser.py +71 -0
- quantum/core/parsers/jobs/schedule_parser.py +62 -0
- quantum/core/parsers/jobs/thread_parser.py +57 -0
- quantum/core/parsers/messaging/__init__.py +17 -0
- quantum/core/parsers/messaging/message_ack_parser.py +30 -0
- quantum/core/parsers/messaging/message_nack_parser.py +30 -0
- quantum/core/parsers/messaging/message_parser.py +114 -0
- quantum/core/parsers/messaging/queue_parser.py +61 -0
- quantum/core/parsers/messaging/websocket_parser.py +121 -0
- quantum/core/parsers/persistence/__init__.py +9 -0
- quantum/core/parsers/persistence/persist_parser.py +64 -0
- quantum/core/parsers/routing/__init__.py +9 -0
- quantum/core/parsers/routing/route_parser.py +41 -0
- quantum/core/parsers/scripting/__init__.py +12 -0
- quantum/core/parsers/scripting/pyclass_parser.py +62 -0
- quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
- quantum/core/parsers/scripting/pyimport_parser.py +52 -0
- quantum/core/parsers/scripting/python_parser.py +49 -0
- quantum/core/parsers/services/__init__.py +12 -0
- quantum/core/parsers/services/dump_parser.py +52 -0
- quantum/core/parsers/services/file_parser.py +48 -0
- quantum/core/parsers/services/log_parser.py +46 -0
- quantum/core/parsers/services/mail_parser.py +65 -0
- quantum/core/tiers.py +82 -0
- quantum/packages/__init__.py +28 -0
- quantum/packages/manager.py +413 -0
- quantum/packages/manifest.py +351 -0
- quantum/packages/registry.py +399 -0
- quantum/packages/resolver.py +336 -0
- quantum/plugins/__init__.py +33 -0
- quantum/plugins/hooks.py +329 -0
- quantum/plugins/loader.py +479 -0
- quantum/plugins/manifest.py +336 -0
- quantum/plugins/registry.py +371 -0
- quantum/runtime/__init__.py +28 -0
- quantum/runtime/action_handler.py +443 -0
- quantum/runtime/adapters/__init__.py +88 -0
- quantum/runtime/adapters/memory_adapter.py +690 -0
- quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
- quantum/runtime/adapters/redis_adapter.py +582 -0
- quantum/runtime/adapters/sqlite_adapter.py +414 -0
- quantum/runtime/agent_service.py +1133 -0
- quantum/runtime/api_server.py +86 -0
- quantum/runtime/ast_cache.py +506 -0
- quantum/runtime/auth_service.py +267 -0
- quantum/runtime/component.py +990 -0
- quantum/runtime/component_composer.py +319 -0
- quantum/runtime/component_resolver.py +174 -0
- quantum/runtime/database_service.py +598 -0
- quantum/runtime/email_service.py +162 -0
- quantum/runtime/error_handler.py +295 -0
- quantum/runtime/execution_context.py +286 -0
- quantum/runtime/executor_registry.py +171 -0
- quantum/runtime/executors/__init__.py +71 -0
- quantum/runtime/executors/ai/__init__.py +12 -0
- quantum/runtime/executors/ai/agent_executor.py +217 -0
- quantum/runtime/executors/ai/knowledge_executor.py +114 -0
- quantum/runtime/executors/ai/llm_executor.py +153 -0
- quantum/runtime/executors/ai/team_executor.py +171 -0
- quantum/runtime/executors/base.py +262 -0
- quantum/runtime/executors/control_flow/__init__.py +11 -0
- quantum/runtime/executors/control_flow/if_executor.py +93 -0
- quantum/runtime/executors/control_flow/loop_executor.py +307 -0
- quantum/runtime/executors/control_flow/set_executor.py +412 -0
- quantum/runtime/executors/data/__init__.py +12 -0
- quantum/runtime/executors/data/data_executor.py +145 -0
- quantum/runtime/executors/data/invoke_executor.py +176 -0
- quantum/runtime/executors/data/query_executor.py +256 -0
- quantum/runtime/executors/data/transaction_executor.py +91 -0
- quantum/runtime/executors/jobs/__init__.py +11 -0
- quantum/runtime/executors/jobs/job_executor.py +190 -0
- quantum/runtime/executors/jobs/schedule_executor.py +132 -0
- quantum/runtime/executors/jobs/thread_executor.py +127 -0
- quantum/runtime/executors/messaging/__init__.py +17 -0
- quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
- quantum/runtime/executors/messaging/message_executor.py +174 -0
- quantum/runtime/executors/messaging/queue_executor.py +103 -0
- quantum/runtime/executors/messaging/websocket_executor.py +197 -0
- quantum/runtime/executors/scripting/__init__.py +11 -0
- quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
- quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
- quantum/runtime/executors/scripting/python_executor.py +249 -0
- quantum/runtime/executors/services/__init__.py +12 -0
- quantum/runtime/executors/services/dump_executor.py +72 -0
- quantum/runtime/executors/services/file_executor.py +89 -0
- quantum/runtime/executors/services/log_executor.py +77 -0
- quantum/runtime/executors/services/mail_executor.py +81 -0
- quantum/runtime/expression_cache.py +498 -0
- quantum/runtime/file_upload_service.py +326 -0
- quantum/runtime/function_registry.py +118 -0
- quantum/runtime/game_builder.py +166 -0
- quantum/runtime/game_code_generator.py +2371 -0
- quantum/runtime/game_templates.py +2006 -0
- quantum/runtime/godot_code_generator.py +4681 -0
- quantum/runtime/godot_templates.py +1449 -0
- quantum/runtime/job_executor.py +1599 -0
- quantum/runtime/knowledge_service.py +500 -0
- quantum/runtime/llm_cache.py +100 -0
- quantum/runtime/llm_providers.py +704 -0
- quantum/runtime/llm_service.py +287 -0
- quantum/runtime/logging_setup.py +140 -0
- quantum/runtime/message_broker.py +364 -0
- quantum/runtime/message_queue_service.py +571 -0
- quantum/runtime/param_validation.py +184 -0
- quantum/runtime/pypy_compat.py +315 -0
- quantum/runtime/python_bridge.py +698 -0
- quantum/runtime/query_validators.py +304 -0
- quantum/runtime/renderer.py +733 -0
- quantum/runtime/service_container.py +444 -0
- quantum/runtime/terminal_builder.py +76 -0
- quantum/runtime/terminal_code_generator.py +607 -0
- quantum/runtime/terminal_templates.py +243 -0
- quantum/runtime/testing_builder.py +77 -0
- quantum/runtime/testing_code_generator.py +833 -0
- quantum/runtime/testing_templates.py +85 -0
- quantum/runtime/ui_builder.py +188 -0
- quantum/runtime/ui_desktop_adapter.py +1730 -0
- quantum/runtime/ui_desktop_templates.py +307 -0
- quantum/runtime/ui_html_adapter.py +2691 -0
- quantum/runtime/ui_html_templates.py +2297 -0
- quantum/runtime/ui_mobile_adapter.py +1832 -0
- quantum/runtime/ui_mobile_templates.py +1003 -0
- quantum/runtime/ui_textual_adapter.py +1866 -0
- quantum/runtime/ui_textual_templates.py +45 -0
- quantum/runtime/ui_tokens.py +465 -0
- quantum/runtime/ui_validator.py +365 -0
- quantum/runtime/validators.py +256 -0
- quantum/runtime/web_server.py +1766 -0
- quantum/runtime/websocket_adapter.py +501 -0
- quantum/runtime/websocket_service.py +585 -0
- quantum/runtime/websocket_transport.py +289 -0
- quantum/runtime/wsgi.py +101 -0
- quantum/utils/__init__.py +1 -0
- quantum_framework-0.9.0.dist-info/METADATA +244 -0
- quantum_framework-0.9.0.dist-info/RECORD +262 -0
- quantum_framework-0.9.0.dist-info/WHEEL +5 -0
- quantum_framework-0.9.0.dist-info/entry_points.txt +2 -0
- quantum_framework-0.9.0.dist-info/licenses/LICENSE +21 -0
- quantum_framework-0.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Fetching Feature - HTML Adapter
|
|
3
|
+
Generates JavaScript fetch() code with state management for the HTML target.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Optional
|
|
7
|
+
from .ast_node import FetchNode, FetchLoadingNode, FetchErrorNode, FetchSuccessNode
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# JavaScript code for the fetch state management system
|
|
11
|
+
FETCH_STATE_JS = '''
|
|
12
|
+
<!-- Quantum Fetch State Management -->
|
|
13
|
+
<script>
|
|
14
|
+
(function() {
|
|
15
|
+
// Fetch cache storage
|
|
16
|
+
window.__qFetchCache = window.__qFetchCache || {};
|
|
17
|
+
|
|
18
|
+
// Fetch state storage
|
|
19
|
+
window.__qFetchState = window.__qFetchState || {};
|
|
20
|
+
|
|
21
|
+
// Active abort controllers
|
|
22
|
+
window.__qFetchControllers = window.__qFetchControllers || {};
|
|
23
|
+
|
|
24
|
+
// Polling intervals
|
|
25
|
+
window.__qFetchIntervals = window.__qFetchIntervals || {};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Execute a fetch request with state management
|
|
29
|
+
* @param {string} fetchId - Unique ID for this fetch
|
|
30
|
+
* @param {Object} options - Fetch configuration
|
|
31
|
+
*/
|
|
32
|
+
window.__qFetch = function(fetchId, options) {
|
|
33
|
+
const {
|
|
34
|
+
name,
|
|
35
|
+
url,
|
|
36
|
+
method = 'GET',
|
|
37
|
+
headers = {},
|
|
38
|
+
body = null,
|
|
39
|
+
cache = null,
|
|
40
|
+
cacheKey = null,
|
|
41
|
+
transform = null,
|
|
42
|
+
timeout = 30000,
|
|
43
|
+
retry = 0,
|
|
44
|
+
retryDelay = 1000,
|
|
45
|
+
interval = null,
|
|
46
|
+
onSuccess = null,
|
|
47
|
+
onError = null,
|
|
48
|
+
credentials = null,
|
|
49
|
+
responseFormat = 'auto'
|
|
50
|
+
} = options;
|
|
51
|
+
|
|
52
|
+
// Generate cache key
|
|
53
|
+
const cKey = cacheKey || `${method}:${url}`;
|
|
54
|
+
|
|
55
|
+
// Check cache first
|
|
56
|
+
if (cache && window.__qFetchCache[cKey]) {
|
|
57
|
+
const cached = window.__qFetchCache[cKey];
|
|
58
|
+
if (Date.now() < cached.expires) {
|
|
59
|
+
__qFetchSetState(fetchId, name, { loading: false, error: null, data: cached.data });
|
|
60
|
+
__qFetchUpdateUI(fetchId, 'success');
|
|
61
|
+
return Promise.resolve(cached.data);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Abort any existing request for this fetch
|
|
66
|
+
if (window.__qFetchControllers[fetchId]) {
|
|
67
|
+
window.__qFetchControllers[fetchId].abort();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Create abort controller
|
|
71
|
+
const controller = new AbortController();
|
|
72
|
+
window.__qFetchControllers[fetchId] = controller;
|
|
73
|
+
|
|
74
|
+
// Set loading state
|
|
75
|
+
__qFetchSetState(fetchId, name, { loading: true, error: null });
|
|
76
|
+
__qFetchUpdateUI(fetchId, 'loading');
|
|
77
|
+
|
|
78
|
+
// Build fetch options
|
|
79
|
+
const fetchOptions = {
|
|
80
|
+
method: method,
|
|
81
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
82
|
+
signal: controller.signal
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (body && method !== 'GET' && method !== 'HEAD') {
|
|
86
|
+
fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (credentials) {
|
|
90
|
+
fetchOptions.credentials = credentials;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Timeout promise
|
|
94
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
95
|
+
|
|
96
|
+
// Execute fetch with retry support
|
|
97
|
+
const doFetch = (attempt = 0) => {
|
|
98
|
+
return fetch(url, fetchOptions)
|
|
99
|
+
.then(response => {
|
|
100
|
+
clearTimeout(timeoutId);
|
|
101
|
+
if (!response.ok) {
|
|
102
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Parse response based on format
|
|
106
|
+
if (responseFormat === 'text') {
|
|
107
|
+
return response.text();
|
|
108
|
+
} else if (responseFormat === 'blob') {
|
|
109
|
+
return response.blob();
|
|
110
|
+
} else {
|
|
111
|
+
// Auto-detect: try JSON first
|
|
112
|
+
const contentType = response.headers.get('content-type');
|
|
113
|
+
if (contentType && contentType.includes('application/json')) {
|
|
114
|
+
return response.json();
|
|
115
|
+
}
|
|
116
|
+
return response.text().then(text => {
|
|
117
|
+
try { return JSON.parse(text); } catch { return text; }
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
})
|
|
121
|
+
.then(data => {
|
|
122
|
+
// Apply transform if specified
|
|
123
|
+
if (transform && typeof transform === 'function') {
|
|
124
|
+
data = transform(data);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Cache the result
|
|
128
|
+
if (cache) {
|
|
129
|
+
const cacheSeconds = __qFetchParseDuration(cache);
|
|
130
|
+
if (cacheSeconds > 0) {
|
|
131
|
+
window.__qFetchCache[cKey] = {
|
|
132
|
+
data: data,
|
|
133
|
+
expires: Date.now() + (cacheSeconds * 1000)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Update state
|
|
139
|
+
__qFetchSetState(fetchId, name, { loading: false, error: null, data: data });
|
|
140
|
+
__qFetchUpdateUI(fetchId, 'success');
|
|
141
|
+
|
|
142
|
+
// Call success callback
|
|
143
|
+
if (onSuccess && typeof window[onSuccess] === 'function') {
|
|
144
|
+
window[onSuccess](data);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return data;
|
|
148
|
+
})
|
|
149
|
+
.catch(error => {
|
|
150
|
+
clearTimeout(timeoutId);
|
|
151
|
+
|
|
152
|
+
// Handle abort
|
|
153
|
+
if (error.name === 'AbortError') {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Retry logic
|
|
158
|
+
if (attempt < retry) {
|
|
159
|
+
return new Promise(resolve => {
|
|
160
|
+
setTimeout(() => resolve(doFetch(attempt + 1)), retryDelay);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Update error state
|
|
165
|
+
const errorMsg = error.message || 'Fetch failed';
|
|
166
|
+
__qFetchSetState(fetchId, name, { loading: false, error: errorMsg, data: null });
|
|
167
|
+
__qFetchUpdateUI(fetchId, 'error');
|
|
168
|
+
|
|
169
|
+
// Call error callback
|
|
170
|
+
if (onError && typeof window[onError] === 'function') {
|
|
171
|
+
window[onError](errorMsg);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
throw error;
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const fetchPromise = doFetch();
|
|
179
|
+
|
|
180
|
+
// Set up polling if interval specified
|
|
181
|
+
if (interval) {
|
|
182
|
+
const intervalMs = __qFetchParseDuration(interval) * 1000;
|
|
183
|
+
if (intervalMs > 0) {
|
|
184
|
+
// Clear existing interval
|
|
185
|
+
if (window.__qFetchIntervals[fetchId]) {
|
|
186
|
+
clearInterval(window.__qFetchIntervals[fetchId]);
|
|
187
|
+
}
|
|
188
|
+
window.__qFetchIntervals[fetchId] = setInterval(() => {
|
|
189
|
+
__qFetch(fetchId, options);
|
|
190
|
+
}, intervalMs);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return fetchPromise;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Set fetch state and update global variable
|
|
199
|
+
*/
|
|
200
|
+
function __qFetchSetState(fetchId, name, state) {
|
|
201
|
+
window.__qFetchState[fetchId] = { ...window.__qFetchState[fetchId], ...state };
|
|
202
|
+
|
|
203
|
+
// Update global variable with data
|
|
204
|
+
if (state.data !== undefined) {
|
|
205
|
+
window[name] = state.data;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Update error variable
|
|
209
|
+
window[name + '_error'] = state.error;
|
|
210
|
+
window[name + '_loading'] = state.loading;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Update UI based on fetch state
|
|
215
|
+
*/
|
|
216
|
+
function __qFetchUpdateUI(fetchId, state) {
|
|
217
|
+
const loadingEl = document.getElementById(fetchId + '-loading');
|
|
218
|
+
const errorEl = document.getElementById(fetchId + '-error');
|
|
219
|
+
const successEl = document.getElementById(fetchId + '-success');
|
|
220
|
+
|
|
221
|
+
if (loadingEl) loadingEl.style.display = state === 'loading' ? '' : 'none';
|
|
222
|
+
if (errorEl) errorEl.style.display = state === 'error' ? '' : 'none';
|
|
223
|
+
if (successEl) successEl.style.display = state === 'success' ? '' : 'none';
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Parse duration string to seconds
|
|
228
|
+
*/
|
|
229
|
+
function __qFetchParseDuration(duration) {
|
|
230
|
+
if (!duration) return 0;
|
|
231
|
+
const str = String(duration).toLowerCase().trim();
|
|
232
|
+
const match = str.match(/^(\\d+)(ms|s|m|h)?$/);
|
|
233
|
+
if (match) {
|
|
234
|
+
const value = parseInt(match[1]);
|
|
235
|
+
const unit = match[2] || 's';
|
|
236
|
+
const multipliers = { ms: 0.001, s: 1, m: 60, h: 3600 };
|
|
237
|
+
return value * (multipliers[unit] || 1);
|
|
238
|
+
}
|
|
239
|
+
return parseInt(str) || 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Abort a fetch request
|
|
244
|
+
*/
|
|
245
|
+
window.__qFetchAbort = function(fetchId) {
|
|
246
|
+
if (window.__qFetchControllers[fetchId]) {
|
|
247
|
+
window.__qFetchControllers[fetchId].abort();
|
|
248
|
+
delete window.__qFetchControllers[fetchId];
|
|
249
|
+
}
|
|
250
|
+
if (window.__qFetchIntervals[fetchId]) {
|
|
251
|
+
clearInterval(window.__qFetchIntervals[fetchId]);
|
|
252
|
+
delete window.__qFetchIntervals[fetchId];
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Refetch data for a specific fetch
|
|
258
|
+
*/
|
|
259
|
+
window.__qFetchRefetch = function(fetchId) {
|
|
260
|
+
const state = window.__qFetchState[fetchId];
|
|
261
|
+
if (state && state._options) {
|
|
262
|
+
return __qFetch(fetchId, state._options);
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
})();
|
|
267
|
+
</script>
|
|
268
|
+
'''
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class FetchHtmlAdapter:
|
|
272
|
+
"""Generates HTML/JS for q:fetch nodes."""
|
|
273
|
+
|
|
274
|
+
def __init__(self, html_adapter=None):
|
|
275
|
+
"""
|
|
276
|
+
Initialize the fetch HTML adapter.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
html_adapter: Reference to the parent UIHtmlAdapter for rendering children
|
|
280
|
+
"""
|
|
281
|
+
self._html_adapter = html_adapter
|
|
282
|
+
self._fetch_counter = 0
|
|
283
|
+
self._has_fetch = False
|
|
284
|
+
|
|
285
|
+
def render_fetch(self, node: FetchNode, builder) -> str:
|
|
286
|
+
"""
|
|
287
|
+
Render a FetchNode to HTML with JavaScript fetch code.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
node: FetchNode to render
|
|
291
|
+
builder: HtmlBuilder instance
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
Generated HTML string
|
|
295
|
+
"""
|
|
296
|
+
self._has_fetch = True
|
|
297
|
+
self._fetch_counter += 1
|
|
298
|
+
|
|
299
|
+
fetch_id = f"qfetch-{self._fetch_counter}"
|
|
300
|
+
|
|
301
|
+
# Build container div
|
|
302
|
+
builder.open_tag('div', {'class': 'q-fetch', 'id': fetch_id, 'data-fetch-name': node.name})
|
|
303
|
+
builder.indent()
|
|
304
|
+
|
|
305
|
+
# Render loading state
|
|
306
|
+
loading_id = f"{fetch_id}-loading"
|
|
307
|
+
builder.open_tag('div', {'id': loading_id, 'class': 'q-fetch-loading', 'style': 'display: none'})
|
|
308
|
+
builder.indent()
|
|
309
|
+
if node.loading_node:
|
|
310
|
+
self._render_children(node.loading_node.children, builder)
|
|
311
|
+
else:
|
|
312
|
+
# Default loading indicator
|
|
313
|
+
builder.open_tag('div', {'class': 'q-loading-spinner'})
|
|
314
|
+
builder.text('Loading...')
|
|
315
|
+
builder.close_tag('div')
|
|
316
|
+
builder.dedent()
|
|
317
|
+
builder.close_tag('div')
|
|
318
|
+
|
|
319
|
+
# Render error state
|
|
320
|
+
error_id = f"{fetch_id}-error"
|
|
321
|
+
builder.open_tag('div', {'id': error_id, 'class': 'q-fetch-error', 'style': 'display: none'})
|
|
322
|
+
builder.indent()
|
|
323
|
+
if node.error_node:
|
|
324
|
+
self._render_children(node.error_node.children, builder)
|
|
325
|
+
else:
|
|
326
|
+
# Default error display
|
|
327
|
+
builder.open_tag('div', {'class': 'q-error-message'})
|
|
328
|
+
builder.text('{' + node.name + '_error}')
|
|
329
|
+
builder.close_tag('div')
|
|
330
|
+
builder.dedent()
|
|
331
|
+
builder.close_tag('div')
|
|
332
|
+
|
|
333
|
+
# Render success state
|
|
334
|
+
success_id = f"{fetch_id}-success"
|
|
335
|
+
builder.open_tag('div', {'id': success_id, 'class': 'q-fetch-success', 'style': 'display: none'})
|
|
336
|
+
builder.indent()
|
|
337
|
+
if node.success_node:
|
|
338
|
+
self._render_children(node.success_node.children, builder)
|
|
339
|
+
elif node.fallback_children:
|
|
340
|
+
self._render_children(node.fallback_children, builder)
|
|
341
|
+
builder.dedent()
|
|
342
|
+
builder.close_tag('div')
|
|
343
|
+
|
|
344
|
+
builder.dedent()
|
|
345
|
+
builder.close_tag('div')
|
|
346
|
+
|
|
347
|
+
# Generate fetch invocation script
|
|
348
|
+
builder.raw_html(self._generate_fetch_script(fetch_id, node))
|
|
349
|
+
|
|
350
|
+
def _render_children(self, children, builder):
|
|
351
|
+
"""Render child nodes using the parent HTML adapter."""
|
|
352
|
+
if self._html_adapter:
|
|
353
|
+
for child in children:
|
|
354
|
+
self._html_adapter._render_node(child, builder)
|
|
355
|
+
else:
|
|
356
|
+
# Fallback: render as text
|
|
357
|
+
for child in children:
|
|
358
|
+
if hasattr(child, 'content'):
|
|
359
|
+
builder.text(child.content)
|
|
360
|
+
|
|
361
|
+
def _generate_fetch_script(self, fetch_id: str, node: FetchNode) -> str:
|
|
362
|
+
"""Generate JavaScript to execute the fetch on load."""
|
|
363
|
+
# Build headers object
|
|
364
|
+
headers_js = '{'
|
|
365
|
+
header_parts = []
|
|
366
|
+
for header in node.headers:
|
|
367
|
+
header_parts.append(f"'{header.name}': '{header.value}'")
|
|
368
|
+
headers_js += ', '.join(header_parts) + '}'
|
|
369
|
+
|
|
370
|
+
# Build options object
|
|
371
|
+
options = []
|
|
372
|
+
options.append(f"name: '{node.name}'")
|
|
373
|
+
options.append(f"url: '{node.url}'")
|
|
374
|
+
options.append(f"method: '{node.method}'")
|
|
375
|
+
options.append(f"headers: {headers_js}")
|
|
376
|
+
|
|
377
|
+
if node.body:
|
|
378
|
+
options.append(f"body: {node.body}")
|
|
379
|
+
|
|
380
|
+
if node.cache:
|
|
381
|
+
options.append(f"cache: '{node.cache}'")
|
|
382
|
+
|
|
383
|
+
if node.cache_key:
|
|
384
|
+
options.append(f"cacheKey: '{node.cache_key}'")
|
|
385
|
+
|
|
386
|
+
if node.interval:
|
|
387
|
+
options.append(f"interval: '{node.interval}'")
|
|
388
|
+
|
|
389
|
+
if node.timeout != 30000:
|
|
390
|
+
options.append(f"timeout: {node.timeout}")
|
|
391
|
+
|
|
392
|
+
if node.retry > 0:
|
|
393
|
+
options.append(f"retry: {node.retry}")
|
|
394
|
+
options.append(f"retryDelay: {node.retry_delay}")
|
|
395
|
+
|
|
396
|
+
if node.transform:
|
|
397
|
+
options.append(f"transform: {node.transform}")
|
|
398
|
+
|
|
399
|
+
if node.on_success:
|
|
400
|
+
options.append(f"onSuccess: '{node.on_success}'")
|
|
401
|
+
|
|
402
|
+
if node.on_error:
|
|
403
|
+
options.append(f"onError: '{node.on_error}'")
|
|
404
|
+
|
|
405
|
+
if node.credentials:
|
|
406
|
+
options.append(f"credentials: '{node.credentials}'")
|
|
407
|
+
|
|
408
|
+
if node.response_format != 'auto':
|
|
409
|
+
options.append(f"responseFormat: '{node.response_format}'")
|
|
410
|
+
|
|
411
|
+
options_js = '{' + ', '.join(options) + '}'
|
|
412
|
+
|
|
413
|
+
# Generate script
|
|
414
|
+
script = f'''
|
|
415
|
+
<script>
|
|
416
|
+
document.addEventListener('DOMContentLoaded', function() {{
|
|
417
|
+
__qFetch('{fetch_id}', {options_js});
|
|
418
|
+
}});
|
|
419
|
+
</script>
|
|
420
|
+
'''
|
|
421
|
+
return script
|
|
422
|
+
|
|
423
|
+
def get_fetch_js(self) -> str:
|
|
424
|
+
"""Get the fetch state management JavaScript if needed."""
|
|
425
|
+
if self._has_fetch:
|
|
426
|
+
return FETCH_STATE_JS
|
|
427
|
+
return ''
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# CSS for fetch states
|
|
431
|
+
FETCH_CSS = '''
|
|
432
|
+
/* Quantum Fetch Styles */
|
|
433
|
+
.q-fetch {
|
|
434
|
+
position: relative;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.q-fetch-loading {
|
|
438
|
+
opacity: 0.7;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.q-loading-spinner {
|
|
442
|
+
display: flex;
|
|
443
|
+
align-items: center;
|
|
444
|
+
justify-content: center;
|
|
445
|
+
padding: 1rem;
|
|
446
|
+
color: var(--q-text-secondary, #666);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.q-loading-spinner::before {
|
|
450
|
+
content: '';
|
|
451
|
+
width: 1rem;
|
|
452
|
+
height: 1rem;
|
|
453
|
+
margin-right: 0.5rem;
|
|
454
|
+
border: 2px solid currentColor;
|
|
455
|
+
border-top-color: transparent;
|
|
456
|
+
border-radius: 50%;
|
|
457
|
+
animation: q-spin 0.75s linear infinite;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
@keyframes q-spin {
|
|
461
|
+
to { transform: rotate(360deg); }
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
.q-fetch-error {
|
|
465
|
+
color: var(--q-error, #dc2626);
|
|
466
|
+
padding: 0.5rem;
|
|
467
|
+
background: var(--q-error-bg, #fef2f2);
|
|
468
|
+
border-radius: var(--q-radius, 4px);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.q-fetch-success {
|
|
472
|
+
/* Success state - no specific styles needed */
|
|
473
|
+
}
|
|
474
|
+
'''
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Fetching Feature - Parser
|
|
3
|
+
Parses q:fetch elements from XML into FetchNode AST
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from xml.etree import ElementTree as ET
|
|
7
|
+
from typing import Optional, List
|
|
8
|
+
|
|
9
|
+
from .ast_node import (
|
|
10
|
+
FetchNode,
|
|
11
|
+
FetchLoadingNode,
|
|
12
|
+
FetchErrorNode,
|
|
13
|
+
FetchSuccessNode,
|
|
14
|
+
FetchHeaderNode,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class FetchParseError(Exception):
|
|
19
|
+
"""Error during fetch element parsing."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_fetch(element: ET.Element, parent_parser) -> FetchNode:
|
|
24
|
+
"""
|
|
25
|
+
Parse a <q:fetch> element into a FetchNode.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
element: XML element for q:fetch
|
|
29
|
+
parent_parser: Reference to QuantumParser for parsing child elements
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
FetchNode AST node
|
|
33
|
+
|
|
34
|
+
Raises:
|
|
35
|
+
FetchParseError: If required attributes are missing or invalid
|
|
36
|
+
"""
|
|
37
|
+
# Required attributes
|
|
38
|
+
name = element.get('name')
|
|
39
|
+
url = element.get('url')
|
|
40
|
+
|
|
41
|
+
if not name:
|
|
42
|
+
raise FetchParseError("q:fetch requires 'name' attribute")
|
|
43
|
+
if not url:
|
|
44
|
+
raise FetchParseError("q:fetch requires 'url' attribute")
|
|
45
|
+
|
|
46
|
+
fetch_node = FetchNode(name, url)
|
|
47
|
+
|
|
48
|
+
# HTTP method
|
|
49
|
+
method = element.get('method', 'GET').upper()
|
|
50
|
+
fetch_node.method = method
|
|
51
|
+
|
|
52
|
+
# Request body
|
|
53
|
+
fetch_node.body = element.get('body')
|
|
54
|
+
|
|
55
|
+
# Content type
|
|
56
|
+
content_type = element.get('contentType')
|
|
57
|
+
if content_type:
|
|
58
|
+
fetch_node.content_type = content_type
|
|
59
|
+
|
|
60
|
+
# Caching
|
|
61
|
+
fetch_node.cache = element.get('cache')
|
|
62
|
+
fetch_node.cache_key = element.get('cacheKey')
|
|
63
|
+
|
|
64
|
+
# Polling
|
|
65
|
+
fetch_node.interval = element.get('interval')
|
|
66
|
+
fetch_node.refetch_on = element.get('refetchOn')
|
|
67
|
+
|
|
68
|
+
# Timeout
|
|
69
|
+
timeout_attr = element.get('timeout')
|
|
70
|
+
if timeout_attr:
|
|
71
|
+
try:
|
|
72
|
+
fetch_node.timeout = int(timeout_attr)
|
|
73
|
+
except ValueError:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
# Retry
|
|
77
|
+
retry_attr = element.get('retry')
|
|
78
|
+
if retry_attr:
|
|
79
|
+
try:
|
|
80
|
+
fetch_node.retry = int(retry_attr)
|
|
81
|
+
except ValueError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
retry_delay_attr = element.get('retryDelay')
|
|
85
|
+
if retry_delay_attr:
|
|
86
|
+
try:
|
|
87
|
+
fetch_node.retry_delay = int(retry_delay_attr)
|
|
88
|
+
except ValueError:
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
# Response handling
|
|
92
|
+
fetch_node.transform = element.get('transform')
|
|
93
|
+
response_format = element.get('responseFormat')
|
|
94
|
+
if response_format:
|
|
95
|
+
fetch_node.response_format = response_format
|
|
96
|
+
|
|
97
|
+
# Callbacks
|
|
98
|
+
fetch_node.on_success = element.get('onSuccess')
|
|
99
|
+
fetch_node.on_error = element.get('onError')
|
|
100
|
+
|
|
101
|
+
# Abort on unmount
|
|
102
|
+
abort_attr = element.get('abortOnUnmount', 'true').lower()
|
|
103
|
+
fetch_node.abort_on_unmount = abort_attr in ['true', '1', 'yes']
|
|
104
|
+
|
|
105
|
+
# Lazy loading
|
|
106
|
+
lazy_attr = element.get('lazy', 'false').lower()
|
|
107
|
+
fetch_node.lazy = lazy_attr in ['true', '1', 'yes']
|
|
108
|
+
|
|
109
|
+
# Credentials mode
|
|
110
|
+
fetch_node.credentials = element.get('credentials')
|
|
111
|
+
|
|
112
|
+
# Parse child elements
|
|
113
|
+
for child in element:
|
|
114
|
+
child_type = _get_element_name(child)
|
|
115
|
+
|
|
116
|
+
if child_type == 'header':
|
|
117
|
+
header = _parse_fetch_header(child)
|
|
118
|
+
fetch_node.add_header(header)
|
|
119
|
+
elif child_type == 'loading':
|
|
120
|
+
loading = _parse_fetch_loading(child, parent_parser)
|
|
121
|
+
fetch_node.set_loading(loading)
|
|
122
|
+
elif child_type == 'error':
|
|
123
|
+
error = _parse_fetch_error(child, parent_parser)
|
|
124
|
+
fetch_node.set_error(error)
|
|
125
|
+
elif child_type == 'success':
|
|
126
|
+
success = _parse_fetch_success(child, parent_parser)
|
|
127
|
+
fetch_node.set_success(success)
|
|
128
|
+
else:
|
|
129
|
+
# Parse other children as fallback content
|
|
130
|
+
if parent_parser:
|
|
131
|
+
child_node = parent_parser._parse_statement(child)
|
|
132
|
+
if child_node:
|
|
133
|
+
fetch_node.add_fallback_child(child_node)
|
|
134
|
+
|
|
135
|
+
return fetch_node
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _get_element_name(element: ET.Element) -> str:
|
|
139
|
+
"""Extract element name removing namespace."""
|
|
140
|
+
return element.tag.split('}')[-1] if '}' in element.tag else element.tag.split(':')[-1]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def _parse_fetch_header(element: ET.Element) -> FetchHeaderNode:
|
|
144
|
+
"""Parse <q:header> within q:fetch."""
|
|
145
|
+
name = element.get('name')
|
|
146
|
+
value = element.get('value')
|
|
147
|
+
|
|
148
|
+
if not name:
|
|
149
|
+
raise FetchParseError("q:header requires 'name' attribute")
|
|
150
|
+
if value is None:
|
|
151
|
+
raise FetchParseError(f"q:header '{name}' requires 'value' attribute")
|
|
152
|
+
|
|
153
|
+
return FetchHeaderNode(name=name, value=value)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _parse_fetch_loading(element: ET.Element, parent_parser) -> FetchLoadingNode:
|
|
157
|
+
"""Parse <q:loading> within q:fetch."""
|
|
158
|
+
loading_node = FetchLoadingNode()
|
|
159
|
+
|
|
160
|
+
# Parse text content
|
|
161
|
+
if element.text and element.text.strip():
|
|
162
|
+
from quantum.core.ast_nodes import TextNode
|
|
163
|
+
loading_node.add_child(TextNode(element.text.strip()))
|
|
164
|
+
|
|
165
|
+
# Parse child elements
|
|
166
|
+
for child in element:
|
|
167
|
+
if parent_parser:
|
|
168
|
+
child_node = parent_parser._parse_statement(child)
|
|
169
|
+
if child_node:
|
|
170
|
+
loading_node.add_child(child_node)
|
|
171
|
+
|
|
172
|
+
# Add tail text after child
|
|
173
|
+
if child.tail and child.tail.strip():
|
|
174
|
+
from quantum.core.ast_nodes import TextNode
|
|
175
|
+
loading_node.add_child(TextNode(child.tail.strip()))
|
|
176
|
+
|
|
177
|
+
return loading_node
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _parse_fetch_error(element: ET.Element, parent_parser) -> FetchErrorNode:
|
|
181
|
+
"""Parse <q:error> within q:fetch."""
|
|
182
|
+
error_node = FetchErrorNode()
|
|
183
|
+
|
|
184
|
+
# Parse text content
|
|
185
|
+
if element.text and element.text.strip():
|
|
186
|
+
from quantum.core.ast_nodes import TextNode
|
|
187
|
+
error_node.add_child(TextNode(element.text.strip()))
|
|
188
|
+
|
|
189
|
+
# Parse child elements
|
|
190
|
+
for child in element:
|
|
191
|
+
if parent_parser:
|
|
192
|
+
child_node = parent_parser._parse_statement(child)
|
|
193
|
+
if child_node:
|
|
194
|
+
error_node.add_child(child_node)
|
|
195
|
+
|
|
196
|
+
# Add tail text after child
|
|
197
|
+
if child.tail and child.tail.strip():
|
|
198
|
+
from quantum.core.ast_nodes import TextNode
|
|
199
|
+
error_node.add_child(TextNode(child.tail.strip()))
|
|
200
|
+
|
|
201
|
+
return error_node
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _parse_fetch_success(element: ET.Element, parent_parser) -> FetchSuccessNode:
|
|
205
|
+
"""Parse <q:success> within q:fetch."""
|
|
206
|
+
success_node = FetchSuccessNode()
|
|
207
|
+
|
|
208
|
+
# Parse text content
|
|
209
|
+
if element.text and element.text.strip():
|
|
210
|
+
from quantum.core.ast_nodes import TextNode
|
|
211
|
+
success_node.add_child(TextNode(element.text.strip()))
|
|
212
|
+
|
|
213
|
+
# Parse child elements
|
|
214
|
+
for child in element:
|
|
215
|
+
if parent_parser:
|
|
216
|
+
child_node = parent_parser._parse_statement(child)
|
|
217
|
+
if child_node:
|
|
218
|
+
success_node.add_child(child_node)
|
|
219
|
+
|
|
220
|
+
# Add tail text after child
|
|
221
|
+
if child.tail and child.tail.strip():
|
|
222
|
+
from quantum.core.ast_nodes import TextNode
|
|
223
|
+
success_node.add_child(TextNode(child.tail.strip()))
|
|
224
|
+
|
|
225
|
+
return success_node
|
|
File without changes
|