1
//! XML and XHTML parsing for declarative UI definitions.
2
//!
3
//! This module provides comprehensive XML parsing and manipulation for Azul's XML-based
4
//! UI format (`.azul` files). It supports:
5
//!
6
//! - **XHTML parsing**: Parse HTML-like syntax into DOM structures
7
//! - **CSS extraction**: Extract `<style>` blocks and inline styles
8
//! - **Component system**: Define reusable UI components with arguments
9
//! - **Hot reload**: Track file changes and rebuild UI incrementally
10
//! - **Error reporting**: Detailed syntax error messages with line/column info
11
//!
12
//! # Examples
13
//!
14
//! ```rust,no_run,ignore
15
//! use azul_core::xml::{XmlNode, XmlParseOptions};
16
//!
17
//! let xml = "<div>Hello</div>";
18
//! // let node = XmlNode::parse(xml)?;
19
//! ```
20

            
21
use alloc::{
22
    boxed::Box,
23
    collections::BTreeMap,
24
    string::{String, ToString},
25
    vec::Vec,
26
};
27
use core::{fmt, hash::Hash};
28

            
29
use azul_css::{
30
    css::{
31
        Css, CssDeclaration, CssPath, CssPathPseudoSelector, CssPathSelector, CssRuleBlock,
32
        NodeTypeTag,
33
    },
34
    format_rust_code::VecContents,
35
    parser2::{CssParseErrorOwned, ErrorLocation},
36
    props::{
37
        basic::{ColorU, StyleFontFamilyVec},
38
        property::CssProperty,
39
        style::{
40
            NormalizedLinearColorStopVec, NormalizedRadialColorStopVec, StyleBackgroundContentVec,
41
            StyleBackgroundPositionVec, StyleBackgroundRepeatVec, StyleBackgroundSizeVec,
42
            StyleTransformVec,
43
        },
44
    },
45
    AzString, OptionString, StringVec, U8Vec,
46
};
47

            
48
use crate::{
49
    dom::{Dom, NodeType, OptionNodeType},
50
    styled_dom::StyledDom,
51
    window::{AzStringPair, StringPairVec},
52
};
53

            
54
/// Error that can occur during XML parsing or hot-reload.
55
///
56
/// Stringified for error reporting; not part of the public API.
57
pub type SyntaxError = String;
58

            
59
/// Tag of an XML node, such as the "button" in `<button>Hello</button>`.
60
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61
#[repr(C)]
62
pub struct XmlTagName {
63
    pub inner: AzString,
64
}
65

            
66
impl From<AzString> for XmlTagName {
67
    fn from(s: AzString) -> Self {
68
        Self { inner: s }
69
    }
70
}
71

            
72
impl From<String> for XmlTagName {
73
35315
    fn from(s: String) -> Self {
74
35315
        Self { inner: s.into() }
75
35315
    }
76
}
77

            
78
impl From<&str> for XmlTagName {
79
3
    fn from(s: &str) -> Self {
80
3
        Self { inner: s.into() }
81
3
    }
82
}
83

            
84
impl core::ops::Deref for XmlTagName {
85
    type Target = AzString;
86
128858
    fn deref(&self) -> &Self::Target {
87
128858
        &self.inner
88
128858
    }
89
}
90

            
91
/// (Unparsed) text content of an XML node, such as the "Hello" in `<button>Hello</button>`.
92
pub type XmlTextContent = OptionString;
93

            
94
/// Attributes of an XML node, such as `["color" => "blue"]` in `<button color="blue" />`.
95
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
96
#[repr(C)]
97
pub struct XmlAttributeMap {
98
    pub inner: StringPairVec,
99
}
100

            
101
impl From<StringPairVec> for XmlAttributeMap {
102
35316
    fn from(v: StringPairVec) -> Self {
103
35316
        Self { inner: v }
104
35316
    }
105
}
106

            
107
impl core::ops::Deref for XmlAttributeMap {
108
    type Target = StringPairVec;
109
175568
    fn deref(&self) -> &Self::Target {
110
175568
        &self.inner
111
175568
    }
112
}
113

            
114
impl core::ops::DerefMut for XmlAttributeMap {
115
47320
    fn deref_mut(&mut self) -> &mut Self::Target {
116
47320
        &mut self.inner
117
47320
    }
118
}
119

            
120
/// Name of a component argument (e.g. `"text"`, `"href"`).
121
type ComponentArgumentName = String;
122
/// Type of a component argument as a string (e.g. `"String"`, `"bool"`).
123
type ComponentArgumentType = String;
124
/// Zero-based position of an argument in the component's argument list.
125
type ComponentArgumentOrder = usize;
126

            
127
/// FFI-safe replacement for `(ComponentArgumentName, ComponentArgumentType)` tuple.
128
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
129
#[repr(C)]
130
pub struct ComponentArgument {
131
    pub name: AzString,
132
    pub arg_type: AzString,
133
}
134

            
135
impl_vec!(ComponentArgument, ComponentArgumentVec, ComponentArgumentVecDestructor, ComponentArgumentVecDestructorType, ComponentArgumentVecSlice, OptionComponentArgument);
136
impl_option!(ComponentArgument, OptionComponentArgument, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
137
impl_vec_debug!(ComponentArgument, ComponentArgumentVec);
138
impl_vec_partialeq!(ComponentArgument, ComponentArgumentVec);
139
impl_vec_eq!(ComponentArgument, ComponentArgumentVec);
140
impl_vec_partialord!(ComponentArgument, ComponentArgumentVec);
141
impl_vec_ord!(ComponentArgument, ComponentArgumentVec);
142
impl_vec_hash!(ComponentArgument, ComponentArgumentVec);
143
impl_vec_clone!(ComponentArgument, ComponentArgumentVec, ComponentArgumentVecDestructor);
144
impl_vec_mut!(ComponentArgument, ComponentArgumentVec);
145

            
146
/// Holds the list of arguments and whether the component accepts text content.
147
/// Used by the compile pipeline to generate Rust function signatures.
148
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
149
pub struct ComponentArguments {
150
    pub args: ComponentArgumentVec,
151
    pub accepts_text: bool,
152
}
153

            
154
/// Name of an XML/HTML component (e.g. `"button"`, `"my-widget"`).
155
type ComponentName = String;
156
/// Compiled source code string for a component.
157
type CompiledComponent = String;
158

            
159
/// Universal HTML attribute names that are handled by the framework
160
/// and should not be passed through to component-specific argument lists.
161
const DEFAULT_ARGS: [&str; 8] = [
162
    "id",
163
    "class",
164
    "tabindex",
165
    "focusable",
166
    "accepts_text",
167
    "name",
168
    "style",
169
    "args",
170
];
171

            
172
/// Opaque void type for FFI pointers. Uses a custom definition instead of
173
/// `core::ffi::c_void` for `#[repr(C)]` compatibility in the generated API.
174
#[allow(non_camel_case_types)]
175
pub enum c_void {}
176

            
177
/// Type of an XML node in the parsed tree.
178
#[repr(C)]
179
pub enum XmlNodeType {
180
    Root,
181
    Element,
182
    PI,
183
    Comment,
184
    Text,
185
}
186

            
187
/// A namespace-qualified XML name (e.g. `svg:rect` has namespace `"svg"` and local name `"rect"`).
188
#[repr(C)]
189
pub struct XmlQualifiedName {
190
    pub local_name: AzString,
191
    pub namespace: OptionString,
192
}
193

            
194
/// Classification of an external resource referenced in HTML/XML
195
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
196
#[repr(C)]
197
pub enum ExternalResourceKind {
198
    /// Image resource (img src, background-image, etc.)
199
    Image,
200
    /// Font resource (@font-face src, link rel="preload" as="font")
201
    Font,
202
    /// Stylesheet (link rel="stylesheet", @import)
203
    Stylesheet,
204
    /// Script (script src)
205
    Script,
206
    /// Favicon or icon
207
    Icon,
208
    /// Video source
209
    Video,
210
    /// Audio source
211
    Audio,
212
    /// Generic link or unknown resource type
213
    Unknown,
214
}
215

            
216
/// MIME type hint for an external resource
217
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
218
#[repr(C)]
219
pub struct MimeTypeHint {
220
    pub inner: AzString,
221
}
222

            
223
impl MimeTypeHint {
224
    pub fn new(s: &str) -> Self {
225
        Self { inner: AzString::from(s) }
226
    }
227
    
228
    pub fn from_extension(ext: &str) -> Self {
229
        let mime = match ext.to_lowercase().as_str() {
230
            // Images
231
            "png" => "image/png",
232
            "jpg" | "jpeg" => "image/jpeg",
233
            "gif" => "image/gif",
234
            "webp" => "image/webp",
235
            "svg" => "image/svg+xml",
236
            "ico" => "image/x-icon",
237
            "bmp" => "image/bmp",
238
            "avif" => "image/avif",
239
            // Fonts
240
            "ttf" => "font/ttf",
241
            "otf" => "font/otf",
242
            "woff" => "font/woff",
243
            "woff2" => "font/woff2",
244
            "eot" => "application/vnd.ms-fontobject",
245
            // Stylesheets
246
            "css" => "text/css",
247
            // Scripts
248
            "js" => "application/javascript",
249
            "mjs" => "application/javascript",
250
            // Video
251
            "mp4" => "video/mp4",
252
            "webm" => "video/webm",
253
            "ogg" => "video/ogg",
254
            // Audio
255
            "mp3" => "audio/mpeg",
256
            "wav" => "audio/wav",
257
            "flac" => "audio/flac",
258
            // Default
259
            _ => "application/octet-stream",
260
        };
261
        Self { inner: AzString::from(mime) }
262
    }
263
}
264

            
265
impl_option!(
266
    MimeTypeHint,
267
    OptionMimeTypeHint,
268
    copy = false,
269
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
270
);
271

            
272
/// An external resource URL found in an XML/HTML document
273
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
274
#[repr(C)]
275
pub struct ExternalResource {
276
    /// The URL as found in the document (may be relative or absolute)
277
    pub url: AzString,
278
    /// Classification of the resource type
279
    pub kind: ExternalResourceKind,
280
    /// MIME type hint (from type attribute, file extension, or heuristics)
281
    pub mime_type: OptionMimeTypeHint,
282
    /// The HTML element that referenced this resource (e.g., "img", "link", "script")
283
    pub source_element: AzString,
284
    /// The attribute that contained the URL (e.g., "src", "href")
285
    pub source_attribute: AzString,
286
}
287

            
288
impl_option!(
289
    ExternalResource,
290
    OptionExternalResource,
291
    copy = false,
292
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
293
);
294

            
295
impl_vec!(ExternalResource, ExternalResourceVec, ExternalResourceVecDestructor, ExternalResourceVecDestructorType, ExternalResourceVecSlice, OptionExternalResource);
296
impl_vec_mut!(ExternalResource, ExternalResourceVec);
297
impl_vec_debug!(ExternalResource, ExternalResourceVec);
298
impl_vec_partialeq!(ExternalResource, ExternalResourceVec);
299
impl_vec_eq!(ExternalResource, ExternalResourceVec);
300
impl_vec_partialord!(ExternalResource, ExternalResourceVec);
301
impl_vec_ord!(ExternalResource, ExternalResourceVec);
302
impl_vec_hash!(ExternalResource, ExternalResourceVec);
303
impl_vec_clone!(ExternalResource, ExternalResourceVec, ExternalResourceVecDestructor);
304

            
305
#[derive(Debug, PartialEq, PartialOrd, Clone)]
306
#[repr(C)]
307
pub struct Xml {
308
    pub root: XmlNodeChildVec,
309
}
310

            
311
impl Xml {
312
    /// Scan the XML/HTML document for external resource URLs.
313
    /// 
314
    /// This function traverses the entire document tree and extracts URLs from:
315
    /// - `<img src="...">` - Images
316
    /// - `<link href="...">` - Stylesheets, icons, fonts
317
    /// - `<script src="...">` - Scripts
318
    /// - `<video src="...">`, `<source src="...">` - Video
319
    /// - `<audio src="...">` - Audio
320
    /// - `<a href="...">` - Links (classified as Unknown)
321
    /// - CSS `url()` in style attributes
322
    /// - `<style>` blocks with @import or url()
323
    pub fn scan_external_resources(&self) -> ExternalResourceVec {
324
        let mut resources = Vec::new();
325
        
326
        for child in self.root.as_ref().iter() {
327
            Self::scan_node_child(child, &mut resources);
328
        }
329
        
330
        resources.into()
331
    }
332
    
333
    fn scan_node_child(child: &XmlNodeChild, resources: &mut Vec<ExternalResource>) {
334
        match child {
335
            XmlNodeChild::Text(text) => {
336
                // Check for CSS @import or url() in text content (inside <style> tags)
337
                Self::extract_css_urls(text.as_str(), resources);
338
            }
339
            XmlNodeChild::Element(node) => {
340
                Self::scan_node(node, resources);
341
            }
342
        }
343
    }
344
    
345
    fn scan_node(node: &XmlNode, resources: &mut Vec<ExternalResource>) {
346
        let tag_name = node.node_type.inner.as_str().to_lowercase();
347
        
348
        // Get attribute lookup helper
349
        let get_attr = |name: &str| -> Option<String> {
350
            node.attributes.inner.as_ref().iter()
351
                .find(|pair| pair.key.as_str().eq_ignore_ascii_case(name))
352
                .map(|pair| pair.value.as_str().to_string())
353
        };
354
        
355
        match tag_name.as_str() {
356
            "img" => {
357
                if let Some(src) = get_attr("src") {
358
                    let mime = Self::guess_mime_from_url(&src, "image");
359
                    resources.push(ExternalResource {
360
                        url: AzString::from(src),
361
                        kind: ExternalResourceKind::Image,
362
                        mime_type: mime.into(),
363
                        source_element: AzString::from("img"),
364
                        source_attribute: AzString::from("src"),
365
                    });
366
                }
367
                // Also check srcset
368
                if let Some(srcset) = get_attr("srcset") {
369
                    for src in Self::parse_srcset(&srcset) {
370
                        let mime = Self::guess_mime_from_url(&src, "image");
371
                        resources.push(ExternalResource {
372
                            url: AzString::from(src),
373
                            kind: ExternalResourceKind::Image,
374
                            mime_type: mime.into(),
375
                            source_element: AzString::from("img"),
376
                            source_attribute: AzString::from("srcset"),
377
                        });
378
                    }
379
                }
380
            }
381
            "link" => {
382
                if let Some(href) = get_attr("href") {
383
                    let rel = get_attr("rel").unwrap_or_default().to_lowercase();
384
                    let type_attr = get_attr("type");
385
                    let as_attr = get_attr("as").unwrap_or_default().to_lowercase();
386
                    
387
                    let (kind, category) = if rel.contains("stylesheet") {
388
                        (ExternalResourceKind::Stylesheet, "stylesheet")
389
                    } else if rel.contains("icon") || rel.contains("apple-touch-icon") {
390
                        (ExternalResourceKind::Icon, "image")
391
                    } else if as_attr == "font" {
392
                        (ExternalResourceKind::Font, "font")
393
                    } else if as_attr == "script" {
394
                        (ExternalResourceKind::Script, "script")
395
                    } else if as_attr == "image" {
396
                        (ExternalResourceKind::Image, "image")
397
                    } else {
398
                        (ExternalResourceKind::Unknown, "")
399
                    };
400
                    
401
                    let mime = type_attr.map(|t| MimeTypeHint::new(&t))
402
                        .or_else(|| Self::guess_mime_from_url(&href, category));
403
                    
404
                    resources.push(ExternalResource {
405
                        url: AzString::from(href),
406
                        kind,
407
                        mime_type: mime.into(),
408
                        source_element: AzString::from("link"),
409
                        source_attribute: AzString::from("href"),
410
                    });
411
                }
412
            }
413
            "script" => {
414
                if let Some(src) = get_attr("src") {
415
                    let type_attr = get_attr("type");
416
                    let mime = type_attr.map(|t| MimeTypeHint::new(&t))
417
                        .or_else(|| Some(MimeTypeHint::new("application/javascript")));
418
                    
419
                    resources.push(ExternalResource {
420
                        url: AzString::from(src),
421
                        kind: ExternalResourceKind::Script,
422
                        mime_type: mime.into(),
423
                        source_element: AzString::from("script"),
424
                        source_attribute: AzString::from("src"),
425
                    });
426
                }
427
            }
428
            "video" => {
429
                if let Some(src) = get_attr("src") {
430
                    let mime = Self::guess_mime_from_url(&src, "video");
431
                    resources.push(ExternalResource {
432
                        url: AzString::from(src),
433
                        kind: ExternalResourceKind::Video,
434
                        mime_type: mime.into(),
435
                        source_element: AzString::from("video"),
436
                        source_attribute: AzString::from("src"),
437
                    });
438
                }
439
                if let Some(poster) = get_attr("poster") {
440
                    let mime = Self::guess_mime_from_url(&poster, "image");
441
                    resources.push(ExternalResource {
442
                        url: AzString::from(poster),
443
                        kind: ExternalResourceKind::Image,
444
                        mime_type: mime.into(),
445
                        source_element: AzString::from("video"),
446
                        source_attribute: AzString::from("poster"),
447
                    });
448
                }
449
            }
450
            "audio" => {
451
                if let Some(src) = get_attr("src") {
452
                    let mime = Self::guess_mime_from_url(&src, "audio");
453
                    resources.push(ExternalResource {
454
                        url: AzString::from(src),
455
                        kind: ExternalResourceKind::Audio,
456
                        mime_type: mime.into(),
457
                        source_element: AzString::from("audio"),
458
                        source_attribute: AzString::from("src"),
459
                    });
460
                }
461
            }
462
            "source" => {
463
                if let Some(src) = get_attr("src") {
464
                    let type_attr = get_attr("type");
465
                    // Determine kind based on type or parent (heuristic: assume video)
466
                    let kind = if type_attr.as_ref().map(|t| t.starts_with("audio")).unwrap_or(false) {
467
                        ExternalResourceKind::Audio
468
                    } else {
469
                        ExternalResourceKind::Video
470
                    };
471
                    let mime = type_attr.map(|t| MimeTypeHint::new(&t))
472
                        .or_else(|| Self::guess_mime_from_url(&src, if kind == ExternalResourceKind::Audio { "audio" } else { "video" }));
473
                    
474
                    resources.push(ExternalResource {
475
                        url: AzString::from(src),
476
                        kind,
477
                        mime_type: mime.into(),
478
                        source_element: AzString::from("source"),
479
                        source_attribute: AzString::from("src"),
480
                    });
481
                }
482
                // Also handle srcset for picture elements
483
                if let Some(srcset) = get_attr("srcset") {
484
                    for src in Self::parse_srcset(&srcset) {
485
                        let mime = Self::guess_mime_from_url(&src, "image");
486
                        resources.push(ExternalResource {
487
                            url: AzString::from(src),
488
                            kind: ExternalResourceKind::Image,
489
                            mime_type: mime.into(),
490
                            source_element: AzString::from("source"),
491
                            source_attribute: AzString::from("srcset"),
492
                        });
493
                    }
494
                }
495
            }
496
            "a" => {
497
                if let Some(href) = get_attr("href") {
498
                    // Only include if it looks like a resource, not a page link
499
                    if Self::looks_like_resource(&href) {
500
                        let mime = Self::guess_mime_from_url(&href, "");
501
                        resources.push(ExternalResource {
502
                            url: AzString::from(href),
503
                            kind: ExternalResourceKind::Unknown,
504
                            mime_type: mime.into(),
505
                            source_element: AzString::from("a"),
506
                            source_attribute: AzString::from("href"),
507
                        });
508
                    }
509
                }
510
            }
511
            "virtualized-view" | "embed" | "object" => {
512
                let src_attr = if tag_name == "object" { "data" } else { "src" };
513
                if let Some(src) = get_attr(src_attr) {
514
                    resources.push(ExternalResource {
515
                        url: AzString::from(src),
516
                        kind: ExternalResourceKind::Unknown,
517
                        mime_type: OptionMimeTypeHint::None,
518
                        source_element: AzString::from(tag_name.clone()),
519
                        source_attribute: AzString::from(src_attr),
520
                    });
521
                }
522
            }
523
            "style" => {
524
                // Scan text content for CSS URLs
525
                for child in node.children.as_ref().iter() {
526
                    if let XmlNodeChild::Text(text) = child {
527
                        Self::extract_css_urls(text.as_str(), resources);
528
                    }
529
                }
530
            }
531
            _ => {}
532
        }
533
        
534
        // Check inline style attribute for url()
535
        if let Some(style) = get_attr("style") {
536
            Self::extract_css_urls(&style, resources);
537
        }
538
        
539
        // Check for background attribute (deprecated but still used)
540
        if let Some(bg) = get_attr("background") {
541
            let mime = Self::guess_mime_from_url(&bg, "image");
542
            resources.push(ExternalResource {
543
                url: AzString::from(bg),
544
                kind: ExternalResourceKind::Image,
545
                mime_type: mime.into(),
546
                source_element: AzString::from(tag_name),
547
                source_attribute: AzString::from("background"),
548
            });
549
        }
550
        
551
        // Recurse into children
552
        for child in node.children.as_ref().iter() {
553
            Self::scan_node_child(child, resources);
554
        }
555
    }
556
    
557
    /// Extract URLs from CSS content (handles url() and @import)
558
    fn extract_css_urls(css: &str, resources: &mut Vec<ExternalResource>) {
559
        // Simple regex-like parsing for url(...) and @import
560
        let mut remaining = css;
561
        
562
        while let Some(pos) = remaining.find("url(") {
563
            let after_url = &remaining[pos + 4..];
564
            if let Some(url) = Self::extract_url_value(after_url) {
565
                let mime = Self::guess_mime_from_url(&url, "");
566
                let kind = Self::guess_kind_from_url(&url);
567
                resources.push(ExternalResource {
568
                    url: AzString::from(url),
569
                    kind,
570
                    mime_type: mime.into(),
571
                    source_element: AzString::from("style"),
572
                    source_attribute: AzString::from("url()"),
573
                });
574
            }
575
            remaining = after_url;
576
        }
577
        
578
        // Handle @import "url" or @import url(...)
579
        remaining = css;
580
        while let Some(pos) = remaining.to_lowercase().find("@import") {
581
            let after_import = &remaining[pos + 7..];
582
            let trimmed = after_import.trim_start();
583
            
584
            if trimmed.starts_with("url(") {
585
                if let Some(url) = Self::extract_url_value(&trimmed[4..]) {
586
                    resources.push(ExternalResource {
587
                        url: AzString::from(url),
588
                        kind: ExternalResourceKind::Stylesheet,
589
                        mime_type: Some(MimeTypeHint::new("text/css")).into(),
590
                        source_element: AzString::from("style"),
591
                        source_attribute: AzString::from("@import"),
592
                    });
593
                }
594
            } else if let Some(url) = Self::extract_quoted_string(trimmed) {
595
                resources.push(ExternalResource {
596
                    url: AzString::from(url),
597
                    kind: ExternalResourceKind::Stylesheet,
598
                    mime_type: Some(MimeTypeHint::new("text/css")).into(),
599
                    source_element: AzString::from("style"),
600
                    source_attribute: AzString::from("@import"),
601
                });
602
            }
603
            
604
            remaining = after_import;
605
        }
606
    }
607
    
608
    /// Extract value from url(...) - handles quoted and unquoted URLs
609
    fn extract_url_value(s: &str) -> Option<String> {
610
        let trimmed = s.trim_start();
611
        if trimmed.starts_with('"') {
612
            Self::extract_quoted_string(trimmed)
613
        } else if trimmed.starts_with('\'') {
614
            let end = trimmed[1..].find('\'')?;
615
            Some(trimmed[1..1+end].to_string())
616
        } else {
617
            let end = trimmed.find(')')?;
618
            Some(trimmed[..end].trim().to_string())
619
        }
620
    }
621
    
622
    /// Extract a quoted string value
623
    fn extract_quoted_string(s: &str) -> Option<String> {
624
        if s.starts_with('"') {
625
            let end = s[1..].find('"')?;
626
            Some(s[1..1+end].to_string())
627
        } else if s.starts_with('\'') {
628
            let end = s[1..].find('\'')?;
629
            Some(s[1..1+end].to_string())
630
        } else {
631
            None
632
        }
633
    }
634
    
635
    /// Parse srcset attribute into individual URLs
636
    fn parse_srcset(srcset: &str) -> Vec<String> {
637
        srcset.split(',')
638
            .filter_map(|entry| {
639
                let trimmed = entry.trim();
640
                // srcset format: "url 1x" or "url 100w"
641
                trimmed.split_whitespace().next().map(|s| s.to_string())
642
            })
643
            .filter(|url| !url.is_empty())
644
            .collect()
645
    }
646
    
647
    /// Check if a URL looks like a downloadable resource (not a page)
648
    fn looks_like_resource(url: &str) -> bool {
649
        let lower = url.to_lowercase();
650
        // Check for common resource extensions
651
        let resource_exts = [
652
            ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".bmp",
653
            ".ttf", ".otf", ".woff", ".woff2", ".eot",
654
            ".css", ".js",
655
            ".mp4", ".webm", ".ogg", ".mp3", ".wav",
656
            ".pdf", ".zip", ".tar", ".gz",
657
        ];
658
        resource_exts.iter().any(|ext| lower.ends_with(ext))
659
    }
660
    
661
    /// Guess the resource kind from URL based on file extension.
662
    fn guess_kind_from_url(url: &str) -> ExternalResourceKind {
663
        let lower = url.to_lowercase();
664
        // Strip query string before checking extension
665
        let path = lower.split('?').next().unwrap_or(&lower);
666
        if path.ends_with(".png") || path.ends_with(".jpg") || path.ends_with(".jpeg")
667
            || path.ends_with(".gif") || path.ends_with(".webp") || path.ends_with(".svg")
668
            || path.ends_with(".bmp") || path.ends_with(".avif") {
669
            ExternalResourceKind::Image
670
        } else if path.ends_with(".ttf") || path.ends_with(".otf") || path.ends_with(".woff")
671
            || path.ends_with(".woff2") || path.ends_with(".eot") {
672
            ExternalResourceKind::Font
673
        } else if path.ends_with(".css") {
674
            ExternalResourceKind::Stylesheet
675
        } else if path.ends_with(".js") || path.ends_with(".mjs") {
676
            ExternalResourceKind::Script
677
        } else if path.ends_with(".mp4") || path.ends_with(".webm") || path.ends_with(".ogg") {
678
            ExternalResourceKind::Video
679
        } else if path.ends_with(".mp3") || path.ends_with(".wav") || path.ends_with(".flac") {
680
            ExternalResourceKind::Audio
681
        } else if path.ends_with(".ico") {
682
            ExternalResourceKind::Icon
683
        } else {
684
            ExternalResourceKind::Unknown
685
        }
686
    }
687
    
688
    /// Guess MIME type from URL based on extension
689
    fn guess_mime_from_url(url: &str, category: &str) -> Option<MimeTypeHint> {
690
        let lower = url.to_lowercase();
691
        // Find extension
692
        let ext = lower.rsplit('.').next()?;
693
        // Remove query string if present
694
        let ext = ext.split('?').next()?;
695
        
696
        // Check if it's a valid extension
697
        let valid_exts = [
698
            "png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "bmp", "avif",
699
            "ttf", "otf", "woff", "woff2", "eot",
700
            "css", "js", "mjs",
701
            "mp4", "webm", "ogg", "mp3", "wav", "flac",
702
        ];
703
        
704
        if valid_exts.contains(&ext) {
705
            Some(MimeTypeHint::from_extension(ext))
706
        } else if !category.is_empty() {
707
            // Use category hint for default
708
            match category {
709
                "image" => Some(MimeTypeHint::new("image/*")),
710
                "font" => Some(MimeTypeHint::new("font/*")),
711
                "stylesheet" => Some(MimeTypeHint::new("text/css")),
712
                "script" => Some(MimeTypeHint::new("application/javascript")),
713
                "video" => Some(MimeTypeHint::new("video/*")),
714
                "audio" => Some(MimeTypeHint::new("audio/*")),
715
                _ => None,
716
            }
717
        } else {
718
            None
719
        }
720
    }
721
}
722

            
723
#[derive(Debug, PartialEq, PartialOrd, Clone)]
724
#[repr(C)]
725
pub struct NonXmlCharError {
726
    pub ch: u32, /* u32 = char, but ABI stable */
727
    pub pos: XmlTextPos,
728
}
729

            
730
#[derive(Debug, PartialEq, PartialOrd, Clone)]
731
#[repr(C)]
732
pub struct InvalidCharError {
733
    pub expected: u8,
734
    pub got: u8,
735
    pub pos: XmlTextPos,
736
}
737

            
738
#[derive(Debug, PartialEq, PartialOrd, Clone)]
739
#[repr(C)]
740
pub struct InvalidCharMultipleError {
741
    pub expected: u8,
742
    pub got: U8Vec,
743
    pub pos: XmlTextPos,
744
}
745

            
746
#[derive(Debug, PartialEq, PartialOrd, Clone)]
747
#[repr(C)]
748
pub struct InvalidQuoteError {
749
    pub got: u8,
750
    pub pos: XmlTextPos,
751
}
752

            
753
#[derive(Debug, PartialEq, PartialOrd, Clone)]
754
#[repr(C)]
755
pub struct InvalidSpaceError {
756
    pub got: u8,
757
    pub pos: XmlTextPos,
758
}
759

            
760
#[derive(Debug, PartialEq, PartialOrd, Clone)]
761
#[repr(C)]
762
pub struct InvalidStringError {
763
    pub got: AzString,
764
    pub pos: XmlTextPos,
765
}
766

            
767
#[derive(Debug, PartialEq, PartialOrd, Clone)]
768
#[repr(C, u8)]
769
pub enum XmlStreamError {
770
    UnexpectedEndOfStream,
771
    InvalidName,
772
    NonXmlChar(NonXmlCharError),
773
    InvalidChar(InvalidCharError),
774
    InvalidCharMultiple(InvalidCharMultipleError),
775
    InvalidQuote(InvalidQuoteError),
776
    InvalidSpace(InvalidSpaceError),
777
    InvalidString(InvalidStringError),
778
    InvalidReference,
779
    InvalidExternalID,
780
    InvalidCommentData,
781
    InvalidCommentEnd,
782
    InvalidCharacterData,
783
}
784

            
785
impl fmt::Display for XmlStreamError {
786
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
787
        use self::XmlStreamError::*;
788
        match self {
789
            UnexpectedEndOfStream => write!(f, "Unexpected end of stream"),
790
            InvalidName => write!(f, "Invalid name"),
791
            NonXmlChar(nx) => write!(
792
                f,
793
                "Non-XML character: {:?} at {}",
794
                core::char::from_u32(nx.ch),
795
                nx.pos
796
            ),
797
            InvalidChar(ic) => write!(
798
                f,
799
                "Invalid character: expected: {}, got: {} at {}",
800
                ic.expected as char, ic.got as char, ic.pos
801
            ),
802
            InvalidCharMultiple(imc) => write!(
803
                f,
804
                "Multiple invalid characters: expected: {}, got: {:?} at {}",
805
                imc.expected,
806
                imc.got.as_ref(),
807
                imc.pos
808
            ),
809
            InvalidQuote(iq) => write!(f, "Invalid quote: got {} at {}", iq.got as char, iq.pos),
810
            InvalidSpace(is) => write!(f, "Invalid space: got {} at {}", is.got as char, is.pos),
811
            InvalidString(ise) => write!(
812
                f,
813
                "Invalid string: got \"{}\" at {}",
814
                ise.got.as_str(),
815
                ise.pos
816
            ),
817
            InvalidReference => write!(f, "Invalid reference"),
818
            InvalidExternalID => write!(f, "Invalid external ID"),
819
            InvalidCommentData => write!(f, "Invalid comment data"),
820
            InvalidCommentEnd => write!(f, "Invalid comment end"),
821
            InvalidCharacterData => write!(f, "Invalid character data"),
822
        }
823
    }
824
}
825

            
826
#[derive(Debug, PartialEq, PartialOrd, Clone, Ord, Hash, Eq)]
827
#[repr(C)]
828
pub struct XmlTextPos {
829
    pub row: u32,
830
    pub col: u32,
831
}
832

            
833
impl fmt::Display for XmlTextPos {
834
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
835
        write!(f, "line {}:{}", self.row, self.col)
836
    }
837
}
838

            
839
#[derive(Debug, PartialEq, PartialOrd, Clone)]
840
#[repr(C)]
841
pub struct XmlTextError {
842
    pub stream_error: XmlStreamError,
843
    pub pos: XmlTextPos,
844
}
845

            
846
#[derive(Debug, PartialEq, PartialOrd, Clone)]
847
#[repr(C, u8)]
848
pub enum XmlParseError {
849
    InvalidDeclaration(XmlTextError),
850
    InvalidComment(XmlTextError),
851
    InvalidPI(XmlTextError),
852
    InvalidDoctype(XmlTextError),
853
    InvalidEntity(XmlTextError),
854
    InvalidElement(XmlTextError),
855
    InvalidAttribute(XmlTextError),
856
    InvalidCdata(XmlTextError),
857
    InvalidCharData(XmlTextError),
858
    UnknownToken(XmlTextPos),
859
}
860

            
861
impl fmt::Display for XmlParseError {
862
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
863
        use self::XmlParseError::*;
864
        match self {
865
            InvalidDeclaration(e) => {
866
                write!(f, "Invalid declaration: {} at {}", e.stream_error, e.pos)
867
            }
868
            InvalidComment(e) => write!(f, "Invalid comment: {} at {}", e.stream_error, e.pos),
869
            InvalidPI(e) => write!(
870
                f,
871
                "Invalid processing instruction: {} at {}",
872
                e.stream_error, e.pos
873
            ),
874
            InvalidDoctype(e) => write!(f, "Invalid doctype: {} at {}", e.stream_error, e.pos),
875
            InvalidEntity(e) => write!(f, "Invalid entity: {} at {}", e.stream_error, e.pos),
876
            InvalidElement(e) => write!(f, "Invalid element: {} at {}", e.stream_error, e.pos),
877
            InvalidAttribute(e) => write!(f, "Invalid attribute: {} at {}", e.stream_error, e.pos),
878
            InvalidCdata(e) => write!(f, "Invalid CDATA: {} at {}", e.stream_error, e.pos),
879
            InvalidCharData(e) => write!(f, "Invalid char data: {} at {}", e.stream_error, e.pos),
880
            UnknownToken(e) => write!(f, "Unknown token at {}", e),
881
        }
882
    }
883
}
884

            
885
impl_result!(
886
    Xml,
887
    XmlError,
888
    ResultXmlXmlError,
889
    copy = false,
890
    [Debug, PartialEq, PartialOrd, Clone]
891
);
892

            
893
#[derive(Debug, PartialEq, PartialOrd, Clone)]
894
#[repr(C)]
895
pub struct DuplicatedNamespaceError {
896
    pub ns: AzString,
897
    pub pos: XmlTextPos,
898
}
899

            
900
#[derive(Debug, PartialEq, PartialOrd, Clone)]
901
#[repr(C)]
902
pub struct UnknownNamespaceError {
903
    pub ns: AzString,
904
    pub pos: XmlTextPos,
905
}
906

            
907
#[derive(Debug, PartialEq, PartialOrd, Clone)]
908
#[repr(C)]
909
pub struct UnexpectedCloseTagError {
910
    pub expected: AzString,
911
    pub actual: AzString,
912
    pub pos: XmlTextPos,
913
}
914

            
915
#[derive(Debug, PartialEq, PartialOrd, Clone)]
916
#[repr(C)]
917
pub struct UnknownEntityReferenceError {
918
    pub entity: AzString,
919
    pub pos: XmlTextPos,
920
}
921

            
922
#[derive(Debug, PartialEq, PartialOrd, Clone)]
923
#[repr(C)]
924
pub struct DuplicatedAttributeError {
925
    pub attribute: AzString,
926
    pub pos: XmlTextPos,
927
}
928

            
929
/// Error for mismatched open/close tags in XML hierarchy
930
#[derive(Debug, PartialEq, PartialOrd, Clone)]
931
#[repr(C)]
932
pub struct MalformedHierarchyError {
933
    /// The tag that was expected (from the opening tag)
934
    pub expected: AzString,
935
    /// The tag that was actually found (the closing tag)
936
    pub got: AzString,
937
}
938

            
939
#[derive(Debug, PartialEq, PartialOrd, Clone)]
940
#[repr(C, u8)]
941
pub enum XmlError {
942
    NoParserAvailable,
943
    InvalidXmlPrefixUri(XmlTextPos),
944
    UnexpectedXmlUri(XmlTextPos),
945
    UnexpectedXmlnsUri(XmlTextPos),
946
    InvalidElementNamePrefix(XmlTextPos),
947
    DuplicatedNamespace(DuplicatedNamespaceError),
948
    UnknownNamespace(UnknownNamespaceError),
949
    UnexpectedCloseTag(UnexpectedCloseTagError),
950
    UnexpectedEntityCloseTag(XmlTextPos),
951
    UnknownEntityReference(UnknownEntityReferenceError),
952
    MalformedEntityReference(XmlTextPos),
953
    EntityReferenceLoop(XmlTextPos),
954
    InvalidAttributeValue(XmlTextPos),
955
    DuplicatedAttribute(DuplicatedAttributeError),
956
    NoRootNode,
957
    SizeLimit,
958
    DtdDetected,
959
    /// Invalid hierarchy close tags, i.e `<app></p></app>`
960
    MalformedHierarchy(MalformedHierarchyError),
961
    ParserError(XmlParseError),
962
    UnclosedRootNode,
963
    UnexpectedDeclaration(XmlTextPos),
964
    NodesLimitReached,
965
    AttributesLimitReached,
966
    NamespacesLimitReached,
967
    InvalidName(XmlTextPos),
968
    NonXmlChar(XmlTextPos),
969
    InvalidChar(XmlTextPos),
970
    InvalidChar2(XmlTextPos),
971
    InvalidString(XmlTextPos),
972
    InvalidExternalID(XmlTextPos),
973
    InvalidComment(XmlTextPos),
974
    InvalidCharacterData(XmlTextPos),
975
    UnknownToken(XmlTextPos),
976
    UnexpectedEndOfStream,
977
}
978

            
979
impl fmt::Display for XmlError {
980
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
981
        use self::XmlError::*;
982
        match self {
983
            NoParserAvailable => write!(
984
                f,
985
                "Library was compiled without XML parser (XML parser not available)"
986
            ),
987
            InvalidXmlPrefixUri(pos) => {
988
                write!(f, "Invalid XML Prefix URI at line {}:{}", pos.row, pos.col)
989
            }
990
            UnexpectedXmlUri(pos) => {
991
                write!(f, "Unexpected XML URI at line {}:{}", pos.row, pos.col)
992
            }
993
            UnexpectedXmlnsUri(pos) => write!(
994
                f,
995
                "Unexpected XML namespace URI at line {}:{}",
996
                pos.row, pos.col
997
            ),
998
            InvalidElementNamePrefix(pos) => write!(
999
                f,
                "Invalid element name prefix at line {}:{}",
                pos.row, pos.col
            ),
            DuplicatedNamespace(ns) => write!(
                f,
                "Duplicated namespace: \"{}\" at {}",
                ns.ns.as_str(),
                ns.pos
            ),
            UnknownNamespace(uns) => write!(
                f,
                "Unknown namespace: \"{}\" at {}",
                uns.ns.as_str(),
                uns.pos
            ),
            UnexpectedCloseTag(ct) => write!(
                f,
                "Unexpected close tag: expected \"{}\", got \"{}\" at {}",
                ct.expected.as_str(),
                ct.actual.as_str(),
                ct.pos
            ),
            UnexpectedEntityCloseTag(pos) => write!(
                f,
                "Unexpected entity close tag at line {}:{}",
                pos.row, pos.col
            ),
            UnknownEntityReference(uer) => write!(
                f,
                "Unexpected entity reference: \"{}\" at {}",
                uer.entity, uer.pos
            ),
            MalformedEntityReference(pos) => write!(
                f,
                "Malformed entity reference at line {}:{}",
                pos.row, pos.col
            ),
            EntityReferenceLoop(pos) => write!(
                f,
                "Entity reference loop (recursive entity reference) at line {}:{}",
                pos.row, pos.col
            ),
            InvalidAttributeValue(pos) => {
                write!(f, "Invalid attribute value at line {}:{}", pos.row, pos.col)
            }
            DuplicatedAttribute(ae) => write!(
                f,
                "Duplicated attribute \"{}\" at line {}:{}",
                ae.attribute.as_str(),
                ae.pos.row,
                ae.pos.col
            ),
            NoRootNode => write!(f, "No root node found"),
            SizeLimit => write!(f, "XML file too large (size limit reached)"),
            DtdDetected => write!(f, "Document type descriptor detected"),
            MalformedHierarchy(e) => write!(
                f,
                "Malformed hierarchy: expected <{}/> closing tag, got <{}/>",
                e.expected.as_str(),
                e.got.as_str()
            ),
            ParserError(p) => write!(f, "{}", p),
            UnclosedRootNode => write!(f, "unclosed root node"),
            UnexpectedDeclaration(tp) => write!(f, "unexpected declaration at {tp}"),
            NodesLimitReached => write!(f, "nodes limit reached"),
            AttributesLimitReached => write!(f, "attributes limit reached"),
            NamespacesLimitReached => write!(f, "namespaces limit reached"),
            InvalidName(tp) => write!(f, "invalid name at {tp}"),
            NonXmlChar(tp) => write!(f, "non xml char at {tp}"),
            InvalidChar(tp) => write!(f, "invalid char at {tp}"),
            InvalidChar2(tp) => write!(f, "invalid char2 at {tp}"),
            InvalidString(tp) => write!(f, "invalid string at {tp}"),
            InvalidExternalID(tp) => write!(f, "invalid externalid at {tp}"),
            InvalidComment(tp) => write!(f, "invalid comment at {tp}"),
            InvalidCharacterData(tp) => write!(f, "invalid character data at {tp}"),
            UnknownToken(tp) => write!(f, "unknown token at {tp}"),
            UnexpectedEndOfStream => write!(f, "unexpected end of stream"),
        }
    }
}
// ============================================================================
// New repr(C) component system
// ============================================================================
/// Identifies a component within a library collection.
/// e.g. collection="builtin", name="div" for the `<div>` element,
/// or collection="shadcn", name="avatar" for a custom component.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct ComponentId {
    /// Library / collection name: "builtin", "shadcn", "myproject"
    pub collection: AzString,
    /// Component name within the collection: "div", "avatar", "card"
    pub name: AzString,
}
impl ComponentId {
362250
    pub fn builtin(name: &str) -> Self {
362250
        Self {
362250
            collection: AzString::from_const_str("builtin"),
362250
            name: AzString::from(name),
362250
        }
362250
    }
    pub fn new(collection: &str, name: &str) -> Self {
        Self {
            collection: AzString::from(collection),
            name: AzString::from(name),
        }
    }
    /// Returns "collection:name" format string
    pub fn qualified_name(&self) -> String {
        format!("{}:{}", self.collection.as_str(), self.name.as_str())
    }
}
// ============================================================================
// Component type system — rich type descriptors for component fields
// ============================================================================
/// A single argument in a callback signature.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct ComponentCallbackArg {
    /// Argument name, e.g. "button_id"
    pub name: AzString,
    /// Argument type
    pub arg_type: ComponentFieldType,
}
impl_vec!(ComponentCallbackArg, ComponentCallbackArgVec, ComponentCallbackArgVecDestructor, ComponentCallbackArgVecDestructorType, ComponentCallbackArgVecSlice, OptionComponentCallbackArg);
impl_option!(ComponentCallbackArg, OptionComponentCallbackArg, copy = false, [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]);
impl_vec_debug!(ComponentCallbackArg, ComponentCallbackArgVec);
impl_vec_partialeq!(ComponentCallbackArg, ComponentCallbackArgVec);
impl_vec_eq!(ComponentCallbackArg, ComponentCallbackArgVec);
impl_vec_partialord!(ComponentCallbackArg, ComponentCallbackArgVec);
impl_vec_ord!(ComponentCallbackArg, ComponentCallbackArgVec);
impl_vec_hash!(ComponentCallbackArg, ComponentCallbackArgVec);
impl_vec_clone!(ComponentCallbackArg, ComponentCallbackArgVec, ComponentCallbackArgVecDestructor);
/// Callback signature: return type + argument list.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct ComponentCallbackSignature {
    /// Return type name, e.g. "Update"
    pub return_type: AzString,
    /// Callback arguments (excluding the implicit `&mut RefAny` and `&mut CallbackInfo`)
    pub args: ComponentCallbackArgVec,
}
/// Heap-allocated box for recursive `ComponentFieldType` (e.g. `Option<String>`).
/// Uses raw pointer indirection to break the infinite size.
#[repr(C)]
pub struct ComponentFieldTypeBox {
    pub ptr: *mut ComponentFieldType,
}
impl ComponentFieldTypeBox {
    pub fn new(t: ComponentFieldType) -> Self {
        Self { ptr: Box::into_raw(Box::new(t)) }
    }
    pub fn as_ref(&self) -> &ComponentFieldType {
        unsafe { &*self.ptr }
    }
}
impl Clone for ComponentFieldTypeBox {
    fn clone(&self) -> Self {
        Self::new(unsafe { (*self.ptr).clone() })
    }
}
impl Drop for ComponentFieldTypeBox {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe { let _ = Box::from_raw(self.ptr); }
        }
    }
}
impl fmt::Debug for ComponentFieldTypeBox {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.ptr.is_null() {
            write!(f, "ComponentFieldTypeBox(null)")
        } else {
            write!(f, "ComponentFieldTypeBox({:?})", unsafe { &*self.ptr })
        }
    }
}
impl PartialEq for ComponentFieldTypeBox {
    fn eq(&self, other: &Self) -> bool {
        if self.ptr.is_null() && other.ptr.is_null() { return true; }
        if self.ptr.is_null() || other.ptr.is_null() { return false; }
        unsafe { *self.ptr == *other.ptr }
    }
}
impl Eq for ComponentFieldTypeBox {}
impl PartialOrd for ComponentFieldTypeBox {
    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
        Some(self.cmp(other))
    }
}
impl Ord for ComponentFieldTypeBox {
    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
        match (self.ptr.is_null(), other.ptr.is_null()) {
            (true, true) => core::cmp::Ordering::Equal,
            (true, false) => core::cmp::Ordering::Less,
            (false, true) => core::cmp::Ordering::Greater,
            (false, false) => unsafe { (*self.ptr).cmp(&*other.ptr) },
        }
    }
}
impl core::hash::Hash for ComponentFieldTypeBox {
    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
        if !self.ptr.is_null() {
            unsafe { (*self.ptr).hash(state); }
        }
    }
}
/// Heap-allocated box for recursive `ComponentFieldValue` (e.g. `Some(value)`).
/// Uses raw pointer indirection to break the infinite size.
#[repr(C)]
pub struct ComponentFieldValueBox {
    pub ptr: *mut ComponentFieldValue,
}
impl ComponentFieldValueBox {
    pub fn new(v: ComponentFieldValue) -> Self {
        Self { ptr: Box::into_raw(Box::new(v)) }
    }
    pub fn as_ref(&self) -> &ComponentFieldValue {
        unsafe { &*self.ptr }
    }
}
impl Clone for ComponentFieldValueBox {
    fn clone(&self) -> Self {
        Self::new(unsafe { (*self.ptr).clone() })
    }
}
impl Drop for ComponentFieldValueBox {
    fn drop(&mut self) {
        if !self.ptr.is_null() {
            unsafe { let _ = Box::from_raw(self.ptr); }
        }
    }
}
impl fmt::Debug for ComponentFieldValueBox {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.ptr.is_null() {
            write!(f, "ComponentFieldValueBox(null)")
        } else {
            write!(f, "ComponentFieldValueBox({:?})", unsafe { &*self.ptr })
        }
    }
}
impl PartialEq for ComponentFieldValueBox {
    fn eq(&self, other: &Self) -> bool {
        if self.ptr.is_null() && other.ptr.is_null() { return true; }
        if self.ptr.is_null() || other.ptr.is_null() { return false; }
        unsafe { *self.ptr == *other.ptr }
    }
}
/// Rich type descriptor for a component field.
/// Replaces the old `AzString` type names ("String", "bool", etc.) with
/// a structured enum that the debugger can use for type-aware editing.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum ComponentFieldType {
    String,
    Bool,
    I32,
    I64,
    U32,
    U64,
    Usize,
    F32,
    F64,
    ColorU,
    CssProperty,
    ImageRef,
    FontRef,
    /// StyledDom slot — field name = slot name
    StyledDom,
    /// Callback with typed signature
    Callback(ComponentCallbackSignature),
    /// RefAny data binding with type hint
    RefAny(AzString),
    /// Optional value (recursive via Box)
    OptionType(ComponentFieldTypeBox),
    /// Vec of values (recursive via Box)
    VecType(ComponentFieldTypeBox),
    /// Reference to a struct defined in the same library
    StructRef(AzString),
    /// Reference to an enum defined in the same library
    EnumRef(AzString),
}
impl ComponentFieldType {
    /// Parse a field type string like "String", "Option<Bool>", "Vec<I32>",
    /// "Callback(fn(LayoutCallbackInfo) -> Dom)", "StructRef(MyStruct)" etc.
    /// Returns `None` if the string cannot be parsed.
    pub fn parse(s: &str) -> Option<Self> {
        let s = s.trim();
        match s {
            "String" | "string" => return Some(ComponentFieldType::String),
            "Bool" | "bool" => return Some(ComponentFieldType::Bool),
            "I32" | "i32" => return Some(ComponentFieldType::I32),
            "I64" | "i64" => return Some(ComponentFieldType::I64),
            "U32" | "u32" => return Some(ComponentFieldType::U32),
            "U64" | "u64" => return Some(ComponentFieldType::U64),
            "Usize" | "usize" => return Some(ComponentFieldType::Usize),
            "F32" | "f32" => return Some(ComponentFieldType::F32),
            "F64" | "f64" => return Some(ComponentFieldType::F64),
            "ColorU" => return Some(ComponentFieldType::ColorU),
            "CssProperty" => return Some(ComponentFieldType::CssProperty),
            "ImageRef" => return Some(ComponentFieldType::ImageRef),
            "FontRef" => return Some(ComponentFieldType::FontRef),
            "StyledDom" => return Some(ComponentFieldType::StyledDom),
            "RefAny" => return Some(ComponentFieldType::RefAny(AzString::from(""))),
            _ => {}
        }
        // Option<T>
        if let Some(inner) = s.strip_prefix("Option<").and_then(|r| r.strip_suffix('>')) {
            let inner_type = ComponentFieldType::parse(inner)?;
            return Some(ComponentFieldType::OptionType(ComponentFieldTypeBox::new(inner_type)));
        }
        // Vec<T>
        if let Some(inner) = s.strip_prefix("Vec<").and_then(|r| r.strip_suffix('>')) {
            let inner_type = ComponentFieldType::parse(inner)?;
            return Some(ComponentFieldType::VecType(ComponentFieldTypeBox::new(inner_type)));
        }
        // Callback(signature)
        if let Some(sig) = s.strip_prefix("Callback(").and_then(|r| r.strip_suffix(')')) {
            return Some(ComponentFieldType::Callback(ComponentCallbackSignature {
                return_type: AzString::from(sig),
                args: Vec::new().into(),
            }));
        }
        // RefAny(TypeHint)
        if let Some(hint) = s.strip_prefix("RefAny(").and_then(|r| r.strip_suffix(')')) {
            return Some(ComponentFieldType::RefAny(AzString::from(hint)));
        }
        // EnumRef(Name) — explicit
        if let Some(name) = s.strip_prefix("EnumRef(").and_then(|r| r.strip_suffix(')')) {
            return Some(ComponentFieldType::EnumRef(AzString::from(name)));
        }
        // StructRef(Name) — explicit
        if let Some(name) = s.strip_prefix("StructRef(").and_then(|r| r.strip_suffix(')')) {
            return Some(ComponentFieldType::StructRef(AzString::from(name)));
        }
        // If starts with uppercase, treat as StructRef
        if s.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) {
            return Some(ComponentFieldType::StructRef(AzString::from(s)));
        }
        None
    }
    /// Format this field type to its canonical string representation.
    /// This is the inverse of `parse`.
    pub fn format(&self) -> String {
        match self {
            ComponentFieldType::String => "String".to_string(),
            ComponentFieldType::Bool => "Bool".to_string(),
            ComponentFieldType::I32 => "I32".to_string(),
            ComponentFieldType::I64 => "I64".to_string(),
            ComponentFieldType::U32 => "U32".to_string(),
            ComponentFieldType::U64 => "U64".to_string(),
            ComponentFieldType::Usize => "Usize".to_string(),
            ComponentFieldType::F32 => "F32".to_string(),
            ComponentFieldType::F64 => "F64".to_string(),
            ComponentFieldType::ColorU => "ColorU".to_string(),
            ComponentFieldType::CssProperty => "CssProperty".to_string(),
            ComponentFieldType::ImageRef => "ImageRef".to_string(),
            ComponentFieldType::FontRef => "FontRef".to_string(),
            ComponentFieldType::StyledDom => "StyledDom".to_string(),
            ComponentFieldType::Callback(sig) => format!("Callback({})", sig.return_type.as_str()),
            ComponentFieldType::RefAny(hint) => {
                if hint.as_str().is_empty() {
                    "RefAny".to_string()
                } else {
                    format!("RefAny({})", hint.as_str())
                }
            }
            ComponentFieldType::OptionType(inner) => format!("Option<{}>", inner.as_ref().format()),
            ComponentFieldType::VecType(inner) => format!("Vec<{}>", inner.as_ref().format()),
            ComponentFieldType::StructRef(name) => name.as_str().to_string(),
            ComponentFieldType::EnumRef(name) => name.as_str().to_string(),
        }
    }
}
impl core::fmt::Display for ComponentFieldType {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        f.write_str(&self.format())
    }
}
/// A single variant in a component enum model.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ComponentEnumVariant {
    /// Variant name, e.g. "Admin", "Editor", "Viewer"
    pub name: AzString,
    /// Human-readable description for this variant
    pub description: AzString,
    /// Optional associated fields for this variant
    pub fields: ComponentDataFieldVec,
}
impl_vec!(ComponentEnumVariant, ComponentEnumVariantVec, ComponentEnumVariantVecDestructor, ComponentEnumVariantVecDestructorType, ComponentEnumVariantVecSlice, OptionComponentEnumVariant);
impl_option!(ComponentEnumVariant, OptionComponentEnumVariant, copy = false, [Debug, Clone, PartialEq]);
impl_vec_debug!(ComponentEnumVariant, ComponentEnumVariantVec);
impl_vec_partialeq!(ComponentEnumVariant, ComponentEnumVariantVec);
impl_vec_clone!(ComponentEnumVariant, ComponentEnumVariantVec, ComponentEnumVariantVecDestructor);
/// A named enum model for code generation.
/// Stored in `ComponentLibrary::enum_models`.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ComponentEnumModel {
    /// Enum name, e.g. "UserRole"
    pub name: AzString,
    /// Human-readable description
    pub description: AzString,
    /// Variants
    pub variants: ComponentEnumVariantVec,
}
impl_vec!(ComponentEnumModel, ComponentEnumModelVec, ComponentEnumModelVecDestructor, ComponentEnumModelVecDestructorType, ComponentEnumModelVecSlice, OptionComponentEnumModel);
impl_option!(ComponentEnumModel, OptionComponentEnumModel, copy = false, [Debug, Clone, PartialEq]);
impl_vec_debug!(ComponentEnumModel, ComponentEnumModelVec);
impl_vec_partialeq!(ComponentEnumModel, ComponentEnumModelVec);
impl_vec_clone!(ComponentEnumModel, ComponentEnumModelVec, ComponentEnumModelVecDestructor);
/// Default value for a component field.
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum ComponentDefaultValue {
    /// No default value (field is required)
    None,
    /// String literal default
    String(AzString),
    /// Boolean default
    Bool(bool),
    /// i32 default
    I32(i32),
    /// i64 default
    I64(i64),
    /// u32 default
    U32(u32),
    /// u64 default
    U64(u64),
    /// usize default
    Usize(usize),
    /// f32 default
    F32(f32),
    /// f64 default
    F64(f64),
    /// ColorU default
    ColorU(ColorU),
    /// Default is an instance of another component
    ComponentInstance(ComponentInstanceDefault),
    /// Default callback function pointer name
    CallbackFnPointer(AzString),
    /// JSON string representing a complex default value
    Json(AzString),
}
impl_option!(ComponentDefaultValue, OptionComponentDefaultValue, copy = false, [Debug, Clone, PartialEq]);
/// Default component instance for a StyledDom slot.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ComponentInstanceDefault {
    /// Library name, e.g. "builtin"
    pub library: AzString,
    /// Component tag, e.g. "a"
    pub component: AzString,
    /// Field overrides for this instance
    pub field_overrides: ComponentFieldOverrideVec,
}
/// An override for a single field in a component instance.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ComponentFieldOverride {
    /// Field name to override
    pub field_name: AzString,
    /// Value source for this override
    pub source: ComponentFieldValueSource,
}
impl_vec!(ComponentFieldOverride, ComponentFieldOverrideVec, ComponentFieldOverrideVecDestructor, ComponentFieldOverrideVecDestructorType, ComponentFieldOverrideVecSlice, OptionComponentFieldOverride);
impl_option!(ComponentFieldOverride, OptionComponentFieldOverride, copy = false, [Debug, Clone, PartialEq]);
impl_vec_debug!(ComponentFieldOverride, ComponentFieldOverrideVec);
impl_vec_partialeq!(ComponentFieldOverride, ComponentFieldOverrideVec);
impl_vec_clone!(ComponentFieldOverride, ComponentFieldOverrideVec, ComponentFieldOverrideVecDestructor);
/// How a field value is sourced at the instance level.
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum ComponentFieldValueSource {
    /// Use the component's default value
    Default,
    /// Hardcoded literal value (as string, parsed at runtime)
    Literal(AzString),
    /// Bound to an app state path (e.g. "app_state.user.name")
    Binding(AzString),
}
/// Runtime value for a component field — the "instance" counterpart
/// to `ComponentFieldType` (which is the "class" / type descriptor).
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum ComponentFieldValue {
    String(AzString),
    Bool(bool),
    I32(i32),
    I64(i64),
    U32(u32),
    U64(u64),
    Usize(usize),
    F32(f32),
    F64(f64),
    ColorU(ColorU),
    /// Option<T> with no value
    None,
    /// Option<T> with a value
    Some(ComponentFieldValueBox),
    /// Vec of values
    Vec(ComponentFieldValueVec),
    /// StyledDom slot content
    StyledDom(StyledDom),
    /// Struct fields, in order
    Struct(ComponentFieldNamedValueVec),
    /// Enum variant
    Enum { variant: AzString, fields: ComponentFieldNamedValueVec },
    /// Callback function reference (function name as string)
    Callback(AzString),
    /// Opaque reference-counted data
    RefAny(crate::refany::RefAny),
}
/// Named field value: (field_name, value) pair.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ComponentFieldNamedValue {
    pub name: AzString,
    pub value: ComponentFieldValue,
}
impl_vec!(ComponentFieldNamedValue, ComponentFieldNamedValueVec, ComponentFieldNamedValueVecDestructor, ComponentFieldNamedValueVecDestructorType, ComponentFieldNamedValueVecSlice, OptionComponentFieldNamedValue);
impl_option!(ComponentFieldNamedValue, OptionComponentFieldNamedValue, copy = false, [Debug, Clone, PartialEq]);
impl_vec_debug!(ComponentFieldNamedValue, ComponentFieldNamedValueVec);
impl_vec_partialeq!(ComponentFieldNamedValue, ComponentFieldNamedValueVec);
impl_vec_clone!(ComponentFieldNamedValue, ComponentFieldNamedValueVec, ComponentFieldNamedValueVecDestructor);
impl ComponentFieldNamedValueVec {
    /// Look up a field by name, return a reference to its value.
    pub fn get_field(&self, name: &str) -> Option<&ComponentFieldValue> {
        self.as_ref().iter().find_map(|v| {
            if v.name.as_str() == name { Some(&v.value) } else { None }
        })
    }
    /// Convenience: get a field as `&str` if it is `ComponentFieldValue::String`.
    pub fn get_string(&self, name: &str) -> Option<&AzString> {
        match self.get_field(name) {
            Some(ComponentFieldValue::String(s)) => Some(s),
            _ => None,
        }
    }
}
impl_vec!(ComponentFieldValue, ComponentFieldValueVec, ComponentFieldValueVecDestructor, ComponentFieldValueVecDestructorType, ComponentFieldValueVecSlice, OptionComponentFieldValue);
impl_option!(ComponentFieldValue, OptionComponentFieldValue, copy = false, [Debug, Clone, PartialEq]);
impl_vec_debug!(ComponentFieldValue, ComponentFieldValueVec);
impl_vec_partialeq!(ComponentFieldValue, ComponentFieldValueVec);
impl_vec_clone!(ComponentFieldValue, ComponentFieldValueVec, ComponentFieldValueVecDestructor);
/// A field in the component's internal data model.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct ComponentDataField {
    /// Field name, e.g. "counter", "text", "number"
    pub name: AzString,
    /// Rich type descriptor for this field
    pub field_type: ComponentFieldType,
    /// Typed default value, or None if the field is required
    pub default_value: OptionComponentDefaultValue,
    /// Whether this field is required (must be provided by the parent)
    pub required: bool,
    /// Human-readable description
    pub description: AzString,
}
impl_vec!(ComponentDataField, ComponentDataFieldVec, ComponentDataFieldVecDestructor, ComponentDataFieldVecDestructorType, ComponentDataFieldVecSlice, OptionComponentDataField);
impl_option!(ComponentDataField, OptionComponentDataField, copy = false, [Debug, Clone, PartialEq]);
impl_vec_debug!(ComponentDataField, ComponentDataFieldVec);
impl_vec_partialeq!(ComponentDataField, ComponentDataFieldVec);
impl_vec_clone!(ComponentDataField, ComponentDataFieldVec, ComponentDataFieldVecDestructor);
/// A named data model (struct definition) for code generation.
///
/// Stored in `ComponentLibrary::data_models`. Components reference these
/// by name in `ComponentDataField::field_type`, enabling nested/structured
/// data models. For example, a `UserCard` component might have a field
/// `user: UserProfile` where `UserProfile` is a `ComponentDataModel`.
#[derive(Debug, Clone)]
#[repr(C)]
pub struct ComponentDataModel {
    /// Type name, e.g. "UserProfile", "TodoItem"
    pub name: AzString,
    /// Human-readable description
    pub description: AzString,
    /// Fields in this struct
    pub fields: ComponentDataFieldVec,
}
impl ComponentDataModel {
    /// Look up a field by name.
    pub fn get_field(&self, name: &str) -> Option<&ComponentDataField> {
        self.fields.as_ref().iter().find(|f| f.name.as_str() == name)
    }
    /// Look up a field's default value as a string, if it exists and is a String variant.
    pub fn get_default_string(&self, name: &str) -> Option<&AzString> {
        self.get_field(name).and_then(|f| {
            match &f.default_value {
                OptionComponentDefaultValue::Some(ComponentDefaultValue::String(s)) => Some(s),
                _ => None,
            }
        })
    }
    /// Clone this data model, overriding the default value for a field by name.
    /// If the field is not found, the data model is returned unchanged.
    pub fn with_default(mut self, name: &str, value: ComponentDefaultValue) -> Self {
        let mut fields_vec = core::mem::replace(
            &mut self.fields,
            ComponentDataFieldVec::from_const_slice(&[]),
        ).into_library_owned_vec();
        for f in fields_vec.iter_mut() {
            if f.name.as_str() == name {
                f.default_value = OptionComponentDefaultValue::Some(value);
                break;
            }
        }
        self.fields = ComponentDataFieldVec::from_vec(fields_vec);
        self
    }
}
impl_vec!(ComponentDataModel, ComponentDataModelVec, ComponentDataModelVecDestructor, ComponentDataModelVecDestructorType, ComponentDataModelVecSlice, OptionComponentDataModel);
impl_option!(ComponentDataModel, OptionComponentDataModel, copy = false, [Debug, Clone]);
impl_vec_debug!(ComponentDataModel, ComponentDataModelVec);
impl_vec_clone!(ComponentDataModel, ComponentDataModelVec, ComponentDataModelVecDestructor);
impl_vec_mut!(ComponentDataModel, ComponentDataModelVec);
// ============================================================================
// Serde support for ComponentDataModel (feature-gated)
// ============================================================================
#[cfg(feature = "serde-json")]
mod serde_impl {
    use super::*;
    use serde::{Serialize, Serializer, Deserialize, Deserializer};
    use serde::ser::SerializeStruct;
    // --- AzString helpers ---
    fn ser_azstring<S: Serializer>(s: &AzString, serializer: S) -> Result<S::Ok, S::Error> {
        serializer.serialize_str(s.as_str())
    }
    fn de_azstring<'de, D: Deserializer<'de>>(deserializer: D) -> Result<AzString, D::Error> {
        let s = alloc::string::String::deserialize(deserializer)?;
        Ok(AzString::from(s.as_str()))
    }
    // --- ComponentFieldType ---
    impl Serialize for ComponentFieldType {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            serializer.serialize_str(&field_type_to_string(self))
        }
    }
    impl<'de> Deserialize<'de> for ComponentFieldType {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            let s = alloc::string::String::deserialize(deserializer)?;
            Ok(string_to_field_type(&s))
        }
    }
    fn field_type_to_string(ft: &ComponentFieldType) -> alloc::string::String {
        match ft {
            ComponentFieldType::String => "String".into(),
            ComponentFieldType::Bool => "bool".into(),
            ComponentFieldType::I32 => "i32".into(),
            ComponentFieldType::I64 => "i64".into(),
            ComponentFieldType::U32 => "u32".into(),
            ComponentFieldType::U64 => "u64".into(),
            ComponentFieldType::Usize => "usize".into(),
            ComponentFieldType::F32 => "f32".into(),
            ComponentFieldType::F64 => "f64".into(),
            ComponentFieldType::ColorU => "ColorU".into(),
            ComponentFieldType::CssProperty => "CssProperty".into(),
            ComponentFieldType::ImageRef => "ImageRef".into(),
            ComponentFieldType::FontRef => "FontRef".into(),
            ComponentFieldType::StyledDom => "Dom".into(),
            ComponentFieldType::Callback(sig) => alloc::format!(
                "Callback({})",
                sig.return_type.as_str()
            ),
            ComponentFieldType::RefAny(hint) => alloc::format!("RefAny({})", hint.as_str()),
            ComponentFieldType::OptionType(inner) => alloc::format!(
                "Option<{}>",
                field_type_to_string(inner.as_ref())
            ),
            ComponentFieldType::VecType(inner) => alloc::format!(
                "Vec<{}>",
                field_type_to_string(inner.as_ref())
            ),
            ComponentFieldType::StructRef(name) => alloc::format!("struct:{}", name.as_str()),
            ComponentFieldType::EnumRef(name) => alloc::format!("enum:{}", name.as_str()),
        }
    }
    fn string_to_field_type(s: &str) -> ComponentFieldType {
        match s {
            "String" | "string" => ComponentFieldType::String,
            "bool" | "Bool" => ComponentFieldType::Bool,
            "i32" | "I32" => ComponentFieldType::I32,
            "i64" | "I64" => ComponentFieldType::I64,
            "u32" | "U32" => ComponentFieldType::U32,
            "u64" | "U64" => ComponentFieldType::U64,
            "usize" | "Usize" => ComponentFieldType::Usize,
            "f32" | "F32" => ComponentFieldType::F32,
            "f64" | "F64" => ComponentFieldType::F64,
            "ColorU" | "Color" | "color" => ComponentFieldType::ColorU,
            "CssProperty" => ComponentFieldType::CssProperty,
            "ImageRef" | "Image" => ComponentFieldType::ImageRef,
            "FontRef" | "Font" => ComponentFieldType::FontRef,
            "Dom" | "StyledDom" | "Children" => ComponentFieldType::StyledDom,
            other => {
                if let Some(inner) = other.strip_prefix("Option<").and_then(|s| s.strip_suffix('>')) {
                    ComponentFieldType::OptionType(ComponentFieldTypeBox::new(string_to_field_type(inner)))
                } else if let Some(inner) = other.strip_prefix("Vec<").and_then(|s| s.strip_suffix('>')) {
                    ComponentFieldType::VecType(ComponentFieldTypeBox::new(string_to_field_type(inner)))
                } else if let Some(name) = other.strip_prefix("struct:") {
                    ComponentFieldType::StructRef(AzString::from(name))
                } else if let Some(name) = other.strip_prefix("enum:") {
                    ComponentFieldType::EnumRef(AzString::from(name))
                } else if other.starts_with("Callback") {
                    let ret = other.strip_prefix("Callback(")
                        .and_then(|s| s.strip_suffix(')'))
                        .unwrap_or("()");
                    ComponentFieldType::Callback(ComponentCallbackSignature {
                        return_type: AzString::from(ret),
                        args: ComponentCallbackArgVec::from_const_slice(&[]),
                    })
                } else if other.starts_with("RefAny") {
                    let hint = other.strip_prefix("RefAny(")
                        .and_then(|s| s.strip_suffix(')'))
                        .unwrap_or("");
                    ComponentFieldType::RefAny(AzString::from(hint))
                } else {
                    ComponentFieldType::String // fallback
                }
            }
        }
    }
    // --- ComponentDefaultValue ---
    impl Serialize for ComponentDefaultValue {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            use serde::ser::SerializeMap;
            match self {
                ComponentDefaultValue::None => serializer.serialize_none(),
                ComponentDefaultValue::String(s) => serializer.serialize_str(s.as_str()),
                ComponentDefaultValue::Bool(b) => serializer.serialize_bool(*b),
                ComponentDefaultValue::I32(v) => serializer.serialize_i32(*v),
                ComponentDefaultValue::I64(v) => serializer.serialize_i64(*v),
                ComponentDefaultValue::U32(v) => serializer.serialize_u32(*v),
                ComponentDefaultValue::U64(v) => serializer.serialize_u64(*v),
                ComponentDefaultValue::Usize(v) => serializer.serialize_u64(*v as u64),
                ComponentDefaultValue::F32(v) => serializer.serialize_f32(*v),
                ComponentDefaultValue::F64(v) => serializer.serialize_f64(*v),
                ComponentDefaultValue::ColorU(c) => {
                    serializer.serialize_str(&alloc::format!("#{:02x}{:02x}{:02x}{:02x}", c.r, c.g, c.b, c.a))
                }
                ComponentDefaultValue::ComponentInstance(ci) => {
                    let mut map = serializer.serialize_map(Some(2))?;
                    map.serialize_entry("library", ci.library.as_str())?;
                    map.serialize_entry("component", ci.component.as_str())?;
                    map.end()
                }
                ComponentDefaultValue::CallbackFnPointer(name) => {
                    serializer.serialize_str(name.as_str())
                }
                ComponentDefaultValue::Json(json_str) => {
                    // Serialize raw JSON string as-is by parsing and re-emitting
                    match serde_json::from_str::<serde_json::Value>(json_str.as_str()) {
                        Ok(v) => v.serialize(serializer),
                        Err(_) => serializer.serialize_str(json_str.as_str()),
                    }
                }
            }
        }
    }
    impl<'de> Deserialize<'de> for ComponentDefaultValue {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            let val = serde_json::Value::deserialize(deserializer)?;
            Ok(match val {
                serde_json::Value::Null => ComponentDefaultValue::None,
                serde_json::Value::Bool(b) => ComponentDefaultValue::Bool(b),
                serde_json::Value::Number(n) => {
                    if let Some(i) = n.as_i64() {
                        if let Ok(v) = i32::try_from(i) {
                            ComponentDefaultValue::I32(v)
                        } else {
                            ComponentDefaultValue::I64(i)
                        }
                    } else if let Some(f) = n.as_f64() {
                        ComponentDefaultValue::F64(f)
                    } else {
                        ComponentDefaultValue::None
                    }
                }
                serde_json::Value::String(s) => {
                    ComponentDefaultValue::String(AzString::from(s.as_str()))
                }
                _ => ComponentDefaultValue::None,
            })
        }
    }
    // --- OptionComponentDefaultValue ---
    impl Serialize for OptionComponentDefaultValue {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            match self {
                OptionComponentDefaultValue::Some(v) => v.serialize(serializer),
                OptionComponentDefaultValue::None => serializer.serialize_none(),
            }
        }
    }
    impl<'de> Deserialize<'de> for OptionComponentDefaultValue {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            let val = Option::<ComponentDefaultValue>::deserialize(deserializer)?;
            Ok(match val {
                Some(v) => OptionComponentDefaultValue::Some(v),
                None => OptionComponentDefaultValue::None,
            })
        }
    }
    // --- ComponentDataField ---
    impl Serialize for ComponentDataField {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            let mut s = serializer.serialize_struct("ComponentDataField", 5)?;
            s.serialize_field("name", self.name.as_str())?;
            s.serialize_field("type", &self.field_type)?;
            s.serialize_field("default", &self.default_value)?;
            s.serialize_field("required", &self.required)?;
            s.serialize_field("description", self.description.as_str())?;
            s.end()
        }
    }
    impl<'de> Deserialize<'de> for ComponentDataField {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            #[derive(Deserialize)]
            struct Helper {
                name: alloc::string::String,
                #[serde(rename = "type", default = "default_type")]
                field_type: ComponentFieldType,
                #[serde(default)]
                default: OptionComponentDefaultValue,
                #[serde(default)]
                required: bool,
                #[serde(default)]
                description: alloc::string::String,
            }
            fn default_type() -> ComponentFieldType { ComponentFieldType::String }
            let h = Helper::deserialize(deserializer)?;
            Ok(ComponentDataField {
                name: AzString::from(h.name.as_str()),
                field_type: h.field_type,
                default_value: h.default,
                required: h.required,
                description: AzString::from(h.description.as_str()),
            })
        }
    }
    // --- ComponentDataModel ---
    impl Serialize for ComponentDataModel {
        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
            let mut s = serializer.serialize_struct("ComponentDataModel", 3)?;
            s.serialize_field("name", self.name.as_str())?;
            s.serialize_field("description", self.description.as_str())?;
            let fields: alloc::vec::Vec<&ComponentDataField> = self.fields.as_ref().iter().collect();
            s.serialize_field("fields", &fields)?;
            s.end()
        }
    }
    impl<'de> Deserialize<'de> for ComponentDataModel {
        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
            #[derive(Deserialize)]
            struct Helper {
                #[serde(default)]
                name: alloc::string::String,
                #[serde(default)]
                description: alloc::string::String,
                #[serde(default)]
                fields: alloc::vec::Vec<ComponentDataField>,
            }
            let h = Helper::deserialize(deserializer)?;
            Ok(ComponentDataModel {
                name: AzString::from(h.name.as_str()),
                description: AzString::from(h.description.as_str()),
                fields: ComponentDataFieldVec::from_vec(h.fields),
            })
        }
    }
}
// Re-export serde impls so they're visible when the feature is enabled
#[cfg(feature = "serde-json")]
pub use serde_impl::*;
#[cfg(feature = "serde-json")]
impl ComponentDataModel {
    /// Serialize this data model to a JSON string.
    pub fn to_json(&self) -> Result<alloc::string::String, alloc::string::String> {
        serde_json::to_string_pretty(self).map_err(|e| alloc::format!("{}", e))
    }
    /// Deserialize a data model from a JSON string.
    pub fn from_json(json: &str) -> Result<Self, alloc::string::String> {
        serde_json::from_str(json).map_err(|e| alloc::format!("{}", e))
    }
}
/// Source of a component definition — determines whether it can be exported
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum ComponentSource {
    /// Built into the DLL (HTML elements). Never exported.
    Builtin,
    /// Compiled Rust widget (Button, TextInput, etc.). Never exported.
    Compiled,
    /// Defined via JSON/XML at runtime. Can be exported.
    UserDefined,
}
impl Default for ComponentSource {
    fn default() -> Self {
        ComponentSource::UserDefined
    }
}
impl ComponentSource {
    pub fn create() -> Self {
        Self::default()
    }
}
/// The target language for code compilation
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum CompileTarget {
    Rust,
    C,
    Cpp,
    Python,
}
impl_result!(
    StyledDom,
    RenderDomError,
    ResultStyledDomRenderDomError,
    copy = false,
    [Debug, Clone, PartialEq]
);
impl_result!(
    AzString,
    CompileError,
    ResultStringCompileError,
    copy = false,
    [Debug, Clone, PartialEq]
);
/// Render function type: takes component definition + data model (with current values
/// in `default_value` fields) + component map for recursive sub-component instantiation,
/// returns StyledDom.
///
/// The `data` parameter is typically `def.data_model` cloned and with caller-provided
/// values substituted into the `default_value` fields.
pub type ComponentRenderFn = fn(
    &ComponentDef,
    &ComponentDataModel,
    &ComponentMap,
) -> ResultStyledDomRenderDomError;
/// Compile function type: takes component definition + target language + data model, returns source code.
pub type ComponentCompileFn = fn(
    &ComponentDef,
    &CompileTarget,
    &ComponentDataModel,
    indent: usize,
) -> ResultStringCompileError;
/// Raw function pointer type that returns a single ComponentDef when called.
/// Used as the `cb` field in `RegisterComponentFn`.
pub type RegisterComponentFnType = extern "C" fn() -> ComponentDef;
/// Callback struct for registering individual components at startup.
///
/// In C: pass a bare `extern "C" fn() -> ComponentDef` function pointer —
/// it converts automatically via `From<RegisterComponentFnType>`.
///
/// In Python: construct this struct with `cb` set to a trampoline and
/// `ctx` set to `Some(RefAny(...))` wrapping the Python callable.
#[repr(C)]
pub struct RegisterComponentFn {
    pub cb: RegisterComponentFnType,
    /// For FFI: stores the foreign callable (e.g., PyFunction).
    /// Native Rust/C code sets this to None.
    pub ctx: crate::refany::OptionRefAny,
}
impl_callback!(RegisterComponentFn, RegisterComponentFnType);
/// Raw function pointer type that returns a complete ComponentLibrary when called.
/// Used as the `cb` field in `RegisterComponentLibraryFn`.
pub type RegisterComponentLibraryFnType = extern "C" fn() -> ComponentLibrary;
/// Callback struct for registering entire component libraries at startup.
///
/// In C: pass a bare `extern "C" fn() -> ComponentLibrary` function pointer —
/// it converts automatically via `From<RegisterComponentLibraryFnType>`.
///
/// In Python: construct this struct with `cb` set to a trampoline and
/// `ctx` set to `Some(RefAny(...))` wrapping the Python callable.
#[repr(C)]
pub struct RegisterComponentLibraryFn {
    pub cb: RegisterComponentLibraryFnType,
    /// For FFI: stores the foreign callable (e.g., PyFunction).
    /// Native Rust/C code sets this to None.
    pub ctx: crate::refany::OptionRefAny,
}
impl_callback!(RegisterComponentLibraryFn, RegisterComponentLibraryFnType);
/// A component definition — the "class" / "template" of a component.
/// Can come from Rust builtins, compiled widgets, JSON, or user creation in debugger.
///
#[derive(Clone)]
#[repr(C)]
pub struct ComponentDef {
    /// Collection + name, e.g. builtin:div, shadcn:avatar
    pub id: ComponentId,
    /// Human-readable display name, e.g. "Link" for builtin:a, "Avatar" for shadcn:avatar
    pub display_name: AzString,
    /// Markdown documentation for the component
    pub description: AzString,
    /// The component's CSS
    pub css: AzString,
    /// Where this component was defined (determines exportability)
    pub source: ComponentSource,
    /// Unified data model: all value fields, callback slots, and child slots
    /// in a single named struct. Code gen uses `data_model.name` as the
    /// input struct type name (e.g. "ButtonData").
    /// The `default_value` on each field doubles as the "current value" for
    /// preview rendering — callers override defaults before calling `render_fn`.
    pub data_model: ComponentDataModel,
    /// Render to live DOM
    pub render_fn: ComponentRenderFn,
    /// Compile to source code in target language
    pub compile_fn: ComponentCompileFn,
    /// Source code for render_fn (user-defined components only)
    pub render_fn_source: OptionString,
    /// Source code for compile_fn (user-defined components only)
    pub compile_fn_source: OptionString,
}
impl fmt::Debug for ComponentDef {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("ComponentDef")
            .field("id", &self.id)
            .field("display_name", &self.display_name)
            .field("source", &self.source)
            .field("data_model", &self.data_model.name)
            .finish()
    }
}
impl_vec!(ComponentDef, ComponentDefVec, ComponentDefVecDestructor, ComponentDefVecDestructorType, ComponentDefVecSlice, OptionComponentDef);
impl_option!(ComponentDef, OptionComponentDef, copy = false, [Clone]);
impl_vec_debug!(ComponentDef, ComponentDefVec);
impl_vec_clone!(ComponentDef, ComponentDefVec, ComponentDefVecDestructor);
impl_vec_mut!(ComponentDef, ComponentDefVec);
/// A named collection of component definitions
#[derive(Debug, Clone)]
#[repr(C)]
pub struct ComponentLibrary {
    /// Library identifier, e.g. "builtin", "shadcn", "myproject"
    pub name: AzString,
    /// Version string
    pub version: AzString,
    /// Human-readable description
    pub description: AzString,
    /// The components in this library
    pub components: ComponentDefVec,
    /// Whether this library can be exported (false for builtin/compiled)
    pub exportable: bool,
    /// Whether this library can be modified by the user (add/remove/edit components).
    /// False for builtin and compiled libraries. True for user-created libraries.
    pub modifiable: bool,
    /// Named data model types defined by this library.
    /// Components reference these by name in their `field_type`.
    pub data_models: ComponentDataModelVec,
    /// Named enum types defined by this library.
    /// Components reference these via `ComponentFieldType::EnumRef(name)`.
    pub enum_models: ComponentEnumModelVec,
}
impl_vec!(ComponentLibrary, ComponentLibraryVec, ComponentLibraryVecDestructor, ComponentLibraryVecDestructorType, ComponentLibraryVecSlice, OptionComponentLibrary);
impl_option!(ComponentLibrary, OptionComponentLibrary, copy = false, [Debug, Clone]);
impl_vec_debug!(ComponentLibrary, ComponentLibraryVec);
impl_vec_clone!(ComponentLibrary, ComponentLibraryVec, ComponentLibraryVecDestructor);
impl_vec_mut!(ComponentLibrary, ComponentLibraryVec);
/// The component map — holds libraries with namespaced components.
#[derive(Debug, Clone)]
#[repr(C)]
pub struct ComponentMap {
    /// Libraries indexed by name. "builtin" is always present.
    pub libraries: ComponentLibraryVec,
}
impl ComponentMap {
    /// Qualified lookup: "shadcn:avatar" -> finds library "shadcn", component "avatar"
    pub fn get(&self, collection: &str, name: &str) -> Option<&ComponentDef> {
        self.libraries
            .iter()
            .find(|lib| lib.name.as_str() == collection)
            .and_then(|lib| lib.components.iter().find(|c| c.id.name.as_str() == name))
    }
    /// Unqualified lookup: "div" -> searches ONLY the "builtin" library.
    pub fn get_unqualified(&self, name: &str) -> Option<&ComponentDef> {
        self.get("builtin", name)
    }
    /// Parse a "collection:name" string into a lookup
    pub fn get_by_qualified_name(&self, qualified: &str) -> Option<&ComponentDef> {
        if let Some((collection, name)) = qualified.split_once(':') {
            self.get(collection, name)
        } else {
            self.get_unqualified(qualified)
        }
    }
    /// Get all libraries that can be exported (user-defined only)
    pub fn get_exportable_libraries(&self) -> Vec<&ComponentLibrary> {
        self.libraries.iter().filter(|lib| lib.exportable).collect()
    }
    /// Get all component definitions across all libraries
    pub fn all_components(&self) -> Vec<&ComponentDef> {
        self.libraries.iter().flat_map(|lib| lib.components.iter()).collect()
    }
}
// ============================================================================
// Builtin component bridge — wraps existing render/compile into ComponentDef
// ============================================================================
/// Map a builtin tag name to its corresponding `NodeType`.
/// Falls back to `NodeType::Div` for unknown tags.
10292
pub fn tag_to_node_type(tag: &str) -> NodeType {
10292
    match tag {
        // Document structure
10292
        "html" => NodeType::Html,
10292
        "head" => NodeType::Head,
10292
        "title" => NodeType::Title,
10292
        "body" => NodeType::Body,
        // Block-level
7142
        "div" => NodeType::Div,
2816
        "header" => NodeType::Header,
2816
        "footer" => NodeType::Footer,
2816
        "section" => NodeType::Section,
2816
        "article" => NodeType::Article,
2816
        "aside" => NodeType::Aside,
2816
        "nav" => NodeType::Nav,
2816
        "main" => NodeType::Main,
2816
        "figure" => NodeType::Figure,
2816
        "figcaption" => NodeType::FigCaption,
2816
        "address" => NodeType::Address,
2816
        "details" => NodeType::Details,
2816
        "summary" => NodeType::Summary,
2816
        "dialog" => NodeType::Dialog,
        // Headings
2816
        "h1" => NodeType::H1,
2816
        "h2" => NodeType::H2,
2816
        "h3" => NodeType::H3,
2816
        "h4" => NodeType::H4,
2816
        "h5" => NodeType::H5,
2816
        "h6" => NodeType::H6,
        // Text content
2816
        "p" => NodeType::P,
2102
        "span" => NodeType::Span,
1976
        "pre" => NodeType::Pre,
1850
        "code" => NodeType::Code,
1850
        "blockquote" => NodeType::BlockQuote,
1850
        "br" => NodeType::Br,
1808
        "hr" => NodeType::Hr,
        // Lists
1808
        "ul" => NodeType::Ul,
1808
        "ol" => NodeType::Ol,
1808
        "li" => NodeType::Li,
1808
        "dl" => NodeType::Dl,
1808
        "dt" => NodeType::Dt,
1808
        "dd" => NodeType::Dd,
1808
        "menu" => NodeType::Menu,
1808
        "menuitem" => NodeType::MenuItem,
1808
        "dir" => NodeType::Dir,
        // Tables
1808
        "table" => NodeType::Table,
1808
        "caption" => NodeType::Caption,
1808
        "thead" => NodeType::THead,
1808
        "tbody" => NodeType::TBody,
1808
        "tfoot" => NodeType::TFoot,
1808
        "tr" => NodeType::Tr,
1808
        "th" => NodeType::Th,
1808
        "td" => NodeType::Td,
1808
        "colgroup" => NodeType::ColGroup,
1808
        "col" => NodeType::Col,
        // Forms
1808
        "form" => NodeType::Form,
1808
        "fieldset" => NodeType::FieldSet,
1808
        "legend" => NodeType::Legend,
1808
        "label" => NodeType::Label,
1808
        "input" => NodeType::Input,
1808
        "button" => NodeType::Button,
1808
        "select" => NodeType::Select,
1808
        "optgroup" => NodeType::OptGroup,
1808
        "option" => NodeType::SelectOption,
1808
        "textarea" => NodeType::TextArea,
1808
        "output" => NodeType::Output,
1808
        "progress" => NodeType::Progress,
1808
        "meter" => NodeType::Meter,
1808
        "datalist" => NodeType::DataList,
        // Inline
1808
        "a" => NodeType::A,
1808
        "strong" => NodeType::Strong,
1808
        "em" => NodeType::Em,
1808
        "b" => NodeType::B,
1808
        "i" => NodeType::I,
1808
        "u" => NodeType::U,
1808
        "s" => NodeType::S,
1808
        "small" => NodeType::Small,
1808
        "mark" => NodeType::Mark,
1808
        "del" => NodeType::Del,
1808
        "ins" => NodeType::Ins,
1808
        "samp" => NodeType::Samp,
1808
        "kbd" => NodeType::Kbd,
1808
        "var" => NodeType::Var,
1808
        "cite" => NodeType::Cite,
1808
        "dfn" => NodeType::Dfn,
1808
        "abbr" => NodeType::Abbr,
1808
        "acronym" => NodeType::Acronym,
1808
        "q" => NodeType::Q,
1808
        "time" => NodeType::Time,
1808
        "sub" => NodeType::Sub,
1808
        "sup" => NodeType::Sup,
1808
        "big" => NodeType::Big,
1808
        "bdo" => NodeType::Bdo,
1808
        "bdi" => NodeType::Bdi,
1808
        "wbr" => NodeType::Wbr,
1808
        "ruby" => NodeType::Ruby,
1808
        "rt" => NodeType::Rt,
1808
        "rtc" => NodeType::Rtc,
1808
        "rp" => NodeType::Rp,
1808
        "data" => NodeType::Data,
        // Embedded content
        // `<img>` becomes a replaced `NodeType::Image`. The `src` attribute is not
        // available here, so a placeholder `NullImage` (0x0, empty tag) is created;
        // `xml_node_to_dom_fast` overrides it with a `NullImage` whose `tag` carries
        // the `src` bytes so a renderer (e.g. printpdf) can resolve the actual image.
1808
        "img" => NodeType::Image(azul_css::css::BoxOrStatic::heap(
2
            crate::resources::ImageRef::null_image(
2
                0,
2
                0,
2
                crate::resources::RawImageFormat::RGBA8,
2
                alloc::vec::Vec::new(),
2
            ),
2
        )),
1806
        "canvas" => NodeType::Canvas,
1806
        "object" => NodeType::Object,
1806
        "param" => NodeType::Param,
1806
        "embed" => NodeType::Embed,
1806
        "audio" => NodeType::Audio,
1806
        "video" => NodeType::Video,
1806
        "source" => NodeType::Source,
1806
        "track" => NodeType::Track,
1806
        "map" => NodeType::Map,
1806
        "area" => NodeType::Area,
        // SVG elements
1806
        "svg" => NodeType::Svg,
1008
        "g" => NodeType::SvgG,
924
        "defs" => NodeType::SvgDefs,
924
        "symbol" => NodeType::SvgSymbol,
924
        "use" => NodeType::SvgUse,
924
        "switch" => NodeType::SvgSwitch,
924
        "path" => NodeType::SvgPath,
714
        "circle" => NodeType::SvgCircle,
504
        "rect" => NodeType::SvgRect,
252
        "ellipse" => NodeType::SvgEllipse,
168
        "line" => NodeType::SvgLine,
126
        "polygon" => NodeType::SvgPolygon,
42
        "polyline" => NodeType::SvgPolyline,
        "tspan" => NodeType::SvgTspan,
        "textpath" => NodeType::SvgTextPath,
        "lineargradient" => NodeType::SvgLinearGradient,
        "radialgradient" => NodeType::SvgRadialGradient,
        "stop" => NodeType::SvgStop,
        "pattern" => NodeType::SvgPattern,
        "clippath" => NodeType::SvgClipPathElement,
        "mask" => NodeType::SvgMask,
        "filter" => NodeType::SvgFilter,
        "feblend" => NodeType::SvgFeBlend,
        "fecolormatrix" => NodeType::SvgFeColorMatrix,
        "fecomponenttransfer" => NodeType::SvgFeComponentTransfer,
        "fecomposite" => NodeType::SvgFeComposite,
        "feconvolvematrix" => NodeType::SvgFeConvolveMatrix,
        "fediffuselighting" => NodeType::SvgFeDiffuseLighting,
        "fedisplacementmap" => NodeType::SvgFeDisplacementMap,
        "fedistantlight" => NodeType::SvgFeDistantLight,
        "fedropshadow" => NodeType::SvgFeDropShadow,
        "feflood" => NodeType::SvgFeFlood,
        "fefuncr" => NodeType::SvgFeFuncR,
        "fefuncg" => NodeType::SvgFeFuncG,
        "fefuncb" => NodeType::SvgFeFuncB,
        "fefunca" => NodeType::SvgFeFuncA,
        "fegaussianblur" => NodeType::SvgFeGaussianBlur,
        "feimage" => NodeType::SvgFeImage,
        "femerge" => NodeType::SvgFeMerge,
        "femergenode" => NodeType::SvgFeMergeNode,
        "femorphology" => NodeType::SvgFeMorphology,
        "feoffset" => NodeType::SvgFeOffset,
        "fepointlight" => NodeType::SvgFePointLight,
        "fespecularlighting" => NodeType::SvgFeSpecularLighting,
        "fespotlight" => NodeType::SvgFeSpotLight,
        "fetile" => NodeType::SvgFeTile,
        "feturbulence" => NodeType::SvgFeTurbulence,
        "foreignobject" => NodeType::SvgForeignObject,
        "desc" => NodeType::SvgDesc,
        "view" => NodeType::SvgView,
        "animate" => NodeType::SvgAnimate,
        "animatemotion" => NodeType::SvgAnimateMotion,
        "animatetransform" => NodeType::SvgAnimateTransform,
        "set" => NodeType::SvgSet,
        "mpath" => NodeType::SvgMpath,
        // Metadata
        "meta" => NodeType::Meta,
        "link" => NodeType::Link,
        "script" => NodeType::Script,
        "style" => NodeType::Style,
        "base" => NodeType::Base,
        _ => NodeType::Div,
    }
10292
}
/// Map a tag name to its CSS `NodeTypeTag` for CSS matching in the compile pipeline.
/// Falls back to `NodeTypeTag::Div` for unknown tags.
fn tag_to_node_type_tag(tag: &str) -> NodeTypeTag {
    match tag {
        // Document structure
        "html" => NodeTypeTag::Html,
        "head" => NodeTypeTag::Head,
        "title" => NodeTypeTag::Title,
        "body" => NodeTypeTag::Body,
        // Block-level
        "div" => NodeTypeTag::Div,
        "header" => NodeTypeTag::Header,
        "footer" => NodeTypeTag::Footer,
        "section" => NodeTypeTag::Section,
        "article" => NodeTypeTag::Article,
        "aside" => NodeTypeTag::Aside,
        "nav" => NodeTypeTag::Nav,
        "main" => NodeTypeTag::Main,
        "figure" => NodeTypeTag::Figure,
        "figcaption" => NodeTypeTag::FigCaption,
        "address" => NodeTypeTag::Address,
        "details" => NodeTypeTag::Details,
        "summary" => NodeTypeTag::Summary,
        "dialog" => NodeTypeTag::Dialog,
        // Headings
        "h1" => NodeTypeTag::H1,
        "h2" => NodeTypeTag::H2,
        "h3" => NodeTypeTag::H3,
        "h4" => NodeTypeTag::H4,
        "h5" => NodeTypeTag::H5,
        "h6" => NodeTypeTag::H6,
        // Text content
        "p" => NodeTypeTag::P,
        "span" => NodeTypeTag::Span,
        "pre" => NodeTypeTag::Pre,
        "code" => NodeTypeTag::Code,
        "blockquote" => NodeTypeTag::BlockQuote,
        "br" => NodeTypeTag::Br,
        "hr" => NodeTypeTag::Hr,
        // Lists
        "ul" => NodeTypeTag::Ul,
        "ol" => NodeTypeTag::Ol,
        "li" => NodeTypeTag::Li,
        "dl" => NodeTypeTag::Dl,
        "dt" => NodeTypeTag::Dt,
        "dd" => NodeTypeTag::Dd,
        "menu" => NodeTypeTag::Menu,
        "menuitem" => NodeTypeTag::MenuItem,
        "dir" => NodeTypeTag::Dir,
        // Tables
        "table" => NodeTypeTag::Table,
        "caption" => NodeTypeTag::Caption,
        "thead" => NodeTypeTag::THead,
        "tbody" => NodeTypeTag::TBody,
        "tfoot" => NodeTypeTag::TFoot,
        "tr" => NodeTypeTag::Tr,
        "th" => NodeTypeTag::Th,
        "td" => NodeTypeTag::Td,
        "colgroup" => NodeTypeTag::ColGroup,
        "col" => NodeTypeTag::Col,
        // Forms
        "form" => NodeTypeTag::Form,
        "fieldset" => NodeTypeTag::FieldSet,
        "legend" => NodeTypeTag::Legend,
        "label" => NodeTypeTag::Label,
        "input" => NodeTypeTag::Input,
        "button" => NodeTypeTag::Button,
        "select" => NodeTypeTag::Select,
        "optgroup" => NodeTypeTag::OptGroup,
        "option" => NodeTypeTag::SelectOption,
        "textarea" => NodeTypeTag::TextArea,
        "output" => NodeTypeTag::Output,
        "progress" => NodeTypeTag::Progress,
        "meter" => NodeTypeTag::Meter,
        "datalist" => NodeTypeTag::DataList,
        // Inline
        "a" => NodeTypeTag::A,
        "strong" => NodeTypeTag::Strong,
        "em" => NodeTypeTag::Em,
        "b" => NodeTypeTag::B,
        "i" => NodeTypeTag::I,
        "u" => NodeTypeTag::U,
        "s" => NodeTypeTag::S,
        "small" => NodeTypeTag::Small,
        "mark" => NodeTypeTag::Mark,
        "del" => NodeTypeTag::Del,
        "ins" => NodeTypeTag::Ins,
        "samp" => NodeTypeTag::Samp,
        "kbd" => NodeTypeTag::Kbd,
        "var" => NodeTypeTag::Var,
        "cite" => NodeTypeTag::Cite,
        "dfn" => NodeTypeTag::Dfn,
        "abbr" => NodeTypeTag::Abbr,
        "acronym" => NodeTypeTag::Acronym,
        "q" => NodeTypeTag::Q,
        "time" => NodeTypeTag::Time,
        "sub" => NodeTypeTag::Sub,
        "sup" => NodeTypeTag::Sup,
        "big" => NodeTypeTag::Big,
        "bdo" => NodeTypeTag::Bdo,
        "bdi" => NodeTypeTag::Bdi,
        "wbr" => NodeTypeTag::Wbr,
        "ruby" => NodeTypeTag::Ruby,
        "rt" => NodeTypeTag::Rt,
        "rtc" => NodeTypeTag::Rtc,
        "rp" => NodeTypeTag::Rp,
        "data" => NodeTypeTag::Data,
        // Embedded content
        "canvas" => NodeTypeTag::Canvas,
        "object" => NodeTypeTag::Object,
        "param" => NodeTypeTag::Param,
        "embed" => NodeTypeTag::Embed,
        "audio" => NodeTypeTag::Audio,
        "video" => NodeTypeTag::Video,
        "source" => NodeTypeTag::Source,
        "track" => NodeTypeTag::Track,
        "map" => NodeTypeTag::Map,
        "area" => NodeTypeTag::Area,
        "svg" => NodeTypeTag::Svg,
        "g" => NodeTypeTag::SvgG,
        "defs" => NodeTypeTag::SvgDefs,
        "symbol" => NodeTypeTag::SvgSymbol,
        "use" => NodeTypeTag::SvgUse,
        "switch" => NodeTypeTag::SvgSwitch,
        "path" => NodeTypeTag::SvgPath,
        "circle" => NodeTypeTag::SvgCircle,
        "rect" => NodeTypeTag::SvgRect,
        "ellipse" => NodeTypeTag::SvgEllipse,
        "line" => NodeTypeTag::SvgLine,
        "polygon" => NodeTypeTag::SvgPolygon,
        "polyline" => NodeTypeTag::SvgPolyline,
        "tspan" => NodeTypeTag::SvgTspan,
        "textpath" => NodeTypeTag::SvgTextPath,
        "lineargradient" => NodeTypeTag::SvgLinearGradient,
        "radialgradient" => NodeTypeTag::SvgRadialGradient,
        "stop" => NodeTypeTag::SvgStop,
        "pattern" => NodeTypeTag::SvgPattern,
        "clippath" => NodeTypeTag::SvgClipPathElement,
        "mask" => NodeTypeTag::SvgMask,
        "filter" => NodeTypeTag::SvgFilter,
        "feblend" => NodeTypeTag::SvgFeBlend,
        "fecolormatrix" => NodeTypeTag::SvgFeColorMatrix,
        "fecomponenttransfer" => NodeTypeTag::SvgFeComponentTransfer,
        "fecomposite" => NodeTypeTag::SvgFeComposite,
        "feconvolvematrix" => NodeTypeTag::SvgFeConvolveMatrix,
        "fediffuselighting" => NodeTypeTag::SvgFeDiffuseLighting,
        "fedisplacementmap" => NodeTypeTag::SvgFeDisplacementMap,
        "fedistantlight" => NodeTypeTag::SvgFeDistantLight,
        "fedropshadow" => NodeTypeTag::SvgFeDropShadow,
        "feflood" => NodeTypeTag::SvgFeFlood,
        "fefuncr" => NodeTypeTag::SvgFeFuncR,
        "fefuncg" => NodeTypeTag::SvgFeFuncG,
        "fefuncb" => NodeTypeTag::SvgFeFuncB,
        "fefunca" => NodeTypeTag::SvgFeFuncA,
        "fegaussianblur" => NodeTypeTag::SvgFeGaussianBlur,
        "feimage" => NodeTypeTag::SvgFeImage,
        "femerge" => NodeTypeTag::SvgFeMerge,
        "femergenode" => NodeTypeTag::SvgFeMergeNode,
        "femorphology" => NodeTypeTag::SvgFeMorphology,
        "feoffset" => NodeTypeTag::SvgFeOffset,
        "fepointlight" => NodeTypeTag::SvgFePointLight,
        "fespecularlighting" => NodeTypeTag::SvgFeSpecularLighting,
        "fespotlight" => NodeTypeTag::SvgFeSpotLight,
        "fetile" => NodeTypeTag::SvgFeTile,
        "feturbulence" => NodeTypeTag::SvgFeTurbulence,
        "foreignobject" => NodeTypeTag::SvgForeignObject,
        "desc" => NodeTypeTag::SvgDesc,
        "view" => NodeTypeTag::SvgView,
        "animate" => NodeTypeTag::SvgAnimate,
        "animatemotion" => NodeTypeTag::SvgAnimateMotion,
        "animatetransform" => NodeTypeTag::SvgAnimateTransform,
        "set" => NodeTypeTag::SvgSet,
        "mpath" => NodeTypeTag::SvgMpath,
        // Metadata
        "meta" => NodeTypeTag::Meta,
        "link" => NodeTypeTag::Link,
        "script" => NodeTypeTag::Script,
        "style" => NodeTypeTag::Style,
        "base" => NodeTypeTag::Base,
        // Special
        "img" | "image" => NodeTypeTag::Img,
        "icon" => NodeTypeTag::Icon,
        _ => NodeTypeTag::Div,
    }
}
/// Default render function for builtin HTML elements.
/// Delegates to creating a DOM node of the appropriate NodeType.
fn builtin_render_fn(
    def: &ComponentDef,
    data: &ComponentDataModel,
    _component_map: &ComponentMap,
) -> ResultStyledDomRenderDomError {
    let node_type = tag_to_node_type(def.id.name.as_str());
    let mut dom = Dom::create_node(node_type);
    if let Some(text_str) = data.get_default_string("text") {
        let prepared = prepare_string(text_str);
        if !prepared.is_empty() {
            dom = dom.with_children(alloc::vec![Dom::create_text(prepared)].into());
        }
    }
    let r: Result<StyledDom, RenderDomError> = Ok(StyledDom::create(&mut dom, Css::empty()));
    r.into()
}
/// Default compile function for builtin HTML elements.
/// Generates `Dom::create_node(NodeType::Div)` style code for the target language.
fn builtin_compile_fn(
    def: &ComponentDef,
    target: &CompileTarget,
    data: &ComponentDataModel,
    indent: usize,
) -> ResultStringCompileError {
    let node_type = tag_to_node_type(def.id.name.as_str());
    let type_name = format!("{:?}", node_type); // "Div", "Body", "P", etc.
    let text = data.get_default_string("text");
    let r: Result<AzString, CompileError> = match target {
        CompileTarget::Rust => {
            if let Some(text_str) = text {
                Ok(format!(
                    "Dom::create_node(NodeType::{}).with_children(vec![Dom::create_text(AzString::from_const_str(\"{}\"))].into())",
                    type_name,
                    text_str.as_str().replace("\\", "\\\\").replace("\"", "\\\"")
                ).into())
            } else {
                Ok(format!("Dom::create_node(NodeType::{})", type_name).into())
            }
        }
        CompileTarget::C => {
            if let Some(text_str) = text {
                Ok(format!(
                    "AzDom_createText(AzString_fromConstStr(\"{}\"))",
                    text_str.as_str().replace("\\", "\\\\").replace("\"", "\\\"")
                ).into())
            } else {
                Ok(format!("AzDom_create{}()", type_name).into())
            }
        }
        CompileTarget::Cpp => {
            Ok(format!("Dom::create_{}()", type_name.to_lowercase()).into())
        }
        CompileTarget::Python => {
            Ok(format!("Dom.{}()", type_name.to_lowercase()).into())
        }
    };
    r.into()
}
/// Pushes a `<div>` containing `"field_name: value"` text into the children list.
fn push_scalar_field(children: &mut Vec<Dom>, field_name: &str, value: &dyn core::fmt::Display) {
    use crate::dom::{Dom, NodeType};
    let text = alloc::format!("{}: {}", field_name, value);
    children.push(
        Dom::create_node(NodeType::Div)
            .with_children(alloc::vec![Dom::create_text(text)].into()),
    );
}
/// Default render function for user-defined (JSON-imported) components.
///
/// Interprets the `ComponentDef` structure generically:
/// 1. Creates a wrapper `<div>` with the component's CSS class
/// 2. For each data field, renders content based on type:
///    - String fields → text node with current value
///    - Bool fields → conditional display
///    - StyledDom fields → embeds the child DOM subtree
///    - StructRef/EnumRef → recursively renders sub-components if found in ComponentMap
///    - Other scalar fields → text display of the value
/// 3. Applies the component's scoped CSS
pub fn user_defined_render_fn(
    def: &ComponentDef,
    data: &ComponentDataModel,
    component_map: &ComponentMap,
) -> ResultStyledDomRenderDomError {
    use crate::dom::{Dom, NodeType};
    use azul_css::css::Css;
    let mut children: Vec<Dom> = Vec::new();
    for field in data.fields.as_ref().iter() {
        let field_name = field.name.as_str();
        // Get the current value from default_value
        match &field.default_value {
            OptionComponentDefaultValue::None => {
                // Required field with no value — skip in preview
                continue;
            }
            OptionComponentDefaultValue::Some(default_val) => {
                match default_val {
                    ComponentDefaultValue::String(s) => {
                        let text = s.as_str().trim();
                        if !text.is_empty() {
                            let label_dom = Dom::create_node(NodeType::Div)
                                .with_children(alloc::vec![Dom::create_text(text.to_string())].into());
                            children.push(label_dom);
                        }
                    }
                    ComponentDefaultValue::Bool(v) => {
                        push_scalar_field(&mut children, field_name, v);
                    }
                    ComponentDefaultValue::I32(v) => {
                        push_scalar_field(&mut children, field_name, v);
                    }
                    ComponentDefaultValue::I64(v) => {
                        push_scalar_field(&mut children, field_name, v);
                    }
                    ComponentDefaultValue::U32(v) => {
                        push_scalar_field(&mut children, field_name, v);
                    }
                    ComponentDefaultValue::U64(v) => {
                        push_scalar_field(&mut children, field_name, v);
                    }
                    ComponentDefaultValue::Usize(v) => {
                        push_scalar_field(&mut children, field_name, v);
                    }
                    ComponentDefaultValue::F32(v) => {
                        push_scalar_field(&mut children, field_name, v);
                    }
                    ComponentDefaultValue::F64(v) => {
                        push_scalar_field(&mut children, field_name, v);
                    }
                    ComponentDefaultValue::ColorU(c) => {
                        let text = alloc::format!("{}: #{:02x}{:02x}{:02x}{:02x}", field_name, c.r, c.g, c.b, c.a);
                        children.push(Dom::create_node(NodeType::Div)
                            .with_children(alloc::vec![Dom::create_text(text)].into()));
                    }
                    ComponentDefaultValue::ComponentInstance(ci) => {
                        // Recursively instantiate sub-component from ComponentMap
                        if let Some(sub_comp) = component_map.get(ci.library.as_str(), ci.component.as_str()) {
                            let sub_data = sub_comp.data_model.clone();
                            match (sub_comp.render_fn)(sub_comp, &sub_data, component_map) {
                                ResultStyledDomRenderDomError::Ok(_styled_dom) => {
                                    // Sub-component rendered successfully — add a placeholder
                                    // (StyledDom cannot be directly converted back to Dom)
                                    let text = alloc::format!("[{}:{}]", ci.library.as_str(), ci.component.as_str());
                                    children.push(Dom::create_node(NodeType::Div)
                                        .with_children(alloc::vec![Dom::create_text(text)].into()));
                                }
                                ResultStyledDomRenderDomError::Err(_) => {
                                    // On error, show a placeholder
                                    let text = alloc::format!("[Error rendering {}:{}]", ci.library.as_str(), ci.component.as_str());
                                    children.push(Dom::create_node(NodeType::Div)
                                        .with_children(alloc::vec![Dom::create_text(text)].into()));
                                }
                            }
                        } else {
                            let text = alloc::format!("[Unknown component {}:{}]", ci.library.as_str(), ci.component.as_str());
                            children.push(Dom::create_node(NodeType::Div)
                                .with_children(alloc::vec![Dom::create_text(text)].into()));
                        }
                    }
                    ComponentDefaultValue::CallbackFnPointer(name) => {
                        // Callbacks are not rendered, just acknowledged
                        let text = alloc::format!("{}: fn({})", field_name, name.as_str());
                        children.push(Dom::create_node(NodeType::Div)
                            .with_children(alloc::vec![Dom::create_text(text)].into()));
                    }
                    ComponentDefaultValue::Json(json_str) => {
                        let text = alloc::format!("{}: {}", field_name, json_str.as_str());
                        children.push(Dom::create_node(NodeType::Div)
                            .with_children(alloc::vec![Dom::create_text(text)].into()));
                    }
                    ComponentDefaultValue::None => {
                        // No default, skip
                        continue;
                    }
                }
            }
        }
    }
    let mut wrapper = Dom::create_node(NodeType::Div);
    if !children.is_empty() {
        wrapper = wrapper.with_children(children.into());
    }
    // Apply component CSS
    let css = if !def.css.as_str().is_empty() {
        Css::from_string(def.css.clone())
    } else {
        Css::empty()
    };
    let r: Result<StyledDom, RenderDomError> = Ok(StyledDom::create(&mut wrapper, css));
    r.into()
}
/// Default compile function for user-defined (JSON-imported) components.
///
/// Generates source code that creates the component's DOM structure for the
/// target language. For each data field, emits the appropriate code:
/// - String fields → text node creation
/// - Scalar fields → formatted display
/// - ComponentInstance → function call to sub-component's render function
/// - StyledDom slots → child parameter pass-through
pub fn user_defined_compile_fn(
    def: &ComponentDef,
    target: &CompileTarget,
    data: &ComponentDataModel,
    indent: usize,
) -> ResultStringCompileError {
    let tag = def.id.name.as_str();
    let indent_str = " ".repeat(indent * 4);
    let inner_indent = " ".repeat((indent + 1) * 4);
    let r: Result<AzString, CompileError> = match target {
        CompileTarget::Rust => {
            let mut lines = Vec::new();
            lines.push(alloc::format!("{}// Component: {}", indent_str, tag));
            lines.push(alloc::format!("{}let mut children: Vec<Dom> = Vec::new();", indent_str));
            for field in data.fields.as_ref().iter() {
                let fname = field.name.as_str();
                match &field.default_value {
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::String(s)) => {
                        let escaped = s.as_str().replace("\\", "\\\\").replace("\"", "\\\"");
                        lines.push(alloc::format!(
                            "{}children.push(Dom::create_text(AzString::from_const_str(\"{}\")));",
                            inner_indent, escaped
                        ));
                    }
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::Bool(b)) => {
                        lines.push(alloc::format!(
                            "{}children.push(Dom::create_text(AzString::from(format!(\"{{}}: {{}}\", \"{}\", {}).as_str())));",
                            inner_indent, fname, b
                        ));
                    }
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::ComponentInstance(ci)) => {
                        let fn_name = alloc::format!("render_{}", ci.component.as_str().replace("-", "_"));
                        lines.push(alloc::format!(
                            "{}children.push({}()); // sub-component {}:{}",
                            inner_indent, fn_name, ci.library.as_str(), ci.component.as_str()
                        ));
                    }
                    _ => {
                        // For other types, generate a placeholder comment
                        lines.push(alloc::format!(
                            "{}// field '{}': {:?}",
                            inner_indent, fname, field.field_type
                        ));
                    }
                }
            }
            lines.push(alloc::format!(
                "{}Dom::create_node(NodeType::Div).with_children(children.into())",
                indent_str
            ));
            Ok(lines.join("\n").into())
        }
        CompileTarget::C => {
            let mut lines = Vec::new();
            lines.push(alloc::format!("{}/* Component: {} */", indent_str, tag));
            lines.push(alloc::format!("{}AzDom root = AzDom_createDiv();", indent_str));
            for field in data.fields.as_ref().iter() {
                let fname = field.name.as_str();
                match &field.default_value {
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::String(s)) => {
                        let escaped = s.as_str().replace("\\", "\\\\").replace("\"", "\\\"");
                        lines.push(alloc::format!(
                            "{}AzDom_addChild(&root, AzDom_createText(AzString_fromConstStr(\"{}\")));",
                            inner_indent, escaped
                        ));
                    }
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::ComponentInstance(ci)) => {
                        let fn_name = alloc::format!("render_{}", ci.component.as_str().replace("-", "_"));
                        lines.push(alloc::format!(
                            "{}AzDom_addChild(&root, {}());",
                            inner_indent, fn_name
                        ));
                    }
                    _ => {
                        lines.push(alloc::format!(
                            "{}/* field '{}' */",
                            inner_indent, fname
                        ));
                    }
                }
            }
            lines.push(alloc::format!("{}return root;", indent_str));
            Ok(lines.join("\n").into())
        }
        CompileTarget::Cpp => {
            let mut lines = Vec::new();
            lines.push(alloc::format!("{}// Component: {}", indent_str, tag));
            lines.push(alloc::format!("{}auto root = Dom::create_div();", indent_str));
            for field in data.fields.as_ref().iter() {
                let fname = field.name.as_str();
                match &field.default_value {
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::String(s)) => {
                        let escaped = s.as_str().replace("\\", "\\\\").replace("\"", "\\\"");
                        lines.push(alloc::format!(
                            "{}root.add_child(Dom::create_text(\"{}\"));",
                            inner_indent, escaped
                        ));
                    }
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::ComponentInstance(ci)) => {
                        let fn_name = alloc::format!("render_{}", ci.component.as_str().replace("-", "_"));
                        lines.push(alloc::format!(
                            "{}root.add_child({}());",
                            inner_indent, fn_name
                        ));
                    }
                    _ => {
                        lines.push(alloc::format!(
                            "{}// field '{}'",
                            inner_indent, fname
                        ));
                    }
                }
            }
            lines.push(alloc::format!("{}return root;", indent_str));
            Ok(lines.join("\n").into())
        }
        CompileTarget::Python => {
            let mut lines = Vec::new();
            lines.push(alloc::format!("{}# Component: {}", indent_str, tag));
            lines.push(alloc::format!("{}root = Dom.div()", indent_str));
            for field in data.fields.as_ref().iter() {
                let fname = field.name.as_str();
                match &field.default_value {
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::String(s)) => {
                        let escaped = s.as_str().replace("\\", "\\\\").replace("\"", "\\\"").replace("'", "\\'");
                        lines.push(alloc::format!(
                            "{}root.add_child(Dom.text('{}'))",
                            inner_indent, escaped
                        ));
                    }
                    OptionComponentDefaultValue::Some(ComponentDefaultValue::ComponentInstance(ci)) => {
                        let fn_name = alloc::format!("render_{}", ci.component.as_str().replace("-", "_"));
                        lines.push(alloc::format!(
                            "{}root.add_child({}())",
                            inner_indent, fn_name
                        ));
                    }
                    _ => {
                        lines.push(alloc::format!(
                            "{}# field '{}'",
                            inner_indent, fname
                        ));
                    }
                }
            }
            lines.push(alloc::format!("{}return root", indent_str));
            Ok(lines.join("\n").into())
        }
    };
    r.into()
}
/// Create a ComponentDef for a builtin HTML element.
///
/// # Arguments
/// * `tag` - HTML tag name (e.g. "button", "div")
/// * `display_name` - Human-readable name (e.g. "Button", "Div")
/// * `default_text` - Default text content for the preview, or `None` if the element has no text.
///   Pass `Some("Button text")` for `<button>`, `Some("")` for text elements like `<span>` that
///   accept text but have no meaningful default.
/// * `css` - Component-level CSS string. For most builtin elements this is `""` because
///   styling comes from `ua_css.rs` and the `SystemStyle`. Components that need extra
///   styling (e.g. a future high-level button widget) can pass CSS here.
352800
fn builtin_component_def(tag: &str, display_name: &str, default_text: Option<&str>, css: &str) -> ComponentDef {
352800
    let mut fields = builtin_data_model(tag);
    // If a default_text is provided, this element accepts text content
352800
    if let Some(text) = default_text {
185850
        fields.push(data_field("text", ComponentFieldType::String,
185850
            Some(ComponentDefaultValue::String(AzString::from(text))),
185850
            "Text content of the element"));
185850
    }
352800
    let model_name = format!("{}Data", display_name);
352800
    ComponentDef {
352800
        id: ComponentId::builtin(tag),
352800
        display_name: AzString::from(display_name),
352800
        description: AzString::from(format!("HTML <{}> element", tag).as_str()),
352800
        css: AzString::from(css),
352800
        source: ComponentSource::Builtin,
352800
        data_model: ComponentDataModel {
352800
            name: AzString::from(model_name.as_str()),
352800
            description: AzString::from(format!("Data model for <{}>", tag).as_str()),
352800
            fields: fields.into(),
352800
        },
352800
        render_fn: builtin_render_fn,
352800
        compile_fn: builtin_compile_fn,
352800
        render_fn_source: None.into(),
352800
        compile_fn_source: None.into(),
352800
    }
352800
}
/// Helper to create a ComponentDataField with a rich type
585900
fn data_field(name: &str, ft: ComponentFieldType, default: Option<ComponentDefaultValue>, description: &str) -> ComponentDataField {
585900
    let required = default.is_none();
    ComponentDataField {
585900
        name: AzString::from(name),
585900
        field_type: ft,
585900
        default_value: match default {
567000
            Some(d) => OptionComponentDefaultValue::Some(d),
18900
            None => OptionComponentDefaultValue::None,
        },
585900
        required,
585900
        description: AzString::from(description),
    }
585900
}
/// Returns the tag-specific data model fields for builtin HTML elements.
/// These are the component's "main data model" — the attributes that define
/// what the component needs as configuration (e.g., `href` for `<a>`,
/// `src` for `<img>`). Universal HTML attributes (id, class, style, etc.)
/// are NOT included here — they are added separately by the debug server.
352800
fn builtin_data_model(tag: &str) -> Vec<ComponentDataField> {
    use ComponentFieldType::*;
    use ComponentDefaultValue as D;
352800
    match tag {
352800
        "a" => alloc::vec![
3150
            data_field("href", String, Some(D::String(AzString::from_const_str(""))), "URL the link points to"),
3150
            data_field("target", String, Some(D::String(AzString::from_const_str(""))), "Where to open the linked document (_blank, _self, _parent, _top)"),
3150
            data_field("rel", String, Some(D::String(AzString::from_const_str(""))), "Relationship between current and linked document"),
        ],
349650
        "img" | "image" => alloc::vec![
            data_field("src", String, None, "URL of the image"),
            data_field("alt", String, Some(D::String(AzString::from_const_str(""))), "Alternative text for the image"),
            data_field("width", String, Some(D::String(AzString::from_const_str(""))), "Width of the image"),
            data_field("height", String, Some(D::String(AzString::from_const_str(""))), "Height of the image"),
        ],
349650
        "form" => alloc::vec![
3150
            data_field("action", String, Some(D::String(AzString::from_const_str(""))), "URL where form data is submitted"),
3150
            data_field("method", String, Some(D::String(AzString::from_const_str("GET"))), "HTTP method for form submission (GET or POST)"),
        ],
346500
        "label" => alloc::vec![
3150
            data_field("for", String, Some(D::String(AzString::from_const_str(""))), "ID of the form element this label is for"),
        ],
343350
        "button" => alloc::vec![
3150
            data_field("type", String, Some(D::String(AzString::from_const_str("button"))), "Button type (button, submit, reset)"),
3150
            data_field("disabled", Bool, Some(D::Bool(false)), "Whether the button is disabled"),
        ],
340200
        "td" | "th" => alloc::vec![
6300
            data_field("colspan", I32, Some(D::I32(1)), "Number of columns the cell spans"),
6300
            data_field("rowspan", I32, Some(D::I32(1)), "Number of rows the cell spans"),
        ],
333900
        "icon" => alloc::vec![
3150
            data_field("name", String, Some(D::String(AzString::from_const_str(""))), "Icon name"),
        ],
330750
        "ol" => alloc::vec![
3150
            data_field("start", I32, Some(D::I32(1)), "Start value for the ordered list"),
3150
            data_field("type", String, Some(D::String(AzString::from_const_str("1"))), "Numbering type (1, A, a, I, i)"),
        ],
        // Form controls
327600
        "input" => alloc::vec![
3150
            data_field("type", String, Some(D::String(AzString::from_const_str("text"))), "Input type (text, password, email, number, checkbox, radio, etc.)"),
3150
            data_field("name", String, Some(D::String(AzString::from_const_str(""))), "Name of the input for form submission"),
3150
            data_field("value", String, Some(D::String(AzString::from_const_str(""))), "Current value of the input"),
3150
            data_field("placeholder", String, Some(D::String(AzString::from_const_str(""))), "Placeholder text"),
3150
            data_field("disabled", Bool, Some(D::Bool(false)), "Whether the input is disabled"),
3150
            data_field("required", Bool, Some(D::Bool(false)), "Whether the input is required"),
3150
            data_field("readonly", Bool, Some(D::Bool(false)), "Whether the input is read-only"),
3150
            data_field("checked", Bool, Some(D::Bool(false)), "Whether the checkbox/radio is checked"),
3150
            data_field("min", String, Some(D::String(AzString::from_const_str(""))), "Minimum value (for number, range, date)"),
3150
            data_field("max", String, Some(D::String(AzString::from_const_str(""))), "Maximum value (for number, range, date)"),
3150
            data_field("step", String, Some(D::String(AzString::from_const_str(""))), "Step increment (for number, range)"),
3150
            data_field("pattern", String, Some(D::String(AzString::from_const_str(""))), "Regex pattern for validation"),
3150
            data_field("maxlength", String, Some(D::String(AzString::from_const_str(""))), "Maximum number of characters"),
        ],
324450
        "select" => alloc::vec![
3150
            data_field("name", String, Some(D::String(AzString::from_const_str(""))), "Name for form submission"),
3150
            data_field("multiple", Bool, Some(D::Bool(false)), "Whether multiple options can be selected"),
3150
            data_field("disabled", Bool, Some(D::Bool(false)), "Whether the select is disabled"),
3150
            data_field("required", Bool, Some(D::Bool(false)), "Whether selection is required"),
3150
            data_field("size", String, Some(D::String(AzString::from_const_str(""))), "Number of visible options"),
        ],
321300
        "option" => alloc::vec![
3150
            data_field("value", String, Some(D::String(AzString::from_const_str(""))), "Value submitted with the form"),
3150
            data_field("selected", Bool, Some(D::Bool(false)), "Whether this option is selected"),
3150
            data_field("disabled", Bool, Some(D::Bool(false)), "Whether this option is disabled"),
        ],
318150
        "optgroup" => alloc::vec![
3150
            data_field("label", String, Some(D::String(AzString::from_const_str(""))), "Label for the option group"),
3150
            data_field("disabled", Bool, Some(D::Bool(false)), "Whether the group is disabled"),
        ],
315000
        "textarea" => alloc::vec![
3150
            data_field("name", String, Some(D::String(AzString::from_const_str(""))), "Name for form submission"),
3150
            data_field("placeholder", String, Some(D::String(AzString::from_const_str(""))), "Placeholder text"),
3150
            data_field("rows", I32, Some(D::I32(2)), "Number of visible text lines"),
3150
            data_field("cols", I32, Some(D::I32(20)), "Visible width in average character widths"),
3150
            data_field("disabled", Bool, Some(D::Bool(false)), "Whether the textarea is disabled"),
3150
            data_field("required", Bool, Some(D::Bool(false)), "Whether content is required"),
3150
            data_field("readonly", Bool, Some(D::Bool(false)), "Whether the textarea is read-only"),
3150
            data_field("maxlength", String, Some(D::String(AzString::from_const_str(""))), "Maximum number of characters"),
        ],
311850
        "fieldset" => alloc::vec![
3150
            data_field("disabled", Bool, Some(D::Bool(false)), "Whether all controls in the fieldset are disabled"),
        ],
308700
        "output" => alloc::vec![
3150
            data_field("for", String, Some(D::String(AzString::from_const_str(""))), "IDs of elements that contributed to the output"),
3150
            data_field("name", String, Some(D::String(AzString::from_const_str(""))), "Name for form submission"),
        ],
305550
        "progress" => alloc::vec![
3150
            data_field("value", String, Some(D::String(AzString::from_const_str(""))), "Current progress value"),
3150
            data_field("max", String, Some(D::String(AzString::from_const_str("1"))), "Maximum value"),
        ],
302400
        "meter" => alloc::vec![
3150
            data_field("value", String, Some(D::String(AzString::from_const_str(""))), "Current value"),
3150
            data_field("min", String, Some(D::String(AzString::from_const_str("0"))), "Minimum value"),
3150
            data_field("max", String, Some(D::String(AzString::from_const_str("1"))), "Maximum value"),
3150
            data_field("low", String, Some(D::String(AzString::from_const_str(""))), "Low threshold"),
3150
            data_field("high", String, Some(D::String(AzString::from_const_str(""))), "High threshold"),
3150
            data_field("optimum", String, Some(D::String(AzString::from_const_str(""))), "Optimum value"),
        ],
        // Interactive
299250
        "details" => alloc::vec![
3150
            data_field("open", Bool, Some(D::Bool(false)), "Whether the details are visible"),
        ],
296100
        "dialog" => alloc::vec![
3150
            data_field("open", Bool, Some(D::Bool(false)), "Whether the dialog is active and can be interacted with"),
        ],
        // Embedded content
292950
        "audio" | "video" => alloc::vec![
6300
            data_field("src", String, Some(D::String(AzString::from_const_str(""))), "URL of the media resource"),
6300
            data_field("controls", Bool, Some(D::Bool(false)), "Whether to show playback controls"),
6300
            data_field("autoplay", Bool, Some(D::Bool(false)), "Whether to start playing automatically"),
6300
            data_field("loop", Bool, Some(D::Bool(false)), "Whether to loop playback"),
6300
            data_field("muted", Bool, Some(D::Bool(false)), "Whether audio is muted"),
6300
            data_field("preload", String, Some(D::String(AzString::from_const_str("auto"))), "Preload hint (none, metadata, auto)"),
        ],
286650
        "source" => alloc::vec![
3150
            data_field("src", String, None, "URL of the media resource"),
3150
            data_field("type", String, Some(D::String(AzString::from_const_str(""))), "MIME type of the resource"),
        ],
283500
        "track" => alloc::vec![
3150
            data_field("src", String, None, "URL of the track file"),
3150
            data_field("kind", String, Some(D::String(AzString::from_const_str("subtitles"))), "Kind of text track (subtitles, captions, descriptions, chapters, metadata)"),
3150
            data_field("srclang", String, Some(D::String(AzString::from_const_str(""))), "Language of the track text"),
3150
            data_field("label", String, Some(D::String(AzString::from_const_str(""))), "User-readable title for the track"),
3150
            data_field("default", Bool, Some(D::Bool(false)), "Whether this is the default track"),
        ],
280350
        "canvas" => alloc::vec![
3150
            data_field("width", String, Some(D::String(AzString::from_const_str("300"))), "Width of the canvas in pixels"),
3150
            data_field("height", String, Some(D::String(AzString::from_const_str("150"))), "Height of the canvas in pixels"),
        ],
277200
        "embed" => alloc::vec![
3150
            data_field("src", String, None, "URL of the resource to embed"),
3150
            data_field("type", String, Some(D::String(AzString::from_const_str(""))), "MIME type of the embedded content"),
3150
            data_field("width", String, Some(D::String(AzString::from_const_str(""))), "Width"),
3150
            data_field("height", String, Some(D::String(AzString::from_const_str(""))), "Height"),
        ],
274050
        "object" => alloc::vec![
3150
            data_field("data", String, Some(D::String(AzString::from_const_str(""))), "URL of the resource"),
3150
            data_field("type", String, Some(D::String(AzString::from_const_str(""))), "MIME type of the resource"),
3150
            data_field("width", String, Some(D::String(AzString::from_const_str(""))), "Width"),
3150
            data_field("height", String, Some(D::String(AzString::from_const_str(""))), "Height"),
        ],
270900
        "param" => alloc::vec![
3150
            data_field("name", String, None, "Name of the parameter"),
3150
            data_field("value", String, Some(D::String(AzString::from_const_str(""))), "Value of the parameter"),
        ],
267750
        "area" => alloc::vec![
3150
            data_field("shape", String, Some(D::String(AzString::from_const_str("default"))), "Shape of the area (default, rect, circle, poly)"),
3150
            data_field("coords", String, Some(D::String(AzString::from_const_str(""))), "Coordinates of the area"),
3150
            data_field("href", String, Some(D::String(AzString::from_const_str(""))), "URL for the area link"),
3150
            data_field("alt", String, Some(D::String(AzString::from_const_str(""))), "Alternative text"),
3150
            data_field("target", String, Some(D::String(AzString::from_const_str(""))), "Where to open the linked document"),
        ],
264600
        "map" => alloc::vec![
3150
            data_field("name", String, None, "Name of the image map (referenced by usemap)"),
        ],
        // Inline semantics with special attributes
261450
        "time" => alloc::vec![
3150
            data_field("datetime", String, Some(D::String(AzString::from_const_str(""))), "Machine-readable date/time value"),
        ],
258300
        "data" => alloc::vec![
3150
            data_field("value", String, Some(D::String(AzString::from_const_str(""))), "Machine-readable value"),
        ],
255150
        "abbr" | "acronym" | "dfn" => alloc::vec![
9450
            data_field("title", String, Some(D::String(AzString::from_const_str(""))), "Full expansion or definition"),
        ],
245700
        "q" | "blockquote" => alloc::vec![
6300
            data_field("cite", String, Some(D::String(AzString::from_const_str(""))), "URL of the source of the quotation"),
        ],
239400
        "del" | "ins" => alloc::vec![
6300
            data_field("cite", String, Some(D::String(AzString::from_const_str(""))), "URL explaining the change"),
6300
            data_field("datetime", String, Some(D::String(AzString::from_const_str(""))), "Date/time of the change"),
        ],
233100
        "bdo" => alloc::vec![
3150
            data_field("dir", String, Some(D::String(AzString::from_const_str("ltr"))), "Text direction (ltr, rtl)"),
        ],
229950
        "col" | "colgroup" => alloc::vec![
6300
            data_field("span", I32, Some(D::I32(1)), "Number of columns the element spans"),
        ],
        // Metadata
223650
        "meta" => alloc::vec![
3150
            data_field("name", String, Some(D::String(AzString::from_const_str(""))), "Metadata name"),
3150
            data_field("content", String, Some(D::String(AzString::from_const_str(""))), "Metadata value"),
3150
            data_field("charset", String, Some(D::String(AzString::from_const_str(""))), "Character encoding"),
3150
            data_field("http-equiv", String, Some(D::String(AzString::from_const_str(""))), "HTTP header equivalent"),
        ],
220500
        "link" => alloc::vec![
3150
            data_field("rel", String, None, "Relationship type"),
3150
            data_field("href", String, Some(D::String(AzString::from_const_str(""))), "URL of the linked resource"),
3150
            data_field("type", String, Some(D::String(AzString::from_const_str(""))), "MIME type of the linked resource"),
        ],
217350
        "script" => alloc::vec![
3150
            data_field("src", String, Some(D::String(AzString::from_const_str(""))), "URL of external script"),
3150
            data_field("type", String, Some(D::String(AzString::from_const_str(""))), "MIME type or module"),
3150
            data_field("async", Bool, Some(D::Bool(false)), "Execute asynchronously"),
3150
            data_field("defer", Bool, Some(D::Bool(false)), "Defer execution until page load"),
        ],
214200
        "style" => alloc::vec![
3150
            data_field("type", String, Some(D::String(AzString::from_const_str("text/css"))), "MIME type of the style sheet"),
        ],
211050
        "base" => alloc::vec![
3150
            data_field("href", String, Some(D::String(AzString::from_const_str(""))), "Base URL for relative URLs"),
3150
            data_field("target", String, Some(D::String(AzString::from_const_str(""))), "Default target for hyperlinks"),
        ],
207900
        _ => alloc::vec![],
    }
352800
}
impl Default for ComponentMap {
    /// Returns an empty `ComponentMap` with no libraries.
    ///
    /// Use `AppConfig::create()` (which registers the 52 builtins via
    /// `register_builtin_components`) followed by `ComponentMap::from_libraries()`
    /// to get a fully-populated map.
1
    fn default() -> Self {
1
        ComponentMap {
1
            libraries: ComponentLibraryVec::from_const_slice(&[]),
1
        }
1
    }
}
impl ComponentMap {
    pub fn create() -> Self {
        Self::default()
    }
    /// Create a `ComponentMap` with the 52 built-in HTML element components pre-registered.
3150
    pub fn with_builtin() -> Self {
3150
        ComponentMap {
3150
            libraries: alloc::vec![register_builtin_components()].into(),
3150
        }
3150
    }
    /// Build a `ComponentMap` from the libraries stored in an `AppConfig`.
    ///
    /// The `component_libraries` field already contains builtins (registered in
    /// `AppConfig::create()`) plus any user-added libraries.  No merging needed —
    /// `add_component_library` / `add_component` handle insertion at registration time.
    pub fn from_libraries(libs: &ComponentLibraryVec) -> Self {
        ComponentMap {
            libraries: libs.clone(),
        }
    }
}
/// Convert XML attributes to a `ComponentDataModel` by cloning the component's
/// base data model and overriding field defaults with values from the XML attributes.
///
/// This is the bridge between the XML parsing layer (key-value string pairs)
/// and the typed component data model. For each field in the base model,
/// if a matching XML attribute exists, its string value is set as the new default.
///
/// # Arguments
/// * `base_model` - The component's data model template (from `ComponentDef::data_model`)
/// * `xml_attributes` - The XML node's attribute map
/// * `text_content` - Optional text content from child text nodes
///
/// # Returns
/// A cloned `ComponentDataModel` with overridden defaults
fn xml_attrs_to_data_model(
    base_model: &ComponentDataModel,
    xml_attributes: &XmlAttributeMap,
    text_content: Option<&str>,
) -> ComponentDataModel {
    let mut model = base_model.clone();
    // Override defaults from XML attributes
    let mut fields_vec = core::mem::replace(
        &mut model.fields,
        ComponentDataFieldVec::from_const_slice(&[]),
    ).into_library_owned_vec();
    for field in fields_vec.iter_mut() {
        if let Some(attr_value) = xml_attributes.get_key(field.name.as_str()) {
            // Override the default_value with the XML attribute's string value
            field.default_value = OptionComponentDefaultValue::Some(
                ComponentDefaultValue::String(attr_value.clone()),
            );
        }
    }
    model.fields = ComponentDataFieldVec::from_vec(fields_vec);
    // Handle text content — set the "text" field if present
    if let Some(text) = text_content {
        let prepared = prepare_string(text);
        if !prepared.is_empty() {
            model = model.with_default("text", ComponentDefaultValue::String(AzString::from(prepared.as_str())));
        }
    }
    model
}
// ============================================================================
// Structural builtin components: if, for, map
// ============================================================================
/// `builtin:if` — conditional rendering.
/// Takes `condition: Bool`, `then: StyledDom`, and optionally `else: StyledDom`.
3150
fn builtin_if_component() -> ComponentDef {
3150
    ComponentDef {
3150
        id: ComponentId::builtin("if"),
3150
        display_name: AzString::from_const_str("If"),
3150
        description: AzString::from_const_str("Conditional rendering: shows 'then' if condition is true, else shows 'else' (if provided)."),
3150
        css: AzString::from_const_str(""),
3150
        source: ComponentSource::Builtin,
3150
        data_model: ComponentDataModel {
3150
            name: AzString::from_const_str("IfData"),
3150
            description: AzString::from_const_str("Data for conditional rendering"),
3150
            fields: alloc::vec![
3150
                data_field("condition", ComponentFieldType::Bool, Some(ComponentDefaultValue::Bool(false)), "The boolean condition to evaluate"),
3150
            ].into(),
3150
        },
3150
        render_fn: builtin_if_render_fn,
3150
        compile_fn: builtin_if_compile_fn,
3150
        render_fn_source: None.into(),
3150
        compile_fn_source: None.into(),
3150
    }
3150
}
fn builtin_if_render_fn(
    _comp: &ComponentDef,
    data_model: &ComponentDataModel,
    _component_map: &ComponentMap,
) -> ResultStyledDomRenderDomError {
    // Evaluate the condition field
    let condition = data_model.fields.iter().find(|f| f.name.as_str() == "condition")
        .and_then(|f| match &f.default_value { OptionComponentDefaultValue::Some(ComponentDefaultValue::Bool(b)) => Some(*b), _ => None })
        .unwrap_or(false);
    let label = if condition { "if: true (then branch)" } else { "if: false (else branch)" };
    let mut dom = Dom::create_node(NodeType::Div)
        .with_children(alloc::vec![Dom::create_text(label)].into());
    let css = Css::empty();
    ResultStyledDomRenderDomError::Ok(StyledDom::create(&mut dom, css))
}
fn builtin_if_compile_fn(
    _comp: &ComponentDef,
    target: &CompileTarget,
    _data: &ComponentDataModel,
    _indent: usize,
) -> ResultStringCompileError {
    match target {
        CompileTarget::Rust => ResultStringCompileError::Ok(AzString::from(
            "if data.condition {\n    // then branch\n    Dom::div()\n} else {\n    // else branch\n    Dom::div()\n}"
        )),
        CompileTarget::C => ResultStringCompileError::Ok(AzString::from(
            "if (data.condition) {\n    // then branch\n    AzDom_createDiv();\n} else {\n    // else branch\n    AzDom_createDiv();\n}"
        )),
        CompileTarget::Cpp => ResultStringCompileError::Ok(AzString::from(
            "if (data.condition) {\n    // then branch\n    Dom::div();\n} else {\n    // else branch\n    Dom::div();\n}"
        )),
        CompileTarget::Python => ResultStringCompileError::Ok(AzString::from(
            "if data.condition:\n    # then branch\n    Dom.div()\nelse:\n    # else branch\n    Dom.div()"
        )),
    }
}
/// `builtin:for` — iterative rendering.
/// Takes `count: U32` (number of iterations), renders children N times.
3150
fn builtin_for_component() -> ComponentDef {
3150
    ComponentDef {
3150
        id: ComponentId::builtin("for"),
3150
        display_name: AzString::from_const_str("For Loop"),
3150
        description: AzString::from_const_str("Iterative rendering: repeats children 'count' times."),
3150
        css: AzString::from_const_str(""),
3150
        source: ComponentSource::Builtin,
3150
        data_model: ComponentDataModel {
3150
            name: AzString::from_const_str("ForData"),
3150
            description: AzString::from_const_str("Data for iterative rendering"),
3150
            fields: alloc::vec![
3150
                data_field("count", ComponentFieldType::U32, Some(ComponentDefaultValue::U32(3)), "Number of iterations"),
3150
            ].into(),
3150
        },
3150
        render_fn: builtin_for_render_fn,
3150
        compile_fn: builtin_for_compile_fn,
3150
        render_fn_source: None.into(),
3150
        compile_fn_source: None.into(),
3150
    }
3150
}
fn builtin_for_render_fn(
    _comp: &ComponentDef,
    data_model: &ComponentDataModel,
    _component_map: &ComponentMap,
) -> ResultStyledDomRenderDomError {
    let count = data_model.fields.iter().find(|f| f.name.as_str() == "count")
        .and_then(|f| match &f.default_value { OptionComponentDefaultValue::Some(ComponentDefaultValue::U32(n)) => Some(*n), _ => None })
        .unwrap_or(3);
    let mut items: alloc::vec::Vec<Dom> = alloc::vec::Vec::new();
    for i in 0..count {
        items.push(Dom::create_node(NodeType::Div)
            .with_children(alloc::vec![Dom::create_text(alloc::format!("Item {}", i))].into()));
    }
    let mut dom = Dom::create_node(NodeType::Div)
        .with_children(items.into());
    let css = Css::empty();
    ResultStyledDomRenderDomError::Ok(StyledDom::create(&mut dom, css))
}
fn builtin_for_compile_fn(
    _comp: &ComponentDef,
    target: &CompileTarget,
    _data: &ComponentDataModel,
    _indent: usize,
) -> ResultStringCompileError {
    match target {
        CompileTarget::Rust => ResultStringCompileError::Ok(AzString::from(
            "let mut children = Vec::new();\nfor i in 0..data.count {\n    children.push(Dom::div());\n}\nDom::div().with_children(children)"
        )),
        CompileTarget::C => ResultStringCompileError::Ok(AzString::from(
            "AzDom container = AzDom_createDiv();\nfor (uint32_t i = 0; i < data.count; i++) {\n    AzDom_addChild(&container, AzDom_createDiv());\n}"
        )),
        CompileTarget::Cpp => ResultStringCompileError::Ok(AzString::from(
            "auto container = Dom::div();\nfor (uint32_t i = 0; i < data.count; i++) {\n    container.add_child(Dom::div());\n}"
        )),
        CompileTarget::Python => ResultStringCompileError::Ok(AzString::from(
            "container = Dom.div()\nfor i in range(data.count):\n    container.add_child(Dom.div())"
        )),
    }
}
/// `builtin:map` — map data to DOM.
/// Takes `data_json: String` (JSON array) + maps each element.
3150
fn builtin_map_component() -> ComponentDef {
3150
    ComponentDef {
3150
        id: ComponentId::builtin("map"),
3150
        display_name: AzString::from_const_str("Map"),
3150
        description: AzString::from_const_str("Map data to DOM: applies a template to each item in a collection."),
3150
        css: AzString::from_const_str(""),
3150
        source: ComponentSource::Builtin,
3150
        data_model: ComponentDataModel {
3150
            name: AzString::from_const_str("MapData"),
3150
            description: AzString::from_const_str("Data for map rendering"),
3150
            fields: alloc::vec![
3150
                data_field("data_json", ComponentFieldType::String, Some(ComponentDefaultValue::String(AzString::from_const_str("[]"))), "JSON array of items to map over"),
3150
            ].into(),
3150
        },
3150
        render_fn: builtin_map_render_fn,
3150
        compile_fn: builtin_map_compile_fn,
3150
        render_fn_source: None.into(),
3150
        compile_fn_source: None.into(),
3150
    }
3150
}
fn builtin_map_render_fn(
    _comp: &ComponentDef,
    data_model: &ComponentDataModel,
    _component_map: &ComponentMap,
) -> ResultStyledDomRenderDomError {
    // For now, render a placeholder — actual mapping requires callback support
    let data_str = data_model.fields.iter().find(|f| f.name.as_str() == "data_json")
        .and_then(|f| match &f.default_value { OptionComponentDefaultValue::Some(ComponentDefaultValue::String(s)) => Some(s.as_str().to_string()), _ => None })
        .unwrap_or_else(|| "[]".to_string());
    let label = alloc::format!("map: data_json={}", data_str);
    let mut dom = Dom::create_node(NodeType::Div)
        .with_children(alloc::vec![Dom::create_text(label)].into());
    let css = Css::empty();
    ResultStyledDomRenderDomError::Ok(StyledDom::create(&mut dom, css))
}
fn builtin_map_compile_fn(
    _comp: &ComponentDef,
    target: &CompileTarget,
    _data: &ComponentDataModel,
    _indent: usize,
) -> ResultStringCompileError {
    match target {
        CompileTarget::Rust => ResultStringCompileError::Ok(AzString::from(
            "let items: Vec<serde_json::Value> = serde_json::from_str(&data.data_json).unwrap_or_default();\nlet children: Vec<Dom> = items.iter().map(|item| {\n    Dom::div() // map template\n}).collect();\nDom::div().with_children(children)"
        )),
        CompileTarget::C => ResultStringCompileError::Ok(AzString::from(
            "// Parse data.data_json and map each item\nAzDom container = AzDom_createDiv();\n// TODO: iterate parsed JSON array"
        )),
        CompileTarget::Cpp => ResultStringCompileError::Ok(AzString::from(
            "// Parse data.data_json and map each item\nauto container = Dom::div();\n// TODO: iterate parsed JSON array"
        )),
        CompileTarget::Python => ResultStringCompileError::Ok(AzString::from(
            "import json\nitems = json.loads(data.data_json)\ncontainer = Dom.div()\nfor item in items:\n    container.add_child(Dom.div())"
        )),
    }
}
/// Register the 52 built-in HTML element components.
///
/// This is an `extern "C"` function pointer compatible with
/// `RegisterComponentLibraryFnType`, so it can be passed directly to
/// `AppConfig::add_component_library()`.
///
/// Called once during `AppConfig::create()` — the framework dogfoods
/// its own component registration system for builtins.
3150
pub extern "C" fn register_builtin_components() -> ComponentLibrary {
3150
    ComponentLibrary {
3150
        name: AzString::from_const_str("builtin"),
3150
        version: AzString::from_const_str("1.0.0"),
3150
        description: AzString::from_const_str("Built-in HTML elements"),
3150
        exportable: false,
3150
        modifiable: false,
3150
        data_models: Vec::new().into(),
3150
        enum_models: Vec::new().into(),
3150
        components: alloc::vec![
3150
            // Structural
3150
            builtin_component_def("html", "HTML", None, ""),
3150
            builtin_component_def("head", "Head", None, ""),
3150
            builtin_component_def("title", "Title", Some(""), ""),
3150
            builtin_component_def("body", "Body", None, ""),
3150
            // Block-level
3150
            builtin_component_def("div", "Div", None, ""),
3150
            builtin_component_def("header", "Header", None, ""),
3150
            builtin_component_def("footer", "Footer", None, ""),
3150
            builtin_component_def("section", "Section", None, ""),
3150
            builtin_component_def("article", "Article", None, ""),
3150
            builtin_component_def("aside", "Aside", None, ""),
3150
            builtin_component_def("nav", "Nav", None, ""),
3150
            builtin_component_def("main", "Main", None, ""),
3150
            builtin_component_def("figure", "Figure", None, ""),
3150
            builtin_component_def("figcaption", "Figure Caption", Some(""), ""),
3150
            builtin_component_def("address", "Address", Some(""), ""),
3150
            builtin_component_def("details", "Details", None, ""),
3150
            builtin_component_def("summary", "Summary", Some("Details"), ""),
3150
            builtin_component_def("dialog", "Dialog", None, ""),
3150
            // Headings — default text is the heading level name so preview is visible
3150
            builtin_component_def("h1", "Heading 1", Some("Heading 1"), ""),
3150
            builtin_component_def("h2", "Heading 2", Some("Heading 2"), ""),
3150
            builtin_component_def("h3", "Heading 3", Some("Heading 3"), ""),
3150
            builtin_component_def("h4", "Heading 4", Some("Heading 4"), ""),
3150
            builtin_component_def("h5", "Heading 5", Some("Heading 5"), ""),
3150
            builtin_component_def("h6", "Heading 6", Some("Heading 6"), ""),
3150
            // Text content
3150
            builtin_component_def("p", "Paragraph", Some("Paragraph text"), ""),
3150
            builtin_component_def("span", "Span", Some(""), ""),
3150
            builtin_component_def("pre", "Preformatted", Some(""), ""),
3150
            builtin_component_def("code", "Code", Some(""), ""),
3150
            builtin_component_def("blockquote", "Blockquote", Some(""), ""),
3150
            builtin_component_def("br", "Line Break", None, ""),
3150
            builtin_component_def("hr", "Horizontal Rule", None, ""),
3150
            builtin_component_def("icon", "Icon", Some(""), ""),
3150
            // Lists
3150
            builtin_component_def("ul", "Unordered List", None, ""),
3150
            builtin_component_def("ol", "Ordered List", None, ""),
3150
            builtin_component_def("li", "List Item", Some("List item"), ""),
3150
            builtin_component_def("dl", "Description List", None, ""),
3150
            builtin_component_def("dt", "Description Term", Some(""), ""),
3150
            builtin_component_def("dd", "Description Details", Some(""), ""),
3150
            builtin_component_def("menu", "Menu", None, ""),
3150
            builtin_component_def("menuitem", "Menu Item", Some(""), ""),
3150
            builtin_component_def("dir", "Directory List", None, ""),
3150
            // Tables
3150
            builtin_component_def("table", "Table", None, ""),
3150
            builtin_component_def("caption", "Table Caption", Some(""), ""),
3150
            builtin_component_def("thead", "Table Head", None, ""),
3150
            builtin_component_def("tbody", "Table Body", None, ""),
3150
            builtin_component_def("tfoot", "Table Foot", None, ""),
3150
            builtin_component_def("tr", "Table Row", None, ""),
3150
            builtin_component_def("th", "Table Header Cell", Some("Header"), ""),
3150
            builtin_component_def("td", "Table Data Cell", Some(""), ""),
3150
            builtin_component_def("colgroup", "Column Group", None, ""),
3150
            builtin_component_def("col", "Column", None, ""),
3150
            // Inline
3150
            builtin_component_def("a", "Link", Some("Link text"), ""),
3150
            builtin_component_def("strong", "Strong", Some(""), ""),
3150
            builtin_component_def("em", "Emphasis", Some(""), ""),
3150
            builtin_component_def("b", "Bold", Some(""), ""),
3150
            builtin_component_def("i", "Italic", Some(""), ""),
3150
            builtin_component_def("u", "Underline", Some(""), ""),
3150
            builtin_component_def("s", "Strikethrough", Some(""), ""),
3150
            builtin_component_def("small", "Small", Some(""), ""),
3150
            builtin_component_def("mark", "Mark", Some(""), ""),
3150
            builtin_component_def("del", "Deleted Text", Some(""), ""),
3150
            builtin_component_def("ins", "Inserted Text", Some(""), ""),
3150
            builtin_component_def("sub", "Subscript", Some(""), ""),
3150
            builtin_component_def("sup", "Superscript", Some(""), ""),
3150
            builtin_component_def("samp", "Sample Output", Some(""), ""),
3150
            builtin_component_def("kbd", "Keyboard Input", Some(""), ""),
3150
            builtin_component_def("var", "Variable", Some(""), ""),
3150
            builtin_component_def("cite", "Citation", Some(""), ""),
3150
            builtin_component_def("dfn", "Definition", Some(""), ""),
3150
            builtin_component_def("abbr", "Abbreviation", Some(""), ""),
3150
            builtin_component_def("acronym", "Acronym", Some(""), ""),
3150
            builtin_component_def("q", "Inline Quote", Some(""), ""),
3150
            builtin_component_def("time", "Time", Some(""), ""),
3150
            builtin_component_def("big", "Big", Some(""), ""),
3150
            builtin_component_def("bdo", "BiDi Override", Some(""), ""),
3150
            builtin_component_def("bdi", "BiDi Isolate", Some(""), ""),
3150
            builtin_component_def("wbr", "Word Break Opportunity", None, ""),
3150
            builtin_component_def("ruby", "Ruby Annotation", None, ""),
3150
            builtin_component_def("rt", "Ruby Text", Some(""), ""),
3150
            builtin_component_def("rtc", "Ruby Text Container", None, ""),
3150
            builtin_component_def("rp", "Ruby Parenthesis", Some(""), ""),
3150
            builtin_component_def("data", "Data", Some(""), ""),
3150
            // Forms
3150
            builtin_component_def("form", "Form", None, ""),
3150
            builtin_component_def("fieldset", "Field Set", None, ""),
3150
            builtin_component_def("legend", "Legend", Some("Legend"), ""),
3150
            builtin_component_def("label", "Label", Some("Label"), ""),
3150
            builtin_component_def("input", "Input", None, ""),
3150
            builtin_component_def("button", "Button", Some("Button text"), ""),
3150
            builtin_component_def("select", "Select", None, ""),
3150
            builtin_component_def("optgroup", "Option Group", None, ""),
3150
            builtin_component_def("option", "Option", Some(""), ""),
3150
            builtin_component_def("textarea", "Text Area", Some(""), ""),
3150
            builtin_component_def("output", "Output", Some(""), ""),
3150
            builtin_component_def("progress", "Progress", None, ""),
3150
            builtin_component_def("meter", "Meter", None, ""),
3150
            builtin_component_def("datalist", "Data List", None, ""),
3150
            // Embedded content
3150
            builtin_component_def("canvas", "Canvas", None, ""),
3150
            builtin_component_def("object", "Object", None, ""),
3150
            builtin_component_def("param", "Parameter", None, ""),
3150
            builtin_component_def("embed", "Embed", None, ""),
3150
            builtin_component_def("audio", "Audio", None, ""),
3150
            builtin_component_def("video", "Video", None, ""),
3150
            builtin_component_def("source", "Source", None, ""),
3150
            builtin_component_def("track", "Track", None, ""),
3150
            builtin_component_def("map", "Image Map", None, ""),
3150
            builtin_component_def("area", "Map Area", None, ""),
3150
            builtin_component_def("svg", "SVG", None, ""),
3150
            // Metadata
3150
            builtin_component_def("meta", "Meta", None, ""),
3150
            builtin_component_def("link", "Link (Resource)", None, ""),
3150
            builtin_component_def("script", "Script", Some(""), ""),
3150
            builtin_component_def("style", "Style", Some(""), ""),
3150
            builtin_component_def("base", "Base URL", None, ""),
3150
            // Structural control-flow builtins (F1-F3)
3150
            builtin_if_component(),
3150
            builtin_for_component(),
3150
            builtin_map_component(),
3150
        ].into(),
3150
    }
3150
}
// ============================================================================
// End new component system types
// ============================================================================
/// Wrapper for the XML parser - necessary to easily create a Dom from
/// XML without putting an XML solver into `azul-core`.
#[derive(Default)]
pub struct DomXml {
    pub parsed_dom: StyledDom,
}
impl DomXml {
    /// Convenience function, only available in tests, useful for quickly writing UI tests.
    /// Wraps the XML string in the required `<app></app>` braces, panics if the XML couldn't be
    /// parsed.
    ///
    /// ## Example
    ///
    /// ```rust,ignore
    /// # use azul::dom::Dom;
    /// # use azul::xml::DomXml;
    /// let dom = DomXml::mock("<div id='test' />");
    /// dom.assert_eq(Dom::create_div().with_id("test"));
    /// ```
    #[cfg(test)]
    pub fn assert_eq(self, other: StyledDom) {
        let mut body = Dom::create_body();
        let mut fixed = StyledDom::create(&mut body, Css::empty());
        fixed.append_child(other);
        if self.parsed_dom != fixed {
            panic!(
                "\r\nExpected DOM did not match:\r\n\r\nexpected: ----------\r\n{}\r\ngot: \
                 ----------\r\n{}\r\n",
                self.parsed_dom.get_html_string("", "", true),
                fixed.get_html_string("", "", true)
            );
        }
    }
    pub fn into_styled_dom(self) -> StyledDom {
        self.into()
    }
}
impl From<DomXml> for StyledDom {
    fn from(val: DomXml) -> Self {
        val.parsed_dom
    }
}
/// Represents a child of an XML node - either an element or text
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum XmlNodeChild {
    /// A text node
    Text(AzString),
    /// An element node
    Element(XmlNode),
}
impl_option!(
    XmlNodeChild,
    OptionXmlNodeChild,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl XmlNodeChild {
    /// Get the text content if this is a text node
2313
    pub fn as_text(&self) -> Option<&str> {
2313
        match self {
2313
            XmlNodeChild::Text(s) => Some(s.as_str()),
            XmlNodeChild::Element(_) => None,
        }
2313
    }
    /// Get the element if this is an element node
2
    pub fn as_element(&self) -> Option<&XmlNode> {
2
        match self {
            XmlNodeChild::Text(_) => None,
2
            XmlNodeChild::Element(node) => Some(node),
        }
2
    }
    /// Get the element mutably if this is an element node
    pub fn as_element_mut(&mut self) -> Option<&mut XmlNode> {
        match self {
            XmlNodeChild::Text(_) => None,
            XmlNodeChild::Element(node) => Some(node),
        }
    }
}
impl_vec!(XmlNodeChild, XmlNodeChildVec, XmlNodeChildVecDestructor, XmlNodeChildVecDestructorType, XmlNodeChildVecSlice, OptionXmlNodeChild);
impl_vec_mut!(XmlNodeChild, XmlNodeChildVec);
impl_vec_debug!(XmlNodeChild, XmlNodeChildVec);
impl_vec_partialeq!(XmlNodeChild, XmlNodeChildVec);
impl_vec_eq!(XmlNodeChild, XmlNodeChildVec);
impl_vec_partialord!(XmlNodeChild, XmlNodeChildVec);
impl_vec_ord!(XmlNodeChild, XmlNodeChildVec);
impl_vec_hash!(XmlNodeChild, XmlNodeChildVec);
impl_vec_clone!(XmlNodeChild, XmlNodeChildVec, XmlNodeChildVecDestructor);
/// Represents one XML node tag
#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct XmlNode {
    /// Type of the node
    pub node_type: XmlTagName,
    /// Attributes of an XML node (note: not yet filtered and / or broken into function arguments!)
    pub attributes: XmlAttributeMap,
    /// Direct children of this node (can be text or element nodes)
    pub children: XmlNodeChildVec,
}
impl_option!(
    XmlNode,
    OptionXmlNode,
    copy = false,
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl XmlNode {
    pub fn create<I: Into<XmlTagName>>(node_type: I) -> Self {
        XmlNode {
            node_type: node_type.into(),
            ..Default::default()
        }
    }
    pub fn with_children(mut self, v: Vec<XmlNodeChild>) -> Self {
        Self {
            children: v.into(),
            ..self
        }
    }
    /// Get all text content concatenated from direct children
2310
    pub fn get_text_content(&self) -> String {
2310
        self.children
2310
            .as_ref()
2310
            .iter()
2310
            .filter_map(|child| child.as_text())
2310
            .collect::<Vec<_>>()
2310
            .join("")
2310
    }
    /// Check if this node has only text children (no element children)
    pub fn has_only_text_children(&self) -> bool {
        self.children
            .as_ref()
            .iter()
            .all(|child| matches!(child, XmlNodeChild::Text(_)))
    }
}
impl_vec!(XmlNode, XmlNodeVec, XmlNodeVecDestructor, XmlNodeVecDestructorType, XmlNodeVecSlice, OptionXmlNode);
impl_vec_mut!(XmlNode, XmlNodeVec);
impl_vec_debug!(XmlNode, XmlNodeVec);
impl_vec_partialeq!(XmlNode, XmlNodeVec);
impl_vec_eq!(XmlNode, XmlNodeVec);
impl_vec_partialord!(XmlNode, XmlNodeVec);
impl_vec_ord!(XmlNode, XmlNodeVec);
impl_vec_hash!(XmlNode, XmlNodeVec);
impl_vec_clone!(XmlNode, XmlNodeVec, XmlNodeVecDestructor);
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum DomXmlParseError {
    /// No `<html></html>` node component present
    NoHtmlNode,
    /// Multiple `<html>` nodes
    MultipleHtmlRootNodes,
    /// No ´<body></body>´ node in the root HTML
    NoBodyInHtml,
    /// The DOM can only have one <body> node, not multiple.
    MultipleBodyNodes,
    /// Note: Sadly, the error type can only be a string because xmlparser
    /// returns all errors as strings. There is an open PR to fix
    /// this deficiency, but since the XML parsing is only needed for
    /// hot-reloading and compiling, it doesn't matter that much.
    Xml(XmlError),
    /// Invalid hierarchy close tags, i.e `<app></p></app>`
    MalformedHierarchy(MalformedHierarchyError),
    /// A component raised an error while rendering the DOM - holds the component name + error
    /// string
    RenderDom(RenderDomError),
    /// Something went wrong while parsing an XML component
    Component(ComponentParseError),
    /// Error parsing global CSS in head node
    Css(CssParseErrorOwned),
}
impl From<XmlError> for DomXmlParseError {
    fn from(e: XmlError) -> Self {
        Self::Xml(e)
    }
}
impl From<ComponentParseError> for DomXmlParseError {
    fn from(e: ComponentParseError) -> Self {
        Self::Component(e)
    }
}
impl From<RenderDomError> for DomXmlParseError {
    fn from(e: RenderDomError) -> Self {
        Self::RenderDom(e)
    }
}
impl From<CssParseErrorOwned> for DomXmlParseError {
    fn from(e: CssParseErrorOwned) -> Self {
        Self::Css(e)
    }
}
/// Error that can happen from the translation from XML code to Rust code -
/// stringified, since it is only used for printing and is not exposed in the public API
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum CompileError {
    Dom(RenderDomError),
    Xml(DomXmlParseError),
    Css(CssParseErrorOwned),
}
impl From<ComponentError> for CompileError {
    fn from(e: ComponentError) -> Self {
        CompileError::Dom(RenderDomError::Component(e))
    }
}
impl From<CssParseErrorOwned> for CompileError {
    fn from(e: CssParseErrorOwned) -> Self {
        CompileError::Css(e)
    }
}
impl<'a> fmt::Display for CompileError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::CompileError::*;
        match self {
            Dom(d) => write!(f, "{}", d),
            Xml(s) => write!(f, "{}", s),
            Css(s) => write!(f, "{}", s.to_shared()),
        }
    }
}
impl From<RenderDomError> for CompileError {
    fn from(e: RenderDomError) -> Self {
        CompileError::Dom(e)
    }
}
impl From<DomXmlParseError> for CompileError {
    fn from(e: DomXmlParseError) -> Self {
        CompileError::Xml(e)
    }
}
/// Wrapper for UselessFunctionArgument error data.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct UselessFunctionArgumentError {
    pub component_name: AzString,
    pub argument_name: AzString,
    pub valid_args: StringVec,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum ComponentError {
    /// While instantiating a component, a function argument
    /// was encountered that the component won't use or react to.
    UselessFunctionArgument(UselessFunctionArgumentError),
    /// A certain node type can't be rendered, because the
    /// renderer for this node is not available isn't available
    ///
    /// UnknownComponent(component_name)
    UnknownComponent(AzString),
}
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum RenderDomError {
    Component(ComponentError),
    /// Error parsing the CSS on the component style
    CssError(CssParseErrorOwned),
}
impl From<ComponentError> for RenderDomError {
    fn from(e: ComponentError) -> Self {
        Self::Component(e)
    }
}
impl From<CssParseErrorOwned> for RenderDomError {
    fn from(e: CssParseErrorOwned) -> Self {
        Self::CssError(e)
    }
}
/// Wrapper for MissingType error data.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct MissingTypeError {
    pub arg_pos: usize,
    pub arg_name: AzString,
}
/// Wrapper for WhiteSpaceInComponentName error data.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct WhiteSpaceInComponentNameError {
    pub arg_pos: usize,
    pub arg_name: AzString,
}
/// Wrapper for WhiteSpaceInComponentType error data.
#[derive(Debug, Clone, PartialEq)]
#[repr(C)]
pub struct WhiteSpaceInComponentTypeError {
    pub arg_pos: usize,
    pub arg_name: AzString,
    pub arg_type: AzString,
}
#[derive(Debug, Clone, PartialEq)]
#[repr(C, u8)]
pub enum ComponentParseError {
    /// Given XmlNode is not a `<component />` node.
    NotAComponent,
    /// A `<component>` node does not have a `name` attribute.
    UnnamedComponent,
    /// Argument at position `usize` is either empty or has no name
    MissingName(usize),
    /// Argument at position `usize` with the name
    /// `String` doesn't have a `: type`
    MissingType(MissingTypeError),
    /// Component name may not contain a whitespace
    /// (probably missing a `:` between the name and the type)
    WhiteSpaceInComponentName(WhiteSpaceInComponentNameError),
    /// Component type may not contain a whitespace
    /// (probably missing a `,` between the type and the next name)
    WhiteSpaceInComponentType(WhiteSpaceInComponentTypeError),
    /// Error parsing the <style> tag / CSS
    CssError(CssParseErrorOwned),
}
impl<'a> fmt::Display for DomXmlParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::DomXmlParseError::*;
        match self {
            NoHtmlNode => write!(
                f,
                "No <html> node found as the root of the file - empty file?"
            ),
            MultipleHtmlRootNodes => write!(
                f,
                "Multiple <html> nodes found as the root of the file - only one root node allowed"
            ),
            NoBodyInHtml => write!(
                f,
                "No <body> node found as a direct child of an <html> node - malformed DOM \
                 hierarchy?"
            ),
            MultipleBodyNodes => write!(
                f,
                "Multiple <body> nodes present, only one <body> node is allowed"
            ),
            Xml(e) => write!(f, "Error parsing XML: {}", e),
            MalformedHierarchy(e) => write!(
                f,
                "Invalid </{}> tag: expected </{}>",
                e.got.as_str(),
                e.expected.as_str()
            ),
            RenderDom(e) => write!(f, "Error rendering DOM: {}", e),
            Component(c) => write!(f, "Error parsing component in <head> node:\r\n{}", c),
            Css(c) => write!(f, "Error parsing CSS in <head> node:\r\n{}", c.to_shared()),
        }
    }
}
impl<'a> fmt::Display for ComponentParseError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::ComponentParseError::*;
        match self {
            NotAComponent => write!(f, "Expected <component/> node, found no such node"),
            UnnamedComponent => write!(
                f,
                "Found <component/> tag with out a \"name\" attribute, component must have a name"
            ),
            MissingName(arg_pos) => write!(
                f,
                "Argument at position {} is either empty or has no name",
                arg_pos
            ),
            MissingType(e) => write!(
                f,
                "Argument \"{}\" at position {} doesn't have a `: type`",
                e.arg_name, e.arg_pos
            ),
            WhiteSpaceInComponentName(e) => {
                write!(
                    f,
                    "Missing `:` between the name and the type in argument {} (around \"{}\")",
                    e.arg_pos, e.arg_name
                )
            }
            WhiteSpaceInComponentType(e) => {
                write!(
                    f,
                    "Missing `,` between two arguments (in argument {}, position {}, around \
                     \"{}\")",
                    e.arg_name, e.arg_pos, e.arg_type
                )
            }
            CssError(lsf) => write!(f, "Error parsing <style> tag: {}", lsf.to_shared()),
        }
    }
}
impl fmt::Display for ComponentError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::ComponentError::*;
        match self {
            UselessFunctionArgument(e) => {
                write!(
                    f,
                    "Useless component argument \"{}\": \"{}\" - available args are: {:#?}",
                    e.component_name, e.argument_name, e.valid_args
                )
            }
            UnknownComponent(name) => write!(f, "Unknown component: \"{}\"", name),
        }
    }
}
impl<'a> fmt::Display for RenderDomError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::RenderDomError::*;
        match self {
            Component(c) => write!(f, "{}", c),
            CssError(e) => write!(f, "Error parsing CSS in component: {}", e.to_shared()),
        }
    }
}
/// Find the one and only `<body>` node, return error if
/// there is no app node or there are multiple app nodes
3150
pub fn get_html_node<'a>(root_nodes: &'a [XmlNodeChild]) -> Result<&'a XmlNode, DomXmlParseError> {
3150
    let mut html_node_iterator = root_nodes.iter().filter_map(|child| {
3150
        if let XmlNodeChild::Element(node) = child {
3150
            let node_type_normalized = normalize_casing(&node.node_type);
3150
            if &node_type_normalized == "html" {
3150
                Some(node)
            } else {
                None
            }
        } else {
            None
        }
3150
    });
3150
    let html_node = html_node_iterator
3150
        .next()
3150
        .ok_or(DomXmlParseError::NoHtmlNode)?;
3150
    if html_node_iterator.next().is_some() {
        Err(DomXmlParseError::MultipleHtmlRootNodes)
    } else {
3150
        Ok(html_node)
    }
3150
}
/// Find the one and only `<body>` node, return error if
/// there is no app node or there are multiple app nodes
3150
pub fn get_body_node<'a>(root_nodes: &'a [XmlNodeChild]) -> Result<&'a XmlNode, DomXmlParseError> {
    // First try to find body as a direct child (proper HTML structure)
7602
    let direct_body = root_nodes.iter().filter_map(|child| {
7602
        if let XmlNodeChild::Element(node) = child {
5460
            let node_type_normalized = normalize_casing(&node.node_type);
5460
            if &node_type_normalized == "body" {
3150
                Some(node)
            } else {
2310
                None
            }
        } else {
2142
            None
        }
7602
    }).next();
3150
    if let Some(body) = direct_body {
3150
        return Ok(body);
    }
    // If not found as direct child, search recursively (for malformed HTML like example.com)
    // where <body> might be nested inside <head> due to missing </head> tag
    fn find_body_recursive<'a>(nodes: &'a [XmlNodeChild]) -> Option<&'a XmlNode> {
        for child in nodes {
            if let XmlNodeChild::Element(node) = child {
                let node_type_normalized = normalize_casing(&node.node_type);
                if &node_type_normalized == "body" {
                    return Some(node);
                }
                // Recurse into children
                if let Some(found) = find_body_recursive(node.children.as_ref()) {
                    return Some(found);
                }
            }
        }
        None
    }
    find_body_recursive(root_nodes).ok_or(DomXmlParseError::NoBodyInHtml)
3150
}
/// Searches in the the `root_nodes` for a `node_type`, convenience function in order to
/// for example find the first <blah /> node in all these nodes.
/// This function searches recursively through the entire tree.
8148
fn find_node_by_type<'a>(
8148
    root_nodes: &'a [XmlNodeChild],
8148
    node_type: &str,
8148
) -> Option<&'a XmlNode> {
    // First check direct children
11382
    for child in root_nodes {
7854
        if let XmlNodeChild::Element(node) = child {
7308
            if normalize_casing(&node.node_type).as_str() == node_type {
4620
                return Some(node);
2688
            }
546
        }
    }
    // If not found, search recursively (for malformed HTML)
6510
    for child in root_nodes {
2982
        if let XmlNodeChild::Element(node) = child {
2688
            if let Some(found) = find_node_by_type(node.children.as_ref(), node_type) {
                return Some(found);
2688
            }
294
        }
    }
3528
    None
8148
}
pub fn find_attribute<'a>(node: &'a XmlNode, attribute: &str) -> Option<&'a AzString> {
    node.attributes
        .iter()
        .find(|n| normalize_casing(&n.key.as_str()).as_str() == attribute)
        .map(|s| &s.value)
}
/// Normalizes input such as `abcDef`, `AbcDef`, `abc-def` to the normalized form of `abc_def`
26209
pub fn normalize_casing(input: &str) -> String {
26209
    let mut words: Vec<String> = Vec::new();
26209
    let mut cur_str = Vec::new();
100509
    for ch in input.chars() {
100509
        if ch.is_uppercase() || ch == '_' || ch == '-' {
            if !cur_str.is_empty() {
                words.push(cur_str.iter().collect());
                cur_str.clear();
            }
            if ch.is_uppercase() {
                cur_str.extend(ch.to_lowercase());
            }
100509
        } else {
100509
            cur_str.extend(ch.to_lowercase());
100509
        }
    }
26209
    if !cur_str.is_empty() {
26209
        words.push(cur_str.iter().collect());
26209
        cur_str.clear();
26209
    }
26209
    words.join("_")
26209
}
/// Given a root node, traverses along the hierarchy, and returns a
/// mutable reference to the last child node of the root node
#[allow(trivial_casts)]
pub fn get_item<'a>(hierarchy: &[usize], root_node: &'a mut XmlNode) -> Option<&'a mut XmlNode> {
    let mut hierarchy = hierarchy.to_vec();
    hierarchy.reverse();
    let item = match hierarchy.pop() {
        Some(s) => s,
        None => return Some(root_node),
    };
    let child = root_node.children.as_mut().get_mut(item)?;
    match child {
        XmlNodeChild::Element(node) => get_item_internal(&mut hierarchy, node),
        XmlNodeChild::Text(_) => None, // Can't traverse into text nodes
    }
}
fn get_item_internal<'a>(
    hierarchy: &mut Vec<usize>,
    root_node: &'a mut XmlNode,
) -> Option<&'a mut XmlNode> {
    if hierarchy.is_empty() {
        return Some(root_node);
    }
    let cur_item = match hierarchy.pop() {
        Some(s) => s,
        None => return Some(root_node),
    };
    let child = root_node.children.as_mut().get_mut(cur_item)?;
    match child {
        XmlNodeChild::Element(node) => get_item_internal(hierarchy, node),
        XmlNodeChild::Text(_) => None, // Can't traverse into text nodes
    }
}
/// Parses an XML string and returns a `StyledDom` with the components instantiated in the
/// `<app></app>`
3150
pub fn str_to_dom<'a>(
3150
    root_nodes: &'a [XmlNodeChild],
3150
    component_map: &'a ComponentMap,
3150
    max_width: Option<f32>,
3150
) -> Result<StyledDom, DomXmlParseError> {
    // Delegate to the fast path (Dom::Fast / CompactDom arena).
3150
    str_to_dom_fast(root_nodes, component_map, max_width)
3150
}
/// Parse XML to StyledDom via arena-based FastDom (no tree intermediary).
///
/// **Note**: `str_to_dom()` now delegates to this function, so you can use
/// either one. This function is kept for backward compatibility.
3150
fn str_to_dom_fast<'a>(
3150
    root_nodes: &'a [XmlNodeChild],
3150
    component_map: &'a ComponentMap,
3150
    max_width: Option<f32>,
3150
) -> Result<StyledDom, DomXmlParseError> {
3150
    let html_node = get_html_node(root_nodes)?;
3150
    let body_node = get_body_node(html_node.children.as_ref())?;
3150
    let mut global_style = None;
3150
    if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
2310
        if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
2310
            let text = style_node.get_text_content();
2310
            if !text.is_empty() {
2310
                let parsed_css = Css::from_string(text.into());
2310
                global_style = Some(parsed_css);
2310
            }
        }
840
    }
3150
    render_dom_from_body_node_fast(&body_node, global_style, component_map, max_width)
3150
        .map_err(|e| e.into())
3150
}
/// Parses XML nodes and returns a `Dom` with CSS stylesheets attached (but not applied).
///
/// Unlike `str_to_dom` which returns a fully styled `StyledDom`, this function
/// returns an unstyled `Dom` whose `css` field carries the parsed `<style>` rules.
/// The layout framework will apply the CSS during the cascade pass.
///
/// This is the correct function for building a `Dom` from XML in layout callbacks
/// (which must return `Dom`, not `StyledDom`).
pub fn str_to_dom_unstyled<'a>(
    root_nodes: &'a [XmlNodeChild],
    component_map: &'a ComponentMap,
) -> Result<Dom, DomXmlParseError> {
    let html_node = get_html_node(root_nodes)?;
    let body_node = get_body_node(html_node.children.as_ref())?;
    let mut global_style = None;
    if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
        if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
            let text = style_node.get_text_content();
            if !text.is_empty() {
                let parsed_css = Css::from_string(text.into());
                global_style = Some(parsed_css);
            }
        }
    }
    // Build the DOM tree from the body node
    let body_dom = xml_node_to_dom_fast(&body_node, component_map, false)
        .map_err(|e| DomXmlParseError::from(e))?;
    // Wrap in proper HTML structure
    use crate::dom::NodeType;
    let root_node_type = body_dom.root.node_type.clone();
    let mut full_dom = match root_node_type {
        NodeType::Html => body_dom,
        NodeType::Body => Dom::create_html().with_child(body_dom),
        _ => {
            let body_wrapper = Dom::create_body().with_child(body_dom);
            Dom::create_html().with_child(body_wrapper)
        }
    };
    // Attach CSS to the Dom's css field instead of applying it immediately
    if let Some(css) = global_style {
        let mut css_vec: Vec<Css> = Vec::new();
        css_vec.push(css);
        full_dom.css = css_vec.into();
    }
    Ok(full_dom)
}
/// Parses an XML string and returns a `String`, which contains the Rust source code
/// (i.e. it compiles the XML to valid Rust)
pub fn str_to_rust_code<'a>(
    root_nodes: &'a [XmlNodeChild],
    imports: &str,
    component_map: &'a ComponentMap,
) -> Result<String, CompileError> {
    let html_node = get_html_node(&root_nodes)?;
    let body_node = get_body_node(html_node.children.as_ref())?;
    let mut global_style = Css::empty();
    if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
        if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
            let text = style_node.get_text_content();
            if !text.is_empty() {
                let parsed_css = azul_css::parser2::new_from_str(&text).0;
                global_style = parsed_css;
            }
        }
    }
    global_style.sort_by_specificity();
    let mut css_blocks = BTreeMap::new();
    let mut extra_blocks = VecContents::default();
    let app_source = compile_body_node_to_rust_code(
        &body_node,
        component_map,
        &mut extra_blocks,
        &mut css_blocks,
        &global_style,
        CssMatcher {
            path: Vec::new(),
            indices_in_parent: vec![0],
            children_length: vec![body_node.children.as_ref().len()],
        },
    )?;
    let app_source = app_source
        .lines()
        .map(|l| format!("        {}", l))
        .collect::<Vec<String>>()
        .join("\r\n");
    let t = "    ";
    let css_blocks = css_blocks
        .iter()
        .map(|(k, v)| {
            let v = v
                .lines()
                .map(|l| format!("{}{}{}", t, t, l))
                .collect::<Vec<String>>()
                .join("\r\n");
            format!(
                "    const {}_PROPERTIES: &[NodeDataInlineCssProperty] = \
                 &[\r\n{}\r\n{}];\r\n{}const {}: NodeDataInlineCssPropertyVec = \
                 NodeDataInlineCssPropertyVec::from_const_slice({}_PROPERTIES);",
                k, v, t, t, k, k
            )
        })
        .collect::<Vec<_>>()
        .join(&format!("{}\r\n\r\n", t));
    let mut extra_block_string = extra_blocks.format(1);
    let main_func = "
use azul::{
    app::{App, AppConfig, LayoutSolver},
    css::Css,
    dom::Dom,
    callbacks::{RefAny, LayoutCallbackInfo},
    window::{WindowCreateOptions, WindowFrame},
};
struct Data { }
extern \"C\" fn render(_: RefAny, _: LayoutCallbackInfo) -> Dom {
    let dom = crate::ui::render();
    dom.with_component_css(Css::empty()) // styles are applied inline
}
fn main() {
    let app = App::new(RefAny::new(Data { }), AppConfig::new(LayoutSolver::Default));
    let mut window = WindowCreateOptions::new(render);
    window.state.flags.frame = WindowFrame::Maximized;
    app.run(window);
}";
    let source_code = format!(
        "#![windows_subsystem = \"windows\"]\r\n//! Auto-generated UI source \
         code\r\n{}\r\n{}\r\n\r\n{}{}",
        imports,
        compile_components(Vec::new()), // no user-defined components to compile
        format!(
            "#[allow(unused_imports)]\r\npub mod ui {{
    pub use crate::components::*;
    use azul::css::*;
    use azul::str::String as AzString;
    use azul::vec::{{
        DomVec, IdOrClassVec, NodeDataInlineCssPropertyVec,
        StyleBackgroundSizeVec, StyleBackgroundRepeatVec,
        StyleBackgroundContentVec, StyleTransformVec,
        StyleFontFamilyVec, StyleBackgroundPositionVec,
        NormalizedLinearColorStopVec, NormalizedRadialColorStopVec,
    }};
    use azul::dom::{{
        Dom, IdOrClass, TabIndex,
        IdOrClass::{{Id, Class}},
        NodeDataInlineCssProperty,
    }};\r\n\r\n{}\r\n\r\n{}
    pub fn render() -> Dom {{\r\n{}\r\n    }}\r\n}}",
            extra_block_string, css_blocks, app_source
        ),
        main_func,
    );
    Ok(source_code)
}
// Compile all components to source code
fn compile_components(
    components: Vec<(
        ComponentName,
        CompiledComponent,
        ComponentArguments,
        BTreeMap<String, String>,
    )>,
) -> String {
    let cs = components
        .iter()
        .map(|(name, function_body, function_args, css_blocks)| {
            let name = &normalize_casing(&name);
            let f = compile_component(name, function_args, function_body)
                .lines()
                .map(|l| format!("    {}", l))
                .collect::<Vec<String>>()
                .join("\r\n");
            // let css_blocks = ...
            format!(
                "#[allow(unused_imports)]\r\npub mod {} {{\r\n    use azul::dom::Dom;\r\n    use \
                 azul::str::String as AzString;\r\n{}\r\n}}",
                name, f
            )
        })
        .collect::<Vec<String>>()
        .join("\r\n\r\n");
    let cs = cs
        .lines()
        .map(|l| format!("    {}", l))
        .collect::<Vec<String>>()
        .join("\r\n");
    if cs.is_empty() {
        cs
    } else {
        format!("pub mod components {{\r\n{}\r\n}}", cs)
    }
}
fn format_component_args(component_args: &ComponentArgumentVec) -> String {
    let mut args = component_args
        .iter()
        .map(|a| format!("{}: {}", a.name, a.arg_type))
        .collect::<Vec<String>>();
    args.sort_by(|a, b| b.cmp(&a));
    args.join(", ")
}
pub fn compile_component(
    component_name: &str,
    component_args: &ComponentArguments,
    component_function_body: &str,
) -> String {
    let component_name = &normalize_casing(&component_name);
    let function_args = format_component_args(&component_args.args);
    let component_function_body = component_function_body
        .lines()
        .map(|l| format!("    {}", l))
        .collect::<Vec<String>>()
        .join("\r\n");
    let should_inline = component_function_body.lines().count() == 1;
    format!(
        "{}pub fn render({}{}{}) -> Dom {{\r\n{}\r\n}}",
        if should_inline { "#[inline]\r\n" } else { "" },
        // pass the text content as the first
        if component_args.accepts_text {
            "text: AzString"
        } else {
            ""
        },
        if function_args.is_empty() || !component_args.accepts_text {
            ""
        } else {
            ", "
        },
        function_args,
        component_function_body,
    )
}
/// Parse an SVG numeric attribute value to f32.
2646
fn parse_svg_float(attr: Option<&AzString>) -> Option<f32> {
2646
    attr?.as_str().trim().parse::<f32>().ok()
2646
}
/// Parse an SVG `points` attribute (used by `<polygon>` and `<polyline>`).
126
fn parse_svg_points(pts: &str, close: bool) -> Option<crate::svg::SvgMultiPolygon> {
126
    let nums: Vec<f32> = pts
2268
        .split(|c: char| c == ',' || c.is_ascii_whitespace())
840
        .filter(|s| !s.is_empty())
840
        .filter_map(|s| s.parse::<f32>().ok())
126
        .collect();
126
    if nums.len() < 4 || nums.len() % 2 != 0 {
        return None;
126
    }
126
    let mut elements = Vec::new();
126
    let points: Vec<azul_css::props::basic::SvgPoint> = nums
126
        .chunks_exact(2)
420
        .map(|c| azul_css::props::basic::SvgPoint { x: c[0], y: c[1] })
126
        .collect();
294
    for w in points.windows(2) {
294
        elements.push(crate::svg::SvgPathElement::Line(crate::svg::SvgLine::new(w[0], w[1])));
294
    }
126
    if close && points.len() >= 2 {
84
        let first = points[0];
84
        let last = *points.last().unwrap();
84
        if (first.x - last.x).abs() > 0.001 || (first.y - last.y).abs() > 0.001 {
84
            elements.push(crate::svg::SvgPathElement::Line(crate::svg::SvgLine::new(last, first)));
84
        }
42
    }
126
    Some(crate::svg::SvgMultiPolygon {
126
        rings: crate::svg::SvgPathVec::from_vec(vec![
126
            crate::svg::SvgPath {
126
                items: crate::svg::SvgPathElementVec::from_vec(elements),
126
            }
126
        ]),
126
    })
126
}
/// Fast XML to Dom conversion that builds Dom tree directly without intermediate StyledDom
/// This is O(n) instead of O(n²) for large documents
1
fn xml_node_to_dom_fast<'a>(
1
    xml_node: &'a XmlNode,
1
    component_map: &'a ComponentMap,
1
    inside_svg: bool,
1
) -> Result<Dom, RenderDomError> {
    use crate::dom::{Dom, NodeType, IdOrClass, TabIndex};
1
    let component_name = normalize_casing(&xml_node.node_type);
    // Look up the component definition
1
    let node_type = tag_to_node_type(&component_name);
1
    let mut dom = Dom::create_node(node_type);
    // `<img src="...">`: rebuild the placeholder Image node so its `NullImage`
    // carries the `src` string (as UTF-8 bytes in `tag`). The bytes are NOT
    // resolved here — a downstream renderer (printpdf, the compositor, ...) uses
    // the tag to look up and embed the actual image. Optional `width`/`height`
    // attributes set the intrinsic size used for layout (CSS still overrides).
1
    if component_name == "img" {
1
        if let Some(src) = xml_node.attributes.get_key("src") {
1
            let width = xml_node
1
                .attributes
1
                .get_key("width")
1
                .and_then(|w| w.as_str().trim().trim_end_matches("px").trim().parse::<usize>().ok())
1
                .unwrap_or(0);
1
            let height = xml_node
1
                .attributes
1
                .get_key("height")
1
                .and_then(|h| h.as_str().trim().trim_end_matches("px").trim().parse::<usize>().ok())
1
                .unwrap_or(0);
1
            let image_ref = crate::resources::ImageRef::null_image(
1
                width,
1
                height,
1
                crate::resources::RawImageFormat::RGBA8,
1
                src.as_str().as_bytes().to_vec(),
            );
1
            dom.root.set_node_type(NodeType::Image(azul_css::css::BoxOrStatic::heap(image_ref)));
        }
    }
    // Set id and class attributes
1
    let mut ids_and_classes = Vec::new();
1
    if let Some(id_str) = xml_node.attributes.get_key("id") {
        for id in id_str.split_whitespace() {
            ids_and_classes.push(IdOrClass::Id(id.into()));
        }
1
    }
1
    if let Some(class_str) = xml_node.attributes.get_key("class") {
        for class in class_str.split_whitespace() {
            ids_and_classes.push(IdOrClass::Class(class.into()));
        }
1
    }
1
    if !ids_and_classes.is_empty() {
        dom.root.set_ids_and_classes(ids_and_classes.into());
1
    }
    // Handle focusable attribute
1
    if let Some(focusable) = xml_node.attributes.get_key("focusable").and_then(|f| parse_bool(f.as_str())) {
        match focusable {
            true => dom.root.set_tab_index(TabIndex::Auto),
            false => dom.root.set_tab_index(TabIndex::NoKeyboardFocus),
        }
1
    }
    // Handle tabindex attribute
1
    if let Some(tab_index) = xml_node.attributes.get_key("tabindex").and_then(|val| val.parse::<isize>().ok()) {
        match tab_index {
            0 => dom.root.set_tab_index(TabIndex::Auto),
            i if i > 0 => dom.root.set_tab_index(TabIndex::OverrideInParent(i as u32)),
            _ => dom.root.set_tab_index(TabIndex::NoKeyboardFocus),
        }
1
    }
    // Handle inline style attribute
1
    if let Some(style) = xml_node.attributes.get_key("style") {
        let css_key_map = azul_css::props::property::get_css_key_map();
        let mut attributes = Vec::new();
        for s in style.as_str().split(";") {
            let mut s = s.split(":");
            let key = match s.next() { Some(s) => s, None => continue };
            let value = match s.next() { Some(s) => s, None => continue };
            let _ = azul_css::parser2::parse_css_declaration(
                key.trim(), value.trim(),
                azul_css::parser2::ErrorLocationRange::default(),
                &css_key_map, &mut Vec::new(), &mut attributes,
            );
        }
        let props = attributes.into_iter().filter_map(|s| {
            use azul_css::dynamic_selector::CssPropertyWithConditions;
            match s {
                CssDeclaration::Static(s) => Some(CssPropertyWithConditions::simple(s)),
                _ => None,
            }
        }).collect::<Vec<_>>();
        if !props.is_empty() {
            dom.root.set_css_props(props.into());
        }
1
    }
    // Handle SVG shape elements when inside an <svg> context
1
    let tag = component_name.as_str();
1
    let child_inside_svg = inside_svg || tag == "svg";
1
    let is_svg_shape = inside_svg && matches!(tag, "path" | "circle" | "rect" | "ellipse" | "line" | "polygon" | "polyline");
1
    if is_svg_shape {
        let clip = match tag {
            "path" => {
                xml_node.attributes.get_key("d").and_then(|d| {
                    crate::svg_path_parser::parse_svg_path_d(d.as_str()).ok()
                })
            }
            "circle" => {
                let cx = parse_svg_float(xml_node.attributes.get_key("cx")).unwrap_or(0.0);
                let cy = parse_svg_float(xml_node.attributes.get_key("cy")).unwrap_or(0.0);
                let r = parse_svg_float(xml_node.attributes.get_key("r")).unwrap_or(0.0);
                if r > 0.0 {
                    Some(crate::svg::SvgMultiPolygon {
                        rings: crate::svg::SvgPathVec::from_vec(vec![
                            crate::svg_path_parser::svg_circle_to_paths(cx, cy, r)
                        ]),
                    })
                } else {
                    None
                }
            }
            "rect" => {
                let x = parse_svg_float(xml_node.attributes.get_key("x")).unwrap_or(0.0);
                let y = parse_svg_float(xml_node.attributes.get_key("y")).unwrap_or(0.0);
                let w = parse_svg_float(xml_node.attributes.get_key("width")).unwrap_or(0.0);
                let h = parse_svg_float(xml_node.attributes.get_key("height")).unwrap_or(0.0);
                let rx = parse_svg_float(xml_node.attributes.get_key("rx")).unwrap_or(0.0);
                let ry = parse_svg_float(xml_node.attributes.get_key("ry")).unwrap_or(rx);
                if w > 0.0 && h > 0.0 {
                    Some(crate::svg::SvgMultiPolygon {
                        rings: crate::svg::SvgPathVec::from_vec(vec![
                            crate::svg_path_parser::svg_rect_to_path(x, y, w, h, rx, ry)
                        ]),
                    })
                } else {
                    None
                }
            }
            "ellipse" => {
                let cx = parse_svg_float(xml_node.attributes.get_key("cx")).unwrap_or(0.0);
                let cy = parse_svg_float(xml_node.attributes.get_key("cy")).unwrap_or(0.0);
                let rx = parse_svg_float(xml_node.attributes.get_key("rx")).unwrap_or(0.0);
                let ry = parse_svg_float(xml_node.attributes.get_key("ry")).unwrap_or(0.0);
                if rx > 0.0 && ry > 0.0 {
                    // Approximate ellipse with 4 cubic beziers (using rx for x-kappa, ry for y-kappa)
                    use azul_css::props::basic::{SvgPoint, SvgCubicCurve};
                    const KAPPA: f32 = 0.5522847498;
                    let kx = rx * KAPPA;
                    let ky = ry * KAPPA;
                    let elements = vec![
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
                            start: SvgPoint { x: cx, y: cy - ry },
                            ctrl_1: SvgPoint { x: cx + kx, y: cy - ry },
                            ctrl_2: SvgPoint { x: cx + rx, y: cy - ky },
                            end: SvgPoint { x: cx + rx, y: cy },
                        }),
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
                            start: SvgPoint { x: cx + rx, y: cy },
                            ctrl_1: SvgPoint { x: cx + rx, y: cy + ky },
                            ctrl_2: SvgPoint { x: cx + kx, y: cy + ry },
                            end: SvgPoint { x: cx, y: cy + ry },
                        }),
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
                            start: SvgPoint { x: cx, y: cy + ry },
                            ctrl_1: SvgPoint { x: cx - kx, y: cy + ry },
                            ctrl_2: SvgPoint { x: cx - rx, y: cy + ky },
                            end: SvgPoint { x: cx - rx, y: cy },
                        }),
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
                            start: SvgPoint { x: cx - rx, y: cy },
                            ctrl_1: SvgPoint { x: cx - rx, y: cy - ky },
                            ctrl_2: SvgPoint { x: cx - kx, y: cy - ry },
                            end: SvgPoint { x: cx, y: cy - ry },
                        }),
                    ];
                    Some(crate::svg::SvgMultiPolygon {
                        rings: crate::svg::SvgPathVec::from_vec(vec![
                            crate::svg::SvgPath { items: crate::svg::SvgPathElementVec::from_vec(elements) }
                        ]),
                    })
                } else {
                    None
                }
            }
            "line" => {
                let x1 = parse_svg_float(xml_node.attributes.get_key("x1")).unwrap_or(0.0);
                let y1 = parse_svg_float(xml_node.attributes.get_key("y1")).unwrap_or(0.0);
                let x2 = parse_svg_float(xml_node.attributes.get_key("x2")).unwrap_or(0.0);
                let y2 = parse_svg_float(xml_node.attributes.get_key("y2")).unwrap_or(0.0);
                Some(crate::svg::SvgMultiPolygon {
                    rings: crate::svg::SvgPathVec::from_vec(vec![
                        crate::svg::SvgPath {
                            items: crate::svg::SvgPathElementVec::from_vec(vec![
                                crate::svg::SvgPathElement::Line(crate::svg::SvgLine::new(
                                    azul_css::props::basic::SvgPoint { x: x1, y: y1 },
                                    azul_css::props::basic::SvgPoint { x: x2, y: y2 },
                                ))
                            ]),
                        }
                    ]),
                })
            }
            "polygon" | "polyline" => {
                xml_node.attributes.get_key("points").and_then(|pts| {
                    parse_svg_points(pts.as_str(), tag == "polygon")
                })
            }
            _ => None,
        };
        if let Some(mp) = clip {
            dom.root.set_svg_data(crate::dom::SvgNodeData::Path(mp));
        }
1
    }
    // Recursively convert children
1
    let mut children = Vec::new();
1
    for child in xml_node.children.as_ref().iter() {
        match child {
            XmlNodeChild::Element(child_node) => {
                let child_dom = xml_node_to_dom_fast(child_node, component_map, child_inside_svg)?;
                children.push(child_dom);
            }
            XmlNodeChild::Text(text) => {
                let text_dom = Dom::create_text(AzString::from(text.as_str()));
                children.push(text_dom);
            }
        }
    }
1
    if !children.is_empty() {
        dom = dom.with_children(children.into());
1
    }
1
    Ok(dom)
1
}
/// Builder for arena-based DOM construction (FastDom).
/// Builds two parallel Vecs (hierarchy + node_data) in a single DFS pass.
pub struct CompactDomBuilder {
    hierarchy: Vec<crate::styled_dom::NodeHierarchyItem>,
    node_data: Vec<crate::dom::NodeData>,
    css: Vec<crate::dom::CssWithNodeId>,
    /// Stack of (node_index, previous_child_index) for open elements
    stack: Vec<(usize, Option<usize>)>,
}
impl CompactDomBuilder {
3150
    pub fn new() -> Self {
3150
        Self {
3150
            hierarchy: Vec::new(),
3150
            node_data: Vec::new(),
3150
            css: Vec::new(),
3150
            stack: Vec::new(),
3150
        }
3150
    }
    pub fn with_capacity(cap: usize) -> Self {
        Self {
            hierarchy: Vec::with_capacity(cap),
            node_data: Vec::with_capacity(cap),
            css: Vec::new(),
            stack: Vec::new(),
        }
    }
    /// Open a new element node. Must be paired with `close_node()`.
20454
    pub fn open_node(&mut self, node_data: crate::dom::NodeData) {
        use crate::id::NodeId;
        use crate::styled_dom::NodeHierarchyItem;
20454
        let idx = self.hierarchy.len();
        // Determine parent from stack
20454
        let parent_raw = if let Some(&(parent_idx, _)) = self.stack.last() {
17304
            NodeId::into_raw(&Some(NodeId::new(parent_idx)))
        } else {
3150
            0 // No parent (root)
        };
        // Determine previous sibling from parent's last child tracking
20454
        let prev_sibling_raw = if let Some(&(_, prev_child)) = self.stack.last() {
17304
            prev_child.map(|pi| NodeId::into_raw(&Some(NodeId::new(pi)))).unwrap_or(0)
        } else {
3150
            0
        };
        // If there's a previous sibling, set its next_sibling to us
20454
        if let Some(&(_, Some(prev_idx))) = self.stack.last() {
7560
            self.hierarchy[prev_idx].next_sibling = NodeId::into_raw(&Some(NodeId::new(idx)));
12894
        }
        // Update parent's "last seen child" to us
20454
        if let Some(parent) = self.stack.last_mut() {
17304
            parent.1 = Some(idx);
17304
        }
        // Push the hierarchy item (last_child will be set in close_node)
20454
        self.hierarchy.push(NodeHierarchyItem {
20454
            parent: parent_raw,
20454
            previous_sibling: prev_sibling_raw,
20454
            next_sibling: 0, // Will be set by next sibling's open_node
20454
            last_child: 0,   // Will be set in close_node
20454
        });
20454
        self.node_data.push(node_data);
        // Push onto stack: this node is now the "open" element, no children yet
20454
        self.stack.push((idx, None));
20454
    }
    /// Close the current element. Sets the `last_child` pointer.
20454
    pub fn close_node(&mut self) {
        use crate::id::NodeId;
20454
        if let Some((idx, last_child_idx)) = self.stack.pop() {
            // Set last_child on this node's hierarchy item
20454
            self.hierarchy[idx].last_child = last_child_idx
20454
                .map(|lc| NodeId::into_raw(&Some(NodeId::new(lc))))
20454
                .unwrap_or(0);
        }
20454
    }
    /// Add a leaf node (text, br, hr, etc.) that has no children.
7014
    pub fn add_leaf(&mut self, node_data: crate::dom::NodeData) {
7014
        self.open_node(node_data);
7014
        self.close_node();
7014
    }
    /// Add a CSS stylesheet scoped to a node ID.
    pub fn add_css(&mut self, node_id: usize, css: azul_css::css::Css) {
        self.css.push(crate::dom::CssWithNodeId { node_id, css });
    }
    /// Finish building and produce a FastDom.
3150
    pub fn finish(self) -> crate::dom::FastDom {
3150
        crate::dom::FastDom {
3150
            node_hierarchy: self.hierarchy.into(),
3150
            node_data: self.node_data.into(),
3150
            css: self.css.into(),
3150
        }
3150
    }
}
/// Convert an XML node tree into a FastDom (arena-based) in a single DFS pass.
/// This is the fast path equivalent of `xml_node_to_dom_fast`.
10290
fn xml_node_to_fast_dom<'a>(
10290
    xml_node: &'a XmlNode,
10290
    component_map: &'a ComponentMap,
10290
    inside_svg: bool,
10290
    builder: &mut CompactDomBuilder,
10290
) -> Result<(), RenderDomError> {
    use crate::dom::{Dom, NodeType, NodeData, IdOrClass, TabIndex};
10290
    let component_name = normalize_casing(&xml_node.node_type);
10290
    let node_type = tag_to_node_type(&component_name);
10290
    let mut node_data = NodeData::create_node(node_type);
    // `<img src="...">`: rebuild the placeholder Image node so its `NullImage`
    // carries the `src` string (UTF-8 bytes in `tag`). The bytes are resolved
    // downstream (e.g. printpdf) via the tag. Mirrors `xml_node_to_dom_fast`.
10290
    if component_name == "img" {
        if let Some(src) = xml_node.attributes.get_key("src") {
            let width = xml_node
                .attributes
                .get_key("width")
                .and_then(|w| w.as_str().trim().trim_end_matches("px").trim().parse::<usize>().ok())
                .unwrap_or(0);
            let height = xml_node
                .attributes
                .get_key("height")
                .and_then(|h| h.as_str().trim().trim_end_matches("px").trim().parse::<usize>().ok())
                .unwrap_or(0);
            let image_ref = crate::resources::ImageRef::null_image(
                width,
                height,
                crate::resources::RawImageFormat::RGBA8,
                src.as_str().as_bytes().to_vec(),
            );
            node_data.set_node_type(NodeType::Image(azul_css::css::BoxOrStatic::heap(image_ref)));
        }
10290
    }
    // Set id and class attributes
10290
    let mut ids_and_classes = Vec::new();
10290
    if let Some(id_str) = xml_node.attributes.get_key("id") {
        for id in id_str.split_whitespace() {
            ids_and_classes.push(IdOrClass::Id(id.into()));
        }
10290
    }
10290
    if let Some(class_str) = xml_node.attributes.get_key("class") {
4116
        for class in class_str.split_whitespace() {
4116
            ids_and_classes.push(IdOrClass::Class(class.into()));
4116
        }
6174
    }
10290
    if !ids_and_classes.is_empty() {
4116
        node_data.set_ids_and_classes(ids_and_classes.into());
6174
    }
    // Handle focusable attribute
10290
    if let Some(focusable) = xml_node.attributes.get_key("focusable").and_then(|f| parse_bool(f.as_str())) {
        match focusable {
            true => node_data.set_tab_index(TabIndex::Auto),
            false => node_data.set_tab_index(TabIndex::NoKeyboardFocus),
        }
10290
    }
    // Handle tabindex attribute
10290
    if let Some(tab_index) = xml_node.attributes.get_key("tabindex").and_then(|val| val.parse::<isize>().ok()) {
        match tab_index {
            0 => node_data.set_tab_index(TabIndex::Auto),
            i if i > 0 => node_data.set_tab_index(TabIndex::OverrideInParent(i as u32)),
            _ => node_data.set_tab_index(TabIndex::NoKeyboardFocus),
        }
10290
    }
    // Handle inline style attribute
10290
    if let Some(style) = xml_node.attributes.get_key("style") {
        let css_key_map = azul_css::props::property::get_css_key_map();
        let mut attributes = Vec::new();
        for s in style.as_str().split(";") {
            let mut s = s.split(":");
            let key = match s.next() { Some(s) => s, None => continue };
            let value = match s.next() { Some(s) => s, None => continue };
            let _ = azul_css::parser2::parse_css_declaration(
                key.trim(), value.trim(),
                azul_css::parser2::ErrorLocationRange::default(),
                &css_key_map, &mut Vec::new(), &mut attributes,
            );
        }
        let props = attributes.into_iter().filter_map(|s| {
            use azul_css::dynamic_selector::CssPropertyWithConditions;
            match s {
                CssDeclaration::Static(s) => Some(CssPropertyWithConditions::simple(s)),
                _ => None,
            }
        }).collect::<Vec<_>>();
        if !props.is_empty() {
            node_data.set_css_props(props.into());
        }
10290
    }
    // Handle SVG shape elements
10290
    let tag = component_name.as_str();
10290
    let child_inside_svg = inside_svg || tag == "svg";
10290
    let is_svg_shape = inside_svg && matches!(tag, "path" | "circle" | "rect" | "ellipse" | "line" | "polygon" | "polyline");
10290
    if is_svg_shape {
882
        let clip = match tag {
882
            "path" => {
168
                xml_node.attributes.get_key("d").and_then(|d| {
168
                    crate::svg_path_parser::parse_svg_path_d(d.as_str()).ok()
168
                })
            }
714
            "circle" => {
210
                let cx = parse_svg_float(xml_node.attributes.get_key("cx")).unwrap_or(0.0);
210
                let cy = parse_svg_float(xml_node.attributes.get_key("cy")).unwrap_or(0.0);
210
                let r = parse_svg_float(xml_node.attributes.get_key("r")).unwrap_or(0.0);
210
                if r > 0.0 {
168
                    Some(crate::svg::SvgMultiPolygon {
168
                        rings: crate::svg::SvgPathVec::from_vec(vec![
168
                            crate::svg_path_parser::svg_circle_to_paths(cx, cy, r)
168
                        ]),
168
                    })
                } else {
42
                    None
                }
            }
504
            "rect" => {
252
                let x = parse_svg_float(xml_node.attributes.get_key("x")).unwrap_or(0.0);
252
                let y = parse_svg_float(xml_node.attributes.get_key("y")).unwrap_or(0.0);
252
                let w = parse_svg_float(xml_node.attributes.get_key("width")).unwrap_or(0.0);
252
                let h = parse_svg_float(xml_node.attributes.get_key("height")).unwrap_or(0.0);
252
                let rx = parse_svg_float(xml_node.attributes.get_key("rx")).unwrap_or(0.0);
252
                let ry = parse_svg_float(xml_node.attributes.get_key("ry")).unwrap_or(rx);
252
                if w > 0.0 && h > 0.0 {
210
                    Some(crate::svg::SvgMultiPolygon {
210
                        rings: crate::svg::SvgPathVec::from_vec(vec![
210
                            crate::svg_path_parser::svg_rect_to_path(x, y, w, h, rx, ry)
210
                        ]),
210
                    })
                } else {
42
                    None
                }
            }
252
            "ellipse" => {
84
                let cx = parse_svg_float(xml_node.attributes.get_key("cx")).unwrap_or(0.0);
84
                let cy = parse_svg_float(xml_node.attributes.get_key("cy")).unwrap_or(0.0);
84
                let rx = parse_svg_float(xml_node.attributes.get_key("rx")).unwrap_or(0.0);
84
                let ry = parse_svg_float(xml_node.attributes.get_key("ry")).unwrap_or(0.0);
84
                if rx > 0.0 && ry > 0.0 {
                    use azul_css::props::basic::{SvgPoint, SvgCubicCurve};
                    const KAPPA: f32 = 0.5522847498;
84
                    let kx = rx * KAPPA;
84
                    let ky = ry * KAPPA;
84
                    let elements = vec![
84
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
84
                            start: SvgPoint { x: cx, y: cy - ry },
84
                            ctrl_1: SvgPoint { x: cx + kx, y: cy - ry },
84
                            ctrl_2: SvgPoint { x: cx + rx, y: cy - ky },
84
                            end: SvgPoint { x: cx + rx, y: cy },
84
                        }),
84
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
84
                            start: SvgPoint { x: cx + rx, y: cy },
84
                            ctrl_1: SvgPoint { x: cx + rx, y: cy + ky },
84
                            ctrl_2: SvgPoint { x: cx + kx, y: cy + ry },
84
                            end: SvgPoint { x: cx, y: cy + ry },
84
                        }),
84
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
84
                            start: SvgPoint { x: cx, y: cy + ry },
84
                            ctrl_1: SvgPoint { x: cx - kx, y: cy + ry },
84
                            ctrl_2: SvgPoint { x: cx - rx, y: cy + ky },
84
                            end: SvgPoint { x: cx - rx, y: cy },
84
                        }),
84
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
84
                            start: SvgPoint { x: cx - rx, y: cy },
84
                            ctrl_1: SvgPoint { x: cx - rx, y: cy - ky },
84
                            ctrl_2: SvgPoint { x: cx - kx, y: cy - ry },
84
                            end: SvgPoint { x: cx, y: cy - ry },
84
                        }),
                    ];
84
                    Some(crate::svg::SvgMultiPolygon {
84
                        rings: crate::svg::SvgPathVec::from_vec(vec![
84
                            crate::svg::SvgPath { items: crate::svg::SvgPathElementVec::from_vec(elements) }
84
                        ]),
84
                    })
                } else {
                    None
                }
            }
168
            "line" => {
42
                let x1 = parse_svg_float(xml_node.attributes.get_key("x1")).unwrap_or(0.0);
42
                let y1 = parse_svg_float(xml_node.attributes.get_key("y1")).unwrap_or(0.0);
42
                let x2 = parse_svg_float(xml_node.attributes.get_key("x2")).unwrap_or(0.0);
42
                let y2 = parse_svg_float(xml_node.attributes.get_key("y2")).unwrap_or(0.0);
42
                Some(crate::svg::SvgMultiPolygon {
42
                    rings: crate::svg::SvgPathVec::from_vec(vec![
42
                        crate::svg::SvgPath {
42
                            items: crate::svg::SvgPathElementVec::from_vec(vec![
42
                                crate::svg::SvgPathElement::Line(crate::svg::SvgLine::new(
42
                                    azul_css::props::basic::SvgPoint { x: x1, y: y1 },
42
                                    azul_css::props::basic::SvgPoint { x: x2, y: y2 },
42
                                ))
42
                            ]),
42
                        }
42
                    ]),
42
                })
            }
126
            "polygon" | "polyline" => {
126
                xml_node.attributes.get_key("points").and_then(|pts| {
126
                    parse_svg_points(pts.as_str(), tag == "polygon")
126
                })
            }
            _ => None,
        };
882
        if let Some(mp) = clip {
798
            node_data.set_svg_data(crate::dom::SvgNodeData::Path(mp));
798
        }
9408
    }
    // Open this node in the builder
10290
    builder.open_node(node_data);
    // Recursively convert children
14154
    for child in xml_node.children.as_ref().iter() {
14154
        match child {
7140
            XmlNodeChild::Element(child_node) => {
7140
                xml_node_to_fast_dom(child_node, component_map, child_inside_svg, builder)?;
            }
7014
            XmlNodeChild::Text(text) => {
7014
                builder.add_leaf(NodeData::create_text(AzString::from(text.as_str())));
7014
            }
        }
    }
    // Close this node
10290
    builder.close_node();
10290
    Ok(())
10290
}
/// Render a DOM from an XML body node using the fast arena-based path.
/// Builds a FastDom directly (no tree intermediary), then creates StyledDom.
3150
fn render_dom_from_body_node_fast<'a>(
3150
    body_node: &'a XmlNode,
3150
    mut global_css: Option<Css>,
3150
    component_map: &'a ComponentMap,
3150
    max_width: Option<f32>,
3150
) -> Result<StyledDom, RenderDomError> {
    use crate::dom::{NodeData, NodeType};
3150
    let mut builder = CompactDomBuilder::new();
    // Build the HTML > Body wrapper + body content in one pass
    // Open <html>
3150
    builder.open_node(NodeData::create_node(NodeType::Html));
    // Open <body> (the body_node content goes inside)
3150
    xml_node_to_fast_dom(body_node, component_map, false, &mut builder)?;
    // Close <html>
3150
    builder.close_node();
    // Collect CSS rules from each source.
3150
    let mut combined_rules: Vec<azul_css::css::CssRuleBlock> = Vec::new();
3150
    if let Some(max_width) = max_width {
        let max_width_css = Css::from_string(
            format!("html {{ max-width: {max_width}px; }}").into(),
        );
        combined_rules.extend(max_width_css.rules.into_library_owned_vec());
3150
    }
3150
    if let Some(css) = global_css.take() {
2310
        combined_rules.extend(css.rules.into_library_owned_vec());
2310
    }
3150
    let combined_css = Css::new(combined_rules);
    // Add CSS to the FastDom
3150
    let mut fast_dom = builder.finish();
3150
    fast_dom.css = vec![crate::dom::CssWithNodeId {
3150
        node_id: 0, // Global scope (root)
3150
        css: combined_css,
3150
    }].into();
    // Create StyledDom via the fast path (no tree→arena conversion)
3150
    let styled = StyledDom::create_from_fast_dom(fast_dom);
3150
    Ok(styled)
3150
}
// render_dom_from_body_node() removed — use render_dom_from_body_node_fast() or str_to_dom()
fn set_stringified_attributes(
    dom_string: &mut String,
    xml_attributes: &XmlAttributeMap,
    filtered_xml_attributes: &ComponentArgumentVec,
    tabs: usize,
) {
    let t0 = String::from("    ").repeat(tabs);
    let t = String::from("    ").repeat(tabs + 1);
    // push ids and classes
    let mut ids_and_classes = String::new();
    for id in xml_attributes
        .get_key("id")
        .map(|s| s.split_whitespace().collect::<Vec<_>>())
        .unwrap_or_default()
    {
        ids_and_classes.push_str(&format!(
            "{}    Id(AzString::from_const_str(\"{}\")),\r\n",
            t0,
            format_args_dynamic(id, &filtered_xml_attributes)
        ));
    }
    for class in xml_attributes
        .get_key("class")
        .map(|s| s.split_whitespace().collect::<Vec<_>>())
        .unwrap_or_default()
    {
        ids_and_classes.push_str(&format!(
            "{}    Class(AzString::from_const_str(\"{}\")),\r\n",
            t0,
            format_args_dynamic(class, &filtered_xml_attributes)
        ));
    }
    if !ids_and_classes.is_empty() {
        use azul_css::format_rust_code::GetHash;
        let id = ids_and_classes.get_hash();
        dom_string.push_str(&format!(
            "\r\n{t0}.with_ids_and_classes({{\r\n{t}const IDS_AND_CLASSES_{id}: &[IdOrClass] = \
             &[\r\n{t}{ids_and_classes}\r\n{t}];\r\\
             n{t}IdOrClassVec::from_const_slice(IDS_AND_CLASSES_{id})\r\n{t0}}})",
            t0 = t0,
            t = t,
            ids_and_classes = ids_and_classes,
            id = id
        ));
    }
    if let Some(focusable) = xml_attributes
        .get_key("focusable")
        .map(|f| format_args_dynamic(f, &filtered_xml_attributes))
        .and_then(|f| parse_bool(&f))
    {
        match focusable {
            true => dom_string.push_str(&format!("\r\n{}.with_tab_index(TabIndex::Auto)", t)),
            false => dom_string.push_str(&format!(
                "\r\n{}.with_tab_index(TabIndex::NoKeyboardFocus)",
                t
            )),
        }
    }
    if let Some(tab_index) = xml_attributes
        .get_key("tabindex")
        .map(|val| format_args_dynamic(val, &filtered_xml_attributes))
        .and_then(|val| val.parse::<isize>().ok())
    {
        match tab_index {
            0 => dom_string.push_str(&format!("\r\n{}.with_tab_index(TabIndex::Auto)", t)),
            i if i > 0 => dom_string.push_str(&format!(
                "\r\n{}.with_tab_index(TabIndex::OverrideInParent({}))",
                t, i as usize
            )),
            _ => dom_string.push_str(&format!(
                "\r\n{}.with_tab_index(TabIndex::NoKeyboardFocus)",
                t
            )),
        }
    }
}
/// Item of a split string - either a variable name (with optional format spec) or a string
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum DynamicItem {
    /// A variable reference, e.g. {counter} or {counter:?} or {price:.2}
    Var {
        name: String,
        /// Optional format specifier after the colon: "?" for debug, ".2" for precision, etc.
        format_spec: Option<String>,
    },
    Str(String),
}
/// Splits a string into formatting arguments, supporting format specifiers like `{var:?}`
/// ```rust
/// # use azul_core::xml::DynamicItem::*;
/// # use azul_core::xml::split_dynamic_string;
/// let s = "hello {a}, {b}{{ {c} }}";
/// let split = split_dynamic_string(s);
/// let output = vec![
///     Str("hello ".to_string()),
///     Var { name: "a".to_string(), format_spec: None },
///     Str(", ".to_string()),
///     Var { name: "b".to_string(), format_spec: None },
///     Str("{ ".to_string()),
///     Var { name: "c".to_string(), format_spec: None },
///     Str(" }".to_string()),
/// ];
/// assert_eq!(output, split);
/// ```
pub fn split_dynamic_string(input: &str) -> Vec<DynamicItem> {
    use self::DynamicItem::*;
    let input: Vec<char> = input.chars().collect();
    let input_chars_len = input.len();
    let mut items = Vec::new();
    let mut current_idx = 0;
    let mut last_idx = 0;
    while current_idx < input_chars_len {
        let c = input[current_idx];
        match c {
            '{' if input.get(current_idx + 1).copied() != Some('{') => {
                // variable start, search until next closing brace or whitespace or end of string
                let mut start_offset = 1;
                let mut has_found_variable = false;
                while let Some(c) = input.get(current_idx + start_offset) {
                    if c.is_whitespace() {
                        break;
                    }
                    if *c == '}' && input.get(current_idx + start_offset + 1).copied() != Some('}')
                    {
                        start_offset += 1;
                        has_found_variable = true;
                        break;
                    }
                    start_offset += 1;
                }
                // advance current_idx accordingly
                // on fail, set cursor to end
                // set last_idx accordingly
                if has_found_variable {
                    if last_idx != current_idx {
                        items.push(Str(input[last_idx..current_idx].iter().collect()));
                    }
                    // subtract 1 from start for opening brace, one from end for closing brace
                    let var_content: String = input
                        [(current_idx + 1)..(current_idx + start_offset - 1)]
                        .iter()
                        .collect();
                    // Split on first ':' to separate variable name from format specifier
                    let (var_name, format_spec) = if let Some(colon_pos) = var_content.find(':') {
                        let name = var_content[..colon_pos].to_string();
                        let spec = var_content[(colon_pos + 1)..].to_string();
                        (name, Some(spec))
                    } else {
                        (var_content, None)
                    };
                    items.push(Var { name: var_name, format_spec });
                    current_idx = current_idx + start_offset;
                    last_idx = current_idx;
                } else {
                    current_idx += start_offset;
                }
            }
            _ => {
                current_idx += 1;
            }
        }
    }
    if current_idx != last_idx {
        items.push(Str(input[last_idx..].iter().collect()));
    }
    for item in &mut items {
        // replace {{ with { in strings
        if let Str(s) = item {
            *s = s.replace("{{", "{").replace("}}", "}");
        }
    }
    items
}
/// Combines the split string back into its original form while replacing the variables with their
/// values
///
/// let variables = btreemap!{ "a" => "value1", "b" => "value2" };
/// [Str("hello "), Var("a"), Str(", "), Var("b"), Str("{ "), Var("c"), Str(" }}")]
/// => "hello value1, valuec{ {c} }"
fn combine_and_replace_dynamic_items(
    input: &[DynamicItem],
    variables: &ComponentArgumentVec,
) -> String {
    let mut s = String::new();
    for item in input {
        match item {
            DynamicItem::Var { name, format_spec } => {
                let variable_name = normalize_casing(name.trim());
                match variables
                    .iter()
                    .find(|s| s.name.as_str() == variable_name)
                    .map(|q| &q.arg_type)
                {
                    Some(resolved_var) => {
                        // Format specifiers are applied at compile time, not at runtime replacement
                        s.push_str(&resolved_var);
                    }
                    None => {
                        s.push('{');
                        s.push_str(name);
                        if let Some(spec) = format_spec {
                            s.push(':');
                            s.push_str(spec);
                        }
                        s.push('}');
                    }
                }
            }
            DynamicItem::Str(dynamic_str) => {
                s.push_str(&dynamic_str);
            }
        }
    }
    s
}
/// Given a string and a key => value mapping, replaces parts of the string with the value, i.e.:
///
/// ```rust
/// # use azul_core::xml::{format_args_dynamic, ComponentArgument, ComponentArgumentVec};
/// # use azul_css::AzString;
/// let variables: ComponentArgumentVec = vec![
///     ComponentArgument { name: AzString::from("a"), arg_type: AzString::from("value1") },
///     ComponentArgument { name: AzString::from("b"), arg_type: AzString::from("value2") },
/// ].into();
///
/// let initial = "hello {a}, {b}{{ {c} }}";
/// let expected = "hello value1, value2{ {c} }".to_string();
/// assert_eq!(format_args_dynamic(initial, &variables), expected);
/// ```
///
/// Note: the number (0, 1, etc.) is the order of the argument, it is irrelevant for
/// runtime formatting, only important for keeping the component / function arguments
/// in order when compiling the arguments to Rust code
pub fn format_args_dynamic(input: &str, variables: &ComponentArgumentVec) -> String {
    let dynamic_str_items = split_dynamic_string(input);
    combine_and_replace_dynamic_items(&dynamic_str_items, variables)
}
// NOTE: Two sequential returns count as a single return, while single returns get ignored.
pub fn prepare_string(input: &str) -> String {
    const SPACE: &str = " ";
    const RETURN: &str = "\n";
    let input = input.trim();
    if input.is_empty() {
        return String::new();
    }
    let input = input.replace("&lt;", "<");
    let input = input.replace("&gt;", ">");
    let input_len = input.len();
    let mut final_lines: Vec<String> = Vec::new();
    let mut last_line_was_empty = false;
    for line in input.lines() {
        let line = line.trim();
        let line = line.replace("&nbsp;", " ");
        let current_line_is_empty = line.is_empty();
        if !current_line_is_empty {
            if last_line_was_empty {
                final_lines.push(format!("{}{}", RETURN, line));
            } else {
                final_lines.push(line.to_string());
            }
        }
        last_line_was_empty = current_line_is_empty;
    }
    let line_len = final_lines.len();
    let mut target = String::with_capacity(input_len);
    for (line_idx, line) in final_lines.iter().enumerate() {
        if !(line.starts_with(RETURN) || line_idx == 0 || line_idx == line_len.saturating_sub(1)) {
            target.push_str(SPACE);
        }
        target.push_str(line);
    }
    target
}
/// Parses a string ("true" or "false")
pub fn parse_bool(input: &str) -> Option<bool> {
    match input {
        "true" => Some(true),
        "false" => Some(false),
        _ => None,
    }
}
#[derive(Clone)]
pub struct CssMatcher {
    path: Vec<CssPathSelector>,
    indices_in_parent: Vec<usize>,
    children_length: Vec<usize>,
}
impl CssMatcher {
    fn get_hash(&self) -> u64 {
        use core::hash::Hash;
        use core::hash::Hasher;
        let mut hasher = crate::hash::DefaultHasher::new();
        for p in self.path.iter() {
            p.hash(&mut hasher);
        }
        hasher.finish()
    }
}
impl CssMatcher {
    fn matches(&self, path: &CssPath) -> bool {
        use azul_css::css::CssPathSelector::*;
        use crate::style::{CssGroupIterator, CssGroupSplitReason};
        if self.path.is_empty() {
            return false;
        }
        if path.selectors.as_ref().is_empty() {
            return false;
        }
        // self_matcher is only ever going to contain "Children" selectors, never "DirectChildren"
        let mut path_groups = CssGroupIterator::new(path.selectors.as_ref()).collect::<Vec<_>>();
        path_groups.reverse();
        if path_groups.is_empty() {
            return false;
        }
        let mut self_groups = CssGroupIterator::new(self.path.as_ref()).collect::<Vec<_>>();
        self_groups.reverse();
        if self_groups.is_empty() {
            return false;
        }
        if self.indices_in_parent.len() != self_groups.len() {
            return false;
        }
        if self.children_length.len() != self_groups.len() {
            return false;
        }
        // self_groups = [ // HTML
        //     "body",
        //     "div.__azul_native-ribbon-container"
        //     "div.__azul_native-ribbon-tabs"
        //     "p.home"
        // ]
        //
        // path_groups = [ // CSS
        //     ".__azul_native-ribbon-tabs"
        //     "div.after-tabs"
        // ]
        // get the first path group and see if it matches anywhere in the self group
        let mut cur_selfgroup_scan = 0;
        let mut cur_pathgroup_scan = 0;
        let mut valid = false;
        let mut path_group = path_groups[cur_pathgroup_scan].clone();
        while cur_selfgroup_scan < self_groups.len() {
            let mut advance = None;
            // scan all remaining path groups
            for (id, cg) in self_groups[cur_selfgroup_scan..].iter().enumerate() {
                let gm = group_matches(
                    &path_group.0,
                    &self_groups[cur_selfgroup_scan + id].0,
                    self.indices_in_parent[cur_selfgroup_scan + id],
                    self.children_length[cur_selfgroup_scan + id],
                );
                if gm {
                    // ok: ".__azul_native-ribbon-tabs" was found within self_groups
                    // advance the self_groups by n
                    advance = Some(id);
                    break;
                }
            }
            match advance {
                Some(n) => {
                    // group was found in remaining items
                    // advance cur_pathgroup_scan by 1 and cur_selfgroup_scan by n
                    if cur_pathgroup_scan == path_groups.len() - 1 {
                        // last path group
                        return cur_selfgroup_scan + n == self_groups.len() - 1;
                    } else {
                        cur_pathgroup_scan += 1;
                        cur_selfgroup_scan += n;
                        path_group = path_groups[cur_pathgroup_scan].clone();
                    }
                }
                None => return false, // group was not found in remaining items
            }
        }
        // only return true if all path_groups matched
        return cur_pathgroup_scan == path_groups.len() - 1;
    }
}
// does p.home match div.after-tabs?
// a: div.after-tabs
fn group_matches(
    a: &[&CssPathSelector],
    b: &[&CssPathSelector],
    idx_in_parent: usize,
    parent_children: usize,
) -> bool {
    use azul_css::css::{CssNthChildSelector, CssPathPseudoSelector, CssPathSelector::*};
    for selector in a {
        match selector {
            // always matches
            Global => {}
            PseudoSelector(CssPathPseudoSelector::Hover) => {}
            PseudoSelector(CssPathPseudoSelector::Active) => {}
            PseudoSelector(CssPathPseudoSelector::Focus) => {}
            Type(tag) => {
                if !b.iter().any(|t| **t == Type(tag.clone())) {
                    return false;
                }
            }
            Class(class) => {
                if !b.iter().any(|t| **t == Class(class.clone())) {
                    return false;
                }
            }
            Id(id) => {
                if !b.iter().any(|t| **t == Id(id.clone())) {
                    return false;
                }
            }
            PseudoSelector(CssPathPseudoSelector::First) => {
                if idx_in_parent != 0 {
                    return false;
                }
            }
            PseudoSelector(CssPathPseudoSelector::Last) => {
                if idx_in_parent != parent_children.saturating_sub(1) {
                    return false;
                }
            }
            PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Number(i))) => {
                if idx_in_parent != *i as usize {
                    return false;
                }
            }
            PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Even)) => {
                if idx_in_parent % 2 != 0 {
                    return false;
                }
            }
            PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Odd)) => {
                if idx_in_parent % 2 == 0 {
                    return false;
                }
            }
            PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Pattern(p))) => {
                if idx_in_parent.saturating_sub(p.offset as usize) % p.pattern_repeat as usize != 0
                {
                    return false;
                }
            }
            _ => return false, // can't happen
        }
    }
    true
}
struct CssBlock {
    ending: Option<CssPathPseudoSelector>,
    block: CssRuleBlock,
}
pub fn compile_body_node_to_rust_code<'a>(
    body_node: &'a XmlNode,
    component_map: &'a ComponentMap,
    extra_blocks: &mut VecContents,
    css_blocks: &mut BTreeMap<String, String>,
    css: &Css,
    mut matcher: CssMatcher,
) -> Result<String, CompileError> {
    use azul_css::css::CssDeclaration;
    let t = "";
    let t2 = "    ";
    let mut dom_string = String::from("Dom::create_body()");
    let node_type = CssPathSelector::Type(NodeTypeTag::Body);
    matcher.path.push(node_type);
    let ids = body_node
        .attributes
        .get_key("id")
        .map(|s| s.split_whitespace().collect::<Vec<_>>())
        .unwrap_or_default();
    matcher.path.extend(
        ids.into_iter()
            .map(|id| CssPathSelector::Id(id.to_string().into())),
    );
    let classes = body_node
        .attributes
        .get_key("class")
        .map(|s| s.split_whitespace().collect::<Vec<_>>())
        .unwrap_or_default();
    matcher.path.extend(
        classes
            .into_iter()
            .map(|class| CssPathSelector::Class(class.to_string().into())),
    );
    let matcher_hash = matcher.get_hash();
    let css_blocks_for_this_node = get_css_blocks(css, &matcher);
    if !css_blocks_for_this_node.is_empty() {
        use azul_css::props::property::format_static_css_prop;
        let css_strings = css_blocks_for_this_node
            .iter()
            .rev()
            .map(|css_block| {
                let wrapper = match css_block.ending {
                    Some(CssPathPseudoSelector::Hover) => "Hover",
                    Some(CssPathPseudoSelector::Active) => "Active",
                    Some(CssPathPseudoSelector::Focus) => "Focus",
                    _ => "Normal",
                };
                for declaration in css_block.block.declarations.as_ref().iter() {
                    let prop = match declaration {
                        CssDeclaration::Static(s) => s,
                        CssDeclaration::Dynamic(d) => &d.default_value,
                    };
                    extra_blocks.insert_from_css_property(prop);
                }
                let formatted = css_block
                    .block
                    .declarations
                    .as_ref()
                    .iter()
                    .rev()
                    .map(|s| match &s {
                        CssDeclaration::Static(s) => format!(
                            "NodeDataInlineCssProperty::{}({})",
                            wrapper,
                            format_static_css_prop(s, 1)
                        ),
                        CssDeclaration::Dynamic(d) => format!(
                            "NodeDataInlineCssProperty::{}({})",
                            wrapper,
                            format_static_css_prop(&d.default_value, 1)
                        ),
                    })
                    .collect::<Vec<String>>();
                format!("// {}\r\n{}", css_block.block.path, formatted.join(",\r\n"))
            })
            .collect::<Vec<_>>()
            .join(",\r\n");
        css_blocks.insert(format!("CSS_MATCH_{:09}", matcher_hash), css_strings);
        dom_string.push_str(&format!(
            "\r\n{}.with_inline_css_props(CSS_MATCH_{:09})",
            t2, matcher_hash
        ));
    }
    if !body_node.children.as_ref().is_empty() {
        use azul_css::format_rust_code::GetHash;
        let children_hash = body_node.children.as_ref().get_hash();
        dom_string.push_str(&format!("\r\n.with_children(DomVec::from_vec(vec![\r\n"));
        for (child_idx, child) in body_node.children.as_ref().iter().enumerate() {
            match child {
                XmlNodeChild::Element(child_node) => {
                    let mut matcher = matcher.clone();
                    matcher.path.push(CssPathSelector::Children);
                    matcher.indices_in_parent.push(child_idx);
                    matcher.children_length.push(body_node.children.len());
                    dom_string.push_str(&format!(
                        "{}{},\r\n",
                        t,
                        compile_node_to_rust_code_inner(
                            child_node,
                            component_map,
                            1,
                            extra_blocks,
                            css_blocks,
                            css,
                            matcher,
                        )?
                    ));
                }
                XmlNodeChild::Text(text) => {
                    let text = text.trim();
                    if !text.is_empty() {
                        let escaped = text.replace("\\", "\\\\").replace("\"", "\\\"");
                        dom_string
                            .push_str(&format!("{}Dom::create_text(\"{}\".into()),\r\n", t, escaped));
                    }
                }
            }
        }
        dom_string.push_str(&format!("\r\n{}]))", t));
    }
    let dom_string = dom_string.trim();
    Ok(dom_string.to_string())
}
fn get_css_blocks(css: &Css, matcher: &CssMatcher) -> Vec<CssBlock> {
    let mut blocks = Vec::new();
    for css_block in css.rules.as_ref() {
        if matcher.matches(&css_block.path) {
            let mut ending = None;
            if let Some(CssPathSelector::PseudoSelector(p)) =
                css_block.path.selectors.as_ref().last()
            {
                ending = Some(p.clone());
            }
            blocks.push(CssBlock {
                ending,
                block: css_block.clone(),
            });
        }
    }
    blocks
}
fn compile_and_format_dynamic_items(input: &[DynamicItem]) -> String {
    use self::DynamicItem::*;
    if input.is_empty() {
        String::from("AzString::from_const_str(\"\")")
    } else if input.len() == 1 {
        // common: there is only one "dynamic item" - skip the "format!()" macro
        match &input[0] {
            Var { name, format_spec } => {
                let var_name = normalize_casing(name.trim());
                if let Some(spec) = format_spec {
                    format!("format!(\"{{:{}}}\", {}).into()", spec, var_name)
                } else {
                    var_name
                }
            }
            Str(s) => format!("AzString::from_const_str(\"{}\")", s),
        }
    } else {
        // build a "format!("{var}, blah", var)" string
        let mut formatted_str = String::from("format!(\"");
        let mut variables = Vec::new();
        for item in input {
            match item {
                Var { name, format_spec } => {
                    let variable_name = normalize_casing(name.trim());
                    if let Some(spec) = format_spec {
                        formatted_str.push_str(&format!("{{{}:{}}}", variable_name, spec));
                    } else {
                        formatted_str.push_str(&format!("{{{}}}", variable_name));
                    }
                    variables.push(variable_name.clone());
                }
                Str(s) => {
                    let s = s.replace("\"", "\\\"");
                    formatted_str.push_str(&s);
                }
            }
        }
        formatted_str.push('\"');
        if !variables.is_empty() {
            formatted_str.push_str(", ");
        }
        formatted_str.push_str(&variables.join(", "));
        formatted_str.push_str(").into()");
        formatted_str
    }
}
fn format_args_for_rust_code(input: &str) -> String {
    let dynamic_str_items = split_dynamic_string(input);
    compile_and_format_dynamic_items(&dynamic_str_items)
}
fn compile_node_to_rust_code_inner<'a>(
    node: &XmlNode,
    component_map: &'a ComponentMap,
    tabs: usize,
    extra_blocks: &mut VecContents,
    css_blocks: &mut BTreeMap<String, String>,
    css: &Css,
    mut matcher: CssMatcher,
) -> Result<String, CompileError> {
    use azul_css::css::CssDeclaration;
    let t = String::from("    ").repeat(tabs - 1);
    let t2 = String::from("    ").repeat(tabs);
    let component_name = normalize_casing(&node.node_type);
    // Build the data model from XML attributes
    let def = component_map.get_unqualified(&component_name);
    // Look up the CSS NodeTypeTag
    let node_type_tag = tag_to_node_type_tag(&component_name);
    let node_type = CssPathSelector::Type(node_type_tag);
    // Generate DOM creation code using the component's compile_fn
    let text_content = node.get_text_content();
    let text_content_trimmed = text_content.trim();
    let mut dom_string = if let Some(d) = def {
        let data_model = xml_attrs_to_data_model(
            &d.data_model,
            &node.attributes,
            if text_content_trimmed.is_empty() { None } else { Some(text_content_trimmed) },
        );
        match (d.compile_fn)(d, &CompileTarget::Rust, &data_model, tabs) {
            ResultStringCompileError::Ok(s) => format!("{}{}", t2, s.as_str()),
            ResultStringCompileError::Err(_) => {
                // Fallback: generate basic DOM node
                let node_type = tag_to_node_type(&component_name);
                format!("{}Dom::create_node(NodeType::{:?})", t2, node_type)
            }
        }
    } else {
        // Unknown component, generate div fallback
        format!("{}Dom::create_node(NodeType::Div) /* {} */", t2, component_name)
    };
    matcher.path.push(node_type);
    let ids = node
        .attributes
        .get_key("id")
        .map(|s| s.split_whitespace().collect::<Vec<_>>())
        .unwrap_or_default();
    matcher.path.extend(
        ids.into_iter()
            .map(|id| CssPathSelector::Id(id.to_string().into())),
    );
    let classes = node
        .attributes
        .get_key("class")
        .map(|s| s.split_whitespace().collect::<Vec<_>>())
        .unwrap_or_default();
    matcher.path.extend(
        classes
            .into_iter()
            .map(|class| CssPathSelector::Class(class.to_string().into())),
    );
    let matcher_hash = matcher.get_hash();
    let css_blocks_for_this_node = get_css_blocks(css, &matcher);
    if !css_blocks_for_this_node.is_empty() {
        use azul_css::props::property::format_static_css_prop;
        let css_strings = css_blocks_for_this_node
            .iter()
            .rev()
            .map(|css_block| {
                let wrapper = match css_block.ending {
                    Some(CssPathPseudoSelector::Hover) => "Hover",
                    Some(CssPathPseudoSelector::Active) => "Active",
                    Some(CssPathPseudoSelector::Focus) => "Focus",
                    _ => "Normal",
                };
                for declaration in css_block.block.declarations.as_ref().iter() {
                    let prop = match declaration {
                        CssDeclaration::Static(s) => s,
                        CssDeclaration::Dynamic(d) => &d.default_value,
                    };
                    extra_blocks.insert_from_css_property(prop);
                }
                let formatted = css_block
                    .block
                    .declarations
                    .as_ref()
                    .iter()
                    .rev()
                    .map(|s| match &s {
                        CssDeclaration::Static(s) => format!(
                            "NodeDataInlineCssProperty::{}({})",
                            wrapper,
                            format_static_css_prop(s, 1)
                        ),
                        CssDeclaration::Dynamic(d) => format!(
                            "NodeDataInlineCssProperty::{}({})",
                            wrapper,
                            format_static_css_prop(&d.default_value, 1)
                        ),
                    })
                    .collect::<Vec<String>>();
                format!("// {}\r\n{}", css_block.block.path, formatted.join(",\r\n"))
            })
            .collect::<Vec<_>>()
            .join(",\r\n");
        css_blocks.insert(format!("CSS_MATCH_{:09}", matcher_hash), css_strings);
        dom_string.push_str(&format!(
            "\r\n{}.with_inline_css_props(CSS_MATCH_{:09})",
            t2, matcher_hash
        ));
    }
    set_stringified_attributes(
        &mut dom_string,
        &node.attributes,
        &ComponentArgumentVec::new(),
        tabs,
    );
    let mut children_string = node
        .children
        .as_ref()
        .iter()
        .enumerate()
        .filter_map(|(child_idx, c)| match c {
            XmlNodeChild::Element(child_node) => {
                let mut matcher = matcher.clone();
                matcher.path.push(CssPathSelector::Children);
                matcher.indices_in_parent.push(child_idx);
                matcher.children_length.push(node.children.len());
                Some(compile_node_to_rust_code_inner(
                    child_node,
                    component_map,
                    tabs + 1,
                    extra_blocks,
                    css_blocks,
                    css,
                    matcher,
                ))
            }
            XmlNodeChild::Text(text) => {
                let text = text.trim();
                if text.is_empty() {
                    None
                } else {
                    let t2 = String::from("    ").repeat(tabs);
                    let escaped = text.replace("\\", "\\\\").replace("\"", "\\\"");
                    Some(Ok(format!("{}Dom::create_text(\"{}\".into())", t2, escaped)))
                }
            }
        })
        .collect::<Result<Vec<_>, _>>()?
        .join(&format!(",\r\n"));
    if !children_string.is_empty() {
        dom_string.push_str(&format!(
            "\r\n{}.with_children(DomVec::from_vec(vec![\r\n{}\r\n{}]))",
            t2, children_string, t2
        ));
    }
    Ok(dom_string)
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::dom::{Dom, NodeType};
    #[test]
1
    fn test_inline_span_parsing() {
        // This test verifies that HTML with inline spans is parsed correctly
        // The DOM structure should preserve text nodes before, inside, and after the span
1
        let html = r#"<p>Text before <span class="highlight">inline text</span> text after.</p>"#;
        // Expected DOM structure:
        // <p>
        //   ├─ TextNode: "Text before "
        //   ├─ <span class="highlight">
        //   │   └─ TextNode: "inline text"
        //   └─ TextNode: " text after."
        // For this test, we'll create the DOM structure manually
        // since we're testing the parsing logic
1
        let expected_dom = Dom::create_p().with_children(
1
            vec![
1
                Dom::create_text("Text before "),
1
                Dom::create_node(NodeType::Span)
1
                    .with_children(vec![Dom::create_text("inline text")].into()),
1
                Dom::create_text(" text after."),
            ]
1
            .into(),
        );
        // Verify the structure has 3 children at the top level
1
        assert_eq!(expected_dom.children.as_ref().len(), 3);
        // Verify the middle child is a span
1
        match &expected_dom.children.as_ref()[1].root.node_type {
1
            NodeType::Span => {}
            other => panic!("Expected Span, got {:?}", other),
        }
        // Verify the span has 1 child (the text node)
1
        assert_eq!(expected_dom.children.as_ref()[1].children.as_ref().len(), 1);
1
        println!("Test passed: Inline span parsing structure is correct");
1
    }
    #[test]
1
    fn test_xml_node_structure() {
        // Test the basic XmlNode structure to ensure text content is preserved
        // Updated to use XmlNodeChild enum (Text/Element)
1
        let node = XmlNode {
1
            node_type: "p".into(),
1
            attributes: XmlAttributeMap {
1
                inner: StringPairVec::from_const_slice(&[]),
1
            },
1
            children: vec![
1
                XmlNodeChild::Text("Before ".into()),
1
                XmlNodeChild::Element(XmlNode {
1
                    node_type: "span".into(),
1
                    children: vec![XmlNodeChild::Text("inline".into())].into(),
1
                    ..Default::default()
1
                }),
1
                XmlNodeChild::Text(" after".into()),
1
            ]
1
            .into(),
1
        };
        // Verify structure
1
        assert_eq!(node.children.as_ref().len(), 3);
1
        assert_eq!(node.children.as_ref()[0].as_text(), Some("Before "));
1
        assert_eq!(
1
            node.children.as_ref()[1]
1
                .as_element()
1
                .unwrap()
1
                .node_type
1
                .as_str(),
            "span"
        );
1
        assert_eq!(node.children.as_ref()[2].as_text(), Some(" after"));
        // Verify span's child
1
        let span = node.children.as_ref()[1].as_element().unwrap();
1
        assert_eq!(span.children.as_ref().len(), 1);
1
        assert_eq!(span.children.as_ref()[0].as_text(), Some("inline"));
1
        println!("Test passed: XmlNode structure preserves text nodes correctly");
1
    }
    #[test]
1
    fn test_img_tag_becomes_image_node_with_src_tag() {
        // `<img src="cat.jpg" width="300" height="169">` must become a
        // `NodeType::Image` whose `NullImage` carries the `src` string as its
        // `tag` (so a renderer can resolve the bytes later), plus the declared
        // intrinsic size.
        use crate::resources::DecodedImage;
        use crate::window::{AzStringPair, StringPairVec};
1
        let img_node = XmlNode {
1
            node_type: "img".into(),
1
            attributes: XmlAttributeMap::from(StringPairVec::from_vec(alloc::vec![
1
                AzStringPair { key: "src".into(), value: "cat.jpg".into() },
1
                AzStringPair { key: "width".into(), value: "300".into() },
1
                AzStringPair { key: "height".into(), value: "169".into() },
1
            ])),
1
            children: alloc::vec::Vec::new().into(),
1
        };
1
        let component_map = ComponentMap::default();
1
        let dom = super::xml_node_to_dom_fast(&img_node, &component_map, false)
1
            .expect("xml_node_to_dom_fast for <img> should succeed");
1
        match dom.root.get_node_type() {
1
            NodeType::Image(image_ref) => match image_ref.as_ref().get_data() {
1
                DecodedImage::NullImage { tag, width, height, .. } => {
1
                    assert_eq!(
1
                        core::str::from_utf8(tag).unwrap(),
                        "cat.jpg",
                        "image tag must carry the src string"
                    );
1
                    assert_eq!(*width, 300, "width attribute should set intrinsic width");
1
                    assert_eq!(*height, 169, "height attribute should set intrinsic height");
                }
                other => panic!("expected NullImage carrying the src tag, got {:?}", other),
            },
            other => panic!("expected NodeType::Image for <img>, got {:?}", other),
        }
1
        println!("Test passed: <img src=\"cat.jpg\"> -> NodeType::Image tagged \"cat.jpg\"");
1
    }
    #[test]
1
    fn test_tag_to_node_type_img_is_image() {
        // The bare tag mapping should also yield an Image (placeholder, empty tag).
1
        match tag_to_node_type("img") {
1
            NodeType::Image(_) => {}
            other => panic!("tag_to_node_type(\"img\") should be Image, got {:?}", other),
        }
1
    }
}