azul_core/
xml.rs

1//! XML structure definitions
2
3use alloc::{
4    boxed::Box,
5    collections::BTreeMap,
6    string::{String, ToString},
7    vec::Vec,
8};
9use core::{fmt, hash::Hash};
10
11use azul_css::{
12    parser::{CssApiWrapper, CssParseErrorOwned, ErrorLocation},
13    AzString, Css, CssDeclaration, CssPath, CssPathPseudoSelector, CssPathSelector, CssProperty,
14    CssRuleBlock, NodeTypeTag, NormalizedLinearColorStopVec, NormalizedRadialColorStopVec,
15    OptionAzString, StyleBackgroundContentVec, StyleBackgroundPositionVec,
16    StyleBackgroundRepeatVec, StyleBackgroundSizeVec, StyleFontFamilyVec, StyleTransformVec, U8Vec,
17};
18
19use crate::{
20    css::VecContents,
21    dom::Dom,
22    styled_dom::StyledDom,
23    window::{AzStringPair, StringPairVec},
24};
25
26/// Error that can happen during hot-reload -
27/// stringified, since it is only used for printing and is not exposed in the public API
28pub type SyntaxError = String;
29/// Tag of an XML node, such as the "button" in `<button>Hello</button>`.
30pub type XmlTagName = AzString;
31/// (Unparsed) text content of an XML node, such as the "Hello" in `<button>Hello</button>`.
32pub type XmlTextContent = OptionAzString;
33/// Attributes of an XML node, such as `["color" => "blue"]` in `<button color="blue" />`.
34pub type XmlAttributeMap = StringPairVec;
35
36pub type ComponentArgumentName = String;
37pub type ComponentArgumentType = String;
38pub type ComponentArgumentOrder = usize;
39pub type ComponentArgumentTypes = Vec<(ComponentArgumentName, ComponentArgumentType)>;
40pub type ComponentName = String;
41pub type CompiledComponent = String;
42
43pub const DEFAULT_ARGS: [&str; 8] = [
44    "id",
45    "class",
46    "tabindex",
47    "focusable",
48    "accepts_text",
49    "name",
50    "style",
51    "args",
52];
53
54#[allow(non_camel_case_types)]
55pub enum c_void {}
56
57#[repr(C)]
58pub enum XmlNodeType {
59    Root,
60    Element,
61    PI,
62    Comment,
63    Text,
64}
65
66#[repr(C)]
67pub struct XmlQualifiedName {
68    pub name: AzString,
69    pub namespace: OptionAzString,
70}
71
72#[derive(Debug, PartialEq, PartialOrd, Clone)]
73#[repr(C)]
74pub struct Xml {
75    pub root: XmlNodeVec,
76}
77
78#[derive(Debug, PartialEq, PartialOrd, Clone)]
79#[repr(C)]
80pub struct NonXmlCharError {
81    pub ch: u32, /* u32 = char, but ABI stable */
82    pub pos: XmlTextPos,
83}
84
85#[derive(Debug, PartialEq, PartialOrd, Clone)]
86#[repr(C)]
87pub struct InvalidCharError {
88    pub expected: u8,
89    pub got: u8,
90    pub pos: XmlTextPos,
91}
92
93#[derive(Debug, PartialEq, PartialOrd, Clone)]
94#[repr(C)]
95pub struct InvalidCharMultipleError {
96    pub expected: u8,
97    pub got: U8Vec,
98    pub pos: XmlTextPos,
99}
100
101#[derive(Debug, PartialEq, PartialOrd, Clone)]
102#[repr(C)]
103pub struct InvalidQuoteError {
104    pub got: u8,
105    pub pos: XmlTextPos,
106}
107
108#[derive(Debug, PartialEq, PartialOrd, Clone)]
109#[repr(C)]
110pub struct InvalidSpaceError {
111    pub got: u8,
112    pub pos: XmlTextPos,
113}
114
115#[derive(Debug, PartialEq, PartialOrd, Clone)]
116#[repr(C)]
117pub struct InvalidStringError {
118    pub got: AzString,
119    pub pos: XmlTextPos,
120}
121
122#[derive(Debug, PartialEq, PartialOrd, Clone)]
123#[repr(C, u8)]
124pub enum XmlStreamError {
125    UnexpectedEndOfStream,
126    InvalidName,
127    NonXmlChar(NonXmlCharError),
128    InvalidChar(InvalidCharError),
129    InvalidCharMultiple(InvalidCharMultipleError),
130    InvalidQuote(InvalidQuoteError),
131    InvalidSpace(InvalidSpaceError),
132    InvalidString(InvalidStringError),
133    InvalidReference,
134    InvalidExternalID,
135    InvalidCommentData,
136    InvalidCommentEnd,
137    InvalidCharacterData,
138}
139
140impl fmt::Display for XmlStreamError {
141    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142        use self::XmlStreamError::*;
143        match self {
144            UnexpectedEndOfStream => write!(f, "Unexpected end of stream"),
145            InvalidName => write!(f, "Invalid name"),
146            NonXmlChar(nx) => write!(
147                f,
148                "Non-XML character: {:?} at {}",
149                core::char::from_u32(nx.ch),
150                nx.pos
151            ),
152            InvalidChar(ic) => write!(
153                f,
154                "Invalid character: expected: {}, got: {} at {}",
155                ic.expected as char, ic.got as char, ic.pos
156            ),
157            InvalidCharMultiple(imc) => write!(
158                f,
159                "Multiple invalid characters: expected: {}, got: {:?} at {}",
160                imc.expected,
161                imc.got.as_ref(),
162                imc.pos
163            ),
164            InvalidQuote(iq) => write!(f, "Invalid quote: got {} at {}", iq.got as char, iq.pos),
165            InvalidSpace(is) => write!(f, "Invalid space: got {} at {}", is.got as char, is.pos),
166            InvalidString(ise) => write!(
167                f,
168                "Invalid string: got \"{}\" at {}",
169                ise.got.as_str(),
170                ise.pos
171            ),
172            InvalidReference => write!(f, "Invalid reference"),
173            InvalidExternalID => write!(f, "Invalid external ID"),
174            InvalidCommentData => write!(f, "Invalid comment data"),
175            InvalidCommentEnd => write!(f, "Invalid comment end"),
176            InvalidCharacterData => write!(f, "Invalid character data"),
177        }
178    }
179}
180
181#[derive(Debug, PartialEq, PartialOrd, Clone, Ord, Hash, Eq)]
182#[repr(C)]
183pub struct XmlTextPos {
184    pub row: u32,
185    pub col: u32,
186}
187
188impl fmt::Display for XmlTextPos {
189    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190        write!(f, "line {}:{}", self.row, self.col)
191    }
192}
193
194#[derive(Debug, PartialEq, PartialOrd, Clone)]
195#[repr(C)]
196pub struct XmlTextError {
197    pub stream_error: XmlStreamError,
198    pub pos: XmlTextPos,
199}
200
201#[derive(Debug, PartialEq, PartialOrd, Clone)]
202#[repr(C, u8)]
203pub enum XmlParseError {
204    InvalidDeclaration(XmlTextError),
205    InvalidComment(XmlTextError),
206    InvalidPI(XmlTextError),
207    InvalidDoctype(XmlTextError),
208    InvalidEntity(XmlTextError),
209    InvalidElement(XmlTextError),
210    InvalidAttribute(XmlTextError),
211    InvalidCdata(XmlTextError),
212    InvalidCharData(XmlTextError),
213    UnknownToken(XmlTextPos),
214}
215
216impl fmt::Display for XmlParseError {
217    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
218        use self::XmlParseError::*;
219        match self {
220            InvalidDeclaration(e) => {
221                write!(f, "Invalid declaraction: {} at {}", e.stream_error, e.pos)
222            }
223            InvalidComment(e) => write!(f, "Invalid comment: {} at {}", e.stream_error, e.pos),
224            InvalidPI(e) => write!(
225                f,
226                "Invalid processing instruction: {} at {}",
227                e.stream_error, e.pos
228            ),
229            InvalidDoctype(e) => write!(f, "Invalid doctype: {} at {}", e.stream_error, e.pos),
230            InvalidEntity(e) => write!(f, "Invalid entity: {} at {}", e.stream_error, e.pos),
231            InvalidElement(e) => write!(f, "Invalid element: {} at {}", e.stream_error, e.pos),
232            InvalidAttribute(e) => write!(f, "Invalid attribute: {} at {}", e.stream_error, e.pos),
233            InvalidCdata(e) => write!(f, "Invalid CDATA: {} at {}", e.stream_error, e.pos),
234            InvalidCharData(e) => write!(f, "Invalid char data: {} at {}", e.stream_error, e.pos),
235            UnknownToken(e) => write!(f, "Unknown token at {}", e),
236        }
237    }
238}
239
240impl_result!(
241    Xml,
242    XmlError,
243    ResultXmlXmlError,
244    copy = false,
245    [Debug, PartialEq, PartialOrd, Clone]
246);
247
248#[derive(Debug, PartialEq, PartialOrd, Clone)]
249#[repr(C)]
250pub struct DuplicatedNamespaceError {
251    pub ns: AzString,
252    pub pos: XmlTextPos,
253}
254
255#[derive(Debug, PartialEq, PartialOrd, Clone)]
256#[repr(C)]
257pub struct UnknownNamespaceError {
258    pub ns: AzString,
259    pub pos: XmlTextPos,
260}
261
262#[derive(Debug, PartialEq, PartialOrd, Clone)]
263#[repr(C)]
264pub struct UnexpectedCloseTagError {
265    pub expected: AzString,
266    pub actual: AzString,
267    pub pos: XmlTextPos,
268}
269
270#[derive(Debug, PartialEq, PartialOrd, Clone)]
271#[repr(C)]
272pub struct UnknownEntityReferenceError {
273    pub entity: AzString,
274    pub pos: XmlTextPos,
275}
276
277#[derive(Debug, PartialEq, PartialOrd, Clone)]
278#[repr(C)]
279pub struct DuplicatedAttributeError {
280    pub attribute: AzString,
281    pub pos: XmlTextPos,
282}
283
284#[derive(Debug, PartialEq, PartialOrd, Clone)]
285#[repr(C, u8)]
286pub enum XmlError {
287    NoParserAvailable,
288    InvalidXmlPrefixUri(XmlTextPos),
289    UnexpectedXmlUri(XmlTextPos),
290    UnexpectedXmlnsUri(XmlTextPos),
291    InvalidElementNamePrefix(XmlTextPos),
292    DuplicatedNamespace(DuplicatedNamespaceError),
293    UnknownNamespace(UnknownNamespaceError),
294    UnexpectedCloseTag(UnexpectedCloseTagError),
295    UnexpectedEntityCloseTag(XmlTextPos),
296    UnknownEntityReference(UnknownEntityReferenceError),
297    MalformedEntityReference(XmlTextPos),
298    EntityReferenceLoop(XmlTextPos),
299    InvalidAttributeValue(XmlTextPos),
300    DuplicatedAttribute(DuplicatedAttributeError),
301    NoRootNode,
302    SizeLimit,
303    DtdDetected,
304    /// Invalid hierarchy close tags, i.e `<app></p></app>`
305    MalformedHierarchy(AzString, AzString),
306    ParserError(XmlParseError),
307}
308
309impl fmt::Display for XmlError {
310    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
311        use self::XmlError::*;
312        match self {
313            NoParserAvailable => write!(
314                f,
315                "Library was compiled without XML parser (XML parser not available)"
316            ),
317            InvalidXmlPrefixUri(pos) => {
318                write!(f, "Invalid XML Prefix URI at line {}:{}", pos.row, pos.col)
319            }
320            UnexpectedXmlUri(pos) => {
321                write!(f, "Unexpected XML URI at at line {}:{}", pos.row, pos.col)
322            }
323            UnexpectedXmlnsUri(pos) => write!(
324                f,
325                "Unexpected XML namespace URI at line {}:{}",
326                pos.row, pos.col
327            ),
328            InvalidElementNamePrefix(pos) => write!(
329                f,
330                "Invalid element name prefix at line {}:{}",
331                pos.row, pos.col
332            ),
333            DuplicatedNamespace(ns) => write!(
334                f,
335                "Duplicated namespace: \"{}\" at {}",
336                ns.ns.as_str(),
337                ns.pos
338            ),
339            UnknownNamespace(uns) => write!(
340                f,
341                "Unknown namespace: \"{}\" at {}",
342                uns.ns.as_str(),
343                uns.pos
344            ),
345            UnexpectedCloseTag(ct) => write!(
346                f,
347                "Unexpected close tag: expected \"{}\", got \"{}\" at {}",
348                ct.expected.as_str(),
349                ct.actual.as_str(),
350                ct.pos
351            ),
352            UnexpectedEntityCloseTag(pos) => write!(
353                f,
354                "Unexpected entity close tag at line {}:{}",
355                pos.row, pos.col
356            ),
357            UnknownEntityReference(uer) => write!(
358                f,
359                "Unexpected entity reference: \"{}\" at {}",
360                uer.entity, uer.pos
361            ),
362            MalformedEntityReference(pos) => write!(
363                f,
364                "Malformed entity reference at line {}:{}",
365                pos.row, pos.col
366            ),
367            EntityReferenceLoop(pos) => write!(
368                f,
369                "Entity reference loop (recursive entity reference) at line {}:{}",
370                pos.row, pos.col
371            ),
372            InvalidAttributeValue(pos) => {
373                write!(f, "Invalid attribute value at line {}:{}", pos.row, pos.col)
374            }
375            DuplicatedAttribute(ae) => write!(
376                f,
377                "Duplicated attribute \"{}\" at line {}:{}",
378                ae.attribute.as_str(),
379                ae.pos.row,
380                ae.pos.col
381            ),
382            NoRootNode => write!(f, "No root node found"),
383            SizeLimit => write!(f, "XML file too large (size limit reached)"),
384            DtdDetected => write!(f, "Document type descriptor detected"),
385            MalformedHierarchy(expected, got) => write!(
386                f,
387                "Malformed hierarchy: expected <{}/> closing tag, got <{}/>",
388                expected.as_str(),
389                got.as_str()
390            ),
391            ParserError(p) => write!(f, "{}", p),
392        }
393    }
394}
395
396/// A component can take various arguments (to pass down to its children), which are then
397/// later compiled into Rust function arguments - for example
398///
399/// ```xml,no_run,ignore
400/// <component name="test" args="a: String, b: bool, c: HashMap<X, Y>">
401///     <Button id="my_button" class="test_{{ a }}"> Is this true? Scientists say: {{ b }}</Button>
402/// </component>
403/// ```
404///
405/// ... will turn into the following (generated) Rust code:
406///
407/// ```rust,no_run,ignore
408/// struct TestRendererArgs<'a> {
409///     a: &'a String,
410///     b: &'a bool,
411///     c: &'a HashMap<X, Y>,
412/// }
413///
414/// fn render_component_test<'a, T>(args: &TestRendererArgs<'a>) -> StyledDom {
415///     Button::with_label(format!("Is this true? Scientists say: {:?}", args.b)).with_class(format!("test_{}", args.a))
416/// }
417/// ```
418///
419/// For this to work, a component has to note all its arguments and types that it can take.
420/// If a type is not `str` or `String`, it will be formatted using the `{:?}` formatter
421/// in the generated source code, otherwise the compiler will use the `{}` formatter.
422#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
423pub struct ComponentArguments {
424    /// The arguments of the component, i.e. `date => String`
425    pub args: ComponentArgumentTypes,
426    /// Whether this widget accepts text. Note that this will be passed as the first
427    /// argument when rendering the Rust code.
428    pub accepts_text: bool,
429}
430
431impl Default for ComponentArguments {
432    fn default() -> Self {
433        Self {
434            args: ComponentArgumentTypes::default(),
435            accepts_text: false,
436        }
437    }
438}
439
440impl ComponentArguments {
441    fn new() -> Self {
442        Self::default()
443    }
444}
445
446#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
447pub struct FilteredComponentArguments {
448    /// The types of the component, i.e. `date => String`, in order
449    pub types: ComponentArgumentTypes,
450    /// The types of the component, i.e. `date => "01.01.1998"`
451    pub values: BTreeMap<String, String>,
452    /// Whether this widget accepts text. Note that this will be passed as the first
453    /// argument when rendering the Rust code.
454    pub accepts_text: bool,
455}
456
457impl Default for FilteredComponentArguments {
458    fn default() -> Self {
459        Self {
460            types: Vec::new(),
461            values: BTreeMap::default(),
462            accepts_text: false,
463        }
464    }
465}
466
467impl FilteredComponentArguments {
468    fn new() -> Self {
469        Self::default()
470    }
471}
472
473/// Specifies a component that reacts to a parsed XML node
474pub trait XmlComponentTrait {
475    /// Returns the type ID of this component, default = `div`
476    fn get_type_id(&self) -> String {
477        "div".to_string()
478    }
479
480    /// Returns the XML node for this component, used in the `get_html_string` debugging code
481    /// (necessary to compile the component into a function during the Rust compilation stage)
482    fn get_xml_node(&self) -> XmlNode {
483        XmlNode::new(self.get_type_id())
484    }
485
486    /// (Optional): Should return all arguments that this component can take - for example if you
487    /// have a component called `Calendar`, which can take a `selectedDate` argument:
488    ///
489    /// ```xml,no_run,ignore
490    /// <Calendar
491    ///     selectedDate='01.01.2018'
492    ///     minimumDate='01.01.1970'
493    ///     maximumDate='31.12.2034'
494    ///     firstDayOfWeek='sunday'
495    ///     gridVisible='false'
496    /// />
497    /// ```
498    /// ... then the `ComponentArguments` returned by this function should look something like this:
499    ///
500    /// ```rust,no_run,ignore
501    /// impl XmlComponentTrait for CalendarRenderer {
502    ///     fn get_available_arguments(&self) -> ComponentArguments {
503    ///         btreemap![
504    ///             "selected_date" => "DateTime",
505    ///             "minimum_date" => "DateTime",
506    ///             "maximum_date" => "DateTime",
507    ///             "first_day_of_week" => "WeekDay",
508    ///             "grid_visible" => "bool",
509    ///             /* ... */
510    ///         ]
511    ///     }
512    /// }
513    /// ```
514    ///
515    /// If a user instantiates a component with an invalid argument (i.e. `<Calendar
516    /// asdf="false">`), the user will get an error that the component can't handle this
517    /// argument. The types are not checked, but they are necessary for the XML-to-Rust
518    /// compiler.
519    ///
520    /// When the XML is then compiled to Rust, the generated Rust code will look like this:
521    ///
522    /// ```rust,no_run,ignore
523    /// calendar(&CalendarRendererArgs {
524    ///     selected_date: DateTime::from("01.01.2018")
525    ///     minimum_date: DateTime::from("01.01.2018")
526    ///     maximum_date: DateTime::from("01.01.2018")
527    ///     first_day_of_week: WeekDay::from("sunday")
528    ///     grid_visible: false,
529    ///     .. Default::default()
530    /// })
531    /// ```
532    ///
533    /// Of course, the code generation isn't perfect: For non-builtin types, the compiler will use
534    /// `Type::from` to make the conversion. You can then take that generated Rust code and clean it
535    /// up, put it somewhere else and create another component out of it - XML should only be
536    /// seen as a high-level prototyping tool (to get around the problem of compile times), not
537    /// as the final data format.
538    fn get_available_arguments(&self) -> ComponentArguments {
539        ComponentArguments::new()
540    }
541
542    // - necessary functions
543
544    /// Given a root node and a list of possible arguments, returns a DOM or a syntax error
545    fn render_dom(
546        &self,
547        components: &XmlComponentMap,
548        arguments: &FilteredComponentArguments,
549        content: &XmlTextContent,
550    ) -> Result<StyledDom, RenderDomError>;
551
552    /// (Optional): Used to compile the XML component to Rust code - input
553    fn compile_to_rust_code(
554        &self,
555        components: &XmlComponentMap,
556        attributes: &ComponentArguments,
557        content: &XmlTextContent,
558    ) -> Result<String, CompileError> {
559        Ok(String::new())
560    }
561}
562
563/// Wrapper for the XML parser - necessary to easily create a Dom from
564/// XML without putting an XML solver into `azul-core`.
565#[derive(Default)]
566pub struct DomXml {
567    pub parsed_dom: StyledDom,
568}
569
570impl DomXml {
571    /// Convenience function, only available in tests, useful for quickly writing UI tests.
572    /// Wraps the XML string in the required `<app></app>` braces, panics if the XML couldn't be
573    /// parsed.
574    ///
575    /// ## Example
576    ///
577    /// ```rust,ignore
578    /// # use azul::dom::Dom;
579    /// # use azul::xml::DomXml;
580    /// let dom = DomXml::mock("<div id='test' />");
581    /// dom.assert_eq(Dom::div().with_id("test"));
582    /// ```
583    #[cfg(test)]
584    pub fn assert_eq(self, other: StyledDom) {
585        let mut fixed = Dom::body().style(CssApiWrapper::empty());
586        fixed.append_child(other);
587        if self.parsed_dom != fixed {
588            panic!(
589                "\r\nExpected DOM did not match:\r\n\r\nexpected: ----------\r\n{}\r\ngot: \
590                 ----------\r\n{}\r\n",
591                self.parsed_dom.get_html_string("", "", true),
592                fixed.get_html_string("", "", true)
593            );
594        }
595    }
596
597    pub fn into_styled_dom(self) -> StyledDom {
598        self.into()
599    }
600}
601
602impl Into<StyledDom> for DomXml {
603    fn into(self) -> StyledDom {
604        self.parsed_dom
605    }
606}
607
608/// Represents one XML node tag
609#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
610#[repr(C)]
611pub struct XmlNode {
612    /// Type of the node
613    pub node_type: XmlTagName,
614    /// Attributes of an XML node (note: not yet filtered and / or broken into function arguments!)
615    pub attributes: XmlAttributeMap,
616    /// Direct children of this node
617    pub children: XmlNodeVec,
618    /// String content of the node, i.e the "Hello" in `<p>Hello</p>`
619    pub text: XmlTextContent,
620}
621
622impl XmlNode {
623    pub fn new<I: Into<XmlTagName>>(node_type: I) -> Self {
624        XmlNode {
625            node_type: node_type.into(),
626            ..Default::default()
627        }
628    }
629}
630
631impl_vec!(XmlNode, XmlNodeVec, XmlNodeVecDestructor);
632impl_vec_mut!(XmlNode, XmlNodeVec);
633impl_vec_debug!(XmlNode, XmlNodeVec);
634impl_vec_partialeq!(XmlNode, XmlNodeVec);
635impl_vec_eq!(XmlNode, XmlNodeVec);
636impl_vec_partialord!(XmlNode, XmlNodeVec);
637impl_vec_ord!(XmlNode, XmlNodeVec);
638impl_vec_hash!(XmlNode, XmlNodeVec);
639impl_vec_clone!(XmlNode, XmlNodeVec, XmlNodeVecDestructor);
640
641pub struct XmlComponent {
642    pub id: String,
643    /// DOM rendering component (boxed trait)
644    pub renderer: Box<dyn XmlComponentTrait>,
645    /// Whether this component should inherit variables from the parent scope
646    pub inherit_vars: bool,
647}
648
649impl core::fmt::Debug for XmlComponent {
650    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
651        f.debug_struct("XmlComponent")
652            .field("id", &self.id)
653            .field("args", &self.renderer.get_available_arguments())
654            .field("inherit_vars", &self.inherit_vars)
655            .finish()
656    }
657}
658
659/// Holds all XML components - builtin components
660pub struct XmlComponentMap {
661    /// Stores all known components that can be used during DOM rendering
662    /// + whether this component should inherit variables from the parent scope
663    pub components: Vec<XmlComponent>,
664}
665
666impl Default for XmlComponentMap {
667    fn default() -> Self {
668        let mut map = Self {
669            components: Vec::new(),
670        };
671        map.register_component(XmlComponent {
672            id: normalize_casing("body"),
673            renderer: Box::new(BodyRenderer::new()),
674            inherit_vars: true,
675        });
676        map.register_component(XmlComponent {
677            id: normalize_casing("div"),
678            renderer: Box::new(DivRenderer::new()),
679            inherit_vars: true,
680        });
681        map.register_component(XmlComponent {
682            id: normalize_casing("p"),
683            renderer: Box::new(TextRenderer::new()),
684            inherit_vars: true,
685        });
686        map
687    }
688}
689
690impl XmlComponentMap {
691    pub fn register_component(&mut self, comp: XmlComponent) {
692        self.components.push(comp);
693    }
694}
695
696#[derive(Debug, Clone, PartialEq)]
697pub enum DomXmlParseError {
698    /// No `<html></html>` node component present
699    NoHtmlNode,
700    /// Multiple `<html>` nodes
701    MultipleHtmlRootNodes,
702    /// No ´<body></body>´ node in the root HTML
703    NoBodyInHtml,
704    /// The DOM can only have one <body> node, not multiple.
705    MultipleBodyNodes,
706    /// Note: Sadly, the error type can only be a string because xmlparser
707    /// returns all errors as strings. There is an open PR to fix
708    /// this deficiency, but since the XML parsing is only needed for
709    /// hot-reloading and compiling, it doesn't matter that much.
710    Xml(XmlError),
711    /// Invalid hierarchy close tags, i.e `<app></p></app>`
712    MalformedHierarchy(AzString, AzString),
713    /// A component raised an error while rendering the DOM - holds the component name + error
714    /// string
715    RenderDom(RenderDomError),
716    /// Something went wrong while parsing an XML component
717    Component(ComponentParseError),
718    /// Error parsing global CSS in head node
719    Css(CssParseErrorOwned),
720}
721
722impl From<XmlError> for DomXmlParseError {
723    fn from(e: XmlError) -> Self {
724        Self::Xml(e)
725    }
726}
727
728impl From<ComponentParseError> for DomXmlParseError {
729    fn from(e: ComponentParseError) -> Self {
730        Self::Component(e)
731    }
732}
733
734impl From<RenderDomError> for DomXmlParseError {
735    fn from(e: RenderDomError) -> Self {
736        Self::RenderDom(e)
737    }
738}
739
740impl From<CssParseErrorOwned> for DomXmlParseError {
741    fn from(e: CssParseErrorOwned) -> Self {
742        Self::Css(e)
743    }
744}
745
746/// Error that can happen from the translation from XML code to Rust code -
747/// stringified, since it is only used for printing and is not exposed in the public API
748#[derive(Debug, Clone, PartialEq)]
749pub enum CompileError {
750    Dom(RenderDomError),
751    Xml(DomXmlParseError),
752    Css(CssParseErrorOwned),
753}
754
755impl From<ComponentError> for CompileError {
756    fn from(e: ComponentError) -> Self {
757        CompileError::Dom(RenderDomError::Component(e))
758    }
759}
760
761impl From<CssParseErrorOwned> for CompileError {
762    fn from(e: CssParseErrorOwned) -> Self {
763        CompileError::Css(e)
764    }
765}
766
767impl<'a> fmt::Display for CompileError {
768    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
769        use self::CompileError::*;
770        match self {
771            Dom(d) => write!(f, "{}", d),
772            Xml(s) => write!(f, "{}", s),
773            Css(s) => write!(f, "{}", s.to_shared()),
774        }
775    }
776}
777
778impl From<RenderDomError> for CompileError {
779    fn from(e: RenderDomError) -> Self {
780        CompileError::Dom(e)
781    }
782}
783
784impl From<DomXmlParseError> for CompileError {
785    fn from(e: DomXmlParseError) -> Self {
786        CompileError::Xml(e)
787    }
788}
789
790#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
791pub enum ComponentError {
792    /// While instantiating a component, a function argument
793    /// was encountered that the component won't use or react to.
794    UselessFunctionArgument(AzString, AzString, Vec<String>),
795    /// A certain node type can't be rendered, because the
796    /// renderer for this node is not available isn't available
797    ///
798    /// UnknownComponent(component_name)
799    UnknownComponent(AzString),
800}
801
802#[derive(Debug, Clone, PartialEq)]
803pub enum RenderDomError {
804    Component(ComponentError),
805    /// Error parsing the CSS on the component style
806    CssError(CssParseErrorOwned),
807}
808
809impl From<ComponentError> for RenderDomError {
810    fn from(e: ComponentError) -> Self {
811        Self::Component(e)
812    }
813}
814
815impl From<CssParseErrorOwned> for RenderDomError {
816    fn from(e: CssParseErrorOwned) -> Self {
817        Self::CssError(e)
818    }
819}
820
821#[derive(Debug, Clone, PartialEq)]
822pub enum ComponentParseError {
823    /// Given XmlNode is not a `<component />` node.
824    NotAComponent,
825    /// A `<component>` node does not have a `name` attribute.
826    UnnamedComponent,
827    /// Argument at position `usize` is either empty or has no name
828    MissingName(usize),
829    /// Argument at position `usize` with the name
830    /// `String` doesn't have a `: type`
831    MissingType(usize, AzString),
832    /// Component name may not contain a whitespace
833    /// (probably missing a `:` between the name and the type)
834    WhiteSpaceInComponentName(usize, AzString),
835    /// Component type may not contain a whitespace
836    /// (probably missing a `,` between the type and the next name)
837    WhiteSpaceInComponentType(usize, AzString, AzString),
838    /// Error parsing the <style> tag / CSS
839    CssError(CssParseErrorOwned),
840}
841
842impl<'a> fmt::Display for DomXmlParseError {
843    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
844        use self::DomXmlParseError::*;
845        match self {
846            NoHtmlNode => write!(
847                f,
848                "No <html> node found as the root of the file - empty file?"
849            ),
850            MultipleHtmlRootNodes => write!(
851                f,
852                "Multiple <html> nodes found as the root of the file - only one root node allowed"
853            ),
854            NoBodyInHtml => write!(
855                f,
856                "No <body> node found as a direct child of an <html> node - malformed DOM \
857                 hierarchy?"
858            ),
859            MultipleBodyNodes => write!(
860                f,
861                "Multiple <body> nodes present, only one <body> node is allowed"
862            ),
863            Xml(e) => write!(f, "Error parsing XML: {}", e),
864            MalformedHierarchy(got, expected) => write!(
865                f,
866                "Invalid </{}> tag: expected </{}>",
867                got.as_str(),
868                expected.as_str()
869            ),
870            RenderDom(e) => write!(f, "Error rendering DOM: {}", e),
871            Component(c) => write!(f, "Error parsing component in <head> node:\r\n{}", c),
872            Css(c) => write!(f, "Error parsing CSS in <head> node:\r\n{}", c.to_shared()),
873        }
874    }
875}
876
877impl<'a> fmt::Display for ComponentParseError {
878    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
879        use self::ComponentParseError::*;
880        match self {
881            NotAComponent => write!(f, "Expected <component/> node, found no such node"),
882            UnnamedComponent => write!(
883                f,
884                "Found <component/> tag with out a \"name\" attribute, component must have a name"
885            ),
886            MissingName(arg_pos) => write!(
887                f,
888                "Argument at position {} is either empty or has no name",
889                arg_pos
890            ),
891            MissingType(arg_pos, arg_name) => write!(
892                f,
893                "Argument \"{}\" at position {} doesn't have a `: type`",
894                arg_pos, arg_name
895            ),
896            WhiteSpaceInComponentName(arg_pos, arg_name_unparsed) => {
897                write!(
898                    f,
899                    "Missing `:` between the name and the type in argument {} (around \"{}\")",
900                    arg_pos, arg_name_unparsed
901                )
902            }
903            WhiteSpaceInComponentType(arg_pos, arg_name, arg_type_unparsed) => {
904                write!(
905                    f,
906                    "Missing `,` between two arguments (in argument {}, position {}, around \
907                     \"{}\")",
908                    arg_name, arg_pos, arg_type_unparsed
909                )
910            }
911            CssError(lsf) => write!(f, "Error parsing <style> tag: {}", lsf.to_shared()),
912        }
913    }
914}
915
916impl fmt::Display for ComponentError {
917    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
918        use self::ComponentError::*;
919        match self {
920            UselessFunctionArgument(k, v, available_args) => {
921                write!(
922                    f,
923                    "Useless component argument \"{}\": \"{}\" - available args are: {:#?}",
924                    k, v, available_args
925                )
926            }
927            UnknownComponent(name) => write!(f, "Unknown component: \"{}\"", name),
928        }
929    }
930}
931
932impl<'a> fmt::Display for RenderDomError {
933    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
934        use self::RenderDomError::*;
935        match self {
936            Component(c) => write!(f, "{}", c),
937            CssError(e) => write!(f, "Error parsing CSS in component: {}", e.to_shared()),
938        }
939    }
940}
941
942// --- Renderers for various built-in types
943
944/// Render for a `div` component
945#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
946pub struct DivRenderer {
947    node: XmlNode,
948}
949
950impl DivRenderer {
951    pub fn new() -> Self {
952        Self {
953            node: XmlNode::new("div"),
954        }
955    }
956}
957
958impl XmlComponentTrait for DivRenderer {
959    fn get_available_arguments(&self) -> ComponentArguments {
960        ComponentArguments::new()
961    }
962
963    fn render_dom(
964        &self,
965        _: &XmlComponentMap,
966        _: &FilteredComponentArguments,
967        _: &XmlTextContent,
968    ) -> Result<StyledDom, RenderDomError> {
969        Ok(Dom::div().style(CssApiWrapper::empty()))
970    }
971
972    fn compile_to_rust_code(
973        &self,
974        _: &XmlComponentMap,
975        _: &ComponentArguments,
976        _: &XmlTextContent,
977    ) -> Result<String, CompileError> {
978        Ok("Dom::div()".into())
979    }
980
981    fn get_xml_node(&self) -> XmlNode {
982        self.node.clone()
983    }
984}
985
986/// Render for a `body` component
987#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
988pub struct BodyRenderer {
989    node: XmlNode,
990}
991
992impl BodyRenderer {
993    pub fn new() -> Self {
994        Self {
995            node: XmlNode::new("body"),
996        }
997    }
998}
999
1000impl XmlComponentTrait for BodyRenderer {
1001    fn get_available_arguments(&self) -> ComponentArguments {
1002        ComponentArguments::new()
1003    }
1004
1005    fn render_dom(
1006        &self,
1007        _: &XmlComponentMap,
1008        _: &FilteredComponentArguments,
1009        _: &XmlTextContent,
1010    ) -> Result<StyledDom, RenderDomError> {
1011        Ok(Dom::body().style(CssApiWrapper::empty()))
1012    }
1013
1014    fn compile_to_rust_code(
1015        &self,
1016        _: &XmlComponentMap,
1017        _: &ComponentArguments,
1018        _: &XmlTextContent,
1019    ) -> Result<String, CompileError> {
1020        Ok("Dom::body()".into())
1021    }
1022
1023    fn get_xml_node(&self) -> XmlNode {
1024        self.node.clone()
1025    }
1026}
1027
1028/// Render for a `p` component
1029#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1030pub struct TextRenderer {
1031    node: XmlNode,
1032}
1033
1034impl TextRenderer {
1035    pub fn new() -> Self {
1036        Self {
1037            node: XmlNode::new("p"),
1038        }
1039    }
1040}
1041
1042impl XmlComponentTrait for TextRenderer {
1043    fn get_available_arguments(&self) -> ComponentArguments {
1044        ComponentArguments {
1045            args: ComponentArgumentTypes::default(),
1046            accepts_text: true, // important!
1047        }
1048    }
1049
1050    fn render_dom(
1051        &self,
1052        _: &XmlComponentMap,
1053        _: &FilteredComponentArguments,
1054        content: &XmlTextContent,
1055    ) -> Result<StyledDom, RenderDomError> {
1056        let content = content
1057            .as_ref()
1058            .map(|s| prepare_string(&s))
1059            .unwrap_or_default();
1060        Ok(Dom::text(content).style(CssApiWrapper::empty()))
1061    }
1062
1063    fn compile_to_rust_code(
1064        &self,
1065        _: &XmlComponentMap,
1066        args: &ComponentArguments,
1067        content: &XmlTextContent,
1068    ) -> Result<String, CompileError> {
1069        Ok(String::from("Dom::text(text)"))
1070    }
1071
1072    fn get_xml_node(&self) -> XmlNode {
1073        self.node.clone()
1074    }
1075}
1076
1077/// Compiles a XML `args="a: String, b: bool"` into a `["a" => "String", "b" => "bool"]` map
1078pub fn parse_component_arguments<'a>(
1079    input: &'a str,
1080) -> Result<ComponentArgumentTypes, ComponentParseError> {
1081    use self::ComponentParseError::*;
1082
1083    let mut args = ComponentArgumentTypes::default();
1084
1085    for (arg_idx, arg) in input.split(",").enumerate() {
1086        let mut colon_iterator = arg.split(":");
1087
1088        let arg_name = colon_iterator.next().ok_or(MissingName(arg_idx))?;
1089        let arg_name = arg_name.trim();
1090
1091        if arg_name.is_empty() {
1092            return Err(MissingName(arg_idx));
1093        }
1094        if arg_name.chars().any(char::is_whitespace) {
1095            return Err(WhiteSpaceInComponentName(arg_idx, arg_name.into()));
1096        }
1097
1098        let arg_type = colon_iterator
1099            .next()
1100            .ok_or(MissingType(arg_idx, arg_name.into()))?;
1101        let arg_type = arg_type.trim();
1102
1103        if arg_type.is_empty() {
1104            return Err(MissingType(arg_idx, arg_name.into()));
1105        }
1106
1107        if arg_type.chars().any(char::is_whitespace) {
1108            return Err(WhiteSpaceInComponentType(
1109                arg_idx,
1110                arg_name.into(),
1111                arg_type.into(),
1112            ));
1113        }
1114
1115        let arg_name = normalize_casing(arg_name);
1116        let arg_type = arg_type.to_string();
1117
1118        args.push((arg_name, arg_type));
1119    }
1120
1121    Ok(args)
1122}
1123
1124/// Filters the XML attributes of a component given XmlAttributeMap
1125pub fn validate_and_filter_component_args(
1126    xml_attributes: &XmlAttributeMap,
1127    valid_args: &ComponentArguments,
1128) -> Result<FilteredComponentArguments, ComponentError> {
1129    let mut map = FilteredComponentArguments {
1130        types: ComponentArgumentTypes::default(),
1131        values: BTreeMap::new(),
1132        accepts_text: valid_args.accepts_text,
1133    };
1134
1135    for AzStringPair { key, value } in xml_attributes.as_ref().iter() {
1136        let xml_attribute_name = key;
1137        let xml_attribute_value = value;
1138        if let Some(valid_arg_type) = valid_args
1139            .args
1140            .iter()
1141            .find(|s| s.0 == xml_attribute_name.as_str())
1142            .map(|q| &q.1)
1143        {
1144            map.types.push((
1145                xml_attribute_name.as_str().to_string(),
1146                valid_arg_type.clone(),
1147            ));
1148            map.values.insert(
1149                xml_attribute_name.as_str().to_string(),
1150                xml_attribute_value.as_str().to_string(),
1151            );
1152        } else if DEFAULT_ARGS.contains(&xml_attribute_name.as_str()) {
1153            // no error, but don't insert the attribute name
1154            map.values.insert(
1155                xml_attribute_name.as_str().to_string(),
1156                xml_attribute_value.as_str().to_string(),
1157            );
1158        } else {
1159            // key was not expected for this component
1160            let keys = valid_args.args.iter().map(|s| s.0.clone()).collect();
1161            return Err(ComponentError::UselessFunctionArgument(
1162                xml_attribute_name.clone(),
1163                xml_attribute_value.clone(),
1164                keys,
1165            ));
1166        }
1167    }
1168
1169    Ok(map)
1170}
1171
1172/// Find the one and only `<body>` node, return error if
1173/// there is no app node or there are multiple app nodes
1174pub fn get_html_node<'a>(root_nodes: &'a [XmlNode]) -> Result<&'a XmlNode, DomXmlParseError> {
1175    let mut html_node_iterator = root_nodes.iter().filter(|node| {
1176        let node_type_normalized = normalize_casing(&node.node_type);
1177        &node_type_normalized == "html"
1178    });
1179
1180    let html_node = html_node_iterator
1181        .next()
1182        .ok_or(DomXmlParseError::NoHtmlNode)?;
1183    if html_node_iterator.next().is_some() {
1184        Err(DomXmlParseError::MultipleHtmlRootNodes)
1185    } else {
1186        Ok(html_node)
1187    }
1188}
1189
1190/// Find the one and only `<body>` node, return error if
1191/// there is no app node or there are multiple app nodes
1192pub fn get_body_node<'a>(root_nodes: &'a [XmlNode]) -> Result<&'a XmlNode, DomXmlParseError> {
1193    let mut body_node_iterator = root_nodes.iter().filter(|node| {
1194        let node_type_normalized = normalize_casing(&node.node_type);
1195        &node_type_normalized == "body"
1196    });
1197
1198    let body_node = body_node_iterator
1199        .next()
1200        .ok_or(DomXmlParseError::NoBodyInHtml)?;
1201    if body_node_iterator.next().is_some() {
1202        Err(DomXmlParseError::MultipleBodyNodes)
1203    } else {
1204        Ok(body_node)
1205    }
1206}
1207
1208static DEFAULT_STR: &str = "";
1209
1210/// Searches in the the `root_nodes` for a `node_type`, convenience function in order to
1211/// for example find the first <blah /> node in all these nodes.
1212pub fn find_node_by_type<'a>(root_nodes: &'a [XmlNode], node_type: &str) -> Option<&'a XmlNode> {
1213    root_nodes
1214        .iter()
1215        .find(|n| normalize_casing(&n.node_type).as_str() == node_type)
1216}
1217
1218pub fn find_attribute<'a>(node: &'a XmlNode, attribute: &str) -> Option<&'a AzString> {
1219    node.attributes
1220        .iter()
1221        .find(|n| normalize_casing(&n.key.as_str()).as_str() == attribute)
1222        .map(|s| &s.value)
1223}
1224
1225/// Normalizes input such as `abcDef`, `AbcDef`, `abc-def` to the normalized form of `abc_def`
1226pub fn normalize_casing(input: &str) -> String {
1227    let mut words: Vec<String> = Vec::new();
1228    let mut cur_str = Vec::new();
1229
1230    for ch in input.chars() {
1231        if ch.is_uppercase() || ch == '_' || ch == '-' {
1232            if !cur_str.is_empty() {
1233                words.push(cur_str.iter().collect());
1234                cur_str.clear();
1235            }
1236            if ch.is_uppercase() {
1237                cur_str.extend(ch.to_lowercase());
1238            }
1239        } else {
1240            cur_str.extend(ch.to_lowercase());
1241        }
1242    }
1243
1244    if !cur_str.is_empty() {
1245        words.push(cur_str.iter().collect());
1246        cur_str.clear();
1247    }
1248
1249    words.join("_")
1250}
1251
1252/// Given a root node, traverses along the hierarchy, and returns a
1253/// mutable reference to the last child node of the root node
1254#[allow(trivial_casts)]
1255pub fn get_item<'a>(hierarchy: &[usize], root_node: &'a mut XmlNode) -> Option<&'a mut XmlNode> {
1256    let mut hierarchy = hierarchy.to_vec();
1257    hierarchy.reverse();
1258    let item = match hierarchy.pop() {
1259        Some(s) => s,
1260        None => return Some(root_node),
1261    };
1262    let node = root_node.children.as_mut().get_mut(item)?;
1263    get_item_internal(&mut hierarchy, node)
1264}
1265
1266fn get_item_internal<'a>(
1267    hierarchy: &mut Vec<usize>,
1268    root_node: &'a mut XmlNode,
1269) -> Option<&'a mut XmlNode> {
1270    if hierarchy.is_empty() {
1271        return Some(root_node);
1272    }
1273    let cur_item = match hierarchy.pop() {
1274        Some(s) => s,
1275        None => return Some(root_node),
1276    };
1277    let node = root_node.children.as_mut().get_mut(cur_item)?;
1278    get_item_internal(hierarchy, node)
1279}
1280
1281/// Parses an XML string and returns a `StyledDom` with the components instantiated in the
1282/// `<app></app>`
1283pub fn str_to_dom<'a>(
1284    root_nodes: &'a [XmlNode],
1285    component_map: &'a mut XmlComponentMap,
1286    max_width: Option<f32>,
1287) -> Result<StyledDom, DomXmlParseError> {
1288    let html_node = get_html_node(root_nodes)?;
1289    let body_node = get_body_node(html_node.children.as_ref())?;
1290
1291    let mut global_style = None;
1292
1293    if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
1294        // parse all dynamic XML components from the head node
1295        for node in head_node.children.as_ref() {
1296            match DynamicXmlComponent::new(node) {
1297                Ok(node) => {
1298                    let node_name = node.name.clone();
1299                    component_map.register_component(XmlComponent {
1300                        id: normalize_casing(&node_name),
1301                        renderer: Box::new(node),
1302                        inherit_vars: false,
1303                    });
1304                }
1305                Err(ComponentParseError::NotAComponent) => {} // not a <component /> node, ignore
1306                Err(e) => return Err(e.into()),               /* Error during parsing the XML
1307                                                                * component, bail */
1308            }
1309        }
1310
1311        // parse the <style></style> tag contents, if present
1312        if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
1313            if let Some(text) = style_node.text.as_ref().map(|s| s.as_str()) {
1314                let parsed_css = CssApiWrapper::from_string(text.clone().into());
1315                global_style = Some(parsed_css);
1316            }
1317        }
1318    }
1319
1320    render_dom_from_body_node(&body_node, global_style, component_map, max_width)
1321        .map_err(|e| e.into())
1322}
1323
1324/// Parses an XML string and returns a `String`, which contains the Rust source code
1325/// (i.e. it compiles the XML to valid Rust)
1326pub fn str_to_rust_code<'a>(
1327    root_nodes: &'a [XmlNode],
1328    imports: &str,
1329    component_map: &'a mut XmlComponentMap,
1330) -> Result<String, CompileError> {
1331    let html_node = get_html_node(&root_nodes)?;
1332    let body_node = get_body_node(html_node.children.as_ref())?;
1333    let mut global_style = Css::empty();
1334
1335    if let Some(head_node) = html_node
1336        .children
1337        .as_ref()
1338        .iter()
1339        .find(|n| normalize_casing(&n.node_type).as_str() == "head")
1340    {
1341        for node in head_node.children.as_ref() {
1342            match DynamicXmlComponent::new(node) {
1343                Ok(node) => {
1344                    let node_name = node.name.clone();
1345                    component_map.register_component(XmlComponent {
1346                        id: normalize_casing(&node_name),
1347                        renderer: Box::new(node),
1348                        inherit_vars: false,
1349                    });
1350                }
1351                Err(ComponentParseError::NotAComponent) => {} // not a <component /> node, ignore
1352                Err(e) => return Err(CompileError::Xml(e.into())), /* Error during parsing the XML
1353                                                                * component, bail */
1354            }
1355        }
1356
1357        if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
1358            if let Some(text) = style_node.text.as_ref().map(|s| s.as_str()) {
1359                let parsed_css =
1360                    azul_css::parser::new_from_str(&text).map_err(|e| e.to_contained())?;
1361                global_style = parsed_css;
1362            }
1363        }
1364    }
1365
1366    global_style.sort_by_specificity();
1367
1368    let mut css_blocks = BTreeMap::new();
1369    let mut extra_blocks = VecContents::default();
1370    let app_source = compile_body_node_to_rust_code(
1371        &body_node,
1372        component_map,
1373        &mut extra_blocks,
1374        &mut css_blocks,
1375        &global_style,
1376        CssMatcher {
1377            path: Vec::new(),
1378            indices_in_parent: vec![0],
1379            children_length: vec![body_node.children.as_ref().len()],
1380        },
1381    )?;
1382
1383    let app_source = app_source
1384        .lines()
1385        .map(|l| format!("        {}", l))
1386        .collect::<Vec<String>>()
1387        .join("\r\n");
1388
1389    let t = "    ";
1390    let css_blocks = css_blocks
1391        .iter()
1392        .map(|(k, v)| {
1393            let v = v
1394                .lines()
1395                .map(|l| format!("{}{}{}", t, t, l))
1396                .collect::<Vec<String>>()
1397                .join("\r\n");
1398
1399            format!(
1400                "    const {}_PROPERTIES: &[NodeDataInlineCssProperty] = \
1401                 &[\r\n{}\r\n{}];\r\n{}const {}: NodeDataInlineCssPropertyVec = \
1402                 NodeDataInlineCssPropertyVec::from_const_slice({}_PROPERTIES);",
1403                k, v, t, t, k, k
1404            )
1405        })
1406        .collect::<Vec<_>>()
1407        .join(&format!("{}\r\n\r\n", t));
1408
1409    let mut extra_block_string = extra_blocks.format(1);
1410
1411    let main_func = "
1412
1413use azul::{
1414    app::{App, AppConfig, LayoutSolver},
1415    css::Css,
1416    style::StyledDom,
1417    callbacks::{RefAny, LayoutCallbackInfo},
1418    window::{WindowCreateOptions, WindowFrame},
1419};
1420
1421struct Data { }
1422
1423extern \"C\" fn render(_: &mut RefAny, _: &mut LayoutCallbackInfo) -> StyledDom {
1424    crate::ui::render()
1425    .style(Css::empty()) // styles are applied inline
1426}
1427
1428fn main() {
1429    let app = App::new(RefAny::new(Data { }), AppConfig::new(LayoutSolver::Default));
1430    let mut window = WindowCreateOptions::new(render);
1431    window.state.flags.frame = WindowFrame::Maximized;
1432    app.run(window);
1433}";
1434
1435    let source_code = format!(
1436        "#![windows_subsystem = \"windows\"]\r\n//! Auto-generated UI source \
1437         code\r\n{}\r\n{}\r\n\r\n{}{}",
1438        imports,
1439        compile_components(compile_components_to_rust_code(component_map)?),
1440        format!(
1441            "#[allow(unused_imports)]\r\npub mod ui {{
1442
1443    pub use crate::components::*;
1444
1445    use azul::css::*;
1446    use azul::str::String as AzString;
1447    use azul::vec::{{
1448        DomVec, IdOrClassVec, NodeDataInlineCssPropertyVec,
1449        StyleBackgroundSizeVec, StyleBackgroundRepeatVec,
1450        StyleBackgroundContentVec, StyleTransformVec,
1451        StyleFontFamilyVec, StyleBackgroundPositionVec,
1452        NormalizedLinearColorStopVec, NormalizedRadialColorStopVec,
1453    }};
1454    use azul::dom::{{
1455        Dom, IdOrClass, TabIndex,
1456        IdOrClass::{{Id, Class}},
1457        NodeDataInlineCssProperty,
1458    }};\r\n\r\n{}\r\n\r\n{}
1459
1460    pub fn render() -> Dom {{\r\n{}\r\n    }}\r\n}}",
1461            extra_block_string, css_blocks, app_source
1462        ),
1463        main_func,
1464    );
1465
1466    Ok(source_code)
1467}
1468
1469// Compile all components to source code
1470pub fn compile_components(
1471    components: Vec<(
1472        ComponentName,
1473        CompiledComponent,
1474        ComponentArguments,
1475        BTreeMap<String, String>,
1476    )>,
1477) -> String {
1478    let cs = components
1479        .iter()
1480        .map(|(name, function_body, function_args, css_blocks)| {
1481            let name = &normalize_casing(&name);
1482            let f = compile_component(name, function_args, function_body)
1483                .lines()
1484                .map(|l| format!("    {}", l))
1485                .collect::<Vec<String>>()
1486                .join("\r\n");
1487
1488            // let css_blocks = ...
1489
1490            format!(
1491                "#[allow(unused_imports)]\r\npub mod {} {{\r\n    use azul::dom::Dom;\r\n    use \
1492                 azul::str::String as AzString;\r\n{}\r\n}}",
1493                name, f
1494            )
1495        })
1496        .collect::<Vec<String>>()
1497        .join("\r\n\r\n");
1498
1499    let cs = cs
1500        .lines()
1501        .map(|l| format!("    {}", l))
1502        .collect::<Vec<String>>()
1503        .join("\r\n");
1504
1505    if cs.is_empty() {
1506        cs
1507    } else {
1508        format!("pub mod components {{\r\n{}\r\n}}", cs)
1509    }
1510}
1511
1512pub fn format_component_args(component_args: &ComponentArgumentTypes) -> String {
1513    let mut args = component_args
1514        .iter()
1515        .map(|(arg_name, arg_type)| format!("{}: {}", arg_name, arg_type))
1516        .collect::<Vec<String>>();
1517
1518    args.sort_by(|a, b| b.cmp(&a));
1519
1520    args.join(", ")
1521}
1522
1523pub fn compile_component(
1524    component_name: &str,
1525    component_args: &ComponentArguments,
1526    component_function_body: &str,
1527) -> String {
1528    let component_name = &normalize_casing(&component_name);
1529    let function_args = format_component_args(&component_args.args);
1530    let component_function_body = component_function_body
1531        .lines()
1532        .map(|l| format!("    {}", l))
1533        .collect::<Vec<String>>()
1534        .join("\r\n");
1535    let should_inline = component_function_body.lines().count() == 1;
1536    format!(
1537        "{}pub fn render({}{}{}) -> Dom {{\r\n{}\r\n}}",
1538        if should_inline { "#[inline]\r\n" } else { "" },
1539        // pass the text content as the first
1540        if component_args.accepts_text {
1541            "text: AzString"
1542        } else {
1543            ""
1544        },
1545        if function_args.is_empty() || !component_args.accepts_text {
1546            ""
1547        } else {
1548            ", "
1549        },
1550        function_args,
1551        component_function_body,
1552    )
1553}
1554
1555pub fn render_dom_from_body_node<'a>(
1556    body_node: &'a XmlNode,
1557    mut global_css: Option<CssApiWrapper>,
1558    component_map: &'a XmlComponentMap,
1559    max_width: Option<f32>,
1560) -> Result<StyledDom, RenderDomError> {
1561    // Don't actually render the <body></body> node itself
1562    let mut dom = StyledDom::default();
1563
1564    if let Some(max_width) = max_width {
1565        dom.restyle(CssApiWrapper::from_string(
1566            format!("body, html {{ max-width: {max_width}px; }}").into(),
1567        ));
1568    }
1569
1570    for child_node in body_node.children.as_ref() {
1571        dom.append_child(render_dom_from_body_node_inner(
1572            child_node,
1573            component_map,
1574            &FilteredComponentArguments::default(),
1575        )?);
1576    }
1577
1578    if let Some(global_css) = global_css.clone() {
1579        dom.restyle(global_css); // apply the CSS again
1580    }
1581
1582    Ok(dom)
1583}
1584
1585/// Takes a single (expanded) app node and renders the DOM or returns an error
1586pub fn render_dom_from_body_node_inner<'a>(
1587    xml_node: &'a XmlNode,
1588    component_map: &'a XmlComponentMap,
1589    parent_xml_attributes: &FilteredComponentArguments,
1590) -> Result<StyledDom, RenderDomError> {
1591    let component_name = normalize_casing(&xml_node.node_type);
1592
1593    let xml_component = component_map
1594        .components
1595        .iter()
1596        .find(|s| normalize_casing(&s.id) == component_name)
1597        .ok_or(ComponentError::UnknownComponent(
1598            component_name.clone().into(),
1599        ))?;
1600
1601    // Arguments of the current node
1602    let available_function_args = xml_component.renderer.get_available_arguments();
1603    let mut filtered_xml_attributes =
1604        validate_and_filter_component_args(&xml_node.attributes, &available_function_args)?;
1605
1606    if xml_component.inherit_vars {
1607        // Append all variables that are in scope for the parent node
1608        filtered_xml_attributes
1609            .types
1610            .extend(parent_xml_attributes.types.clone().into_iter());
1611    }
1612
1613    // Instantiate the parent arguments in the current child arguments
1614    for v in filtered_xml_attributes.types.iter_mut() {
1615        v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.types).to_string();
1616    }
1617
1618    let text = xml_node
1619        .text
1620        .as_ref()
1621        .map(|t| AzString::from(format_args_dynamic(t, &filtered_xml_attributes.types)));
1622
1623    let mut dom =
1624        xml_component
1625            .renderer
1626            .render_dom(component_map, &filtered_xml_attributes, &text.into())?;
1627    set_attributes(&mut dom, &xml_node.attributes, &filtered_xml_attributes);
1628
1629    for child_node in xml_node.children.as_ref() {
1630        dom.append_child(render_dom_from_body_node_inner(
1631            child_node,
1632            component_map,
1633            &filtered_xml_attributes,
1634        )?);
1635    }
1636
1637    Ok(dom)
1638}
1639
1640pub fn set_attributes(
1641    dom: &mut StyledDom,
1642    xml_attributes: &XmlAttributeMap,
1643    filtered_xml_attributes: &FilteredComponentArguments,
1644) {
1645    use crate::dom::{
1646        IdOrClass::{Class, Id},
1647        TabIndex,
1648    };
1649
1650    let mut ids_and_classes = Vec::new();
1651    let dom_root = match dom.root.into_crate_internal() {
1652        Some(s) => s,
1653        None => return,
1654    };
1655    let node_data = &mut dom.node_data.as_container_mut()[dom_root];
1656
1657    if let Some(ids) = xml_attributes.get_key("id") {
1658        for id in ids.split_whitespace() {
1659            ids_and_classes.push(Id(
1660                format_args_dynamic(id, &filtered_xml_attributes.types).into()
1661            ));
1662        }
1663    }
1664
1665    if let Some(classes) = xml_attributes.get_key("class") {
1666        for class in classes.split_whitespace() {
1667            ids_and_classes.push(Class(
1668                format_args_dynamic(class, &filtered_xml_attributes.types).into(),
1669            ));
1670        }
1671    }
1672
1673    node_data.set_ids_and_classes(ids_and_classes.into());
1674
1675    if let Some(focusable) = xml_attributes
1676        .get_key("focusable")
1677        .map(|f| format_args_dynamic(f.as_str(), &filtered_xml_attributes.types))
1678        .and_then(|f| parse_bool(&f))
1679    {
1680        match focusable {
1681            true => node_data.set_tab_index(TabIndex::Auto),
1682            false => node_data.set_tab_index(TabIndex::NoKeyboardFocus.into()),
1683        }
1684    }
1685
1686    if let Some(tab_index) = xml_attributes
1687        .get_key("tabindex")
1688        .map(|val| format_args_dynamic(val, &filtered_xml_attributes.types))
1689        .and_then(|val| val.parse::<isize>().ok())
1690    {
1691        match tab_index {
1692            0 => node_data.set_tab_index(TabIndex::Auto),
1693            i if i > 0 => node_data.set_tab_index(TabIndex::OverrideInParent(i as u32)),
1694            _ => node_data.set_tab_index(TabIndex::NoKeyboardFocus),
1695        }
1696    }
1697
1698    if let Some(style) = xml_attributes.get_key("style") {
1699        let css_key_map = azul_css::get_css_key_map();
1700        let mut attributes = Vec::new();
1701        for s in style.as_str().split(";") {
1702            let mut s = s.split(":");
1703            let key = match s.next() {
1704                Some(s) => s,
1705                None => continue,
1706            };
1707            let value = match s.next() {
1708                Some(s) => s,
1709                None => continue,
1710            };
1711            azul_css::parser::parse_css_declaration(
1712                key.trim(),
1713                value.trim(),
1714                (ErrorLocation::default(), ErrorLocation::default()),
1715                &css_key_map,
1716                &mut Vec::new(),
1717                &mut attributes,
1718            );
1719        }
1720
1721        let props = attributes
1722            .into_iter()
1723            .filter_map(|s| {
1724                use crate::dom::NodeDataInlineCssProperty::*;
1725                match s {
1726                    CssDeclaration::Static(s) => Some(Normal(s)),
1727                    _ => return None,
1728                }
1729            })
1730            .collect::<Vec<_>>();
1731
1732        node_data.set_inline_css_props(props.into());
1733    }
1734}
1735
1736pub fn set_stringified_attributes(
1737    dom_string: &mut String,
1738    xml_attributes: &XmlAttributeMap,
1739    filtered_xml_attributes: &ComponentArgumentTypes,
1740    tabs: usize,
1741) {
1742    let t0 = String::from("    ").repeat(tabs);
1743    let t = String::from("    ").repeat(tabs + 1);
1744
1745    // push ids and classes
1746    let mut ids_and_classes = String::new();
1747
1748    for id in xml_attributes
1749        .get_key("id")
1750        .map(|s| s.split_whitespace().collect::<Vec<_>>())
1751        .unwrap_or_default()
1752    {
1753        ids_and_classes.push_str(&format!(
1754            "{}    Id(AzString::from_const_str(\"{}\")),\r\n",
1755            t0,
1756            format_args_dynamic(id, &filtered_xml_attributes)
1757        ));
1758    }
1759
1760    for class in xml_attributes
1761        .get_key("class")
1762        .map(|s| s.split_whitespace().collect::<Vec<_>>())
1763        .unwrap_or_default()
1764    {
1765        ids_and_classes.push_str(&format!(
1766            "{}    Class(AzString::from_const_str(\"{}\")),\r\n",
1767            t0,
1768            format_args_dynamic(class, &filtered_xml_attributes)
1769        ));
1770    }
1771
1772    if !ids_and_classes.is_empty() {
1773        use crate::css::GetHash;
1774        let id = ids_and_classes.get_hash();
1775        dom_string.push_str(&format!(
1776            "\r\n{t0}.with_ids_and_classes({{\r\n{t}const IDS_AND_CLASSES_{id}: &[IdOrClass] = \
1777             &[\r\n{t}{ids_and_classes}\r\n{t}];\r\\
1778             n{t}IdOrClassVec::from_const_slice(IDS_AND_CLASSES_{id})\r\n{t0}}})",
1779            t0 = t0,
1780            t = t,
1781            ids_and_classes = ids_and_classes,
1782            id = id
1783        ));
1784    }
1785
1786    if let Some(focusable) = xml_attributes
1787        .get_key("focusable")
1788        .map(|f| format_args_dynamic(f, &filtered_xml_attributes))
1789        .and_then(|f| parse_bool(&f))
1790    {
1791        match focusable {
1792            true => dom_string.push_str(&format!("\r\n{}.with_tab_index(TabIndex::Auto)", t)),
1793            false => dom_string.push_str(&format!(
1794                "\r\n{}.with_tab_index(TabIndex::NoKeyboardFocus)",
1795                t
1796            )),
1797        }
1798    }
1799
1800    if let Some(tab_index) = xml_attributes
1801        .get_key("tabindex")
1802        .map(|val| format_args_dynamic(val, &filtered_xml_attributes))
1803        .and_then(|val| val.parse::<isize>().ok())
1804    {
1805        match tab_index {
1806            0 => dom_string.push_str(&format!("\r\n{}.with_tab_index(TabIndex::Auto)", t)),
1807            i if i > 0 => dom_string.push_str(&format!(
1808                "\r\n{}.with_tab_index(TabIndex::OverrideInParent({}))",
1809                t, i as usize
1810            )),
1811            _ => dom_string.push_str(&format!(
1812                "\r\n{}.with_tab_index(TabIndex::NoKeyboardFocus)",
1813                t
1814            )),
1815        }
1816    }
1817}
1818
1819/// Item of a split string - either a variable name or a string
1820#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1821pub enum DynamicItem {
1822    Var(String),
1823    Str(String),
1824}
1825
1826/// Splits a string into formatting arguments
1827/// ```rust
1828/// # use azul_core::xml::DynamicItem::*;
1829/// # use azul_core::xml::split_dynamic_string;
1830/// let s = "hello {a}, {b}{{ {c} }}";
1831/// let split = split_dynamic_string(s);
1832/// let output = vec![
1833///     Str("hello ".to_string()),
1834///     Var("a".to_string()),
1835///     Str(", ".to_string()),
1836///     Var("b".to_string()),
1837///     Str("{ ".to_string()),
1838///     Var("c".to_string()),
1839///     Str(" }".to_string()),
1840/// ];
1841/// assert_eq!(output, split);
1842/// ```
1843pub fn split_dynamic_string(input: &str) -> Vec<DynamicItem> {
1844    use self::DynamicItem::*;
1845
1846    let input: Vec<char> = input.chars().collect();
1847    let input_chars_len = input.len();
1848
1849    let mut items = Vec::new();
1850    let mut current_idx = 0;
1851    let mut last_idx = 0;
1852
1853    while current_idx < input_chars_len {
1854        let c = input[current_idx];
1855        match c {
1856            '{' if input.get(current_idx + 1).copied() != Some('{') => {
1857                // variable start, search until next closing brace or whitespace or end of string
1858                let mut start_offset = 1;
1859                let mut has_found_variable = false;
1860                while let Some(c) = input.get(current_idx + start_offset) {
1861                    if c.is_whitespace() {
1862                        break;
1863                    }
1864                    if *c == '}' && input.get(current_idx + start_offset + 1).copied() != Some('}')
1865                    {
1866                        start_offset += 1;
1867                        has_found_variable = true;
1868                        break;
1869                    }
1870                    start_offset += 1;
1871                }
1872
1873                // advance current_idx accordingly
1874                // on fail, set cursor to end
1875                // set last_idx accordingly
1876                if has_found_variable {
1877                    if last_idx != current_idx {
1878                        items.push(Str(input[last_idx..current_idx].iter().collect()));
1879                    }
1880
1881                    // subtract 1 from start for opening brace, one from end for closing brace
1882                    items.push(Var(input
1883                        [(current_idx + 1)..(current_idx + start_offset - 1)]
1884                        .iter()
1885                        .collect()));
1886                    current_idx = current_idx + start_offset;
1887                    last_idx = current_idx;
1888                } else {
1889                    current_idx += start_offset;
1890                }
1891            }
1892            _ => {
1893                current_idx += 1;
1894            }
1895        }
1896    }
1897
1898    if current_idx != last_idx {
1899        items.push(Str(input[last_idx..].iter().collect()));
1900    }
1901
1902    for item in &mut items {
1903        // replace {{ with { in strings
1904        if let Str(s) = item {
1905            *s = s.replace("{{", "{").replace("}}", "}");
1906        }
1907    }
1908
1909    items
1910}
1911
1912/// Combines the split string back into its original form while replacing the variables with their
1913/// values
1914///
1915/// let variables = btreemap!{ "a" => "value1", "b" => "value2" };
1916/// [Str("hello "), Var("a"), Str(", "), Var("b"), Str("{ "), Var("c"), Str(" }}")]
1917/// => "hello value1, valuec{ {c} }"
1918pub fn combine_and_replace_dynamic_items(
1919    input: &[DynamicItem],
1920    variables: &ComponentArgumentTypes,
1921) -> String {
1922    let mut s = String::new();
1923
1924    for item in input {
1925        match item {
1926            DynamicItem::Var(v) => {
1927                let variable_name = normalize_casing(v.trim());
1928                match variables
1929                    .iter()
1930                    .find(|s| s.0 == variable_name)
1931                    .map(|q| &q.1)
1932                {
1933                    Some(resolved_var) => {
1934                        s.push_str(&resolved_var);
1935                    }
1936                    None => {
1937                        s.push('{');
1938                        s.push_str(v);
1939                        s.push('}');
1940                    }
1941                }
1942            }
1943            DynamicItem::Str(dynamic_str) => {
1944                s.push_str(&dynamic_str);
1945            }
1946        }
1947    }
1948
1949    s
1950}
1951
1952/// Given a string and a key => value mapping, replaces parts of the string with the value, i.e.:
1953///
1954/// ```rust
1955/// # use std::collections::BTreeMap;
1956/// # use azul_core::xml::format_args_dynamic;
1957/// let mut variables = vec![
1958///     (String::from("a"), String::from("value1")),
1959///     (String::from("b"), String::from("value2")),
1960/// ];
1961///
1962/// let initial = "hello {a}, {b}{{ {c} }}";
1963/// let expected = "hello value1, value2{ {c} }".to_string();
1964/// assert_eq!(format_args_dynamic(initial, &variables), expected);
1965/// ```
1966///
1967/// Note: the number (0, 1, etc.) is the order of the argument, it is irrelevant for
1968/// runtime formatting, only important for keeping the component / function arguments
1969/// in order when compiling the arguments to Rust code
1970pub fn format_args_dynamic(input: &str, variables: &ComponentArgumentTypes) -> String {
1971    let dynamic_str_items = split_dynamic_string(input);
1972    combine_and_replace_dynamic_items(&dynamic_str_items, variables)
1973}
1974
1975// NOTE: Two sequential returns count as a single return, while single returns get ignored.
1976pub fn prepare_string(input: &str) -> String {
1977    const SPACE: &str = " ";
1978    const RETURN: &str = "\n";
1979
1980    let input = input.trim();
1981
1982    if input.is_empty() {
1983        return String::new();
1984    }
1985
1986    let input = input.replace("&lt;", "<");
1987    let input = input.replace("&gt;", ">");
1988
1989    let input_len = input.len();
1990    let mut final_lines: Vec<String> = Vec::new();
1991    let mut last_line_was_empty = false;
1992
1993    for line in input.lines() {
1994        let line = line.trim();
1995        let line = line.replace("&nbsp;", " ");
1996        let current_line_is_empty = line.is_empty();
1997
1998        if !current_line_is_empty {
1999            if last_line_was_empty {
2000                final_lines.push(format!("{}{}", RETURN, line));
2001            } else {
2002                final_lines.push(line.to_string());
2003            }
2004        }
2005
2006        last_line_was_empty = current_line_is_empty;
2007    }
2008
2009    let line_len = final_lines.len();
2010    let mut target = String::with_capacity(input_len);
2011    for (line_idx, line) in final_lines.iter().enumerate() {
2012        if !(line.starts_with(RETURN) || line_idx == 0 || line_idx == line_len.saturating_sub(1)) {
2013            target.push_str(SPACE);
2014        }
2015        target.push_str(line);
2016    }
2017    target
2018}
2019
2020/// Parses a string ("true" or "false")
2021pub fn parse_bool(input: &str) -> Option<bool> {
2022    match input {
2023        "true" => Some(true),
2024        "false" => Some(false),
2025        _ => None,
2026    }
2027}
2028
2029pub fn render_component_inner<'a>(
2030    map: &mut Vec<(
2031        ComponentName,
2032        CompiledComponent,
2033        ComponentArguments,
2034        BTreeMap<String, String>,
2035    )>,
2036    component_name: String,
2037    xml_component: &'a XmlComponent,
2038    component_map: &'a XmlComponentMap,
2039    parent_xml_attributes: &ComponentArguments,
2040    tabs: usize,
2041) -> Result<(), CompileError> {
2042    let t = String::from("    ").repeat(tabs - 1);
2043    let t1 = String::from("    ").repeat(tabs);
2044
2045    let component_name = normalize_casing(&component_name);
2046    let xml_node = xml_component.renderer.get_xml_node();
2047
2048    let mut css = match find_node_by_type(xml_node.children.as_ref(), "style")
2049        .and_then(|style_node| style_node.text.as_ref().map(|s| s.as_str()))
2050    {
2051        Some(text) => azul_css::parser::new_from_str(&text).map_err(|e| e.to_contained())?,
2052        None => Css::empty(),
2053    };
2054
2055    css.sort_by_specificity();
2056
2057    // Arguments of the current node
2058    let available_function_arg_types = xml_component.renderer.get_available_arguments();
2059    // Types of the filtered xml arguments, important, only for Rust code compilation
2060    let mut filtered_xml_attributes = available_function_arg_types.clone();
2061
2062    if xml_component.inherit_vars {
2063        // Append all variables that are in scope for the parent node
2064        filtered_xml_attributes
2065            .args
2066            .extend(parent_xml_attributes.args.clone().into_iter());
2067    }
2068
2069    // Instantiate the parent arguments in the current child arguments
2070    for v in filtered_xml_attributes.args.iter_mut() {
2071        v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.args).to_string();
2072    }
2073
2074    let text = xml_node
2075        .text
2076        .as_ref()
2077        .map(|t| AzString::from(format_args_dynamic(t, &filtered_xml_attributes.args)));
2078
2079    let mut dom_string = xml_component.renderer.compile_to_rust_code(
2080        component_map,
2081        &filtered_xml_attributes,
2082        &text.into(),
2083    )?;
2084
2085    set_stringified_attributes(
2086        &mut dom_string,
2087        &xml_node.attributes,
2088        &filtered_xml_attributes.args,
2089        tabs,
2090    );
2091
2092    // TODO
2093    let matcher = CssMatcher {
2094        path: vec![CssPathSelector::Type(NodeTypeTag::Body)],
2095        indices_in_parent: Vec::new(),
2096        children_length: Vec::new(),
2097    };
2098
2099    let mut css_blocks = BTreeMap::new();
2100    let mut extra_blocks = VecContents::default();
2101
2102    if !xml_node.children.as_ref().is_empty() {
2103        dom_string.push_str(&format!(
2104            "\r\n{}.with_children(DomVec::from_vec(vec![\r\n",
2105            t
2106        ));
2107        for (child_idx, child_node) in xml_node.children.as_ref().iter().enumerate() {
2108            let mut matcher = matcher.clone();
2109            matcher.indices_in_parent.push(child_idx);
2110            matcher
2111                .children_length
2112                .push(xml_node.children.as_ref().len());
2113
2114            dom_string.push_str(&format!(
2115                "{}{},",
2116                t1,
2117                compile_node_to_rust_code_inner(
2118                    child_node,
2119                    component_map,
2120                    &filtered_xml_attributes,
2121                    tabs + 1,
2122                    &mut extra_blocks,
2123                    &mut css_blocks,
2124                    &css,
2125                    matcher,
2126                )?
2127            ));
2128        }
2129        dom_string.push_str(&format!("\r\n{}]))", t));
2130    }
2131
2132    map.push((
2133        component_name,
2134        dom_string,
2135        filtered_xml_attributes,
2136        css_blocks,
2137    ));
2138
2139    Ok(())
2140}
2141
2142/// Takes all components and generates the source code function from them
2143pub fn compile_components_to_rust_code(
2144    components: &XmlComponentMap,
2145) -> Result<
2146    Vec<(
2147        ComponentName,
2148        CompiledComponent,
2149        ComponentArguments,
2150        BTreeMap<String, String>,
2151    )>,
2152    CompileError,
2153> {
2154    let mut map = Vec::new();
2155
2156    for xml_component in &components.components {
2157        render_component_inner(
2158            &mut map,
2159            normalize_casing(&xml_component.id),
2160            xml_component,
2161            &components,
2162            &ComponentArguments::default(),
2163            1,
2164        )?;
2165    }
2166
2167    Ok(map)
2168}
2169
2170#[derive(Clone)]
2171pub struct CssMatcher {
2172    path: Vec<CssPathSelector>,
2173    indices_in_parent: Vec<usize>,
2174    children_length: Vec<usize>,
2175}
2176
2177impl CssMatcher {
2178    fn get_hash(&self) -> u64 {
2179        use core::hash::Hash;
2180
2181        use highway::{HighwayHash, HighwayHasher, Key};
2182
2183        let mut hasher = HighwayHasher::new(Key([0; 4]));
2184        for p in self.path.iter() {
2185            p.hash(&mut hasher);
2186        }
2187        hasher.finalize64()
2188    }
2189}
2190
2191impl CssMatcher {
2192    fn matches(&self, path: &CssPath) -> bool {
2193        use azul_css::CssPathSelector::*;
2194
2195        use crate::style::{CssGroupIterator, CssGroupSplitReason};
2196
2197        if self.path.is_empty() {
2198            return false;
2199        }
2200        if path.selectors.as_ref().is_empty() {
2201            return false;
2202        }
2203
2204        // self_matcher is only ever going to contain "Children" selectors, never "DirectChildren"
2205        let mut path_groups = CssGroupIterator::new(path.selectors.as_ref()).collect::<Vec<_>>();
2206        path_groups.reverse();
2207
2208        if path_groups.is_empty() {
2209            return false;
2210        }
2211        let mut self_groups = CssGroupIterator::new(self.path.as_ref()).collect::<Vec<_>>();
2212        self_groups.reverse();
2213        if self_groups.is_empty() {
2214            return false;
2215        }
2216
2217        if self.indices_in_parent.len() != self_groups.len() {
2218            return false;
2219        }
2220        if self.children_length.len() != self_groups.len() {
2221            return false;
2222        }
2223
2224        // self_groups = [ // HTML
2225        //     "body",
2226        //     "div.__azul_native-ribbon-container"
2227        //     "div.__azul_native-ribbon-tabs"
2228        //     "p.home"
2229        // ]
2230        //
2231        // path_groups = [ // CSS
2232        //     ".__azul_native-ribbon-tabs"
2233        //     "div.after-tabs"
2234        // ]
2235
2236        // get the first path group and see if it matches anywhere in the self group
2237        let mut cur_selfgroup_scan = 0;
2238        let mut cur_pathgroup_scan = 0;
2239        let mut valid = false;
2240        let mut path_group = path_groups[cur_pathgroup_scan].clone();
2241
2242        while cur_selfgroup_scan < self_groups.len() {
2243            let mut advance = None;
2244
2245            // scan all remaining path groups
2246            for (id, cg) in self_groups[cur_selfgroup_scan..].iter().enumerate() {
2247                let gm = group_matches(
2248                    &path_group.0,
2249                    &self_groups[cur_selfgroup_scan + id].0,
2250                    self.indices_in_parent[cur_selfgroup_scan + id],
2251                    self.children_length[cur_selfgroup_scan + id],
2252                );
2253
2254                if gm {
2255                    // ok: ".__azul_native-ribbon-tabs" was found within self_groups
2256                    // advance the self_groups by n
2257                    advance = Some(id);
2258                    break;
2259                }
2260            }
2261
2262            match advance {
2263                Some(n) => {
2264                    // group was found in remaining items
2265                    // advance cur_pathgroup_scan by 1 and cur_selfgroup_scan by n
2266                    if cur_pathgroup_scan == path_groups.len() - 1 {
2267                        // last path group
2268                        return cur_selfgroup_scan + n == self_groups.len() - 1;
2269                    } else {
2270                        cur_pathgroup_scan += 1;
2271                        cur_selfgroup_scan += n;
2272                        path_group = path_groups[cur_pathgroup_scan].clone();
2273                    }
2274                }
2275                None => return false, // group was not found in remaining items
2276            }
2277        }
2278
2279        // only return true if all path_groups matched
2280        return cur_pathgroup_scan == path_groups.len() - 1;
2281    }
2282}
2283
2284// does p.home match div.after-tabs?
2285// a: div.after-tabs
2286fn group_matches(
2287    a: &[&CssPathSelector],
2288    b: &[&CssPathSelector],
2289    idx_in_parent: usize,
2290    parent_children: usize,
2291) -> bool {
2292    use azul_css::{CssNthChildSelector, CssPathPseudoSelector, CssPathSelector::*};
2293
2294    for selector in a {
2295        match selector {
2296            // always matches
2297            Global => {}
2298            PseudoSelector(CssPathPseudoSelector::Hover) => {}
2299            PseudoSelector(CssPathPseudoSelector::Active) => {}
2300            PseudoSelector(CssPathPseudoSelector::Focus) => {}
2301
2302            Type(tag) => {
2303                if !b.iter().any(|t| **t == Type(tag.clone())) {
2304                    return false;
2305                }
2306            }
2307            Class(class) => {
2308                if !b.iter().any(|t| **t == Class(class.clone())) {
2309                    return false;
2310                }
2311            }
2312            Id(id) => {
2313                if !b.iter().any(|t| **t == Id(id.clone())) {
2314                    return false;
2315                }
2316            }
2317            PseudoSelector(CssPathPseudoSelector::First) => {
2318                if idx_in_parent != 0 {
2319                    return false;
2320                }
2321            }
2322            PseudoSelector(CssPathPseudoSelector::Last) => {
2323                if idx_in_parent != parent_children.saturating_sub(1) {
2324                    return false;
2325                }
2326            }
2327            PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Number(i))) => {
2328                if idx_in_parent != *i as usize {
2329                    return false;
2330                }
2331            }
2332            PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Even)) => {
2333                if idx_in_parent % 2 != 0 {
2334                    return false;
2335                }
2336            }
2337            PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Odd)) => {
2338                if idx_in_parent % 2 == 0 {
2339                    return false;
2340                }
2341            }
2342            PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Pattern(p))) => {
2343                if idx_in_parent.saturating_sub(p.offset as usize) % p.repeat as usize != 0 {
2344                    return false;
2345                }
2346            }
2347
2348            _ => return false, // can't happen
2349        }
2350    }
2351
2352    true
2353}
2354
2355struct CssBlock {
2356    ending: Option<CssPathPseudoSelector>,
2357    block: CssRuleBlock,
2358}
2359
2360pub fn compile_body_node_to_rust_code<'a>(
2361    body_node: &'a XmlNode,
2362    component_map: &'a XmlComponentMap,
2363    extra_blocks: &mut VecContents,
2364    css_blocks: &mut BTreeMap<String, String>,
2365    css: &Css,
2366    mut matcher: CssMatcher,
2367) -> Result<String, CompileError> {
2368    use azul_css::CssDeclaration;
2369
2370    let t = "";
2371    let t2 = "    ";
2372    let mut dom_string = String::from("Dom::body()");
2373    let node_type = CssPathSelector::Type(NodeTypeTag::Body);
2374    matcher.path.push(node_type);
2375
2376    let ids = body_node
2377        .attributes
2378        .get_key("id")
2379        .map(|s| s.split_whitespace().collect::<Vec<_>>())
2380        .unwrap_or_default();
2381    matcher.path.extend(
2382        ids.into_iter()
2383            .map(|id| CssPathSelector::Id(id.to_string().into())),
2384    );
2385    let classes = body_node
2386        .attributes
2387        .get_key("class")
2388        .map(|s| s.split_whitespace().collect::<Vec<_>>())
2389        .unwrap_or_default();
2390    matcher.path.extend(
2391        classes
2392            .into_iter()
2393            .map(|class| CssPathSelector::Class(class.to_string().into())),
2394    );
2395
2396    let matcher_hash = matcher.get_hash();
2397    let css_blocks_for_this_node = get_css_blocks(css, &matcher);
2398    if !css_blocks_for_this_node.is_empty() {
2399        use crate::css::format_static_css_prop;
2400
2401        let css_strings = css_blocks_for_this_node
2402            .iter()
2403            .rev()
2404            .map(|css_block| {
2405                let wrapper = match css_block.ending {
2406                    Some(CssPathPseudoSelector::Hover) => "Hover",
2407                    Some(CssPathPseudoSelector::Active) => "Active",
2408                    Some(CssPathPseudoSelector::Focus) => "Focus",
2409                    _ => "Normal",
2410                };
2411
2412                for declaration in css_block.block.declarations.as_ref().iter() {
2413                    let prop = match declaration {
2414                        CssDeclaration::Static(s) => s,
2415                        CssDeclaration::Dynamic(d) => &d.default_value,
2416                    };
2417                    extra_blocks.insert_from_css_property(prop);
2418                }
2419
2420                let formatted = css_block
2421                    .block
2422                    .declarations
2423                    .as_ref()
2424                    .iter()
2425                    .rev()
2426                    .map(|s| match &s {
2427                        CssDeclaration::Static(s) => format!(
2428                            "NodeDataInlineCssProperty::{}({})",
2429                            wrapper,
2430                            format_static_css_prop(s, 1)
2431                        ),
2432                        CssDeclaration::Dynamic(d) => format!(
2433                            "NodeDataInlineCssProperty::{}({})",
2434                            wrapper,
2435                            format_static_css_prop(&d.default_value, 1)
2436                        ),
2437                    })
2438                    .collect::<Vec<String>>();
2439
2440                format!("// {}\r\n{}", css_block.block.path, formatted.join(",\r\n"))
2441            })
2442            .collect::<Vec<_>>()
2443            .join(",\r\n");
2444
2445        css_blocks.insert(format!("CSS_MATCH_{:09}", matcher_hash), css_strings);
2446        dom_string.push_str(&format!(
2447            "\r\n{}.with_inline_css_props(CSS_MATCH_{:09})",
2448            t2, matcher_hash
2449        ));
2450    }
2451
2452    if !body_node.children.as_ref().is_empty() {
2453        use crate::css::GetHash;
2454        let children_hash = body_node.children.as_ref().get_hash();
2455        dom_string.push_str(&format!("\r\n.with_children(DomVec::from_vec(vec![\r\n"));
2456
2457        for (child_idx, child_node) in body_node.children.as_ref().iter().enumerate() {
2458            let mut matcher = matcher.clone();
2459            matcher.path.push(CssPathSelector::Children);
2460            matcher.indices_in_parent.push(child_idx);
2461            matcher.children_length.push(body_node.children.len());
2462
2463            dom_string.push_str(&format!(
2464                "{}{},\r\n",
2465                t,
2466                compile_node_to_rust_code_inner(
2467                    child_node,
2468                    component_map,
2469                    &ComponentArguments::default(),
2470                    1,
2471                    extra_blocks,
2472                    css_blocks,
2473                    css,
2474                    matcher,
2475                )?
2476            ));
2477        }
2478        dom_string.push_str(&format!("\r\n{}]))", t));
2479    }
2480
2481    let dom_string = dom_string.trim();
2482    Ok(dom_string.to_string())
2483}
2484
2485fn get_css_blocks(css: &Css, matcher: &CssMatcher) -> Vec<CssBlock> {
2486    let mut blocks = Vec::new();
2487
2488    for stylesheet in css.stylesheets.as_ref() {
2489        for css_block in stylesheet.rules.as_ref() {
2490            if matcher.matches(&css_block.path) {
2491                let mut ending = None;
2492
2493                if let Some(CssPathSelector::PseudoSelector(p)) =
2494                    css_block.path.selectors.as_ref().last()
2495                {
2496                    ending = Some(*p);
2497                }
2498
2499                blocks.push(CssBlock {
2500                    ending,
2501                    block: css_block.clone(),
2502                });
2503            }
2504        }
2505    }
2506
2507    blocks
2508}
2509
2510fn compile_and_format_dynamic_items(input: &[DynamicItem]) -> String {
2511    use self::DynamicItem::*;
2512    if input.is_empty() {
2513        String::from("AzString::from_const_str(\"\")")
2514    } else if input.len() == 1 {
2515        // common: there is only one "dynamic item" - skip the "format!()" macro
2516        match &input[0] {
2517            Var(v) => normalize_casing(v.trim()),
2518            Str(s) => format!("AzString::from_const_str(\"{}\")", s),
2519        }
2520    } else {
2521        // build a "format!("{var}, blah", var)" string
2522        let mut formatted_str = String::from("format!(\"");
2523        let mut variables = Vec::new();
2524        for item in input {
2525            match item {
2526                Var(v) => {
2527                    let variable_name = normalize_casing(v.trim());
2528                    formatted_str.push_str(&format!("{{{}}}", variable_name));
2529                    variables.push(variable_name.clone());
2530                }
2531                Str(s) => {
2532                    let s = s.replace("\"", "\\\"");
2533                    formatted_str.push_str(&s);
2534                }
2535            }
2536        }
2537
2538        formatted_str.push('\"');
2539        if !variables.is_empty() {
2540            formatted_str.push_str(", ");
2541        }
2542
2543        formatted_str.push_str(&variables.join(", "));
2544        formatted_str.push_str(").into()");
2545        formatted_str
2546    }
2547}
2548
2549fn format_args_for_rust_code(input: &str) -> String {
2550    let dynamic_str_items = split_dynamic_string(input);
2551    compile_and_format_dynamic_items(&dynamic_str_items)
2552}
2553
2554pub fn compile_node_to_rust_code_inner<'a>(
2555    node: &XmlNode,
2556    component_map: &'a XmlComponentMap,
2557    parent_xml_attributes: &ComponentArguments,
2558    tabs: usize,
2559    extra_blocks: &mut VecContents,
2560    css_blocks: &mut BTreeMap<String, String>,
2561    css: &Css,
2562    mut matcher: CssMatcher,
2563) -> Result<String, CompileError> {
2564    use azul_css::CssDeclaration;
2565
2566    let t = String::from("    ").repeat(tabs - 1);
2567    let t2 = String::from("    ").repeat(tabs);
2568
2569    let component_name = normalize_casing(&node.node_type);
2570
2571    let xml_component = component_map
2572        .components
2573        .iter()
2574        .find(|s| normalize_casing(&s.id) == component_name)
2575        .ok_or(ComponentError::UnknownComponent(
2576            component_name.clone().into(),
2577        ))?;
2578
2579    // Arguments of the current node
2580    let available_function_args = xml_component.renderer.get_available_arguments();
2581    let mut filtered_xml_attributes =
2582        validate_and_filter_component_args(&node.attributes, &available_function_args)?;
2583
2584    if xml_component.inherit_vars {
2585        // Append all variables that are in scope for the parent node
2586        filtered_xml_attributes
2587            .types
2588            .extend(parent_xml_attributes.args.clone().into_iter());
2589    }
2590
2591    // Instantiate the parent arguments in the current child arguments
2592    for v in filtered_xml_attributes.types.iter_mut() {
2593        v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.args).to_string();
2594    }
2595
2596    let instantiated_function_arguments = {
2597        let mut args = filtered_xml_attributes
2598            .types
2599            .iter()
2600            .filter_map(|(xml_attribute_key, _xml_attribute_type)| {
2601                match node.attributes.get_key(xml_attribute_key).cloned() {
2602                    Some(s) => Some(format_args_for_rust_code(&s)),
2603                    None => {
2604                        // __TODO__
2605                        // let node_text = format_args_for_rust_code(&xml_attribute_key);
2606                        //   "{text}" => "text"
2607                        //   "{href}" => "href"
2608                        //   "{blah}_the_thing" => "format!(\"{blah}_the_thing\", blah)"
2609                        None
2610                    }
2611                }
2612            })
2613            .collect::<Vec<String>>();
2614
2615        args.sort_by(|a, b| a.cmp(&b));
2616
2617        args.join(", ")
2618    };
2619
2620    let text_as_first_arg = if filtered_xml_attributes.accepts_text {
2621        let node_text = node.text.clone().into_option().unwrap_or_default();
2622        let node_text = format_args_for_rust_code(node_text.trim());
2623        let trailing_comma = if !instantiated_function_arguments.is_empty() {
2624            ", "
2625        } else {
2626            ""
2627        };
2628
2629        // __TODO__
2630        // let node_text = format_args_for_rust_code(&node_text, &parent_xml_attributes.args);
2631        //   "{text}" => "text"
2632        //   "{href}" => "href"
2633        //   "{blah}_the_thing" => "format!(\"{blah}_the_thing\", blah)"
2634
2635        format!("{}{}", node_text, trailing_comma)
2636    } else {
2637        String::new()
2638    };
2639
2640    let node_type = CssPathSelector::Type(match component_name.as_str() {
2641        "body" => NodeTypeTag::Body,
2642        "div" => NodeTypeTag::Div,
2643        "br" => NodeTypeTag::Br,
2644        "p" => NodeTypeTag::P,
2645        "img" => NodeTypeTag::Img,
2646        other => {
2647            return Err(CompileError::Dom(RenderDomError::Component(
2648                ComponentError::UnknownComponent(other.to_string().into()),
2649            )));
2650        }
2651    });
2652
2653    // The dom string is the function name
2654    let mut dom_string = format!(
2655        "{}{}::render({}{})",
2656        t2, component_name, text_as_first_arg, instantiated_function_arguments
2657    );
2658
2659    matcher.path.push(node_type);
2660    let ids = node
2661        .attributes
2662        .get_key("id")
2663        .map(|s| s.split_whitespace().collect::<Vec<_>>())
2664        .unwrap_or_default();
2665
2666    matcher.path.extend(
2667        ids.into_iter()
2668            .map(|id| CssPathSelector::Id(id.to_string().into())),
2669    );
2670
2671    let classes = node
2672        .attributes
2673        .get_key("class")
2674        .map(|s| s.split_whitespace().collect::<Vec<_>>())
2675        .unwrap_or_default();
2676
2677    matcher.path.extend(
2678        classes
2679            .into_iter()
2680            .map(|class| CssPathSelector::Class(class.to_string().into())),
2681    );
2682
2683    let matcher_hash = matcher.get_hash();
2684    let css_blocks_for_this_node = get_css_blocks(css, &matcher);
2685    if !css_blocks_for_this_node.is_empty() {
2686        use crate::css::format_static_css_prop;
2687
2688        let css_strings = css_blocks_for_this_node
2689            .iter()
2690            .rev()
2691            .map(|css_block| {
2692                let wrapper = match css_block.ending {
2693                    Some(CssPathPseudoSelector::Hover) => "Hover",
2694                    Some(CssPathPseudoSelector::Active) => "Active",
2695                    Some(CssPathPseudoSelector::Focus) => "Focus",
2696                    _ => "Normal",
2697                };
2698
2699                for declaration in css_block.block.declarations.as_ref().iter() {
2700                    let prop = match declaration {
2701                        CssDeclaration::Static(s) => s,
2702                        CssDeclaration::Dynamic(d) => &d.default_value,
2703                    };
2704                    extra_blocks.insert_from_css_property(prop);
2705                }
2706
2707                let formatted = css_block
2708                    .block
2709                    .declarations
2710                    .as_ref()
2711                    .iter()
2712                    .rev()
2713                    .map(|s| match &s {
2714                        CssDeclaration::Static(s) => format!(
2715                            "NodeDataInlineCssProperty::{}({})",
2716                            wrapper,
2717                            format_static_css_prop(s, 1)
2718                        ),
2719                        CssDeclaration::Dynamic(d) => format!(
2720                            "NodeDataInlineCssProperty::{}({})",
2721                            wrapper,
2722                            format_static_css_prop(&d.default_value, 1)
2723                        ),
2724                    })
2725                    .collect::<Vec<String>>();
2726
2727                format!("// {}\r\n{}", css_block.block.path, formatted.join(",\r\n"))
2728            })
2729            .collect::<Vec<_>>()
2730            .join(",\r\n");
2731
2732        css_blocks.insert(format!("CSS_MATCH_{:09}", matcher_hash), css_strings);
2733        dom_string.push_str(&format!(
2734            "\r\n{}.with_inline_css_props(CSS_MATCH_{:09})",
2735            t2, matcher_hash
2736        ));
2737    }
2738
2739    set_stringified_attributes(
2740        &mut dom_string,
2741        &node.attributes,
2742        &filtered_xml_attributes.types,
2743        tabs,
2744    );
2745
2746    let mut children_string = node
2747        .children
2748        .as_ref()
2749        .iter()
2750        .enumerate()
2751        .map(|(child_idx, c)| {
2752            let mut matcher = matcher.clone();
2753            matcher.path.push(CssPathSelector::Children);
2754            matcher.indices_in_parent.push(child_idx);
2755            matcher.children_length.push(node.children.len());
2756
2757            compile_node_to_rust_code_inner(
2758                c,
2759                component_map,
2760                &ComponentArguments {
2761                    args: filtered_xml_attributes.types.clone(),
2762                    accepts_text: filtered_xml_attributes.accepts_text,
2763                },
2764                tabs + 1,
2765                extra_blocks,
2766                css_blocks,
2767                css,
2768                matcher,
2769            )
2770        })
2771        .collect::<Result<Vec<_>, _>>()?
2772        .join(&format!(",\r\n"));
2773
2774    if !children_string.is_empty() {
2775        dom_string.push_str(&format!(
2776            "\r\n{}.with_children(DomVec::from_vec(vec![\r\n{}\r\n{}]))",
2777            t2, children_string, t2
2778        ));
2779    }
2780
2781    Ok(dom_string)
2782}
2783
2784/// Component that was created from a XML node (instead of being registered from Rust code).
2785/// Necessary to
2786pub struct DynamicXmlComponent {
2787    /// What the name of this component is, i.e. "test" for `<component name="test" />`
2788    pub name: String,
2789    /// Whether this component has any `args="a: String"` arguments
2790    pub arguments: ComponentArguments,
2791    /// Root XML node of this component (the `<component />` Node)
2792    pub root: XmlNode,
2793}
2794
2795impl DynamicXmlComponent {
2796    /// Parses a `component` from an XML node
2797    pub fn new<'a>(root: &'a XmlNode) -> Result<Self, ComponentParseError> {
2798        let node_type = normalize_casing(&root.node_type);
2799
2800        if node_type.as_str() != "component" {
2801            return Err(ComponentParseError::NotAComponent);
2802        }
2803
2804        let name = root
2805            .attributes
2806            .get_key("name")
2807            .cloned()
2808            .ok_or(ComponentParseError::NotAComponent)?;
2809        let accepts_text = root
2810            .attributes
2811            .get_key("accepts_text")
2812            .and_then(|p| parse_bool(p.as_str()))
2813            .unwrap_or(false);
2814
2815        let args = match root.attributes.get_key("args") {
2816            Some(s) => parse_component_arguments(s)?,
2817            None => ComponentArgumentTypes::default(),
2818        };
2819
2820        Ok(Self {
2821            name: normalize_casing(&name),
2822            arguments: ComponentArguments { args, accepts_text },
2823            root: root.clone(),
2824        })
2825    }
2826}
2827
2828impl XmlComponentTrait for DynamicXmlComponent {
2829    fn get_available_arguments(&self) -> ComponentArguments {
2830        self.arguments.clone()
2831    }
2832
2833    fn get_xml_node(&self) -> XmlNode {
2834        self.root.clone()
2835    }
2836
2837    fn render_dom<'a>(
2838        &'a self,
2839        components: &'a XmlComponentMap,
2840        arguments: &FilteredComponentArguments,
2841        content: &XmlTextContent,
2842    ) -> Result<StyledDom, RenderDomError> {
2843        let mut component_css = match find_node_by_type(self.root.children.as_ref(), "style") {
2844            Some(style_node) => {
2845                if let Some(text) = style_node.text.as_ref().map(|s| s.as_str()) {
2846                    let parsed_css = CssApiWrapper::from_string(text.to_string().into());
2847                    Some(parsed_css)
2848                } else {
2849                    None
2850                }
2851            }
2852            None => None,
2853        };
2854
2855        let mut dom = StyledDom::default();
2856
2857        for child_node in self.root.children.as_ref() {
2858            dom.append_child(render_dom_from_body_node_inner(
2859                child_node, components, arguments,
2860            )?);
2861        }
2862
2863        if let Some(css) = component_css.clone() {
2864            dom.restyle(css);
2865        }
2866
2867        Ok(dom)
2868    }
2869
2870    fn compile_to_rust_code(
2871        &self,
2872        components: &XmlComponentMap,
2873        attributes: &ComponentArguments,
2874        content: &XmlTextContent,
2875    ) -> Result<String, CompileError> {
2876        Ok("Dom::div()".into()) // TODO!s
2877    }
2878}