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
    codegen::format::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
44748
    fn from(s: String) -> Self {
74
44748
        Self { inner: s.into() }
75
44748
    }
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
157745
    fn deref(&self) -> &Self::Target {
87
157745
        &self.inner
88
157745
    }
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
44749
    fn from(v: StringPairVec) -> Self {
103
44749
        Self { inner: v }
104
44749
    }
105
}
106

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

            
114
impl core::ops::DerefMut for XmlAttributeMap {
115
59664
    fn deref_mut(&mut self) -> &mut Self::Target {
116
59664
        &mut self.inner
117
59664
    }
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!(
136
    ComponentArgument,
137
    ComponentArgumentVec,
138
    ComponentArgumentVecDestructor,
139
    ComponentArgumentVecDestructorType,
140
    ComponentArgumentVecSlice,
141
    OptionComponentArgument
142
);
143
impl_option!(
144
    ComponentArgument,
145
    OptionComponentArgument,
146
    copy = false,
147
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
148
);
149
impl_vec_debug!(ComponentArgument, ComponentArgumentVec);
150
impl_vec_partialeq!(ComponentArgument, ComponentArgumentVec);
151
impl_vec_eq!(ComponentArgument, ComponentArgumentVec);
152
impl_vec_partialord!(ComponentArgument, ComponentArgumentVec);
153
impl_vec_ord!(ComponentArgument, ComponentArgumentVec);
154
impl_vec_hash!(ComponentArgument, ComponentArgumentVec);
155
impl_vec_clone!(
156
    ComponentArgument,
157
    ComponentArgumentVec,
158
    ComponentArgumentVecDestructor
159
);
160
impl_vec_mut!(ComponentArgument, ComponentArgumentVec);
161

            
162
/// Holds the list of arguments and whether the component accepts text content.
163
/// Used by the compile pipeline to generate Rust function signatures.
164
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
165
pub struct ComponentArguments {
166
    pub args: ComponentArgumentVec,
167
    pub accepts_text: bool,
168
}
169

            
170
/// Name of an XML/HTML component (e.g. `"button"`, `"my-widget"`).
171
type ComponentName = String;
172
/// Compiled source code string for a component.
173
type CompiledComponent = String;
174

            
175
/// Universal HTML attribute names that are handled by the framework
176
/// and should not be passed through to component-specific argument lists.
177
const DEFAULT_ARGS: [&str; 8] = [
178
    "id",
179
    "class",
180
    "tabindex",
181
    "focusable",
182
    "accepts_text",
183
    "name",
184
    "style",
185
    "args",
186
];
187

            
188
/// Opaque void type for FFI pointers. Uses a custom definition instead of
189
/// `core::ffi::c_void` for `#[repr(C)]` compatibility in the generated API.
190
#[allow(non_camel_case_types)]
191
pub enum c_void {}
192

            
193
/// Type of an XML node in the parsed tree.
194
#[repr(C)]
195
pub enum XmlNodeType {
196
    Root,
197
    Element,
198
    PI,
199
    Comment,
200
    Text,
201
}
202

            
203
/// A namespace-qualified XML name (e.g. `svg:rect` has namespace `"svg"` and local name `"rect"`).
204
#[repr(C)]
205
pub struct XmlQualifiedName {
206
    pub local_name: AzString,
207
    pub namespace: OptionString,
208
}
209

            
210
/// Classification of an external resource referenced in HTML/XML
211
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
212
#[repr(C)]
213
pub enum ExternalResourceKind {
214
    /// Image resource (img src, background-image, etc.)
215
    Image,
216
    /// Font resource (@font-face src, link rel="preload" as="font")
217
    Font,
218
    /// Stylesheet (link rel="stylesheet", @import)
219
    Stylesheet,
220
    /// Script (script src)
221
    Script,
222
    /// Favicon or icon
223
    Icon,
224
    /// Video source
225
    Video,
226
    /// Audio source
227
    Audio,
228
    /// Generic link or unknown resource type
229
    Unknown,
230
}
231

            
232
/// MIME type hint for an external resource
233
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
234
#[repr(C)]
235
pub struct MimeTypeHint {
236
    pub inner: AzString,
237
}
238

            
239
impl MimeTypeHint {
240
    pub fn new(s: &str) -> Self {
241
        Self {
242
            inner: AzString::from(s),
243
        }
244
    }
245

            
246
    pub fn from_extension(ext: &str) -> Self {
247
        let mime = match ext.to_lowercase().as_str() {
248
            // Images
249
            "png" => "image/png",
250
            "jpg" | "jpeg" => "image/jpeg",
251
            "gif" => "image/gif",
252
            "webp" => "image/webp",
253
            "svg" => "image/svg+xml",
254
            "ico" => "image/x-icon",
255
            "bmp" => "image/bmp",
256
            "avif" => "image/avif",
257
            // Fonts
258
            "ttf" => "font/ttf",
259
            "otf" => "font/otf",
260
            "woff" => "font/woff",
261
            "woff2" => "font/woff2",
262
            "eot" => "application/vnd.ms-fontobject",
263
            // Stylesheets
264
            "css" => "text/css",
265
            // Scripts
266
            "js" => "application/javascript",
267
            "mjs" => "application/javascript",
268
            // Video
269
            "mp4" => "video/mp4",
270
            "webm" => "video/webm",
271
            "ogg" => "video/ogg",
272
            // Audio
273
            "mp3" => "audio/mpeg",
274
            "wav" => "audio/wav",
275
            "flac" => "audio/flac",
276
            // Default
277
            _ => "application/octet-stream",
278
        };
279
        Self {
280
            inner: AzString::from(mime),
281
        }
282
    }
283
}
284

            
285
impl_option!(
286
    MimeTypeHint,
287
    OptionMimeTypeHint,
288
    copy = false,
289
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
290
);
291

            
292
/// An external resource URL found in an XML/HTML document
293
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
294
#[repr(C)]
295
pub struct ExternalResource {
296
    /// The URL as found in the document (may be relative or absolute)
297
    pub url: AzString,
298
    /// Classification of the resource type
299
    pub kind: ExternalResourceKind,
300
    /// MIME type hint (from type attribute, file extension, or heuristics)
301
    pub mime_type: OptionMimeTypeHint,
302
    /// The HTML element that referenced this resource (e.g., "img", "link", "script")
303
    pub source_element: AzString,
304
    /// The attribute that contained the URL (e.g., "src", "href")
305
    pub source_attribute: AzString,
306
}
307

            
308
impl_option!(
309
    ExternalResource,
310
    OptionExternalResource,
311
    copy = false,
312
    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
313
);
314

            
315
impl_vec!(
316
    ExternalResource,
317
    ExternalResourceVec,
318
    ExternalResourceVecDestructor,
319
    ExternalResourceVecDestructorType,
320
    ExternalResourceVecSlice,
321
    OptionExternalResource
322
);
323
impl_vec_mut!(ExternalResource, ExternalResourceVec);
324
impl_vec_debug!(ExternalResource, ExternalResourceVec);
325
impl_vec_partialeq!(ExternalResource, ExternalResourceVec);
326
impl_vec_eq!(ExternalResource, ExternalResourceVec);
327
impl_vec_partialord!(ExternalResource, ExternalResourceVec);
328
impl_vec_ord!(ExternalResource, ExternalResourceVec);
329
impl_vec_hash!(ExternalResource, ExternalResourceVec);
330
impl_vec_clone!(
331
    ExternalResource,
332
    ExternalResourceVec,
333
    ExternalResourceVecDestructor
334
);
335

            
336
#[derive(Debug, PartialEq, PartialOrd, Clone)]
337
#[repr(C)]
338
pub struct Xml {
339
    pub root: XmlNodeChildVec,
340
}
341

            
342
impl Xml {
343
    /// Scan the XML/HTML document for external resource URLs.
344
    ///
345
    /// This function traverses the entire document tree and extracts URLs from:
346
    /// - `<img src="...">` - Images
347
    /// - `<link href="...">` - Stylesheets, icons, fonts
348
    /// - `<script src="...">` - Scripts
349
    /// - `<video src="...">`, `<source src="...">` - Video
350
    /// - `<audio src="...">` - Audio
351
    /// - `<a href="...">` - Links (classified as Unknown)
352
    /// - CSS `url()` in style attributes
353
    /// - `<style>` blocks with @import or url()
354
    pub fn scan_external_resources(&self) -> ExternalResourceVec {
355
        let mut resources = Vec::new();
356

            
357
        for child in self.root.as_ref().iter() {
358
            Self::scan_node_child(child, &mut resources);
359
        }
360

            
361
        resources.into()
362
    }
363

            
364
    fn scan_node_child(child: &XmlNodeChild, resources: &mut Vec<ExternalResource>) {
365
        match child {
366
            XmlNodeChild::Text(text) => {
367
                // Check for CSS @import or url() in text content (inside <style> tags)
368
                Self::extract_css_urls(text.as_str(), resources);
369
            }
370
            XmlNodeChild::Element(node) => {
371
                Self::scan_node(node, resources);
372
            }
373
        }
374
    }
375

            
376
    fn scan_node(node: &XmlNode, resources: &mut Vec<ExternalResource>) {
377
        let tag_name = node.node_type.inner.as_str().to_lowercase();
378

            
379
        // Get attribute lookup helper
380
        let get_attr = |name: &str| -> Option<String> {
381
            node.attributes
382
                .inner
383
                .as_ref()
384
                .iter()
385
                .find(|pair| pair.key.as_str().eq_ignore_ascii_case(name))
386
                .map(|pair| pair.value.as_str().to_string())
387
        };
388

            
389
        match tag_name.as_str() {
390
            "img" => {
391
                if let Some(src) = get_attr("src") {
392
                    let mime = Self::guess_mime_from_url(&src, "image");
393
                    resources.push(ExternalResource {
394
                        url: AzString::from(src),
395
                        kind: ExternalResourceKind::Image,
396
                        mime_type: mime.into(),
397
                        source_element: AzString::from("img"),
398
                        source_attribute: AzString::from("src"),
399
                    });
400
                }
401
                // Also check srcset
402
                if let Some(srcset) = get_attr("srcset") {
403
                    for src in Self::parse_srcset(&srcset) {
404
                        let mime = Self::guess_mime_from_url(&src, "image");
405
                        resources.push(ExternalResource {
406
                            url: AzString::from(src),
407
                            kind: ExternalResourceKind::Image,
408
                            mime_type: mime.into(),
409
                            source_element: AzString::from("img"),
410
                            source_attribute: AzString::from("srcset"),
411
                        });
412
                    }
413
                }
414
            }
415
            "link" => {
416
                if let Some(href) = get_attr("href") {
417
                    let rel = get_attr("rel").unwrap_or_default().to_lowercase();
418
                    let type_attr = get_attr("type");
419
                    let as_attr = get_attr("as").unwrap_or_default().to_lowercase();
420

            
421
                    let (kind, category) = if rel.contains("stylesheet") {
422
                        (ExternalResourceKind::Stylesheet, "stylesheet")
423
                    } else if rel.contains("icon") || rel.contains("apple-touch-icon") {
424
                        (ExternalResourceKind::Icon, "image")
425
                    } else if as_attr == "font" {
426
                        (ExternalResourceKind::Font, "font")
427
                    } else if as_attr == "script" {
428
                        (ExternalResourceKind::Script, "script")
429
                    } else if as_attr == "image" {
430
                        (ExternalResourceKind::Image, "image")
431
                    } else {
432
                        (ExternalResourceKind::Unknown, "")
433
                    };
434

            
435
                    let mime = type_attr
436
                        .map(|t| MimeTypeHint::new(&t))
437
                        .or_else(|| Self::guess_mime_from_url(&href, category));
438

            
439
                    resources.push(ExternalResource {
440
                        url: AzString::from(href),
441
                        kind,
442
                        mime_type: mime.into(),
443
                        source_element: AzString::from("link"),
444
                        source_attribute: AzString::from("href"),
445
                    });
446
                }
447
            }
448
            "script" => {
449
                if let Some(src) = get_attr("src") {
450
                    let type_attr = get_attr("type");
451
                    let mime = type_attr
452
                        .map(|t| MimeTypeHint::new(&t))
453
                        .or_else(|| Some(MimeTypeHint::new("application/javascript")));
454

            
455
                    resources.push(ExternalResource {
456
                        url: AzString::from(src),
457
                        kind: ExternalResourceKind::Script,
458
                        mime_type: mime.into(),
459
                        source_element: AzString::from("script"),
460
                        source_attribute: AzString::from("src"),
461
                    });
462
                }
463
            }
464
            "video" => {
465
                if let Some(src) = get_attr("src") {
466
                    let mime = Self::guess_mime_from_url(&src, "video");
467
                    resources.push(ExternalResource {
468
                        url: AzString::from(src),
469
                        kind: ExternalResourceKind::Video,
470
                        mime_type: mime.into(),
471
                        source_element: AzString::from("video"),
472
                        source_attribute: AzString::from("src"),
473
                    });
474
                }
475
                if let Some(poster) = get_attr("poster") {
476
                    let mime = Self::guess_mime_from_url(&poster, "image");
477
                    resources.push(ExternalResource {
478
                        url: AzString::from(poster),
479
                        kind: ExternalResourceKind::Image,
480
                        mime_type: mime.into(),
481
                        source_element: AzString::from("video"),
482
                        source_attribute: AzString::from("poster"),
483
                    });
484
                }
485
            }
486
            "audio" => {
487
                if let Some(src) = get_attr("src") {
488
                    let mime = Self::guess_mime_from_url(&src, "audio");
489
                    resources.push(ExternalResource {
490
                        url: AzString::from(src),
491
                        kind: ExternalResourceKind::Audio,
492
                        mime_type: mime.into(),
493
                        source_element: AzString::from("audio"),
494
                        source_attribute: AzString::from("src"),
495
                    });
496
                }
497
            }
498
            "source" => {
499
                if let Some(src) = get_attr("src") {
500
                    let type_attr = get_attr("type");
501
                    // Determine kind based on type or parent (heuristic: assume video)
502
                    let kind = if type_attr
503
                        .as_ref()
504
                        .map(|t| t.starts_with("audio"))
505
                        .unwrap_or(false)
506
                    {
507
                        ExternalResourceKind::Audio
508
                    } else {
509
                        ExternalResourceKind::Video
510
                    };
511
                    let mime = type_attr.map(|t| MimeTypeHint::new(&t)).or_else(|| {
512
                        Self::guess_mime_from_url(
513
                            &src,
514
                            if kind == ExternalResourceKind::Audio {
515
                                "audio"
516
                            } else {
517
                                "video"
518
                            },
519
                        )
520
                    });
521

            
522
                    resources.push(ExternalResource {
523
                        url: AzString::from(src),
524
                        kind,
525
                        mime_type: mime.into(),
526
                        source_element: AzString::from("source"),
527
                        source_attribute: AzString::from("src"),
528
                    });
529
                }
530
                // Also handle srcset for picture elements
531
                if let Some(srcset) = get_attr("srcset") {
532
                    for src in Self::parse_srcset(&srcset) {
533
                        let mime = Self::guess_mime_from_url(&src, "image");
534
                        resources.push(ExternalResource {
535
                            url: AzString::from(src),
536
                            kind: ExternalResourceKind::Image,
537
                            mime_type: mime.into(),
538
                            source_element: AzString::from("source"),
539
                            source_attribute: AzString::from("srcset"),
540
                        });
541
                    }
542
                }
543
            }
544
            "a" => {
545
                if let Some(href) = get_attr("href") {
546
                    // Only include if it looks like a resource, not a page link
547
                    if Self::looks_like_resource(&href) {
548
                        let mime = Self::guess_mime_from_url(&href, "");
549
                        resources.push(ExternalResource {
550
                            url: AzString::from(href),
551
                            kind: ExternalResourceKind::Unknown,
552
                            mime_type: mime.into(),
553
                            source_element: AzString::from("a"),
554
                            source_attribute: AzString::from("href"),
555
                        });
556
                    }
557
                }
558
            }
559
            "virtualized-view" | "embed" | "object" => {
560
                let src_attr = if tag_name == "object" { "data" } else { "src" };
561
                if let Some(src) = get_attr(src_attr) {
562
                    resources.push(ExternalResource {
563
                        url: AzString::from(src),
564
                        kind: ExternalResourceKind::Unknown,
565
                        mime_type: OptionMimeTypeHint::None,
566
                        source_element: AzString::from(tag_name.clone()),
567
                        source_attribute: AzString::from(src_attr),
568
                    });
569
                }
570
            }
571
            "style" => {
572
                // Scan text content for CSS URLs
573
                for child in node.children.as_ref().iter() {
574
                    if let XmlNodeChild::Text(text) = child {
575
                        Self::extract_css_urls(text.as_str(), resources);
576
                    }
577
                }
578
            }
579
            _ => {}
580
        }
581

            
582
        // Check inline style attribute for url()
583
        if let Some(style) = get_attr("style") {
584
            Self::extract_css_urls(&style, resources);
585
        }
586

            
587
        // Check for background attribute (deprecated but still used)
588
        if let Some(bg) = get_attr("background") {
589
            let mime = Self::guess_mime_from_url(&bg, "image");
590
            resources.push(ExternalResource {
591
                url: AzString::from(bg),
592
                kind: ExternalResourceKind::Image,
593
                mime_type: mime.into(),
594
                source_element: AzString::from(tag_name),
595
                source_attribute: AzString::from("background"),
596
            });
597
        }
598

            
599
        // Recurse into children
600
        for child in node.children.as_ref().iter() {
601
            Self::scan_node_child(child, resources);
602
        }
603
    }
604

            
605
    /// Extract URLs from CSS content (handles url() and @import)
606
    fn extract_css_urls(css: &str, resources: &mut Vec<ExternalResource>) {
607
        // Simple regex-like parsing for url(...) and @import
608
        let mut remaining = css;
609

            
610
        while let Some(pos) = remaining.find("url(") {
611
            let after_url = &remaining[pos + 4..];
612
            if let Some(url) = Self::extract_url_value(after_url) {
613
                let mime = Self::guess_mime_from_url(&url, "");
614
                let kind = Self::guess_kind_from_url(&url);
615
                resources.push(ExternalResource {
616
                    url: AzString::from(url),
617
                    kind,
618
                    mime_type: mime.into(),
619
                    source_element: AzString::from("style"),
620
                    source_attribute: AzString::from("url()"),
621
                });
622
            }
623
            remaining = after_url;
624
        }
625

            
626
        // Handle @import "url" or @import url(...)
627
        remaining = css;
628
        while let Some(pos) = remaining.to_lowercase().find("@import") {
629
            let after_import = &remaining[pos + 7..];
630
            let trimmed = after_import.trim_start();
631

            
632
            if trimmed.starts_with("url(") {
633
                if let Some(url) = Self::extract_url_value(&trimmed[4..]) {
634
                    resources.push(ExternalResource {
635
                        url: AzString::from(url),
636
                        kind: ExternalResourceKind::Stylesheet,
637
                        mime_type: Some(MimeTypeHint::new("text/css")).into(),
638
                        source_element: AzString::from("style"),
639
                        source_attribute: AzString::from("@import"),
640
                    });
641
                }
642
            } else if let Some(url) = Self::extract_quoted_string(trimmed) {
643
                resources.push(ExternalResource {
644
                    url: AzString::from(url),
645
                    kind: ExternalResourceKind::Stylesheet,
646
                    mime_type: Some(MimeTypeHint::new("text/css")).into(),
647
                    source_element: AzString::from("style"),
648
                    source_attribute: AzString::from("@import"),
649
                });
650
            }
651

            
652
            remaining = after_import;
653
        }
654
    }
655

            
656
    /// Extract value from url(...) - handles quoted and unquoted URLs
657
    fn extract_url_value(s: &str) -> Option<String> {
658
        let trimmed = s.trim_start();
659
        if trimmed.starts_with('"') {
660
            Self::extract_quoted_string(trimmed)
661
        } else if trimmed.starts_with('\'') {
662
            let end = trimmed[1..].find('\'')?;
663
            Some(trimmed[1..1 + end].to_string())
664
        } else {
665
            let end = trimmed.find(')')?;
666
            Some(trimmed[..end].trim().to_string())
667
        }
668
    }
669

            
670
    /// Extract a quoted string value
671
    fn extract_quoted_string(s: &str) -> Option<String> {
672
        if s.starts_with('"') {
673
            let end = s[1..].find('"')?;
674
            Some(s[1..1 + end].to_string())
675
        } else if s.starts_with('\'') {
676
            let end = s[1..].find('\'')?;
677
            Some(s[1..1 + end].to_string())
678
        } else {
679
            None
680
        }
681
    }
682

            
683
    /// Parse srcset attribute into individual URLs
684
    fn parse_srcset(srcset: &str) -> Vec<String> {
685
        srcset
686
            .split(',')
687
            .filter_map(|entry| {
688
                let trimmed = entry.trim();
689
                // srcset format: "url 1x" or "url 100w"
690
                trimmed.split_whitespace().next().map(|s| s.to_string())
691
            })
692
            .filter(|url| !url.is_empty())
693
            .collect()
694
    }
695

            
696
    /// Check if a URL looks like a downloadable resource (not a page)
697
    fn looks_like_resource(url: &str) -> bool {
698
        let lower = url.to_lowercase();
699
        // Check for common resource extensions
700
        let resource_exts = [
701
            ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".bmp", ".ttf", ".otf",
702
            ".woff", ".woff2", ".eot", ".css", ".js", ".mp4", ".webm", ".ogg", ".mp3", ".wav",
703
            ".pdf", ".zip", ".tar", ".gz",
704
        ];
705
        resource_exts.iter().any(|ext| lower.ends_with(ext))
706
    }
707

            
708
    /// Guess the resource kind from URL based on file extension.
709
    fn guess_kind_from_url(url: &str) -> ExternalResourceKind {
710
        let lower = url.to_lowercase();
711
        // Strip query string before checking extension
712
        let path = lower.split('?').next().unwrap_or(&lower);
713
        if path.ends_with(".png")
714
            || path.ends_with(".jpg")
715
            || path.ends_with(".jpeg")
716
            || path.ends_with(".gif")
717
            || path.ends_with(".webp")
718
            || path.ends_with(".svg")
719
            || path.ends_with(".bmp")
720
            || path.ends_with(".avif")
721
        {
722
            ExternalResourceKind::Image
723
        } else if path.ends_with(".ttf")
724
            || path.ends_with(".otf")
725
            || path.ends_with(".woff")
726
            || path.ends_with(".woff2")
727
            || path.ends_with(".eot")
728
        {
729
            ExternalResourceKind::Font
730
        } else if path.ends_with(".css") {
731
            ExternalResourceKind::Stylesheet
732
        } else if path.ends_with(".js") || path.ends_with(".mjs") {
733
            ExternalResourceKind::Script
734
        } else if path.ends_with(".mp4") || path.ends_with(".webm") || path.ends_with(".ogg") {
735
            ExternalResourceKind::Video
736
        } else if path.ends_with(".mp3") || path.ends_with(".wav") || path.ends_with(".flac") {
737
            ExternalResourceKind::Audio
738
        } else if path.ends_with(".ico") {
739
            ExternalResourceKind::Icon
740
        } else {
741
            ExternalResourceKind::Unknown
742
        }
743
    }
744

            
745
    /// Guess MIME type from URL based on extension
746
    fn guess_mime_from_url(url: &str, category: &str) -> Option<MimeTypeHint> {
747
        let lower = url.to_lowercase();
748
        // Find extension
749
        let ext = lower.rsplit('.').next()?;
750
        // Remove query string if present
751
        let ext = ext.split('?').next()?;
752

            
753
        // Check if it's a valid extension
754
        let valid_exts = [
755
            "png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "bmp", "avif", "ttf", "otf", "woff",
756
            "woff2", "eot", "css", "js", "mjs", "mp4", "webm", "ogg", "mp3", "wav", "flac",
757
        ];
758

            
759
        if valid_exts.contains(&ext) {
760
            Some(MimeTypeHint::from_extension(ext))
761
        } else if !category.is_empty() {
762
            // Use category hint for default
763
            match category {
764
                "image" => Some(MimeTypeHint::new("image/*")),
765
                "font" => Some(MimeTypeHint::new("font/*")),
766
                "stylesheet" => Some(MimeTypeHint::new("text/css")),
767
                "script" => Some(MimeTypeHint::new("application/javascript")),
768
                "video" => Some(MimeTypeHint::new("video/*")),
769
                "audio" => Some(MimeTypeHint::new("audio/*")),
770
                _ => None,
771
            }
772
        } else {
773
            None
774
        }
775
    }
776
}
777

            
778
#[derive(Debug, PartialEq, PartialOrd, Clone)]
779
#[repr(C)]
780
pub struct NonXmlCharError {
781
    pub ch: u32, /* u32 = char, but ABI stable */
782
    pub pos: XmlTextPos,
783
}
784

            
785
#[derive(Debug, PartialEq, PartialOrd, Clone)]
786
#[repr(C)]
787
pub struct InvalidCharError {
788
    pub expected: u8,
789
    pub got: u8,
790
    pub pos: XmlTextPos,
791
}
792

            
793
#[derive(Debug, PartialEq, PartialOrd, Clone)]
794
#[repr(C)]
795
pub struct InvalidCharMultipleError {
796
    pub expected: u8,
797
    pub got: U8Vec,
798
    pub pos: XmlTextPos,
799
}
800

            
801
#[derive(Debug, PartialEq, PartialOrd, Clone)]
802
#[repr(C)]
803
pub struct InvalidQuoteError {
804
    pub got: u8,
805
    pub pos: XmlTextPos,
806
}
807

            
808
#[derive(Debug, PartialEq, PartialOrd, Clone)]
809
#[repr(C)]
810
pub struct InvalidSpaceError {
811
    pub got: u8,
812
    pub pos: XmlTextPos,
813
}
814

            
815
#[derive(Debug, PartialEq, PartialOrd, Clone)]
816
#[repr(C)]
817
pub struct InvalidStringError {
818
    pub got: AzString,
819
    pub pos: XmlTextPos,
820
}
821

            
822
#[derive(Debug, PartialEq, PartialOrd, Clone)]
823
#[repr(C, u8)]
824
pub enum XmlStreamError {
825
    UnexpectedEndOfStream,
826
    InvalidName,
827
    NonXmlChar(NonXmlCharError),
828
    InvalidChar(InvalidCharError),
829
    InvalidCharMultiple(InvalidCharMultipleError),
830
    InvalidQuote(InvalidQuoteError),
831
    InvalidSpace(InvalidSpaceError),
832
    InvalidString(InvalidStringError),
833
    InvalidReference,
834
    InvalidExternalID,
835
    InvalidCommentData,
836
    InvalidCommentEnd,
837
    InvalidCharacterData,
838
}
839

            
840
impl fmt::Display for XmlStreamError {
841
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
842
        use self::XmlStreamError::*;
843
        match self {
844
            UnexpectedEndOfStream => write!(f, "Unexpected end of stream"),
845
            InvalidName => write!(f, "Invalid name"),
846
            NonXmlChar(nx) => write!(
847
                f,
848
                "Non-XML character: {:?} at {}",
849
                core::char::from_u32(nx.ch),
850
                nx.pos
851
            ),
852
            InvalidChar(ic) => write!(
853
                f,
854
                "Invalid character: expected: {}, got: {} at {}",
855
                ic.expected as char, ic.got as char, ic.pos
856
            ),
857
            InvalidCharMultiple(imc) => write!(
858
                f,
859
                "Multiple invalid characters: expected: {}, got: {:?} at {}",
860
                imc.expected,
861
                imc.got.as_ref(),
862
                imc.pos
863
            ),
864
            InvalidQuote(iq) => write!(f, "Invalid quote: got {} at {}", iq.got as char, iq.pos),
865
            InvalidSpace(is) => write!(f, "Invalid space: got {} at {}", is.got as char, is.pos),
866
            InvalidString(ise) => write!(
867
                f,
868
                "Invalid string: got \"{}\" at {}",
869
                ise.got.as_str(),
870
                ise.pos
871
            ),
872
            InvalidReference => write!(f, "Invalid reference"),
873
            InvalidExternalID => write!(f, "Invalid external ID"),
874
            InvalidCommentData => write!(f, "Invalid comment data"),
875
            InvalidCommentEnd => write!(f, "Invalid comment end"),
876
            InvalidCharacterData => write!(f, "Invalid character data"),
877
        }
878
    }
879
}
880

            
881
#[derive(Debug, PartialEq, PartialOrd, Clone, Ord, Hash, Eq)]
882
#[repr(C)]
883
pub struct XmlTextPos {
884
    pub row: u32,
885
    pub col: u32,
886
}
887

            
888
impl fmt::Display for XmlTextPos {
889
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
890
        write!(f, "line {}:{}", self.row, self.col)
891
    }
892
}
893

            
894
#[derive(Debug, PartialEq, PartialOrd, Clone)]
895
#[repr(C)]
896
pub struct XmlTextError {
897
    pub stream_error: XmlStreamError,
898
    pub pos: XmlTextPos,
899
}
900

            
901
#[derive(Debug, PartialEq, PartialOrd, Clone)]
902
#[repr(C, u8)]
903
pub enum XmlParseError {
904
    InvalidDeclaration(XmlTextError),
905
    InvalidComment(XmlTextError),
906
    InvalidPI(XmlTextError),
907
    InvalidDoctype(XmlTextError),
908
    InvalidEntity(XmlTextError),
909
    InvalidElement(XmlTextError),
910
    InvalidAttribute(XmlTextError),
911
    InvalidCdata(XmlTextError),
912
    InvalidCharData(XmlTextError),
913
    UnknownToken(XmlTextPos),
914
}
915

            
916
impl fmt::Display for XmlParseError {
917
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
918
        use self::XmlParseError::*;
919
        match self {
920
            InvalidDeclaration(e) => {
921
                write!(f, "Invalid declaration: {} at {}", e.stream_error, e.pos)
922
            }
923
            InvalidComment(e) => write!(f, "Invalid comment: {} at {}", e.stream_error, e.pos),
924
            InvalidPI(e) => write!(
925
                f,
926
                "Invalid processing instruction: {} at {}",
927
                e.stream_error, e.pos
928
            ),
929
            InvalidDoctype(e) => write!(f, "Invalid doctype: {} at {}", e.stream_error, e.pos),
930
            InvalidEntity(e) => write!(f, "Invalid entity: {} at {}", e.stream_error, e.pos),
931
            InvalidElement(e) => write!(f, "Invalid element: {} at {}", e.stream_error, e.pos),
932
            InvalidAttribute(e) => write!(f, "Invalid attribute: {} at {}", e.stream_error, e.pos),
933
            InvalidCdata(e) => write!(f, "Invalid CDATA: {} at {}", e.stream_error, e.pos),
934
            InvalidCharData(e) => write!(f, "Invalid char data: {} at {}", e.stream_error, e.pos),
935
            UnknownToken(e) => write!(f, "Unknown token at {}", e),
936
        }
937
    }
938
}
939

            
940
impl_result!(
941
    Xml,
942
    XmlError,
943
    ResultXmlXmlError,
944
    copy = false,
945
    [Debug, PartialEq, PartialOrd, Clone]
946
);
947

            
948
#[derive(Debug, PartialEq, PartialOrd, Clone)]
949
#[repr(C)]
950
pub struct DuplicatedNamespaceError {
951
    pub ns: AzString,
952
    pub pos: XmlTextPos,
953
}
954

            
955
#[derive(Debug, PartialEq, PartialOrd, Clone)]
956
#[repr(C)]
957
pub struct UnknownNamespaceError {
958
    pub ns: AzString,
959
    pub pos: XmlTextPos,
960
}
961

            
962
#[derive(Debug, PartialEq, PartialOrd, Clone)]
963
#[repr(C)]
964
pub struct UnexpectedCloseTagError {
965
    pub expected: AzString,
966
    pub actual: AzString,
967
    pub pos: XmlTextPos,
968
}
969

            
970
#[derive(Debug, PartialEq, PartialOrd, Clone)]
971
#[repr(C)]
972
pub struct UnknownEntityReferenceError {
973
    pub entity: AzString,
974
    pub pos: XmlTextPos,
975
}
976

            
977
#[derive(Debug, PartialEq, PartialOrd, Clone)]
978
#[repr(C)]
979
pub struct DuplicatedAttributeError {
980
    pub attribute: AzString,
981
    pub pos: XmlTextPos,
982
}
983

            
984
/// Error for mismatched open/close tags in XML hierarchy
985
#[derive(Debug, PartialEq, PartialOrd, Clone)]
986
#[repr(C)]
987
pub struct MalformedHierarchyError {
988
    /// The tag that was expected (from the opening tag)
989
    pub expected: AzString,
990
    /// The tag that was actually found (the closing tag)
991
    pub got: AzString,
992
}
993

            
994
#[derive(Debug, PartialEq, PartialOrd, Clone)]
995
#[repr(C, u8)]
996
pub enum XmlError {
997
    NoParserAvailable,
998
    InvalidXmlPrefixUri(XmlTextPos),
999
    UnexpectedXmlUri(XmlTextPos),
    UnexpectedXmlnsUri(XmlTextPos),
    InvalidElementNamePrefix(XmlTextPos),
    DuplicatedNamespace(DuplicatedNamespaceError),
    UnknownNamespace(UnknownNamespaceError),
    UnexpectedCloseTag(UnexpectedCloseTagError),
    UnexpectedEntityCloseTag(XmlTextPos),
    UnknownEntityReference(UnknownEntityReferenceError),
    MalformedEntityReference(XmlTextPos),
    EntityReferenceLoop(XmlTextPos),
    InvalidAttributeValue(XmlTextPos),
    DuplicatedAttribute(DuplicatedAttributeError),
    NoRootNode,
    SizeLimit,
    DtdDetected,
    /// Invalid hierarchy close tags, i.e `<app></p></app>`
    MalformedHierarchy(MalformedHierarchyError),
    ParserError(XmlParseError),
    UnclosedRootNode,
    UnexpectedDeclaration(XmlTextPos),
    NodesLimitReached,
    AttributesLimitReached,
    NamespacesLimitReached,
    InvalidName(XmlTextPos),
    NonXmlChar(XmlTextPos),
    InvalidChar(XmlTextPos),
    InvalidChar2(XmlTextPos),
    InvalidString(XmlTextPos),
    InvalidExternalID(XmlTextPos),
    InvalidComment(XmlTextPos),
    InvalidCharacterData(XmlTextPos),
    UnknownToken(XmlTextPos),
    UnexpectedEndOfStream,
}
impl fmt::Display for XmlError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        use self::XmlError::*;
        match self {
            NoParserAvailable => write!(
                f,
                "Library was compiled without XML parser (XML parser not available)"
            ),
            InvalidXmlPrefixUri(pos) => {
                write!(f, "Invalid XML Prefix URI at line {}:{}", pos.row, pos.col)
            }
            UnexpectedXmlUri(pos) => {
                write!(f, "Unexpected XML URI at line {}:{}", pos.row, pos.col)
            }
            UnexpectedXmlnsUri(pos) => write!(
                f,
                "Unexpected XML namespace URI at line {}:{}",
                pos.row, pos.col
            ),
            InvalidElementNamePrefix(pos) => write!(
                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 {
445740
    pub fn builtin(name: &str) -> Self {
445740
        Self {
445740
            collection: AzString::from_const_str("builtin"),
445740
            name: AzString::from(name),
445740
        }
445740
    }
    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::ser::SerializeStruct;
    use serde::{Deserialize, Deserializer, Serialize, Serializer};
    // --- 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.
12752
pub fn tag_to_node_type(tag: &str) -> NodeType {
12752
    match tag {
        // Document structure
12752
        "html" => NodeType::Html,
12752
        "head" => NodeType::Head,
12752
        "title" => NodeType::Title,
12752
        "body" => NodeType::Body,
        // Block-level
8876
        "div" => NodeType::Div,
3419
        "header" => NodeType::Header,
3419
        "footer" => NodeType::Footer,
3419
        "section" => NodeType::Section,
3419
        "article" => NodeType::Article,
3419
        "aside" => NodeType::Aside,
3419
        "nav" => NodeType::Nav,
3419
        "main" => NodeType::Main,
3419
        "figure" => NodeType::Figure,
3419
        "figcaption" => NodeType::FigCaption,
3419
        "address" => NodeType::Address,
3419
        "details" => NodeType::Details,
3419
        "summary" => NodeType::Summary,
3419
        "dialog" => NodeType::Dialog,
        // Headings
3419
        "h1" => NodeType::H1,
3419
        "h2" => NodeType::H2,
3419
        "h3" => NodeType::H3,
3419
        "h4" => NodeType::H4,
3419
        "h5" => NodeType::H5,
3419
        "h6" => NodeType::H6,
        // Text content
3419
        "p" => NodeType::P,
2552
        "span" => NodeType::Span,
2399
        "pre" => NodeType::Pre,
2246
        "code" => NodeType::Code,
2246
        "blockquote" => NodeType::BlockQuote,
2246
        "br" => NodeType::Br,
2195
        "hr" => NodeType::Hr,
        // Lists
2195
        "ul" => NodeType::Ul,
2195
        "ol" => NodeType::Ol,
2195
        "li" => NodeType::Li,
2195
        "dl" => NodeType::Dl,
2195
        "dt" => NodeType::Dt,
2195
        "dd" => NodeType::Dd,
2195
        "menu" => NodeType::Menu,
2195
        "menuitem" => NodeType::MenuItem,
2195
        "dir" => NodeType::Dir,
        // Tables
2195
        "table" => NodeType::Table,
2195
        "caption" => NodeType::Caption,
2195
        "thead" => NodeType::THead,
2195
        "tbody" => NodeType::TBody,
2195
        "tfoot" => NodeType::TFoot,
2195
        "tr" => NodeType::Tr,
2195
        "th" => NodeType::Th,
2195
        "td" => NodeType::Td,
2195
        "colgroup" => NodeType::ColGroup,
2195
        "col" => NodeType::Col,
        // Forms
2195
        "form" => NodeType::Form,
2195
        "fieldset" => NodeType::FieldSet,
2195
        "legend" => NodeType::Legend,
2195
        "label" => NodeType::Label,
2195
        "input" => NodeType::Input,
2195
        "button" => NodeType::Button,
2195
        "select" => NodeType::Select,
2195
        "optgroup" => NodeType::OptGroup,
2195
        "option" => NodeType::SelectOption,
2195
        "textarea" => NodeType::TextArea,
2195
        "output" => NodeType::Output,
2195
        "progress" => NodeType::Progress,
2195
        "meter" => NodeType::Meter,
2195
        "datalist" => NodeType::DataList,
        // Inline
2195
        "a" => NodeType::A,
2195
        "strong" => NodeType::Strong,
2195
        "em" => NodeType::Em,
2195
        "b" => NodeType::B,
2195
        "i" => NodeType::I,
2195
        "u" => NodeType::U,
2195
        "s" => NodeType::S,
2195
        "small" => NodeType::Small,
2195
        "mark" => NodeType::Mark,
2195
        "del" => NodeType::Del,
2195
        "ins" => NodeType::Ins,
2195
        "samp" => NodeType::Samp,
2195
        "kbd" => NodeType::Kbd,
2195
        "var" => NodeType::Var,
2195
        "cite" => NodeType::Cite,
2195
        "dfn" => NodeType::Dfn,
2195
        "abbr" => NodeType::Abbr,
2195
        "acronym" => NodeType::Acronym,
2195
        "q" => NodeType::Q,
2195
        "time" => NodeType::Time,
2195
        "sub" => NodeType::Sub,
2195
        "sup" => NodeType::Sup,
2195
        "big" => NodeType::Big,
2195
        "bdo" => NodeType::Bdo,
2195
        "bdi" => NodeType::Bdi,
2195
        "wbr" => NodeType::Wbr,
2195
        "ruby" => NodeType::Ruby,
2195
        "rt" => NodeType::Rt,
2195
        "rtc" => NodeType::Rtc,
2195
        "rp" => NodeType::Rp,
2195
        "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.
2195
        "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
        )),
2193
        "canvas" => NodeType::Canvas,
2193
        "object" => NodeType::Object,
2193
        "param" => NodeType::Param,
2193
        "embed" => NodeType::Embed,
2193
        "audio" => NodeType::Audio,
2193
        "video" => NodeType::Video,
2193
        "source" => NodeType::Source,
2193
        "track" => NodeType::Track,
2193
        "map" => NodeType::Map,
2193
        "area" => NodeType::Area,
        // SVG elements
2193
        "svg" => NodeType::Svg,
1224
        "g" => NodeType::SvgG,
1122
        "defs" => NodeType::SvgDefs,
1122
        "symbol" => NodeType::SvgSymbol,
1122
        "use" => NodeType::SvgUse,
1122
        "switch" => NodeType::SvgSwitch,
1122
        "path" => NodeType::SvgPath,
867
        "circle" => NodeType::SvgCircle,
612
        "rect" => NodeType::SvgRect,
306
        "ellipse" => NodeType::SvgEllipse,
204
        "line" => NodeType::SvgLine,
153
        "polygon" => NodeType::SvgPolygon,
51
        "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,
    }
12752
}
/// 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.
434112
fn builtin_component_def(
434112
    tag: &str,
434112
    display_name: &str,
434112
    default_text: Option<&str>,
434112
    css: &str,
434112
) -> ComponentDef {
434112
    let mut fields = builtin_data_model(tag);
    // If a default_text is provided, this element accepts text content
434112
    if let Some(text) = default_text {
228684
        fields.push(data_field(
228684
            "text",
228684
            ComponentFieldType::String,
228684
            Some(ComponentDefaultValue::String(AzString::from(text))),
228684
            "Text content of the element",
228684
        ));
228684
    }
434112
    let model_name = format!("{}Data", display_name);
434112
    ComponentDef {
434112
        id: ComponentId::builtin(tag),
434112
        display_name: AzString::from(display_name),
434112
        description: AzString::from(format!("HTML <{}> element", tag).as_str()),
434112
        css: AzString::from(css),
434112
        source: ComponentSource::Builtin,
434112
        data_model: ComponentDataModel {
434112
            name: AzString::from(model_name.as_str()),
434112
            description: AzString::from(format!("Data model for <{}>", tag).as_str()),
434112
            fields: fields.into(),
434112
        },
434112
        render_fn: builtin_render_fn,
434112
        compile_fn: builtin_compile_fn,
434112
        render_fn_source: None.into(),
434112
        compile_fn_source: None.into(),
434112
    }
434112
}
/// Helper to create a ComponentDataField with a rich type
720936
fn data_field(
720936
    name: &str,
720936
    ft: ComponentFieldType,
720936
    default: Option<ComponentDefaultValue>,
720936
    description: &str,
720936
) -> ComponentDataField {
720936
    let required = default.is_none();
    ComponentDataField {
720936
        name: AzString::from(name),
720936
        field_type: ft,
720936
        default_value: match default {
697680
            Some(d) => OptionComponentDefaultValue::Some(d),
23256
            None => OptionComponentDefaultValue::None,
        },
720936
        required,
720936
        description: AzString::from(description),
    }
720936
}
/// 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.
434112
fn builtin_data_model(tag: &str) -> Vec<ComponentDataField> {
    use ComponentDefaultValue as D;
    use ComponentFieldType::*;
434112
    match tag {
434112
        "a" => alloc::vec![
3876
            data_field(
3876
                "href",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "URL the link points to"
            ),
3876
            data_field(
3876
                "target",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Where to open the linked document (_blank, _self, _parent, _top)"
            ),
3876
            data_field(
3876
                "rel",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Relationship between current and linked document"
            ),
        ],
430236
        "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"
            ),
        ],
430236
        "form" => alloc::vec![
3876
            data_field(
3876
                "action",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "URL where form data is submitted"
            ),
3876
            data_field(
3876
                "method",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("GET"))),
3876
                "HTTP method for form submission (GET or POST)"
            ),
        ],
426360
        "label" => alloc::vec![data_field(
3876
            "for",
3876
            String,
3876
            Some(D::String(AzString::from_const_str(""))),
3876
            "ID of the form element this label is for"
        ),],
422484
        "button" => alloc::vec![
3876
            data_field(
3876
                "type",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("button"))),
3876
                "Button type (button, submit, reset)"
            ),
3876
            data_field(
3876
                "disabled",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the button is disabled"
            ),
        ],
418608
        "td" | "th" => alloc::vec![
7752
            data_field(
7752
                "colspan",
7752
                I32,
7752
                Some(D::I32(1)),
7752
                "Number of columns the cell spans"
            ),
7752
            data_field(
7752
                "rowspan",
7752
                I32,
7752
                Some(D::I32(1)),
7752
                "Number of rows the cell spans"
            ),
        ],
410856
        "icon" => alloc::vec![data_field(
3876
            "name",
3876
            String,
3876
            Some(D::String(AzString::from_const_str(""))),
3876
            "Icon name"
        ),],
406980
        "ol" => alloc::vec![
3876
            data_field(
3876
                "start",
3876
                I32,
3876
                Some(D::I32(1)),
3876
                "Start value for the ordered list"
            ),
3876
            data_field(
3876
                "type",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("1"))),
3876
                "Numbering type (1, A, a, I, i)"
            ),
        ],
        // Form controls
403104
        "input" => alloc::vec![
3876
            data_field(
3876
                "type",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("text"))),
3876
                "Input type (text, password, email, number, checkbox, radio, etc.)"
            ),
3876
            data_field(
3876
                "name",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Name of the input for form submission"
            ),
3876
            data_field(
3876
                "value",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Current value of the input"
            ),
3876
            data_field(
3876
                "placeholder",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Placeholder text"
            ),
3876
            data_field(
3876
                "disabled",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the input is disabled"
            ),
3876
            data_field(
3876
                "required",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the input is required"
            ),
3876
            data_field(
3876
                "readonly",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the input is read-only"
            ),
3876
            data_field(
3876
                "checked",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the checkbox/radio is checked"
            ),
3876
            data_field(
3876
                "min",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Minimum value (for number, range, date)"
            ),
3876
            data_field(
3876
                "max",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Maximum value (for number, range, date)"
            ),
3876
            data_field(
3876
                "step",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Step increment (for number, range)"
            ),
3876
            data_field(
3876
                "pattern",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Regex pattern for validation"
            ),
3876
            data_field(
3876
                "maxlength",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Maximum number of characters"
            ),
        ],
399228
        "select" => alloc::vec![
3876
            data_field(
3876
                "name",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Name for form submission"
            ),
3876
            data_field(
3876
                "multiple",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether multiple options can be selected"
            ),
3876
            data_field(
3876
                "disabled",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the select is disabled"
            ),
3876
            data_field(
3876
                "required",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether selection is required"
            ),
3876
            data_field(
3876
                "size",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Number of visible options"
            ),
        ],
395352
        "option" => alloc::vec![
3876
            data_field(
3876
                "value",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Value submitted with the form"
            ),
3876
            data_field(
3876
                "selected",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether this option is selected"
            ),
3876
            data_field(
3876
                "disabled",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether this option is disabled"
            ),
        ],
391476
        "optgroup" => alloc::vec![
3876
            data_field(
3876
                "label",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Label for the option group"
            ),
3876
            data_field(
3876
                "disabled",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the group is disabled"
            ),
        ],
387600
        "textarea" => alloc::vec![
3876
            data_field(
3876
                "name",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Name for form submission"
            ),
3876
            data_field(
3876
                "placeholder",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Placeholder text"
            ),
3876
            data_field("rows", I32, Some(D::I32(2)), "Number of visible text lines"),
3876
            data_field(
3876
                "cols",
3876
                I32,
3876
                Some(D::I32(20)),
3876
                "Visible width in average character widths"
            ),
3876
            data_field(
3876
                "disabled",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the textarea is disabled"
            ),
3876
            data_field(
3876
                "required",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether content is required"
            ),
3876
            data_field(
3876
                "readonly",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether the textarea is read-only"
            ),
3876
            data_field(
3876
                "maxlength",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Maximum number of characters"
            ),
        ],
383724
        "fieldset" => alloc::vec![data_field(
3876
            "disabled",
3876
            Bool,
3876
            Some(D::Bool(false)),
3876
            "Whether all controls in the fieldset are disabled"
        ),],
379848
        "output" => alloc::vec![
3876
            data_field(
3876
                "for",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "IDs of elements that contributed to the output"
            ),
3876
            data_field(
3876
                "name",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Name for form submission"
            ),
        ],
375972
        "progress" => alloc::vec![
3876
            data_field(
3876
                "value",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Current progress value"
            ),
3876
            data_field(
3876
                "max",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("1"))),
3876
                "Maximum value"
            ),
        ],
372096
        "meter" => alloc::vec![
3876
            data_field(
3876
                "value",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Current value"
            ),
3876
            data_field(
3876
                "min",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("0"))),
3876
                "Minimum value"
            ),
3876
            data_field(
3876
                "max",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("1"))),
3876
                "Maximum value"
            ),
3876
            data_field(
3876
                "low",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Low threshold"
            ),
3876
            data_field(
3876
                "high",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "High threshold"
            ),
3876
            data_field(
3876
                "optimum",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Optimum value"
            ),
        ],
        // Interactive
368220
        "details" => alloc::vec![data_field(
3876
            "open",
3876
            Bool,
3876
            Some(D::Bool(false)),
3876
            "Whether the details are visible"
        ),],
364344
        "dialog" => alloc::vec![data_field(
3876
            "open",
3876
            Bool,
3876
            Some(D::Bool(false)),
3876
            "Whether the dialog is active and can be interacted with"
        ),],
        // Embedded content
360468
        "audio" | "video" => alloc::vec![
7752
            data_field(
7752
                "src",
7752
                String,
7752
                Some(D::String(AzString::from_const_str(""))),
7752
                "URL of the media resource"
            ),
7752
            data_field(
7752
                "controls",
7752
                Bool,
7752
                Some(D::Bool(false)),
7752
                "Whether to show playback controls"
            ),
7752
            data_field(
7752
                "autoplay",
7752
                Bool,
7752
                Some(D::Bool(false)),
7752
                "Whether to start playing automatically"
            ),
7752
            data_field(
7752
                "loop",
7752
                Bool,
7752
                Some(D::Bool(false)),
7752
                "Whether to loop playback"
            ),
7752
            data_field(
7752
                "muted",
7752
                Bool,
7752
                Some(D::Bool(false)),
7752
                "Whether audio is muted"
            ),
7752
            data_field(
7752
                "preload",
7752
                String,
7752
                Some(D::String(AzString::from_const_str("auto"))),
7752
                "Preload hint (none, metadata, auto)"
            ),
        ],
352716
        "source" => alloc::vec![
3876
            data_field("src", String, None, "URL of the media resource"),
3876
            data_field(
3876
                "type",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "MIME type of the resource"
            ),
        ],
348840
        "track" => alloc::vec![
3876
            data_field("src", String, None, "URL of the track file"),
3876
            data_field(
3876
                "kind",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("subtitles"))),
3876
                "Kind of text track (subtitles, captions, descriptions, chapters, metadata)"
            ),
3876
            data_field(
3876
                "srclang",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Language of the track text"
            ),
3876
            data_field(
3876
                "label",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "User-readable title for the track"
            ),
3876
            data_field(
3876
                "default",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Whether this is the default track"
            ),
        ],
344964
        "canvas" => alloc::vec![
3876
            data_field(
3876
                "width",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("300"))),
3876
                "Width of the canvas in pixels"
            ),
3876
            data_field(
3876
                "height",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("150"))),
3876
                "Height of the canvas in pixels"
            ),
        ],
341088
        "embed" => alloc::vec![
3876
            data_field("src", String, None, "URL of the resource to embed"),
3876
            data_field(
3876
                "type",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "MIME type of the embedded content"
            ),
3876
            data_field(
3876
                "width",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Width"
            ),
3876
            data_field(
3876
                "height",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Height"
            ),
        ],
337212
        "object" => alloc::vec![
3876
            data_field(
3876
                "data",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "URL of the resource"
            ),
3876
            data_field(
3876
                "type",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "MIME type of the resource"
            ),
3876
            data_field(
3876
                "width",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Width"
            ),
3876
            data_field(
3876
                "height",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Height"
            ),
        ],
333336
        "param" => alloc::vec![
3876
            data_field("name", String, None, "Name of the parameter"),
3876
            data_field(
3876
                "value",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Value of the parameter"
            ),
        ],
329460
        "area" => alloc::vec![
3876
            data_field(
3876
                "shape",
3876
                String,
3876
                Some(D::String(AzString::from_const_str("default"))),
3876
                "Shape of the area (default, rect, circle, poly)"
            ),
3876
            data_field(
3876
                "coords",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Coordinates of the area"
            ),
3876
            data_field(
3876
                "href",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "URL for the area link"
            ),
3876
            data_field(
3876
                "alt",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Alternative text"
            ),
3876
            data_field(
3876
                "target",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Where to open the linked document"
            ),
        ],
325584
        "map" => alloc::vec![data_field(
3876
            "name",
3876
            String,
3876
            None,
3876
            "Name of the image map (referenced by usemap)"
        ),],
        // Inline semantics with special attributes
321708
        "time" => alloc::vec![data_field(
3876
            "datetime",
3876
            String,
3876
            Some(D::String(AzString::from_const_str(""))),
3876
            "Machine-readable date/time value"
        ),],
317832
        "data" => alloc::vec![data_field(
3876
            "value",
3876
            String,
3876
            Some(D::String(AzString::from_const_str(""))),
3876
            "Machine-readable value"
        ),],
313956
        "abbr" | "acronym" | "dfn" => alloc::vec![data_field(
11628
            "title",
11628
            String,
11628
            Some(D::String(AzString::from_const_str(""))),
11628
            "Full expansion or definition"
        ),],
302328
        "q" | "blockquote" => alloc::vec![data_field(
7752
            "cite",
7752
            String,
7752
            Some(D::String(AzString::from_const_str(""))),
7752
            "URL of the source of the quotation"
        ),],
294576
        "del" | "ins" => alloc::vec![
7752
            data_field(
7752
                "cite",
7752
                String,
7752
                Some(D::String(AzString::from_const_str(""))),
7752
                "URL explaining the change"
            ),
7752
            data_field(
7752
                "datetime",
7752
                String,
7752
                Some(D::String(AzString::from_const_str(""))),
7752
                "Date/time of the change"
            ),
        ],
286824
        "bdo" => alloc::vec![data_field(
3876
            "dir",
3876
            String,
3876
            Some(D::String(AzString::from_const_str("ltr"))),
3876
            "Text direction (ltr, rtl)"
        ),],
282948
        "col" | "colgroup" => alloc::vec![data_field(
7752
            "span",
7752
            I32,
7752
            Some(D::I32(1)),
7752
            "Number of columns the element spans"
        ),],
        // Metadata
275196
        "meta" => alloc::vec![
3876
            data_field(
3876
                "name",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Metadata name"
            ),
3876
            data_field(
3876
                "content",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Metadata value"
            ),
3876
            data_field(
3876
                "charset",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Character encoding"
            ),
3876
            data_field(
3876
                "http-equiv",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "HTTP header equivalent"
            ),
        ],
271320
        "link" => alloc::vec![
3876
            data_field("rel", String, None, "Relationship type"),
3876
            data_field(
3876
                "href",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "URL of the linked resource"
            ),
3876
            data_field(
3876
                "type",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "MIME type of the linked resource"
            ),
        ],
267444
        "script" => alloc::vec![
3876
            data_field(
3876
                "src",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "URL of external script"
            ),
3876
            data_field(
3876
                "type",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "MIME type or module"
            ),
3876
            data_field(
3876
                "async",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Execute asynchronously"
            ),
3876
            data_field(
3876
                "defer",
3876
                Bool,
3876
                Some(D::Bool(false)),
3876
                "Defer execution until page load"
            ),
        ],
263568
        "style" => alloc::vec![data_field(
3876
            "type",
3876
            String,
3876
            Some(D::String(AzString::from_const_str("text/css"))),
3876
            "MIME type of the style sheet"
        ),],
259692
        "base" => alloc::vec![
3876
            data_field(
3876
                "href",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Base URL for relative URLs"
            ),
3876
            data_field(
3876
                "target",
3876
                String,
3876
                Some(D::String(AzString::from_const_str(""))),
3876
                "Default target for hyperlinks"
            ),
        ],
255816
        _ => alloc::vec![],
    }
434112
}
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.
3876
    pub fn with_builtin() -> Self {
3876
        ComponentMap {
3876
            libraries: alloc::vec![register_builtin_components()].into(),
3876
        }
3876
    }
    /// 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`.
3876
fn builtin_if_component() -> ComponentDef {
3876
    ComponentDef {
3876
        id: ComponentId::builtin("if"),
3876
        display_name: AzString::from_const_str("If"),
3876
        description: AzString::from_const_str("Conditional rendering: shows 'then' if condition is true, else shows 'else' (if provided)."),
3876
        css: AzString::from_const_str(""),
3876
        source: ComponentSource::Builtin,
3876
        data_model: ComponentDataModel {
3876
            name: AzString::from_const_str("IfData"),
3876
            description: AzString::from_const_str("Data for conditional rendering"),
3876
            fields: alloc::vec![
3876
                data_field("condition", ComponentFieldType::Bool, Some(ComponentDefaultValue::Bool(false)), "The boolean condition to evaluate"),
3876
            ].into(),
3876
        },
3876
        render_fn: builtin_if_render_fn,
3876
        compile_fn: builtin_if_compile_fn,
3876
        render_fn_source: None.into(),
3876
        compile_fn_source: None.into(),
3876
    }
3876
}
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.
3876
fn builtin_for_component() -> ComponentDef {
3876
    ComponentDef {
3876
        id: ComponentId::builtin("for"),
3876
        display_name: AzString::from_const_str("For Loop"),
3876
        description: AzString::from_const_str(
3876
            "Iterative rendering: repeats children 'count' times.",
3876
        ),
3876
        css: AzString::from_const_str(""),
3876
        source: ComponentSource::Builtin,
3876
        data_model: ComponentDataModel {
3876
            name: AzString::from_const_str("ForData"),
3876
            description: AzString::from_const_str("Data for iterative rendering"),
3876
            fields: alloc::vec![data_field(
3876
                "count",
3876
                ComponentFieldType::U32,
3876
                Some(ComponentDefaultValue::U32(3)),
3876
                "Number of iterations"
3876
            ),]
3876
            .into(),
3876
        },
3876
        render_fn: builtin_for_render_fn,
3876
        compile_fn: builtin_for_compile_fn,
3876
        render_fn_source: None.into(),
3876
        compile_fn_source: None.into(),
3876
    }
3876
}
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.
3876
fn builtin_map_component() -> ComponentDef {
3876
    ComponentDef {
3876
        id: ComponentId::builtin("map"),
3876
        display_name: AzString::from_const_str("Map"),
3876
        description: AzString::from_const_str(
3876
            "Map data to DOM: applies a template to each item in a collection.",
3876
        ),
3876
        css: AzString::from_const_str(""),
3876
        source: ComponentSource::Builtin,
3876
        data_model: ComponentDataModel {
3876
            name: AzString::from_const_str("MapData"),
3876
            description: AzString::from_const_str("Data for map rendering"),
3876
            fields: alloc::vec![data_field(
3876
                "data_json",
3876
                ComponentFieldType::String,
3876
                Some(ComponentDefaultValue::String(AzString::from_const_str(
3876
                    "[]"
3876
                ))),
3876
                "JSON array of items to map over"
3876
            ),]
3876
            .into(),
3876
        },
3876
        render_fn: builtin_map_render_fn,
3876
        compile_fn: builtin_map_compile_fn,
3876
        render_fn_source: None.into(),
3876
        compile_fn_source: None.into(),
3876
    }
3876
}
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.
3876
pub extern "C" fn register_builtin_components() -> ComponentLibrary {
3876
    ComponentLibrary {
3876
        name: AzString::from_const_str("builtin"),
3876
        version: AzString::from_const_str("1.0.0"),
3876
        description: AzString::from_const_str("Built-in HTML elements"),
3876
        exportable: false,
3876
        modifiable: false,
3876
        data_models: Vec::new().into(),
3876
        enum_models: Vec::new().into(),
3876
        components: alloc::vec![
3876
            // Structural
3876
            builtin_component_def("html", "HTML", None, ""),
3876
            builtin_component_def("head", "Head", None, ""),
3876
            builtin_component_def("title", "Title", Some(""), ""),
3876
            builtin_component_def("body", "Body", None, ""),
3876
            // Block-level
3876
            builtin_component_def("div", "Div", None, ""),
3876
            builtin_component_def("header", "Header", None, ""),
3876
            builtin_component_def("footer", "Footer", None, ""),
3876
            builtin_component_def("section", "Section", None, ""),
3876
            builtin_component_def("article", "Article", None, ""),
3876
            builtin_component_def("aside", "Aside", None, ""),
3876
            builtin_component_def("nav", "Nav", None, ""),
3876
            builtin_component_def("main", "Main", None, ""),
3876
            builtin_component_def("figure", "Figure", None, ""),
3876
            builtin_component_def("figcaption", "Figure Caption", Some(""), ""),
3876
            builtin_component_def("address", "Address", Some(""), ""),
3876
            builtin_component_def("details", "Details", None, ""),
3876
            builtin_component_def("summary", "Summary", Some("Details"), ""),
3876
            builtin_component_def("dialog", "Dialog", None, ""),
3876
            // Headings — default text is the heading level name so preview is visible
3876
            builtin_component_def("h1", "Heading 1", Some("Heading 1"), ""),
3876
            builtin_component_def("h2", "Heading 2", Some("Heading 2"), ""),
3876
            builtin_component_def("h3", "Heading 3", Some("Heading 3"), ""),
3876
            builtin_component_def("h4", "Heading 4", Some("Heading 4"), ""),
3876
            builtin_component_def("h5", "Heading 5", Some("Heading 5"), ""),
3876
            builtin_component_def("h6", "Heading 6", Some("Heading 6"), ""),
3876
            // Text content
3876
            builtin_component_def("p", "Paragraph", Some("Paragraph text"), ""),
3876
            builtin_component_def("span", "Span", Some(""), ""),
3876
            builtin_component_def("pre", "Preformatted", Some(""), ""),
3876
            builtin_component_def("code", "Code", Some(""), ""),
3876
            builtin_component_def("blockquote", "Blockquote", Some(""), ""),
3876
            builtin_component_def("br", "Line Break", None, ""),
3876
            builtin_component_def("hr", "Horizontal Rule", None, ""),
3876
            builtin_component_def("icon", "Icon", Some(""), ""),
3876
            // Lists
3876
            builtin_component_def("ul", "Unordered List", None, ""),
3876
            builtin_component_def("ol", "Ordered List", None, ""),
3876
            builtin_component_def("li", "List Item", Some("List item"), ""),
3876
            builtin_component_def("dl", "Description List", None, ""),
3876
            builtin_component_def("dt", "Description Term", Some(""), ""),
3876
            builtin_component_def("dd", "Description Details", Some(""), ""),
3876
            builtin_component_def("menu", "Menu", None, ""),
3876
            builtin_component_def("menuitem", "Menu Item", Some(""), ""),
3876
            builtin_component_def("dir", "Directory List", None, ""),
3876
            // Tables
3876
            builtin_component_def("table", "Table", None, ""),
3876
            builtin_component_def("caption", "Table Caption", Some(""), ""),
3876
            builtin_component_def("thead", "Table Head", None, ""),
3876
            builtin_component_def("tbody", "Table Body", None, ""),
3876
            builtin_component_def("tfoot", "Table Foot", None, ""),
3876
            builtin_component_def("tr", "Table Row", None, ""),
3876
            builtin_component_def("th", "Table Header Cell", Some("Header"), ""),
3876
            builtin_component_def("td", "Table Data Cell", Some(""), ""),
3876
            builtin_component_def("colgroup", "Column Group", None, ""),
3876
            builtin_component_def("col", "Column", None, ""),
3876
            // Inline
3876
            builtin_component_def("a", "Link", Some("Link text"), ""),
3876
            builtin_component_def("strong", "Strong", Some(""), ""),
3876
            builtin_component_def("em", "Emphasis", Some(""), ""),
3876
            builtin_component_def("b", "Bold", Some(""), ""),
3876
            builtin_component_def("i", "Italic", Some(""), ""),
3876
            builtin_component_def("u", "Underline", Some(""), ""),
3876
            builtin_component_def("s", "Strikethrough", Some(""), ""),
3876
            builtin_component_def("small", "Small", Some(""), ""),
3876
            builtin_component_def("mark", "Mark", Some(""), ""),
3876
            builtin_component_def("del", "Deleted Text", Some(""), ""),
3876
            builtin_component_def("ins", "Inserted Text", Some(""), ""),
3876
            builtin_component_def("sub", "Subscript", Some(""), ""),
3876
            builtin_component_def("sup", "Superscript", Some(""), ""),
3876
            builtin_component_def("samp", "Sample Output", Some(""), ""),
3876
            builtin_component_def("kbd", "Keyboard Input", Some(""), ""),
3876
            builtin_component_def("var", "Variable", Some(""), ""),
3876
            builtin_component_def("cite", "Citation", Some(""), ""),
3876
            builtin_component_def("dfn", "Definition", Some(""), ""),
3876
            builtin_component_def("abbr", "Abbreviation", Some(""), ""),
3876
            builtin_component_def("acronym", "Acronym", Some(""), ""),
3876
            builtin_component_def("q", "Inline Quote", Some(""), ""),
3876
            builtin_component_def("time", "Time", Some(""), ""),
3876
            builtin_component_def("big", "Big", Some(""), ""),
3876
            builtin_component_def("bdo", "BiDi Override", Some(""), ""),
3876
            builtin_component_def("bdi", "BiDi Isolate", Some(""), ""),
3876
            builtin_component_def("wbr", "Word Break Opportunity", None, ""),
3876
            builtin_component_def("ruby", "Ruby Annotation", None, ""),
3876
            builtin_component_def("rt", "Ruby Text", Some(""), ""),
3876
            builtin_component_def("rtc", "Ruby Text Container", None, ""),
3876
            builtin_component_def("rp", "Ruby Parenthesis", Some(""), ""),
3876
            builtin_component_def("data", "Data", Some(""), ""),
3876
            // Forms
3876
            builtin_component_def("form", "Form", None, ""),
3876
            builtin_component_def("fieldset", "Field Set", None, ""),
3876
            builtin_component_def("legend", "Legend", Some("Legend"), ""),
3876
            builtin_component_def("label", "Label", Some("Label"), ""),
3876
            builtin_component_def("input", "Input", None, ""),
3876
            builtin_component_def("button", "Button", Some("Button text"), ""),
3876
            builtin_component_def("select", "Select", None, ""),
3876
            builtin_component_def("optgroup", "Option Group", None, ""),
3876
            builtin_component_def("option", "Option", Some(""), ""),
3876
            builtin_component_def("textarea", "Text Area", Some(""), ""),
3876
            builtin_component_def("output", "Output", Some(""), ""),
3876
            builtin_component_def("progress", "Progress", None, ""),
3876
            builtin_component_def("meter", "Meter", None, ""),
3876
            builtin_component_def("datalist", "Data List", None, ""),
3876
            // Embedded content
3876
            builtin_component_def("canvas", "Canvas", None, ""),
3876
            builtin_component_def("object", "Object", None, ""),
3876
            builtin_component_def("param", "Parameter", None, ""),
3876
            builtin_component_def("embed", "Embed", None, ""),
3876
            builtin_component_def("audio", "Audio", None, ""),
3876
            builtin_component_def("video", "Video", None, ""),
3876
            builtin_component_def("source", "Source", None, ""),
3876
            builtin_component_def("track", "Track", None, ""),
3876
            builtin_component_def("map", "Image Map", None, ""),
3876
            builtin_component_def("area", "Map Area", None, ""),
3876
            builtin_component_def("svg", "SVG", None, ""),
3876
            // Metadata
3876
            builtin_component_def("meta", "Meta", None, ""),
3876
            builtin_component_def("link", "Link (Resource)", None, ""),
3876
            builtin_component_def("script", "Script", Some(""), ""),
3876
            builtin_component_def("style", "Style", Some(""), ""),
3876
            builtin_component_def("base", "Base URL", None, ""),
3876
            // Structural control-flow builtins (F1-F3)
3876
            builtin_if_component(),
3876
            builtin_for_component(),
3876
            builtin_map_component(),
3876
        ]
3876
        .into(),
3876
    }
3876
}
// ============================================================================
// 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
2859
    pub fn as_text(&self) -> Option<&str> {
2859
        match self {
2859
            XmlNodeChild::Text(s) => Some(s.as_str()),
            XmlNodeChild::Element(_) => None,
        }
2859
    }
    /// 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
2856
    pub fn get_text_content(&self) -> String {
2856
        self.children
2856
            .as_ref()
2856
            .iter()
2856
            .filter_map(|child| child.as_text())
2856
            .collect::<Vec<_>>()
2856
            .join("")
2856
    }
    /// 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
3876
pub fn get_html_node<'a>(root_nodes: &'a [XmlNodeChild]) -> Result<&'a XmlNode, DomXmlParseError> {
3876
    let mut html_node_iterator = root_nodes.iter().filter_map(|child| {
3876
        if let XmlNodeChild::Element(node) = child {
3876
            let node_type_normalized = normalize_casing(&node.node_type);
3876
            if &node_type_normalized == "html" {
3876
                Some(node)
            } else {
                None
            }
        } else {
            None
        }
3876
    });
3876
    let html_node = html_node_iterator
3876
        .next()
3876
        .ok_or(DomXmlParseError::NoHtmlNode)?;
3876
    if html_node_iterator.next().is_some() {
        Err(DomXmlParseError::MultipleHtmlRootNodes)
    } else {
3876
        Ok(html_node)
    }
3876
}
/// Find the one and only `<body>` node, return error if
/// there is no app node or there are multiple app nodes
3876
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)
3876
    let direct_body = root_nodes
3876
        .iter()
9435
        .filter_map(|child| {
9435
            if let XmlNodeChild::Element(node) = child {
6732
                let node_type_normalized = normalize_casing(&node.node_type);
6732
                if &node_type_normalized == "body" {
3876
                    Some(node)
                } else {
2856
                    None
                }
            } else {
2703
                None
            }
9435
        })
3876
        .next();
3876
    if let Some(body) = direct_body {
3876
        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)
3876
}
/// 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.
9996
fn find_node_by_type<'a>(root_nodes: &'a [XmlNodeChild], node_type: &str) -> Option<&'a XmlNode> {
    // First check direct children
14025
    for child in root_nodes {
9741
        if let XmlNodeChild::Element(node) = child {
8976
            if normalize_casing(&node.node_type).as_str() == node_type {
5712
                return Some(node);
3264
            }
765
        }
    }
    // If not found, search recursively (for malformed HTML)
7905
    for child in root_nodes {
3621
        if let XmlNodeChild::Element(node) = child {
3264
            if let Some(found) = find_node_by_type(node.children.as_ref(), node_type) {
                return Some(found);
3264
            }
357
        }
    }
4284
    None
9996
}
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`
32335
pub fn normalize_casing(input: &str) -> String {
32335
    let mut words: Vec<String> = Vec::new();
32335
    let mut cur_str = Vec::new();
123933
    for ch in input.chars() {
123933
        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());
            }
123933
        } else {
123933
            cur_str.extend(ch.to_lowercase());
123933
        }
    }
32335
    if !cur_str.is_empty() {
32335
        words.push(cur_str.iter().collect());
32335
        cur_str.clear();
32335
    }
32335
    words.join("_")
32335
}
/// 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>`
3876
pub fn str_to_dom<'a>(
3876
    root_nodes: &'a [XmlNodeChild],
3876
    component_map: &'a ComponentMap,
3876
    max_width: Option<f32>,
3876
) -> Result<StyledDom, DomXmlParseError> {
    // Delegate to the fast path (Dom::Fast / CompactDom arena).
3876
    str_to_dom_fast(root_nodes, component_map, max_width)
3876
}
/// 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.
3876
fn str_to_dom_fast<'a>(
3876
    root_nodes: &'a [XmlNodeChild],
3876
    component_map: &'a ComponentMap,
3876
    max_width: Option<f32>,
3876
) -> Result<StyledDom, DomXmlParseError> {
3876
    let html_node = get_html_node(root_nodes)?;
3876
    let body_node = get_body_node(html_node.children.as_ref())?;
3876
    let mut global_style = None;
3876
    if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
2856
        if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
2856
            let text = style_node.get_text_content();
2856
            if !text.is_empty() {
2856
                let parsed_css = Css::from_string(text.into());
2856
                global_style = Some(parsed_css);
2856
            }
        }
1020
    }
3876
    render_dom_from_body_node_fast(&body_node, global_style, component_map, max_width)
3876
        .map_err(|e| e.into())
3876
}
/// 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_css(\"\") // per-node with_css attaches @scope-like component CSS
}
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.
3213
fn parse_svg_float(attr: Option<&AzString>) -> Option<f32> {
3213
    attr?.as_str().trim().parse::<f32>().ok()
3213
}
/// Parse an SVG `points` attribute (used by `<polygon>` and `<polyline>`).
153
fn parse_svg_points(pts: &str, close: bool) -> Option<crate::svg::SvgMultiPolygon> {
153
    let nums: Vec<f32> = pts
2754
        .split(|c: char| c == ',' || c.is_ascii_whitespace())
1020
        .filter(|s| !s.is_empty())
1020
        .filter_map(|s| s.parse::<f32>().ok())
153
        .collect();
153
    if nums.len() < 4 || nums.len() % 2 != 0 {
        return None;
153
    }
153
    let mut elements = Vec::new();
153
    let points: Vec<azul_css::props::basic::SvgPoint> = nums
153
        .chunks_exact(2)
510
        .map(|c| azul_css::props::basic::SvgPoint { x: c[0], y: c[1] })
153
        .collect();
357
    for w in points.windows(2) {
357
        elements.push(crate::svg::SvgPathElement::Line(crate::svg::SvgLine::new(
357
            w[0], w[1],
357
        )));
357
    }
153
    if close && points.len() >= 2 {
102
        let first = points[0];
102
        let last = *points.last().unwrap();
102
        if (first.x - last.x).abs() > 0.001 || (first.y - last.y).abs() > 0.001 {
102
            elements.push(crate::svg::SvgPathElement::Line(crate::svg::SvgLine::new(
102
                last, first,
102
            )));
102
        }
51
    }
153
    Some(crate::svg::SvgMultiPolygon {
153
        rings: crate::svg::SvgPathVec::from_vec(vec![crate::svg::SvgPath {
153
            items: crate::svg::SvgPathElementVec::from_vec(elements),
153
        }]),
153
    })
153
}
/// 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, IdOrClass, NodeType, 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| {
1
                    w.as_str()
1
                        .trim()
1
                        .trim_end_matches("px")
1
                        .trim()
1
                        .parse::<usize>()
1
                        .ok()
1
                })
1
                .unwrap_or(0);
1
            let height = xml_node
1
                .attributes
1
                .get_key("height")
1
                .and_then(|h| {
1
                    h.as_str()
1
                        .trim()
1
                        .trim_end_matches("px")
1
                        .trim()
1
                        .parse::<usize>()
1
                        .ok()
1
                })
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
1
                .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
1
        .attributes
1
        .get_key("focusable")
1
        .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
1
        .attributes
1
        .get_key("tabindex")
1
        .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::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::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::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::{SvgCubicCurve, SvgPoint};
                    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 {
3876
    pub fn new() -> Self {
3876
        Self {
3876
            hierarchy: Vec::new(),
3876
            node_data: Vec::new(),
3876
            css: Vec::new(),
3876
            stack: Vec::new(),
3876
        }
3876
    }
    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()`.
25602
    pub fn open_node(&mut self, node_data: crate::dom::NodeData) {
        use crate::id::NodeId;
        use crate::styled_dom::NodeHierarchyItem;
25602
        let idx = self.hierarchy.len();
        // Determine parent from stack
25602
        let parent_raw = if let Some(&(parent_idx, _)) = self.stack.last() {
21726
            NodeId::into_raw(&Some(NodeId::new(parent_idx)))
        } else {
3876
            0 // No parent (root)
        };
        // Determine previous sibling from parent's last child tracking
25602
        let prev_sibling_raw = if let Some(&(_, prev_child)) = self.stack.last() {
21726
            prev_child
21726
                .map(|pi| NodeId::into_raw(&Some(NodeId::new(pi))))
21726
                .unwrap_or(0)
        } else {
3876
            0
        };
        // If there's a previous sibling, set its next_sibling to us
25602
        if let Some(&(_, Some(prev_idx))) = self.stack.last() {
9588
            self.hierarchy[prev_idx].next_sibling = NodeId::into_raw(&Some(NodeId::new(idx)));
16014
        }
        // Update parent's "last seen child" to us
25602
        if let Some(parent) = self.stack.last_mut() {
21726
            parent.1 = Some(idx);
21726
        }
        // Push the hierarchy item (last_child will be set in close_node)
25602
        self.hierarchy.push(NodeHierarchyItem {
25602
            parent: parent_raw,
25602
            previous_sibling: prev_sibling_raw,
25602
            next_sibling: 0, // Will be set by next sibling's open_node
25602
            last_child: 0,   // Will be set in close_node
25602
        });
25602
        self.node_data.push(node_data);
        // Push onto stack: this node is now the "open" element, no children yet
25602
        self.stack.push((idx, None));
25602
    }
    /// Close the current element. Sets the `last_child` pointer.
25602
    pub fn close_node(&mut self) {
        use crate::id::NodeId;
25602
        if let Some((idx, last_child_idx)) = self.stack.pop() {
            // Set last_child on this node's hierarchy item
25602
            self.hierarchy[idx].last_child = last_child_idx
25602
                .map(|lc| NodeId::into_raw(&Some(NodeId::new(lc))))
25602
                .unwrap_or(0);
        }
25602
    }
    /// Add a leaf node (text, br, hr, etc.) that has no children.
8976
    pub fn add_leaf(&mut self, node_data: crate::dom::NodeData) {
8976
        self.open_node(node_data);
8976
        self.close_node();
8976
    }
    /// 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.
3876
    pub fn finish(self) -> crate::dom::FastDom {
3876
        crate::dom::FastDom {
3876
            node_hierarchy: self.hierarchy.into(),
3876
            node_data: self.node_data.into(),
3876
            css: self.css.into(),
3876
        }
3876
    }
}
/// 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`.
12750
fn xml_node_to_fast_dom<'a>(
12750
    xml_node: &'a XmlNode,
12750
    component_map: &'a ComponentMap,
12750
    inside_svg: bool,
12750
    builder: &mut CompactDomBuilder,
12750
) -> Result<(), RenderDomError> {
    use crate::dom::{Dom, IdOrClass, NodeData, NodeType, TabIndex};
12750
    let component_name = normalize_casing(&xml_node.node_type);
12750
    let node_type = tag_to_node_type(&component_name);
12750
    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`.
12750
    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)));
        }
12750
    }
    // Set id and class attributes
12750
    let mut ids_and_classes = Vec::new();
12750
    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()));
        }
12750
    }
12750
    if let Some(class_str) = xml_node.attributes.get_key("class") {
5202
        for class in class_str.split_whitespace() {
5202
            ids_and_classes.push(IdOrClass::Class(class.into()));
5202
        }
7548
    }
12750
    if !ids_and_classes.is_empty() {
5202
        node_data.set_ids_and_classes(ids_and_classes.into());
7548
    }
    // Handle focusable attribute
12750
    if let Some(focusable) = xml_node
12750
        .attributes
12750
        .get_key("focusable")
12750
        .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),
        }
12750
    }
    // Handle tabindex attribute
12750
    if let Some(tab_index) = xml_node
12750
        .attributes
12750
        .get_key("tabindex")
12750
        .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),
        }
12750
    }
    // Handle inline style attribute
12750
    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());
        }
12750
    }
    // Handle SVG shape elements
12750
    let tag = component_name.as_str();
12750
    let child_inside_svg = inside_svg || tag == "svg";
12750
    let is_svg_shape = inside_svg
1071
        && matches!(
1173
            tag,
1173
            "path" | "circle" | "rect" | "ellipse" | "line" | "polygon" | "polyline"
        );
12750
    if is_svg_shape {
1071
        let clip = match tag {
1071
            "path" => xml_node
204
                .attributes
204
                .get_key("d")
204
                .and_then(|d| crate::path_parser::parse_svg_path_d(d.as_str()).ok()),
867
            "circle" => {
255
                let cx = parse_svg_float(xml_node.attributes.get_key("cx")).unwrap_or(0.0);
255
                let cy = parse_svg_float(xml_node.attributes.get_key("cy")).unwrap_or(0.0);
255
                let r = parse_svg_float(xml_node.attributes.get_key("r")).unwrap_or(0.0);
255
                if r > 0.0 {
204
                    Some(crate::svg::SvgMultiPolygon {
204
                        rings: crate::svg::SvgPathVec::from_vec(vec![
204
                            crate::path_parser::svg_circle_to_paths(cx, cy, r),
204
                        ]),
204
                    })
                } else {
51
                    None
                }
            }
612
            "rect" => {
306
                let x = parse_svg_float(xml_node.attributes.get_key("x")).unwrap_or(0.0);
306
                let y = parse_svg_float(xml_node.attributes.get_key("y")).unwrap_or(0.0);
306
                let w = parse_svg_float(xml_node.attributes.get_key("width")).unwrap_or(0.0);
306
                let h = parse_svg_float(xml_node.attributes.get_key("height")).unwrap_or(0.0);
306
                let rx = parse_svg_float(xml_node.attributes.get_key("rx")).unwrap_or(0.0);
306
                let ry = parse_svg_float(xml_node.attributes.get_key("ry")).unwrap_or(rx);
306
                if w > 0.0 && h > 0.0 {
255
                    Some(crate::svg::SvgMultiPolygon {
255
                        rings: crate::svg::SvgPathVec::from_vec(vec![
255
                            crate::path_parser::svg_rect_to_path(x, y, w, h, rx, ry),
255
                        ]),
255
                    })
                } else {
51
                    None
                }
            }
306
            "ellipse" => {
102
                let cx = parse_svg_float(xml_node.attributes.get_key("cx")).unwrap_or(0.0);
102
                let cy = parse_svg_float(xml_node.attributes.get_key("cy")).unwrap_or(0.0);
102
                let rx = parse_svg_float(xml_node.attributes.get_key("rx")).unwrap_or(0.0);
102
                let ry = parse_svg_float(xml_node.attributes.get_key("ry")).unwrap_or(0.0);
102
                if rx > 0.0 && ry > 0.0 {
                    use azul_css::props::basic::{SvgCubicCurve, SvgPoint};
                    const KAPPA: f32 = 0.5522847498;
102
                    let kx = rx * KAPPA;
102
                    let ky = ry * KAPPA;
102
                    let elements = vec![
102
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
102
                            start: SvgPoint { x: cx, y: cy - ry },
102
                            ctrl_1: SvgPoint {
102
                                x: cx + kx,
102
                                y: cy - ry,
102
                            },
102
                            ctrl_2: SvgPoint {
102
                                x: cx + rx,
102
                                y: cy - ky,
102
                            },
102
                            end: SvgPoint { x: cx + rx, y: cy },
102
                        }),
102
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
102
                            start: SvgPoint { x: cx + rx, y: cy },
102
                            ctrl_1: SvgPoint {
102
                                x: cx + rx,
102
                                y: cy + ky,
102
                            },
102
                            ctrl_2: SvgPoint {
102
                                x: cx + kx,
102
                                y: cy + ry,
102
                            },
102
                            end: SvgPoint { x: cx, y: cy + ry },
102
                        }),
102
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
102
                            start: SvgPoint { x: cx, y: cy + ry },
102
                            ctrl_1: SvgPoint {
102
                                x: cx - kx,
102
                                y: cy + ry,
102
                            },
102
                            ctrl_2: SvgPoint {
102
                                x: cx - rx,
102
                                y: cy + ky,
102
                            },
102
                            end: SvgPoint { x: cx - rx, y: cy },
102
                        }),
102
                        crate::svg::SvgPathElement::CubicCurve(SvgCubicCurve {
102
                            start: SvgPoint { x: cx - rx, y: cy },
102
                            ctrl_1: SvgPoint {
102
                                x: cx - rx,
102
                                y: cy - ky,
102
                            },
102
                            ctrl_2: SvgPoint {
102
                                x: cx - kx,
102
                                y: cy - ry,
102
                            },
102
                            end: SvgPoint { x: cx, y: cy - ry },
102
                        }),
                    ];
102
                    Some(crate::svg::SvgMultiPolygon {
102
                        rings: crate::svg::SvgPathVec::from_vec(vec![crate::svg::SvgPath {
102
                            items: crate::svg::SvgPathElementVec::from_vec(elements),
102
                        }]),
102
                    })
                } else {
                    None
                }
            }
204
            "line" => {
51
                let x1 = parse_svg_float(xml_node.attributes.get_key("x1")).unwrap_or(0.0);
51
                let y1 = parse_svg_float(xml_node.attributes.get_key("y1")).unwrap_or(0.0);
51
                let x2 = parse_svg_float(xml_node.attributes.get_key("x2")).unwrap_or(0.0);
51
                let y2 = parse_svg_float(xml_node.attributes.get_key("y2")).unwrap_or(0.0);
51
                Some(crate::svg::SvgMultiPolygon {
51
                    rings: crate::svg::SvgPathVec::from_vec(vec![crate::svg::SvgPath {
51
                        items: crate::svg::SvgPathElementVec::from_vec(vec![
51
                            crate::svg::SvgPathElement::Line(crate::svg::SvgLine::new(
51
                                azul_css::props::basic::SvgPoint { x: x1, y: y1 },
51
                                azul_css::props::basic::SvgPoint { x: x2, y: y2 },
51
                            )),
51
                        ]),
51
                    }]),
51
                })
            }
153
            "polygon" | "polyline" => xml_node
153
                .attributes
153
                .get_key("points")
153
                .and_then(|pts| parse_svg_points(pts.as_str(), tag == "polygon")),
            _ => None,
        };
1071
        if let Some(mp) = clip {
969
            node_data.set_svg_data(crate::dom::SvgNodeData::Path(mp));
969
        }
11679
    }
    // Open this node in the builder
12750
    builder.open_node(node_data);
    // Recursively convert children
17850
    for child in xml_node.children.as_ref().iter() {
17850
        match child {
8874
            XmlNodeChild::Element(child_node) => {
8874
                xml_node_to_fast_dom(child_node, component_map, child_inside_svg, builder)?;
            }
8976
            XmlNodeChild::Text(text) => {
8976
                builder.add_leaf(NodeData::create_text(AzString::from(text.as_str())));
8976
            }
        }
    }
    // Close this node
12750
    builder.close_node();
12750
    Ok(())
12750
}
/// Render a DOM from an XML body node using the fast arena-based path.
/// Builds a FastDom directly (no tree intermediary), then creates StyledDom.
3876
fn render_dom_from_body_node_fast<'a>(
3876
    body_node: &'a XmlNode,
3876
    mut global_css: Option<Css>,
3876
    component_map: &'a ComponentMap,
3876
    max_width: Option<f32>,
3876
) -> Result<StyledDom, RenderDomError> {
    use crate::dom::{NodeData, NodeType};
3876
    let mut builder = CompactDomBuilder::new();
    // Build the HTML > Body wrapper + body content in one pass
    // Open <html>
3876
    builder.open_node(NodeData::create_node(NodeType::Html));
    // Open <body> (the body_node content goes inside)
3876
    xml_node_to_fast_dom(body_node, component_map, false, &mut builder)?;
    // Close <html>
3876
    builder.close_node();
    // Collect CSS rules from each source.
3876
    let mut combined_rules: Vec<azul_css::css::CssRuleBlock> = Vec::new();
3876
    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());
3876
    }
3876
    if let Some(css) = global_css.take() {
2856
        combined_rules.extend(css.rules.into_library_owned_vec());
2856
    }
3876
    let combined_css = Css::new(combined_rules);
    // Add CSS to the FastDom
3876
    let mut fast_dom = builder.finish();
3876
    fast_dom.css = vec![crate::dom::CssWithNodeId {
3876
        node_id: 0, // Global scope (root)
3876
        css: combined_css,
3876
    }]
3876
    .into();
    // Create StyledDom via the fast path (no tree→arena conversion)
3876
    let styled = StyledDom::create_from_fast_dom(fast_dom);
3876
    Ok(styled)
3876
}
// 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::codegen::format::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::codegen::format::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 {
1
                    key: "src".into(),
1
                    value: "cat.jpg".into()
1
                },
1
                AzStringPair {
1
                    key: "width".into(),
1
                    value: "300".into()
1
                },
1
                AzStringPair {
1
                    key: "height".into(),
1
                    value: "169".into()
1
                },
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() {
                DecodedImage::NullImage {
1
                    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
    }
}