dirac-lang 0.1.2

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 (110) hide show
  1. package/.env.example +8 -0
  2. package/COMMUNITY.md +465 -0
  3. package/LIBRARIES.md +172 -0
  4. package/NAMESPACES.md +366 -0
  5. package/PROMOTION.md +257 -0
  6. package/QUICKSTART-LIBRARY.md +93 -0
  7. package/README.md +257 -0
  8. package/config.yml +6 -0
  9. package/config.yml.openai +4 -0
  10. package/dirac-http/examples/demo.di +9 -0
  11. package/dirac-http/lib/index.di +12 -0
  12. package/dist/chunk-NDIRTD3D.js +217 -0
  13. package/dist/chunk-S625X7ME.js +1071 -0
  14. package/dist/cli.d.ts +1 -0
  15. package/dist/cli.js +261 -0
  16. package/dist/index.d.ts +144 -0
  17. package/dist/index.js +22 -0
  18. package/dist/session-4QG7OERD.js +42 -0
  19. package/examples/add-demo.di +74 -0
  20. package/examples/add.bk +11 -0
  21. package/examples/advanced-math-demo.di +53 -0
  22. package/examples/calculator.di +32 -0
  23. package/examples/comprehensive.bk +29 -0
  24. package/examples/defvar-variable-demo.di +18 -0
  25. package/examples/direct-call.di +17 -0
  26. package/examples/disk-analysis.di +16 -0
  27. package/examples/executable-hello.di +7 -0
  28. package/examples/execute-demo.di +38 -0
  29. package/examples/file-manager.di +77 -0
  30. package/examples/file-stats.di +18 -0
  31. package/examples/hello.bk +1 -0
  32. package/examples/hello.di +5 -0
  33. package/examples/import-demo.di +31 -0
  34. package/examples/inline-test.bk +7 -0
  35. package/examples/lib/advanced-math.di +81 -0
  36. package/examples/lib/fileops.di +26 -0
  37. package/examples/lib/math.di +25 -0
  38. package/examples/lib/mongodb.di +96 -0
  39. package/examples/llm-agent.di +32 -0
  40. package/examples/llm-basic.di +12 -0
  41. package/examples/llm-command-no-exec.di +13 -0
  42. package/examples/llm-command.di +13 -0
  43. package/examples/llm-complex.di +141 -0
  44. package/examples/llm-recursive.di +31 -0
  45. package/examples/llm-reflection-test.di +19 -0
  46. package/examples/llm-subs.di +132 -0
  47. package/examples/llm-use-subs.di +6 -0
  48. package/examples/loop.di +12 -0
  49. package/examples/math-test.di +22 -0
  50. package/examples/mongodb-count-events.di +8 -0
  51. package/examples/mongodb-import-demo.di +25 -0
  52. package/examples/mongodb-simple-test.di +18 -0
  53. package/examples/nl-agent.di +47 -0
  54. package/examples/parameters-demo.di +68 -0
  55. package/examples/params-test.di +10 -0
  56. package/examples/recipe-chain.di +38 -0
  57. package/examples/recursive-llm.di +44 -0
  58. package/examples/sample-library/README.md +152 -0
  59. package/examples/sample-library/examples/demo.di +34 -0
  60. package/examples/sample-library/lib/index.di +65 -0
  61. package/examples/sample-library/package.json +31 -0
  62. package/examples/seamless.di +45 -0
  63. package/examples/shell-test.bk +10 -0
  64. package/examples/simple-import.di +13 -0
  65. package/examples/simple-recursive.di +26 -0
  66. package/examples/story-builder.di +45 -0
  67. package/examples/subroutine.di +23 -0
  68. package/examples/system-llm.di +21 -0
  69. package/examples/system-simple.di +3 -0
  70. package/examples/system-test.di +13 -0
  71. package/examples/task-assistant.di +27 -0
  72. package/examples/test-parameters.di +50 -0
  73. package/examples/two-styles.di +28 -0
  74. package/examples/var-debug.di +6 -0
  75. package/examples/var-inline.di +4 -0
  76. package/examples/var-test2.di +6 -0
  77. package/examples/variable-simple.di +16 -0
  78. package/examples/variable-test.di +22 -0
  79. package/filePath +1 -0
  80. package/greeting.txt +1 -0
  81. package/package.json +41 -0
  82. package/src/cli.ts +118 -0
  83. package/src/index.ts +33 -0
  84. package/src/llm/ollama.ts +58 -0
  85. package/src/runtime/braket-parser.ts +234 -0
  86. package/src/runtime/interpreter.ts +135 -0
  87. package/src/runtime/parser.ts +151 -0
  88. package/src/runtime/session.ts +228 -0
  89. package/src/tags/assign.ts +37 -0
  90. package/src/tags/call.ts +156 -0
  91. package/src/tags/defvar.ts +56 -0
  92. package/src/tags/eval.ts +68 -0
  93. package/src/tags/execute.ts +52 -0
  94. package/src/tags/expr.ts +128 -0
  95. package/src/tags/if.ts +58 -0
  96. package/src/tags/import.ts +66 -0
  97. package/src/tags/index.ts +37 -0
  98. package/src/tags/llm.ts +207 -0
  99. package/src/tags/loop.ts +43 -0
  100. package/src/tags/mongodb.ts +70 -0
  101. package/src/tags/output.ts +23 -0
  102. package/src/tags/parameters.ts +79 -0
  103. package/src/tags/require_module.ts +19 -0
  104. package/src/tags/subroutine.ts +52 -0
  105. package/src/tags/system.ts +70 -0
  106. package/src/tags/variable.ts +25 -0
  107. package/src/types/index.ts +101 -0
  108. package/src/utils/llm-adapter.ts +113 -0
  109. package/tools/create-library.sh +175 -0
  110. package/tsconfig.json +19 -0
package/src/index.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Main entry point for Dirac interpreter
3
+ */
4
+
5
+ export { DiracParser } from './runtime/parser.js';
6
+ export { createSession, getOutput, getAvailableSubroutines } from './runtime/session.js';
7
+ export { integrate } from './runtime/interpreter.js';
8
+ export { createLLMAdapter, executeUserCommand } from './utils/llm-adapter.js';
9
+ export type { DiracSession, DiracConfig, DiracElement, ParameterMetadata } from './types/index.js';
10
+ export type { LLMPromptGenerator } from './utils/llm-adapter.js';
11
+
12
+ import { DiracParser } from './runtime/parser.js';
13
+ import { createSession, getOutput } from './runtime/session.js';
14
+ import { integrate } from './runtime/interpreter.js';
15
+ import type { DiracConfig } from './types/index.js';
16
+
17
+ /**
18
+ * Execute Dirac source code
19
+ */
20
+ export async function execute(source: string, config: DiracConfig = {}): Promise<string> {
21
+ const parser = new DiracParser();
22
+ const session = createSession(config);
23
+
24
+ // Set current file if provided in config
25
+ if (config.filePath) {
26
+ session.currentFile = config.filePath;
27
+ }
28
+
29
+ const ast = parser.parse(source);
30
+ await integrate(session, ast);
31
+
32
+ return getOutput(session);
33
+ }
@@ -0,0 +1,58 @@
1
+
2
+
3
+ export class OllamaClient {
4
+ baseUrl: string;
5
+
6
+ constructor({ baseUrl = 'http://localhost:11434' }: { baseUrl?: string } = {}) {
7
+ this.baseUrl = baseUrl;
8
+ }
9
+
10
+ async generate({ model, prompt, options = {} }: { model: string; prompt: string; options?: Record<string, any> }) {
11
+ const res = await fetch(`${this.baseUrl}/api/generate`, {
12
+ method: 'POST',
13
+ headers: { 'Content-Type': 'application/json' },
14
+ body: JSON.stringify({ model, prompt, ...options }),
15
+ });
16
+
17
+ let output = '';
18
+ for await (const chunk of res.body as any) {
19
+ const chunkStr = chunk.toString();
20
+ // console.log('[OllamaClient] Raw chunk:', chunkStr);
21
+ const lines = chunkStr.split('\n');
22
+ for (const line of lines) {
23
+ if (line.trim()) {
24
+ let jsonLine = line.trim();
25
+ // If line looks like comma-separated numbers, decode as ASCII
26
+ if (/^\d+(,\d+)*$/.test(jsonLine)) {
27
+ jsonLine = jsonLine.split(',').map(n => String.fromCharCode(Number(n))).join('');
28
+ }
29
+ try {
30
+ const obj = JSON.parse(jsonLine);
31
+ if (obj.response) output += obj.response;
32
+ } catch (err) {
33
+ // Silently skip lines that can't be parsed
34
+ }
35
+ }
36
+ }
37
+ }
38
+ return output;
39
+ }
40
+ }
41
+
42
+ export class OllamaProvider {
43
+ client: OllamaClient;
44
+ model: string;
45
+
46
+ constructor(options: { baseUrl?: string; model?: string } = {}) {
47
+ this.client = new OllamaClient(options);
48
+ this.model = options.model || 'llama2';
49
+ }
50
+
51
+ async complete(prompt: string, opts: Record<string, any> = {}) {
52
+ return await this.client.generate({
53
+ model: this.model,
54
+ prompt,
55
+ options: opts,
56
+ });
57
+ }
58
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Bra-Ket Parser - Converts bra-ket notation to XML
3
+ *
4
+ * Syntax:
5
+ * - Bra (subroutine): <name| ... defines a subroutine
6
+ * - Ket (everything else): |tag attrs> ... can have children/content
7
+ * - Indentation: Defines scope (like Python/YAML)
8
+ *
9
+ * Examples:
10
+ * |output>Hello World → <output>Hello World</output>
11
+ * |variable name=x> → <variable name="x"/>
12
+ * <add| → <subroutine name="add">
13
+ * |output>test → <output>test</output>
14
+ * → </subroutine>
15
+ */
16
+
17
+ interface BraKetLine {
18
+ indent: number;
19
+ type: 'bra' | 'ket' | 'text' | 'empty';
20
+ tag?: string;
21
+ attrs?: string;
22
+ text?: string;
23
+ raw: string;
24
+ }
25
+
26
+ export class BraKetParser {
27
+ private lines: string[] = [];
28
+ private currentLine = 0;
29
+
30
+ /**
31
+ * Parse bra-ket notation and compile to XML
32
+ */
33
+ parse(source: string): string {
34
+ this.lines = source.split('\n');
35
+ this.currentLine = 0;
36
+
37
+ const xml: string[] = ['<dirac>'];
38
+ this.parseBlock(xml, -1);
39
+ xml.push('</dirac>');
40
+
41
+ return xml.join('\n');
42
+ }
43
+
44
+ /**
45
+ * Parse a block of lines at a given indentation level
46
+ */
47
+ private parseBlock(output: string[], parentIndent: number): void {
48
+ while (this.currentLine < this.lines.length) {
49
+ const line = this.parseLine(this.lines[this.currentLine]);
50
+
51
+ // Empty lines are preserved as-is
52
+ if (line.type === 'empty') {
53
+ this.currentLine++;
54
+ continue;
55
+ }
56
+
57
+ // If indent is less than or equal to parent, we're done with this block
58
+ if (line.indent <= parentIndent) {
59
+ break;
60
+ }
61
+
62
+ // Bra: <name| defines a subroutine
63
+ if (line.type === 'bra') {
64
+ const attrs = line.attrs ? ` ${this.convertAttributes(line.attrs)}` : '';
65
+ output.push(`${' '.repeat(line.indent)}<subroutine name="${line.tag}"${attrs}>`);
66
+ this.currentLine++;
67
+ this.parseBlock(output, line.indent);
68
+ output.push(`${' '.repeat(line.indent)}</subroutine>`);
69
+ continue;
70
+ }
71
+
72
+ // Ket: |tag attrs>
73
+ if (line.type === 'ket') {
74
+ const indent = ' '.repeat(line.indent);
75
+ const attrs = line.attrs ? ` ${this.convertAttributes(line.attrs)}` : '';
76
+
77
+ // Check if next line is more indented (has children/content)
78
+ const nextLine = this.currentLine + 1 < this.lines.length
79
+ ? this.parseLine(this.lines[this.currentLine + 1])
80
+ : null;
81
+
82
+ if (nextLine && nextLine.indent > line.indent && nextLine.type !== 'empty') {
83
+ // Has children - opening tag
84
+ output.push(`${indent}<${line.tag}${attrs}>`);
85
+ this.currentLine++;
86
+ this.parseBlock(output, line.indent);
87
+ output.push(`${indent}</${line.tag}>`);
88
+ } else {
89
+ // Self-closing or inline text
90
+ if (line.text) {
91
+ // Inline text with embedded kets
92
+ const content = this.convertInlineKets(line.text);
93
+ output.push(`${indent}<${line.tag}${attrs}>${content}</${line.tag}>`);
94
+ } else {
95
+ // Self-closing
96
+ output.push(`${indent}<${line.tag}${attrs}/>`);
97
+ }
98
+ this.currentLine++;
99
+ }
100
+ continue;
101
+ }
102
+
103
+ // Plain text content
104
+ if (line.type === 'text') {
105
+ const indent = ' '.repeat(line.indent);
106
+ const content = this.convertInlineKets(line.text || '');
107
+ output.push(`${indent}${content}`);
108
+ this.currentLine++;
109
+ continue;
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Parse a single line into structured form
116
+ */
117
+ private parseLine(raw: string): BraKetLine {
118
+ // Count leading spaces for indentation
119
+ const match = raw.match(/^(\s*)(.*)/);
120
+ const indent = match ? Math.floor(match[1].length / 2) : 0;
121
+ const content = match ? match[2] : '';
122
+
123
+ // Empty line
124
+ if (!content.trim()) {
125
+ return { indent, type: 'empty', raw };
126
+ }
127
+
128
+ // Bra: <name| or <name attrs|
129
+ const braMatch = content.match(/^<([a-zA-Z_][a-zA-Z0-9_-]*)\s*([^|]*)\|$/);
130
+ if (braMatch) {
131
+ return {
132
+ indent,
133
+ type: 'bra',
134
+ tag: braMatch[1],
135
+ attrs: braMatch[2].trim() || undefined,
136
+ raw
137
+ };
138
+ }
139
+
140
+ // Ket: |tag> or |tag attrs> or |tag>text
141
+ const ketMatch = content.match(/^\|([a-zA-Z_][a-zA-Z0-9_-]*)\s*([^>]*?)>\s*(.*)/);
142
+ if (ketMatch) {
143
+ return {
144
+ indent,
145
+ type: 'ket',
146
+ tag: ketMatch[1],
147
+ attrs: ketMatch[2].trim() || undefined,
148
+ text: ketMatch[3] || undefined,
149
+ raw
150
+ };
151
+ }
152
+
153
+ // Plain text
154
+ return {
155
+ indent,
156
+ type: 'text',
157
+ text: content,
158
+ raw
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Convert bra-ket attribute syntax to XML
164
+ * Examples:
165
+ * name=value → name="value"
166
+ * x=5 y=10 → x="5" y="10"
167
+ * select=@* → select="@*"
168
+ */
169
+ private convertAttributes(attrs: string): string {
170
+ if (!attrs) return '';
171
+
172
+ // Split by spaces but respect quoted strings
173
+ const parts: string[] = [];
174
+ let current = '';
175
+ let inQuotes = false;
176
+ let quoteChar = '';
177
+
178
+ for (let i = 0; i < attrs.length; i++) {
179
+ const char = attrs[i];
180
+
181
+ if ((char === '"' || char === "'") && (i === 0 || attrs[i - 1] !== '\\')) {
182
+ if (!inQuotes) {
183
+ inQuotes = true;
184
+ quoteChar = char;
185
+ current += char;
186
+ } else if (char === quoteChar) {
187
+ inQuotes = false;
188
+ current += char;
189
+ } else {
190
+ current += char;
191
+ }
192
+ } else if (char === ' ' && !inQuotes) {
193
+ if (current.trim()) {
194
+ parts.push(current.trim());
195
+ current = '';
196
+ }
197
+ } else {
198
+ current += char;
199
+ }
200
+ }
201
+
202
+ if (current.trim()) {
203
+ parts.push(current.trim());
204
+ }
205
+
206
+ // Convert each attribute
207
+ return parts.map(part => {
208
+ const match = part.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)=(.+)$/);
209
+ if (!match) return part;
210
+
211
+ const [, name, value] = match;
212
+
213
+ // Already quoted
214
+ if ((value.startsWith('"') && value.endsWith('"')) ||
215
+ (value.startsWith("'") && value.endsWith("'"))) {
216
+ return `${name}=${value}`;
217
+ }
218
+
219
+ // Quote it
220
+ return `${name}="${value}"`;
221
+ }).join(' ');
222
+ }
223
+
224
+ /**
225
+ * Convert inline kets within text content
226
+ * Example: "Hello |variable name=x> world" → "Hello <variable name="x"/> world"
227
+ */
228
+ private convertInlineKets(text: string): string {
229
+ return text.replace(/\|([a-zA-Z_][a-zA-Z0-9_-]*)\s*([^>]*?)>/g, (match, tag, attrs) => {
230
+ const attrStr = attrs.trim() ? ` ${this.convertAttributes(attrs.trim())}` : '';
231
+ return `<${tag}${attrStr}/>`;
232
+ });
233
+ }
234
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Core interpreter - maps to mask_integrate in MASK
3
+ */
4
+
5
+ import type { DiracElement, DiracSession } from '../types/index.js';
6
+ import { substituteVariables, emit, getSubroutine } from './session.js';
7
+ import { executeDefvar } from '../tags/defvar.js';
8
+ import { executeVariable } from '../tags/variable.js';
9
+ import { executeAssign } from '../tags/assign.js';
10
+ import { executeOutput } from '../tags/output.js';
11
+ import { executeSubroutine } from '../tags/subroutine.js';
12
+ import { executeCall } from '../tags/call.js';
13
+ import { executeLoop } from '../tags/loop.js';
14
+ import { executeIf } from '../tags/if.js';
15
+ import { executeLLM } from '../tags/llm.js';
16
+ import { executeEval } from '../tags/eval.js';
17
+ import { executeExecute } from '../tags/execute.js';
18
+ import { executeImport } from '../tags/import.js';
19
+ import { executeParameters } from '../tags/parameters.js';
20
+ import { executeExpr } from '../tags/expr.js';
21
+ import { executeSystem } from '../tags/system.js';
22
+ import { executeRequireModule } from '../tags/require_module.js';
23
+
24
+ export async function integrate(session: DiracSession, element: DiracElement): Promise<void> {
25
+ // Check execution limits
26
+ if (session.limits.currentDepth >= session.limits.maxDepth) {
27
+ throw new Error('Maximum execution depth exceeded');
28
+ }
29
+
30
+ session.limits.currentDepth++;
31
+
32
+ try {
33
+ // Handle text nodes
34
+ if (element.text && !element.tag) {
35
+ const substituted = substituteVariables(session, element.text);
36
+ emit(session, substituted);
37
+ return;
38
+ }
39
+
40
+ // Check control flow
41
+ if (session.isReturn || session.isBreak) {
42
+ return;
43
+ }
44
+
45
+ // Dispatch to tag handlers
46
+ switch (element.tag.toLowerCase()) {
47
+ case 'defvar':
48
+ await executeDefvar(session, element);
49
+ break;
50
+
51
+ case 'variable':
52
+ await executeVariable(session, element);
53
+ break;
54
+
55
+ case 'assign':
56
+ await executeAssign(session, element);
57
+ break;
58
+
59
+ case 'output':
60
+ await executeOutput(session, element);
61
+ break;
62
+
63
+ case 'subroutine':
64
+ await executeSubroutine(session, element);
65
+ break;
66
+
67
+ case 'call':
68
+ await executeCall(session, element);
69
+ break;
70
+
71
+ case 'loop':
72
+ await executeLoop(session, element);
73
+ break;
74
+
75
+ case 'if':
76
+ await executeIf(session, element);
77
+ break;
78
+
79
+ case 'llm':
80
+ await executeLLM(session, element);
81
+ break;
82
+
83
+ case 'eval':
84
+ await executeEval(session, element);
85
+ break;
86
+
87
+ case 'execute':
88
+ await executeExecute(session, element);
89
+ break;
90
+
91
+ case 'import':
92
+ await executeImport(session, element);
93
+ break;
94
+
95
+ case 'parameters':
96
+ await executeParameters(session, element);
97
+ break;
98
+
99
+ case 'expr':
100
+ await executeExpr(session, element);
101
+ break;
102
+
103
+ case 'system':
104
+ await executeSystem(session, element);
105
+ break;
106
+
107
+ case 'require_module':
108
+ await executeRequireModule(session, element);
109
+ break;
110
+
111
+ default:
112
+ // Unknown tag - check if it's a subroutine name
113
+ const subroutine = getSubroutine(session, element.tag);
114
+ if (subroutine) {
115
+ // Treat unknown tag as subroutine call
116
+ await executeCall(session, element);
117
+ } else {
118
+ // Really unknown - just process children
119
+ for (const child of element.children) {
120
+ await integrate(session, child);
121
+ if (session.isReturn || session.isBreak) break;
122
+ }
123
+ }
124
+ }
125
+ } finally {
126
+ session.limits.currentDepth--;
127
+ }
128
+ }
129
+
130
+ export async function integrateChildren(session: DiracSession, element: DiracElement): Promise<void> {
131
+ for (const child of element.children) {
132
+ await integrate(session, child);
133
+ if (session.isReturn || session.isBreak) break;
134
+ }
135
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * XML Parser for Dirac (.di files)
3
+ */
4
+
5
+ import { XMLParser } from 'fast-xml-parser';
6
+ import type { DiracElement } from '../types/index.js';
7
+
8
+ export class DiracParser {
9
+ private parser: XMLParser;
10
+
11
+ constructor() {
12
+ this.parser = new XMLParser({
13
+ ignoreAttributes: false,
14
+ attributeNamePrefix: '@_',
15
+ trimValues: true,
16
+ parseAttributeValue: false,
17
+ parseTagValue: false,
18
+ textNodeName: '#text',
19
+ cdataPropName: '#cdata',
20
+ preserveOrder: true, // Preserve element order!
21
+ commentPropName: '#comment',
22
+ });
23
+ }
24
+
25
+ parse(source: string): DiracElement {
26
+ // Strip shebang line if present
27
+ if (source.startsWith('#!')) {
28
+ source = source.replace(/^#!.*\n/, '');
29
+ }
30
+
31
+ const result = this.parser.parse(source);
32
+ // With preserveOrder, result is an array
33
+ if (!Array.isArray(result) || result.length === 0) {
34
+ throw new Error('Empty or invalid XML');
35
+ }
36
+
37
+ // Find the first non-comment element
38
+ for (const item of result) {
39
+ if (!item['#comment']) {
40
+ return this.convertOrderedToElement(item);
41
+ }
42
+ }
43
+
44
+ throw new Error('No root element found');
45
+ }
46
+
47
+ private convertOrderedToElement(obj: any): DiracElement {
48
+ // obj is like { "tagname": [...children], ":@": {...attributes} }
49
+ const tagName = Object.keys(obj).find(k => k !== ':@' && k !== '#comment');
50
+
51
+ if (!tagName) {
52
+ throw new Error('Invalid element structure');
53
+ }
54
+
55
+ const element: DiracElement = {
56
+ tag: tagName,
57
+ attributes: {},
58
+ children: [],
59
+ };
60
+
61
+ // Extract attributes
62
+ if (obj[':@']) {
63
+ for (const [key, value] of Object.entries(obj[':@'])) {
64
+ if (key.startsWith('@_')) {
65
+ element.attributes[key.slice(2)] = value as string;
66
+ }
67
+ }
68
+ }
69
+
70
+ // Extract children
71
+ const children = obj[tagName];
72
+ if (Array.isArray(children)) {
73
+ for (const child of children) {
74
+ if (child['#text']) {
75
+ // Text node - add as child AND to element.text for backward compat
76
+ element.children.push({
77
+ tag: '',
78
+ text: child['#text'],
79
+ attributes: {},
80
+ children: []
81
+ });
82
+ // Also set element.text if not set (for simple text-only elements)
83
+ if (!element.text) {
84
+ element.text = child['#text'];
85
+ } else {
86
+ element.text += child['#text'];
87
+ }
88
+ } else if (child['#comment']) {
89
+ // Skip comments
90
+ continue;
91
+ } else {
92
+ // Child element
93
+ element.children.push(this.convertOrderedToElement(child));
94
+ }
95
+ }
96
+ }
97
+
98
+ return element;
99
+ }
100
+
101
+ // Old method - no longer used
102
+ private convertToElement(obj: any, tagName?: string): DiracElement {
103
+ if (!tagName) {
104
+ // Root level - find the actual tag
105
+ const keys = Object.keys(obj);
106
+ if (keys.length === 0) {
107
+ throw new Error('Empty XML');
108
+ }
109
+ tagName = keys[0];
110
+ obj = obj[tagName];
111
+ }
112
+
113
+ const element: DiracElement = {
114
+ tag: tagName,
115
+ attributes: {},
116
+ children: [],
117
+ };
118
+
119
+ if (typeof obj === 'string') {
120
+ element.text = obj;
121
+ return element;
122
+ }
123
+
124
+ if (!obj) {
125
+ return element;
126
+ }
127
+
128
+ // Extract attributes and children
129
+ for (const key of Object.keys(obj)) {
130
+ const value = obj[key];
131
+
132
+ if (key === '#text') {
133
+ element.text = value;
134
+ } else if (key.startsWith('@_')) {
135
+ // Attribute
136
+ element.attributes[key.slice(2)] = value;
137
+ } else {
138
+ // Child element
139
+ if (Array.isArray(value)) {
140
+ for (const item of value) {
141
+ element.children.push(this.convertToElement(item, key));
142
+ }
143
+ } else {
144
+ element.children.push(this.convertToElement(value, key));
145
+ }
146
+ }
147
+ }
148
+
149
+ return element;
150
+ }
151
+ }