1use 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
26pub type SyntaxError = String;
29pub type XmlTagName = AzString;
31pub type XmlTextContent = OptionAzString;
33pub 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, 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 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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
423pub struct ComponentArguments {
424 pub args: ComponentArgumentTypes,
426 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 pub types: ComponentArgumentTypes,
450 pub values: BTreeMap<String, String>,
452 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
473pub trait XmlComponentTrait {
475 fn get_type_id(&self) -> String {
477 "div".to_string()
478 }
479
480 fn get_xml_node(&self) -> XmlNode {
483 XmlNode::new(self.get_type_id())
484 }
485
486 fn get_available_arguments(&self) -> ComponentArguments {
539 ComponentArguments::new()
540 }
541
542 fn render_dom(
546 &self,
547 components: &XmlComponentMap,
548 arguments: &FilteredComponentArguments,
549 content: &XmlTextContent,
550 ) -> Result<StyledDom, RenderDomError>;
551
552 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#[derive(Default)]
566pub struct DomXml {
567 pub parsed_dom: StyledDom,
568}
569
570impl DomXml {
571 #[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#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
610#[repr(C)]
611pub struct XmlNode {
612 pub node_type: XmlTagName,
614 pub attributes: XmlAttributeMap,
616 pub children: XmlNodeVec,
618 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 pub renderer: Box<dyn XmlComponentTrait>,
645 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
659pub struct XmlComponentMap {
661 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 NoHtmlNode,
700 MultipleHtmlRootNodes,
702 NoBodyInHtml,
704 MultipleBodyNodes,
706 Xml(XmlError),
711 MalformedHierarchy(AzString, AzString),
713 RenderDom(RenderDomError),
716 Component(ComponentParseError),
718 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#[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 UselessFunctionArgument(AzString, AzString, Vec<String>),
795 UnknownComponent(AzString),
800}
801
802#[derive(Debug, Clone, PartialEq)]
803pub enum RenderDomError {
804 Component(ComponentError),
805 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 NotAComponent,
825 UnnamedComponent,
827 MissingName(usize),
829 MissingType(usize, AzString),
832 WhiteSpaceInComponentName(usize, AzString),
835 WhiteSpaceInComponentType(usize, AzString, AzString),
838 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#[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#[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#[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, }
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
1077pub 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
1124pub 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 map.values.insert(
1155 xml_attribute_name.as_str().to_string(),
1156 xml_attribute_value.as_str().to_string(),
1157 );
1158 } else {
1159 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
1172pub 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
1190pub 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
1210pub 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
1225pub 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#[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
1281pub 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 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) => {} Err(e) => return Err(e.into()), }
1309 }
1310
1311 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
1324pub 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) => {} Err(e) => return Err(CompileError::Xml(e.into())), }
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
1469pub 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 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 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 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); }
1581
1582 Ok(dom)
1583}
1584
1585pub 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 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 filtered_xml_attributes
1609 .types
1610 .extend(parent_xml_attributes.types.clone().into_iter());
1611 }
1612
1613 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 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#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1821pub enum DynamicItem {
1822 Var(String),
1823 Str(String),
1824}
1825
1826pub 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 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 if has_found_variable {
1877 if last_idx != current_idx {
1878 items.push(Str(input[last_idx..current_idx].iter().collect()));
1879 }
1880
1881 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 if let Str(s) = item {
1905 *s = s.replace("{{", "{").replace("}}", "}");
1906 }
1907 }
1908
1909 items
1910}
1911
1912pub 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
1952pub 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
1975pub 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("<", "<");
1987 let input = input.replace(">", ">");
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(" ", " ");
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
2020pub 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 let available_function_arg_types = xml_component.renderer.get_available_arguments();
2059 let mut filtered_xml_attributes = available_function_arg_types.clone();
2061
2062 if xml_component.inherit_vars {
2063 filtered_xml_attributes
2065 .args
2066 .extend(parent_xml_attributes.args.clone().into_iter());
2067 }
2068
2069 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 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
2142pub 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 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 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 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 advance = Some(id);
2258 break;
2259 }
2260 }
2261
2262 match advance {
2263 Some(n) => {
2264 if cur_pathgroup_scan == path_groups.len() - 1 {
2267 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, }
2277 }
2278
2279 return cur_pathgroup_scan == path_groups.len() - 1;
2281 }
2282}
2283
2284fn 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 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, }
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 match &input[0] {
2517 Var(v) => normalize_casing(v.trim()),
2518 Str(s) => format!("AzString::from_const_str(\"{}\")", s),
2519 }
2520 } else {
2521 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 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 filtered_xml_attributes
2587 .types
2588 .extend(parent_xml_attributes.args.clone().into_iter());
2589 }
2590
2591 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 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 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 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
2784pub struct DynamicXmlComponent {
2787 pub name: String,
2789 pub arguments: ComponentArguments,
2791 pub root: XmlNode,
2793}
2794
2795impl DynamicXmlComponent {
2796 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()) }
2878}