1use alloc::{
22 boxed::Box,
23 collections::BTreeMap,
24 string::{String, ToString},
25 vec::Vec,
26};
27use core::{fmt, hash::Hash};
28
29use azul_css::{
30 css::{
31 Css, CssDeclaration, CssPath, CssPathPseudoSelector, CssPathSelector, CssRuleBlock,
32 NodeTypeTag,
33 },
34 format_rust_code::VecContents,
35 parser2::{CssParseErrorOwned, ErrorLocation},
36 props::{
37 basic::StyleFontFamilyVec,
38 property::CssProperty,
39 style::{
40 NormalizedLinearColorStopVec, NormalizedRadialColorStopVec, StyleBackgroundContentVec,
41 StyleBackgroundPositionVec, StyleBackgroundRepeatVec, StyleBackgroundSizeVec,
42 StyleTransformVec,
43 },
44 },
45 AzString, OptionString, U8Vec,
46};
47
48use crate::{
49 dom::{Dom, NodeType},
50 styled_dom::StyledDom,
51 window::{AzStringPair, StringPairVec},
52};
53
54pub type SyntaxError = String;
58
59#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(C)]
62pub struct XmlTagName {
63 pub inner: AzString,
64}
65
66impl From<AzString> for XmlTagName {
67 fn from(s: AzString) -> Self {
68 Self { inner: s }
69 }
70}
71
72impl From<String> for XmlTagName {
73 fn from(s: String) -> Self {
74 Self { inner: s.into() }
75 }
76}
77
78impl From<&str> for XmlTagName {
79 fn from(s: &str) -> Self {
80 Self { inner: s.into() }
81 }
82}
83
84impl core::ops::Deref for XmlTagName {
85 type Target = AzString;
86 fn deref(&self) -> &Self::Target {
87 &self.inner
88 }
89}
90
91pub type XmlTextContent = OptionString;
93
94#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
96#[repr(C)]
97pub struct XmlAttributeMap {
98 pub inner: StringPairVec,
99}
100
101impl From<StringPairVec> for XmlAttributeMap {
102 fn from(v: StringPairVec) -> Self {
103 Self { inner: v }
104 }
105}
106
107impl core::ops::Deref for XmlAttributeMap {
108 type Target = StringPairVec;
109 fn deref(&self) -> &Self::Target {
110 &self.inner
111 }
112}
113
114impl core::ops::DerefMut for XmlAttributeMap {
115 fn deref_mut(&mut self) -> &mut Self::Target {
116 &mut self.inner
117 }
118}
119
120pub type ComponentArgumentName = String;
121pub type ComponentArgumentType = String;
122pub type ComponentArgumentOrder = usize;
123pub type ComponentArgumentTypes = Vec<(ComponentArgumentName, ComponentArgumentType)>;
124pub type ComponentName = String;
125pub type CompiledComponent = String;
126
127pub const DEFAULT_ARGS: [&str; 8] = [
128 "id",
129 "class",
130 "tabindex",
131 "focusable",
132 "accepts_text",
133 "name",
134 "style",
135 "args",
136];
137
138#[allow(non_camel_case_types)]
139pub enum c_void {}
140
141#[repr(C)]
142pub enum XmlNodeType {
143 Root,
144 Element,
145 PI,
146 Comment,
147 Text,
148}
149
150#[repr(C)]
151pub struct XmlQualifiedName {
152 pub local_name: AzString,
153 pub namespace: OptionString,
154}
155
156#[derive(Debug, PartialEq, PartialOrd, Clone)]
157#[repr(C)]
158pub struct Xml {
159 pub root: XmlNodeChildVec,
160}
161
162#[derive(Debug, PartialEq, PartialOrd, Clone)]
163#[repr(C)]
164pub struct NonXmlCharError {
165 pub ch: u32, pub pos: XmlTextPos,
167}
168
169#[derive(Debug, PartialEq, PartialOrd, Clone)]
170#[repr(C)]
171pub struct InvalidCharError {
172 pub expected: u8,
173 pub got: u8,
174 pub pos: XmlTextPos,
175}
176
177#[derive(Debug, PartialEq, PartialOrd, Clone)]
178#[repr(C)]
179pub struct InvalidCharMultipleError {
180 pub expected: u8,
181 pub got: U8Vec,
182 pub pos: XmlTextPos,
183}
184
185#[derive(Debug, PartialEq, PartialOrd, Clone)]
186#[repr(C)]
187pub struct InvalidQuoteError {
188 pub got: u8,
189 pub pos: XmlTextPos,
190}
191
192#[derive(Debug, PartialEq, PartialOrd, Clone)]
193#[repr(C)]
194pub struct InvalidSpaceError {
195 pub got: u8,
196 pub pos: XmlTextPos,
197}
198
199#[derive(Debug, PartialEq, PartialOrd, Clone)]
200#[repr(C)]
201pub struct InvalidStringError {
202 pub got: AzString,
203 pub pos: XmlTextPos,
204}
205
206#[derive(Debug, PartialEq, PartialOrd, Clone)]
207#[repr(C, u8)]
208pub enum XmlStreamError {
209 UnexpectedEndOfStream,
210 InvalidName,
211 NonXmlChar(NonXmlCharError),
212 InvalidChar(InvalidCharError),
213 InvalidCharMultiple(InvalidCharMultipleError),
214 InvalidQuote(InvalidQuoteError),
215 InvalidSpace(InvalidSpaceError),
216 InvalidString(InvalidStringError),
217 InvalidReference,
218 InvalidExternalID,
219 InvalidCommentData,
220 InvalidCommentEnd,
221 InvalidCharacterData,
222}
223
224impl fmt::Display for XmlStreamError {
225 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
226 use self::XmlStreamError::*;
227 match self {
228 UnexpectedEndOfStream => write!(f, "Unexpected end of stream"),
229 InvalidName => write!(f, "Invalid name"),
230 NonXmlChar(nx) => write!(
231 f,
232 "Non-XML character: {:?} at {}",
233 core::char::from_u32(nx.ch),
234 nx.pos
235 ),
236 InvalidChar(ic) => write!(
237 f,
238 "Invalid character: expected: {}, got: {} at {}",
239 ic.expected as char, ic.got as char, ic.pos
240 ),
241 InvalidCharMultiple(imc) => write!(
242 f,
243 "Multiple invalid characters: expected: {}, got: {:?} at {}",
244 imc.expected,
245 imc.got.as_ref(),
246 imc.pos
247 ),
248 InvalidQuote(iq) => write!(f, "Invalid quote: got {} at {}", iq.got as char, iq.pos),
249 InvalidSpace(is) => write!(f, "Invalid space: got {} at {}", is.got as char, is.pos),
250 InvalidString(ise) => write!(
251 f,
252 "Invalid string: got \"{}\" at {}",
253 ise.got.as_str(),
254 ise.pos
255 ),
256 InvalidReference => write!(f, "Invalid reference"),
257 InvalidExternalID => write!(f, "Invalid external ID"),
258 InvalidCommentData => write!(f, "Invalid comment data"),
259 InvalidCommentEnd => write!(f, "Invalid comment end"),
260 InvalidCharacterData => write!(f, "Invalid character data"),
261 }
262 }
263}
264
265#[derive(Debug, PartialEq, PartialOrd, Clone, Ord, Hash, Eq)]
266#[repr(C)]
267pub struct XmlTextPos {
268 pub row: u32,
269 pub col: u32,
270}
271
272impl fmt::Display for XmlTextPos {
273 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
274 write!(f, "line {}:{}", self.row, self.col)
275 }
276}
277
278#[derive(Debug, PartialEq, PartialOrd, Clone)]
279#[repr(C)]
280pub struct XmlTextError {
281 pub stream_error: XmlStreamError,
282 pub pos: XmlTextPos,
283}
284
285#[derive(Debug, PartialEq, PartialOrd, Clone)]
286#[repr(C, u8)]
287pub enum XmlParseError {
288 InvalidDeclaration(XmlTextError),
289 InvalidComment(XmlTextError),
290 InvalidPI(XmlTextError),
291 InvalidDoctype(XmlTextError),
292 InvalidEntity(XmlTextError),
293 InvalidElement(XmlTextError),
294 InvalidAttribute(XmlTextError),
295 InvalidCdata(XmlTextError),
296 InvalidCharData(XmlTextError),
297 UnknownToken(XmlTextPos),
298}
299
300impl fmt::Display for XmlParseError {
301 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
302 use self::XmlParseError::*;
303 match self {
304 InvalidDeclaration(e) => {
305 write!(f, "Invalid declaraction: {} at {}", e.stream_error, e.pos)
306 }
307 InvalidComment(e) => write!(f, "Invalid comment: {} at {}", e.stream_error, e.pos),
308 InvalidPI(e) => write!(
309 f,
310 "Invalid processing instruction: {} at {}",
311 e.stream_error, e.pos
312 ),
313 InvalidDoctype(e) => write!(f, "Invalid doctype: {} at {}", e.stream_error, e.pos),
314 InvalidEntity(e) => write!(f, "Invalid entity: {} at {}", e.stream_error, e.pos),
315 InvalidElement(e) => write!(f, "Invalid element: {} at {}", e.stream_error, e.pos),
316 InvalidAttribute(e) => write!(f, "Invalid attribute: {} at {}", e.stream_error, e.pos),
317 InvalidCdata(e) => write!(f, "Invalid CDATA: {} at {}", e.stream_error, e.pos),
318 InvalidCharData(e) => write!(f, "Invalid char data: {} at {}", e.stream_error, e.pos),
319 UnknownToken(e) => write!(f, "Unknown token at {}", e),
320 }
321 }
322}
323
324impl_result!(
325 Xml,
326 XmlError,
327 ResultXmlXmlError,
328 copy = false,
329 [Debug, PartialEq, PartialOrd, Clone]
330);
331
332#[derive(Debug, PartialEq, PartialOrd, Clone)]
333#[repr(C)]
334pub struct DuplicatedNamespaceError {
335 pub ns: AzString,
336 pub pos: XmlTextPos,
337}
338
339#[derive(Debug, PartialEq, PartialOrd, Clone)]
340#[repr(C)]
341pub struct UnknownNamespaceError {
342 pub ns: AzString,
343 pub pos: XmlTextPos,
344}
345
346#[derive(Debug, PartialEq, PartialOrd, Clone)]
347#[repr(C)]
348pub struct UnexpectedCloseTagError {
349 pub expected: AzString,
350 pub actual: AzString,
351 pub pos: XmlTextPos,
352}
353
354#[derive(Debug, PartialEq, PartialOrd, Clone)]
355#[repr(C)]
356pub struct UnknownEntityReferenceError {
357 pub entity: AzString,
358 pub pos: XmlTextPos,
359}
360
361#[derive(Debug, PartialEq, PartialOrd, Clone)]
362#[repr(C)]
363pub struct DuplicatedAttributeError {
364 pub attribute: AzString,
365 pub pos: XmlTextPos,
366}
367
368#[derive(Debug, PartialEq, PartialOrd, Clone)]
370#[repr(C)]
371pub struct MalformedHierarchyError {
372 pub expected: AzString,
374 pub got: AzString,
376}
377
378#[derive(Debug, PartialEq, PartialOrd, Clone)]
379#[repr(C, u8)]
380pub enum XmlError {
381 NoParserAvailable,
382 InvalidXmlPrefixUri(XmlTextPos),
383 UnexpectedXmlUri(XmlTextPos),
384 UnexpectedXmlnsUri(XmlTextPos),
385 InvalidElementNamePrefix(XmlTextPos),
386 DuplicatedNamespace(DuplicatedNamespaceError),
387 UnknownNamespace(UnknownNamespaceError),
388 UnexpectedCloseTag(UnexpectedCloseTagError),
389 UnexpectedEntityCloseTag(XmlTextPos),
390 UnknownEntityReference(UnknownEntityReferenceError),
391 MalformedEntityReference(XmlTextPos),
392 EntityReferenceLoop(XmlTextPos),
393 InvalidAttributeValue(XmlTextPos),
394 DuplicatedAttribute(DuplicatedAttributeError),
395 NoRootNode,
396 SizeLimit,
397 DtdDetected,
398 MalformedHierarchy(MalformedHierarchyError),
400 ParserError(XmlParseError),
401 UnclosedRootNode,
402 UnexpectedDeclaration(XmlTextPos),
403 NodesLimitReached,
404 AttributesLimitReached,
405 NamespacesLimitReached,
406 InvalidName(XmlTextPos),
407 NonXmlChar(XmlTextPos),
408 InvalidChar(XmlTextPos),
409 InvalidChar2(XmlTextPos),
410 InvalidString(XmlTextPos),
411 InvalidExternalID(XmlTextPos),
412 InvalidComment(XmlTextPos),
413 InvalidCharacterData(XmlTextPos),
414 UnknownToken(XmlTextPos),
415 UnexpectedEndOfStream,
416}
417
418impl fmt::Display for XmlError {
419 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
420 use self::XmlError::*;
421 match self {
422 NoParserAvailable => write!(
423 f,
424 "Library was compiled without XML parser (XML parser not available)"
425 ),
426 InvalidXmlPrefixUri(pos) => {
427 write!(f, "Invalid XML Prefix URI at line {}:{}", pos.row, pos.col)
428 }
429 UnexpectedXmlUri(pos) => {
430 write!(f, "Unexpected XML URI at at line {}:{}", pos.row, pos.col)
431 }
432 UnexpectedXmlnsUri(pos) => write!(
433 f,
434 "Unexpected XML namespace URI at line {}:{}",
435 pos.row, pos.col
436 ),
437 InvalidElementNamePrefix(pos) => write!(
438 f,
439 "Invalid element name prefix at line {}:{}",
440 pos.row, pos.col
441 ),
442 DuplicatedNamespace(ns) => write!(
443 f,
444 "Duplicated namespace: \"{}\" at {}",
445 ns.ns.as_str(),
446 ns.pos
447 ),
448 UnknownNamespace(uns) => write!(
449 f,
450 "Unknown namespace: \"{}\" at {}",
451 uns.ns.as_str(),
452 uns.pos
453 ),
454 UnexpectedCloseTag(ct) => write!(
455 f,
456 "Unexpected close tag: expected \"{}\", got \"{}\" at {}",
457 ct.expected.as_str(),
458 ct.actual.as_str(),
459 ct.pos
460 ),
461 UnexpectedEntityCloseTag(pos) => write!(
462 f,
463 "Unexpected entity close tag at line {}:{}",
464 pos.row, pos.col
465 ),
466 UnknownEntityReference(uer) => write!(
467 f,
468 "Unexpected entity reference: \"{}\" at {}",
469 uer.entity, uer.pos
470 ),
471 MalformedEntityReference(pos) => write!(
472 f,
473 "Malformed entity reference at line {}:{}",
474 pos.row, pos.col
475 ),
476 EntityReferenceLoop(pos) => write!(
477 f,
478 "Entity reference loop (recursive entity reference) at line {}:{}",
479 pos.row, pos.col
480 ),
481 InvalidAttributeValue(pos) => {
482 write!(f, "Invalid attribute value at line {}:{}", pos.row, pos.col)
483 }
484 DuplicatedAttribute(ae) => write!(
485 f,
486 "Duplicated attribute \"{}\" at line {}:{}",
487 ae.attribute.as_str(),
488 ae.pos.row,
489 ae.pos.col
490 ),
491 NoRootNode => write!(f, "No root node found"),
492 SizeLimit => write!(f, "XML file too large (size limit reached)"),
493 DtdDetected => write!(f, "Document type descriptor detected"),
494 MalformedHierarchy(e) => write!(
495 f,
496 "Malformed hierarchy: expected <{}/> closing tag, got <{}/>",
497 e.expected.as_str(),
498 e.got.as_str()
499 ),
500 ParserError(p) => write!(f, "{}", p),
501 UnclosedRootNode => write!(f, "unclosed root node"),
502 UnexpectedDeclaration(tp) => write!(f, "unexpected declaration at {tp}"),
503 NodesLimitReached => write!(f, "nodes limit reached"),
504 AttributesLimitReached => write!(f, "attributes limit reached"),
505 NamespacesLimitReached => write!(f, "namespaces limit reached"),
506 InvalidName(tp) => write!(f, "invalid name at {tp}"),
507 NonXmlChar(tp) => write!(f, "non xml char at {tp}"),
508 InvalidChar(tp) => write!(f, "invalid char at {tp}"),
509 InvalidChar2(tp) => write!(f, "invalid char2 at {tp}"),
510 InvalidString(tp) => write!(f, "invalid string at {tp}"),
511 InvalidExternalID(tp) => write!(f, "invalid externalid at {tp}"),
512 InvalidComment(tp) => write!(f, "invalid comment at {tp}"),
513 InvalidCharacterData(tp) => write!(f, "invalid character data at {tp}"),
514 UnknownToken(tp) => write!(f, "unknown token at {tp}"),
515 UnexpectedEndOfStream => write!(f, "unexpected end of stream"),
516 }
517 }
518}
519
520#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
547pub struct ComponentArguments {
548 pub args: ComponentArgumentTypes,
550 pub accepts_text: bool,
553}
554
555impl Default for ComponentArguments {
556 fn default() -> Self {
557 Self {
558 args: ComponentArgumentTypes::default(),
559 accepts_text: false,
560 }
561 }
562}
563
564impl ComponentArguments {
565 pub fn new() -> Self {
566 Self::default()
567 }
568}
569
570#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
571pub struct FilteredComponentArguments {
572 pub types: ComponentArgumentTypes,
574 pub values: BTreeMap<String, String>,
576 pub accepts_text: bool,
579}
580
581impl Default for FilteredComponentArguments {
582 fn default() -> Self {
583 Self {
584 types: Vec::new(),
585 values: BTreeMap::default(),
586 accepts_text: false,
587 }
588 }
589}
590
591impl FilteredComponentArguments {
592 fn new() -> Self {
593 Self::default()
594 }
595}
596
597pub trait XmlComponentTrait {
599 fn get_type_id(&self) -> String {
601 "div".to_string()
602 }
603
604 fn get_xml_node(&self) -> XmlNode {
607 XmlNode::create(self.get_type_id())
608 }
609
610 fn get_available_arguments(&self) -> ComponentArguments {
663 ComponentArguments::new()
664 }
665
666 fn render_dom(
670 &self,
671 components: &XmlComponentMap,
672 arguments: &FilteredComponentArguments,
673 content: &XmlTextContent,
674 ) -> Result<StyledDom, RenderDomError>;
675
676 fn compile_to_rust_code(
678 &self,
679 components: &XmlComponentMap,
680 attributes: &ComponentArguments,
681 content: &XmlTextContent,
682 ) -> Result<String, CompileError> {
683 Ok(String::new())
684 }
685}
686
687#[derive(Default)]
690pub struct DomXml {
691 pub parsed_dom: StyledDom,
692}
693
694impl DomXml {
695 #[cfg(test)]
708 pub fn assert_eq(self, other: StyledDom) {
709 let mut fixed = Dom::create_body().style(Css::empty());
710 fixed.append_child(other);
711 if self.parsed_dom != fixed {
712 panic!(
713 "\r\nExpected DOM did not match:\r\n\r\nexpected: ----------\r\n{}\r\ngot: \
714 ----------\r\n{}\r\n",
715 self.parsed_dom.get_html_string("", "", true),
716 fixed.get_html_string("", "", true)
717 );
718 }
719 }
720
721 pub fn into_styled_dom(self) -> StyledDom {
722 self.into()
723 }
724}
725
726impl Into<StyledDom> for DomXml {
727 fn into(self) -> StyledDom {
728 self.parsed_dom
729 }
730}
731
732#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
734#[repr(C, u8)]
735pub enum XmlNodeChild {
736 Text(AzString),
738 Element(XmlNode),
740}
741
742impl XmlNodeChild {
743 pub fn as_text(&self) -> Option<&str> {
745 match self {
746 XmlNodeChild::Text(s) => Some(s.as_str()),
747 XmlNodeChild::Element(_) => None,
748 }
749 }
750
751 pub fn as_element(&self) -> Option<&XmlNode> {
753 match self {
754 XmlNodeChild::Text(_) => None,
755 XmlNodeChild::Element(node) => Some(node),
756 }
757 }
758
759 pub fn as_element_mut(&mut self) -> Option<&mut XmlNode> {
761 match self {
762 XmlNodeChild::Text(_) => None,
763 XmlNodeChild::Element(node) => Some(node),
764 }
765 }
766}
767
768impl_vec!(
769 XmlNodeChild,
770 XmlNodeChildVec,
771 XmlNodeChildVecDestructor,
772 XmlNodeChildVecDestructorType
773);
774impl_vec_mut!(XmlNodeChild, XmlNodeChildVec);
775impl_vec_debug!(XmlNodeChild, XmlNodeChildVec);
776impl_vec_partialeq!(XmlNodeChild, XmlNodeChildVec);
777impl_vec_eq!(XmlNodeChild, XmlNodeChildVec);
778impl_vec_partialord!(XmlNodeChild, XmlNodeChildVec);
779impl_vec_ord!(XmlNodeChild, XmlNodeChildVec);
780impl_vec_hash!(XmlNodeChild, XmlNodeChildVec);
781impl_vec_clone!(XmlNodeChild, XmlNodeChildVec, XmlNodeChildVecDestructor);
782
783#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
785#[repr(C)]
786pub struct XmlNode {
787 pub node_type: XmlTagName,
789 pub attributes: XmlAttributeMap,
791 pub children: XmlNodeChildVec,
793}
794
795impl XmlNode {
796 pub fn create<I: Into<XmlTagName>>(node_type: I) -> Self {
797 XmlNode {
798 node_type: node_type.into(),
799 ..Default::default()
800 }
801 }
802 pub fn with_children(mut self, v: Vec<XmlNodeChild>) -> Self {
803 Self {
804 children: v.into(),
805 ..self
806 }
807 }
808
809 pub fn get_text_content(&self) -> String {
811 self.children
812 .as_ref()
813 .iter()
814 .filter_map(|child| child.as_text())
815 .collect::<Vec<_>>()
816 .join("")
817 }
818
819 pub fn has_only_text_children(&self) -> bool {
821 self.children
822 .as_ref()
823 .iter()
824 .all(|child| matches!(child, XmlNodeChild::Text(_)))
825 }
826}
827
828impl_vec!(
829 XmlNode,
830 XmlNodeVec,
831 XmlNodeVecDestructor,
832 XmlNodeVecDestructorType
833);
834impl_vec_mut!(XmlNode, XmlNodeVec);
835impl_vec_debug!(XmlNode, XmlNodeVec);
836impl_vec_partialeq!(XmlNode, XmlNodeVec);
837impl_vec_eq!(XmlNode, XmlNodeVec);
838impl_vec_partialord!(XmlNode, XmlNodeVec);
839impl_vec_ord!(XmlNode, XmlNodeVec);
840impl_vec_hash!(XmlNode, XmlNodeVec);
841impl_vec_clone!(XmlNode, XmlNodeVec, XmlNodeVecDestructor);
842
843pub struct XmlComponent {
844 pub id: String,
845 pub renderer: Box<dyn XmlComponentTrait>,
847 pub inherit_vars: bool,
849}
850
851impl core::fmt::Debug for XmlComponent {
852 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
853 f.debug_struct("XmlComponent")
854 .field("id", &self.id)
855 .field("args", &self.renderer.get_available_arguments())
856 .field("inherit_vars", &self.inherit_vars)
857 .finish()
858 }
859}
860
861pub struct XmlComponentMap {
863 pub components: Vec<XmlComponent>,
866}
867
868impl Default for XmlComponentMap {
869 fn default() -> Self {
870 let mut map = Self {
871 components: Vec::new(),
872 };
873
874 map.register_component(XmlComponent {
876 id: normalize_casing("html"),
877 renderer: Box::new(HtmlRenderer::new()),
878 inherit_vars: true,
879 });
880 map.register_component(XmlComponent {
881 id: normalize_casing("head"),
882 renderer: Box::new(HeadRenderer::new()),
883 inherit_vars: true,
884 });
885 map.register_component(XmlComponent {
886 id: normalize_casing("title"),
887 renderer: Box::new(TitleRenderer::new()),
888 inherit_vars: true,
889 });
890 map.register_component(XmlComponent {
891 id: normalize_casing("body"),
892 renderer: Box::new(BodyRenderer::new()),
893 inherit_vars: true,
894 });
895
896 map.register_component(XmlComponent {
898 id: normalize_casing("div"),
899 renderer: Box::new(DivRenderer::new()),
900 inherit_vars: true,
901 });
902 map.register_component(XmlComponent {
903 id: normalize_casing("header"),
904 renderer: Box::new(HeaderRenderer::new()),
905 inherit_vars: true,
906 });
907 map.register_component(XmlComponent {
908 id: normalize_casing("footer"),
909 renderer: Box::new(FooterRenderer::new()),
910 inherit_vars: true,
911 });
912 map.register_component(XmlComponent {
913 id: normalize_casing("section"),
914 renderer: Box::new(SectionRenderer::new()),
915 inherit_vars: true,
916 });
917 map.register_component(XmlComponent {
918 id: normalize_casing("article"),
919 renderer: Box::new(ArticleRenderer::new()),
920 inherit_vars: true,
921 });
922 map.register_component(XmlComponent {
923 id: normalize_casing("aside"),
924 renderer: Box::new(AsideRenderer::new()),
925 inherit_vars: true,
926 });
927 map.register_component(XmlComponent {
928 id: normalize_casing("nav"),
929 renderer: Box::new(NavRenderer::new()),
930 inherit_vars: true,
931 });
932 map.register_component(XmlComponent {
933 id: normalize_casing("main"),
934 renderer: Box::new(MainRenderer::new()),
935 inherit_vars: true,
936 });
937
938 map.register_component(XmlComponent {
940 id: normalize_casing("h1"),
941 renderer: Box::new(H1Renderer::new()),
942 inherit_vars: true,
943 });
944 map.register_component(XmlComponent {
945 id: normalize_casing("h2"),
946 renderer: Box::new(H2Renderer::new()),
947 inherit_vars: true,
948 });
949 map.register_component(XmlComponent {
950 id: normalize_casing("h3"),
951 renderer: Box::new(H3Renderer::new()),
952 inherit_vars: true,
953 });
954 map.register_component(XmlComponent {
955 id: normalize_casing("h4"),
956 renderer: Box::new(H4Renderer::new()),
957 inherit_vars: true,
958 });
959 map.register_component(XmlComponent {
960 id: normalize_casing("h5"),
961 renderer: Box::new(H5Renderer::new()),
962 inherit_vars: true,
963 });
964 map.register_component(XmlComponent {
965 id: normalize_casing("h6"),
966 renderer: Box::new(H6Renderer::new()),
967 inherit_vars: true,
968 });
969
970 map.register_component(XmlComponent {
972 id: normalize_casing("p"),
973 renderer: Box::new(TextRenderer::new()),
974 inherit_vars: true,
975 });
976 map.register_component(XmlComponent {
977 id: normalize_casing("span"),
978 renderer: Box::new(SpanRenderer::new()),
979 inherit_vars: true,
980 });
981 map.register_component(XmlComponent {
982 id: normalize_casing("pre"),
983 renderer: Box::new(PreRenderer::new()),
984 inherit_vars: true,
985 });
986 map.register_component(XmlComponent {
987 id: normalize_casing("code"),
988 renderer: Box::new(CodeRenderer::new()),
989 inherit_vars: true,
990 });
991 map.register_component(XmlComponent {
992 id: normalize_casing("blockquote"),
993 renderer: Box::new(BlockquoteRenderer::new()),
994 inherit_vars: true,
995 });
996 map.register_component(XmlComponent {
997 id: normalize_casing("br"),
998 renderer: Box::new(BrRenderer::new()),
999 inherit_vars: true,
1000 });
1001 map.register_component(XmlComponent {
1002 id: normalize_casing("hr"),
1003 renderer: Box::new(HrRenderer::new()),
1004 inherit_vars: true,
1005 });
1006 map.register_component(XmlComponent {
1007 id: normalize_casing("icon"),
1008 renderer: Box::new(IconRenderer::new()),
1009 inherit_vars: true,
1010 });
1011
1012 map.register_component(XmlComponent {
1014 id: normalize_casing("ul"),
1015 renderer: Box::new(UlRenderer::new()),
1016 inherit_vars: true,
1017 });
1018 map.register_component(XmlComponent {
1019 id: normalize_casing("ol"),
1020 renderer: Box::new(OlRenderer::new()),
1021 inherit_vars: true,
1022 });
1023 map.register_component(XmlComponent {
1024 id: normalize_casing("li"),
1025 renderer: Box::new(LiRenderer::new()),
1026 inherit_vars: true,
1027 });
1028 map.register_component(XmlComponent {
1029 id: normalize_casing("dl"),
1030 renderer: Box::new(DlRenderer::new()),
1031 inherit_vars: true,
1032 });
1033 map.register_component(XmlComponent {
1034 id: normalize_casing("dt"),
1035 renderer: Box::new(DtRenderer::new()),
1036 inherit_vars: true,
1037 });
1038 map.register_component(XmlComponent {
1039 id: normalize_casing("dd"),
1040 renderer: Box::new(DdRenderer::new()),
1041 inherit_vars: true,
1042 });
1043
1044 map.register_component(XmlComponent {
1046 id: normalize_casing("table"),
1047 renderer: Box::new(TableRenderer::new()),
1048 inherit_vars: true,
1049 });
1050 map.register_component(XmlComponent {
1051 id: normalize_casing("thead"),
1052 renderer: Box::new(TheadRenderer::new()),
1053 inherit_vars: true,
1054 });
1055 map.register_component(XmlComponent {
1056 id: normalize_casing("tbody"),
1057 renderer: Box::new(TbodyRenderer::new()),
1058 inherit_vars: true,
1059 });
1060 map.register_component(XmlComponent {
1061 id: normalize_casing("tfoot"),
1062 renderer: Box::new(TfootRenderer::new()),
1063 inherit_vars: true,
1064 });
1065 map.register_component(XmlComponent {
1066 id: normalize_casing("tr"),
1067 renderer: Box::new(TrRenderer::new()),
1068 inherit_vars: true,
1069 });
1070 map.register_component(XmlComponent {
1071 id: normalize_casing("th"),
1072 renderer: Box::new(ThRenderer::new()),
1073 inherit_vars: true,
1074 });
1075 map.register_component(XmlComponent {
1076 id: normalize_casing("td"),
1077 renderer: Box::new(TdRenderer::new()),
1078 inherit_vars: true,
1079 });
1080
1081 map.register_component(XmlComponent {
1083 id: normalize_casing("a"),
1084 renderer: Box::new(ARenderer::new()),
1085 inherit_vars: true,
1086 });
1087 map.register_component(XmlComponent {
1088 id: normalize_casing("strong"),
1089 renderer: Box::new(StrongRenderer::new()),
1090 inherit_vars: true,
1091 });
1092 map.register_component(XmlComponent {
1093 id: normalize_casing("em"),
1094 renderer: Box::new(EmRenderer::new()),
1095 inherit_vars: true,
1096 });
1097 map.register_component(XmlComponent {
1098 id: normalize_casing("b"),
1099 renderer: Box::new(BRenderer::new()),
1100 inherit_vars: true,
1101 });
1102 map.register_component(XmlComponent {
1103 id: normalize_casing("i"),
1104 renderer: Box::new(IRenderer::new()),
1105 inherit_vars: true,
1106 });
1107 map.register_component(XmlComponent {
1108 id: normalize_casing("u"),
1109 renderer: Box::new(URenderer::new()),
1110 inherit_vars: true,
1111 });
1112 map.register_component(XmlComponent {
1113 id: normalize_casing("small"),
1114 renderer: Box::new(SmallRenderer::new()),
1115 inherit_vars: true,
1116 });
1117 map.register_component(XmlComponent {
1118 id: normalize_casing("mark"),
1119 renderer: Box::new(MarkRenderer::new()),
1120 inherit_vars: true,
1121 });
1122 map.register_component(XmlComponent {
1123 id: normalize_casing("sub"),
1124 renderer: Box::new(SubRenderer::new()),
1125 inherit_vars: true,
1126 });
1127 map.register_component(XmlComponent {
1128 id: normalize_casing("sup"),
1129 renderer: Box::new(SupRenderer::new()),
1130 inherit_vars: true,
1131 });
1132
1133 map.register_component(XmlComponent {
1135 id: normalize_casing("form"),
1136 renderer: Box::new(FormRenderer::new()),
1137 inherit_vars: true,
1138 });
1139 map.register_component(XmlComponent {
1140 id: normalize_casing("label"),
1141 renderer: Box::new(LabelRenderer::new()),
1142 inherit_vars: true,
1143 });
1144 map.register_component(XmlComponent {
1145 id: normalize_casing("button"),
1146 renderer: Box::new(ButtonRenderer::new()),
1147 inherit_vars: true,
1148 });
1149
1150 map
1151 }
1152}
1153
1154impl XmlComponentMap {
1155 pub fn register_component(&mut self, comp: XmlComponent) {
1156 self.components.push(comp);
1157 }
1158}
1159
1160#[derive(Debug, Clone, PartialEq)]
1161pub enum DomXmlParseError {
1162 NoHtmlNode,
1164 MultipleHtmlRootNodes,
1166 NoBodyInHtml,
1168 MultipleBodyNodes,
1170 Xml(XmlError),
1175 MalformedHierarchy(MalformedHierarchyError),
1177 RenderDom(RenderDomError),
1180 Component(ComponentParseError),
1182 Css(CssParseErrorOwned),
1184}
1185
1186impl From<XmlError> for DomXmlParseError {
1187 fn from(e: XmlError) -> Self {
1188 Self::Xml(e)
1189 }
1190}
1191
1192impl From<ComponentParseError> for DomXmlParseError {
1193 fn from(e: ComponentParseError) -> Self {
1194 Self::Component(e)
1195 }
1196}
1197
1198impl From<RenderDomError> for DomXmlParseError {
1199 fn from(e: RenderDomError) -> Self {
1200 Self::RenderDom(e)
1201 }
1202}
1203
1204impl From<CssParseErrorOwned> for DomXmlParseError {
1205 fn from(e: CssParseErrorOwned) -> Self {
1206 Self::Css(e)
1207 }
1208}
1209
1210#[derive(Debug, Clone, PartialEq)]
1213pub enum CompileError {
1214 Dom(RenderDomError),
1215 Xml(DomXmlParseError),
1216 Css(CssParseErrorOwned),
1217}
1218
1219impl From<ComponentError> for CompileError {
1220 fn from(e: ComponentError) -> Self {
1221 CompileError::Dom(RenderDomError::Component(e))
1222 }
1223}
1224
1225impl From<CssParseErrorOwned> for CompileError {
1226 fn from(e: CssParseErrorOwned) -> Self {
1227 CompileError::Css(e)
1228 }
1229}
1230
1231impl<'a> fmt::Display for CompileError {
1232 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1233 use self::CompileError::*;
1234 match self {
1235 Dom(d) => write!(f, "{}", d),
1236 Xml(s) => write!(f, "{}", s),
1237 Css(s) => write!(f, "{}", s.to_shared()),
1238 }
1239 }
1240}
1241
1242impl From<RenderDomError> for CompileError {
1243 fn from(e: RenderDomError) -> Self {
1244 CompileError::Dom(e)
1245 }
1246}
1247
1248impl From<DomXmlParseError> for CompileError {
1249 fn from(e: DomXmlParseError) -> Self {
1250 CompileError::Xml(e)
1251 }
1252}
1253
1254#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1255pub enum ComponentError {
1256 UselessFunctionArgument(AzString, AzString, Vec<String>),
1259 UnknownComponent(AzString),
1264}
1265
1266#[derive(Debug, Clone, PartialEq)]
1267pub enum RenderDomError {
1268 Component(ComponentError),
1269 CssError(CssParseErrorOwned),
1271}
1272
1273impl From<ComponentError> for RenderDomError {
1274 fn from(e: ComponentError) -> Self {
1275 Self::Component(e)
1276 }
1277}
1278
1279impl From<CssParseErrorOwned> for RenderDomError {
1280 fn from(e: CssParseErrorOwned) -> Self {
1281 Self::CssError(e)
1282 }
1283}
1284
1285#[derive(Debug, Clone, PartialEq)]
1286pub enum ComponentParseError {
1287 NotAComponent,
1289 UnnamedComponent,
1291 MissingName(usize),
1293 MissingType(usize, AzString),
1296 WhiteSpaceInComponentName(usize, AzString),
1299 WhiteSpaceInComponentType(usize, AzString, AzString),
1302 CssError(CssParseErrorOwned),
1304}
1305
1306impl<'a> fmt::Display for DomXmlParseError {
1307 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1308 use self::DomXmlParseError::*;
1309 match self {
1310 NoHtmlNode => write!(
1311 f,
1312 "No <html> node found as the root of the file - empty file?"
1313 ),
1314 MultipleHtmlRootNodes => write!(
1315 f,
1316 "Multiple <html> nodes found as the root of the file - only one root node allowed"
1317 ),
1318 NoBodyInHtml => write!(
1319 f,
1320 "No <body> node found as a direct child of an <html> node - malformed DOM \
1321 hierarchy?"
1322 ),
1323 MultipleBodyNodes => write!(
1324 f,
1325 "Multiple <body> nodes present, only one <body> node is allowed"
1326 ),
1327 Xml(e) => write!(f, "Error parsing XML: {}", e),
1328 MalformedHierarchy(e) => write!(
1329 f,
1330 "Invalid </{}> tag: expected </{}>",
1331 e.got.as_str(),
1332 e.expected.as_str()
1333 ),
1334 RenderDom(e) => write!(f, "Error rendering DOM: {}", e),
1335 Component(c) => write!(f, "Error parsing component in <head> node:\r\n{}", c),
1336 Css(c) => write!(f, "Error parsing CSS in <head> node:\r\n{}", c.to_shared()),
1337 }
1338 }
1339}
1340
1341impl<'a> fmt::Display for ComponentParseError {
1342 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1343 use self::ComponentParseError::*;
1344 match self {
1345 NotAComponent => write!(f, "Expected <component/> node, found no such node"),
1346 UnnamedComponent => write!(
1347 f,
1348 "Found <component/> tag with out a \"name\" attribute, component must have a name"
1349 ),
1350 MissingName(arg_pos) => write!(
1351 f,
1352 "Argument at position {} is either empty or has no name",
1353 arg_pos
1354 ),
1355 MissingType(arg_pos, arg_name) => write!(
1356 f,
1357 "Argument \"{}\" at position {} doesn't have a `: type`",
1358 arg_pos, arg_name
1359 ),
1360 WhiteSpaceInComponentName(arg_pos, arg_name_unparsed) => {
1361 write!(
1362 f,
1363 "Missing `:` between the name and the type in argument {} (around \"{}\")",
1364 arg_pos, arg_name_unparsed
1365 )
1366 }
1367 WhiteSpaceInComponentType(arg_pos, arg_name, arg_type_unparsed) => {
1368 write!(
1369 f,
1370 "Missing `,` between two arguments (in argument {}, position {}, around \
1371 \"{}\")",
1372 arg_name, arg_pos, arg_type_unparsed
1373 )
1374 }
1375 CssError(lsf) => write!(f, "Error parsing <style> tag: {}", lsf.to_shared()),
1376 }
1377 }
1378}
1379
1380impl fmt::Display for ComponentError {
1381 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1382 use self::ComponentError::*;
1383 match self {
1384 UselessFunctionArgument(k, v, available_args) => {
1385 write!(
1386 f,
1387 "Useless component argument \"{}\": \"{}\" - available args are: {:#?}",
1388 k, v, available_args
1389 )
1390 }
1391 UnknownComponent(name) => write!(f, "Unknown component: \"{}\"", name),
1392 }
1393 }
1394}
1395
1396impl<'a> fmt::Display for RenderDomError {
1397 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1398 use self::RenderDomError::*;
1399 match self {
1400 Component(c) => write!(f, "{}", c),
1401 CssError(e) => write!(f, "Error parsing CSS in component: {}", e.to_shared()),
1402 }
1403 }
1404}
1405
1406macro_rules! html_component {
1411 ($name:ident, $tag:expr, $node_type:expr) => {
1412 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1413 pub struct $name {
1414 node: XmlNode,
1415 }
1416
1417 impl $name {
1418 pub fn new() -> Self {
1419 Self {
1420 node: XmlNode::create($tag),
1421 }
1422 }
1423 }
1424
1425 impl XmlComponentTrait for $name {
1426 fn get_available_arguments(&self) -> ComponentArguments {
1427 ComponentArguments {
1428 args: ComponentArgumentTypes::default(),
1429 accepts_text: true,
1430 }
1431 }
1432
1433 fn render_dom(
1434 &self,
1435 _: &XmlComponentMap,
1436 _: &FilteredComponentArguments,
1437 text: &XmlTextContent,
1438 ) -> Result<StyledDom, RenderDomError> {
1439 let mut dom = Dom::create_node($node_type);
1440
1441 if let Some(text_str) = text.as_ref() {
1443 let prepared = prepare_string(text_str);
1444 if !prepared.is_empty() {
1445 dom = dom.with_children(alloc::vec![Dom::create_text(prepared)].into());
1446 }
1447 }
1448
1449 Ok(dom.style(Css::empty()))
1450 }
1451
1452 fn compile_to_rust_code(
1453 &self,
1454 _: &XmlComponentMap,
1455 _: &ComponentArguments,
1456 _: &XmlTextContent,
1457 ) -> Result<String, CompileError> {
1458 Ok(format!(
1459 "Dom::create_node(NodeType::{})",
1460 stringify!($node_type)
1461 ))
1462 }
1463
1464 fn get_xml_node(&self) -> XmlNode {
1465 self.node.clone()
1466 }
1467 }
1468 };
1469}
1470
1471html_component!(HtmlRenderer, "html", NodeType::Html);
1473html_component!(HeadRenderer, "head", NodeType::Head);
1474html_component!(TitleRenderer, "title", NodeType::Title);
1475html_component!(HeaderRenderer, "header", NodeType::Header);
1476html_component!(FooterRenderer, "footer", NodeType::Footer);
1477html_component!(SectionRenderer, "section", NodeType::Section);
1478html_component!(ArticleRenderer, "article", NodeType::Article);
1479html_component!(AsideRenderer, "aside", NodeType::Aside);
1480html_component!(NavRenderer, "nav", NodeType::Nav);
1481html_component!(MainRenderer, "main", NodeType::Main);
1482html_component!(H1Renderer, "h1", NodeType::H1);
1483html_component!(H2Renderer, "h2", NodeType::H2);
1484html_component!(H3Renderer, "h3", NodeType::H3);
1485html_component!(H4Renderer, "h4", NodeType::H4);
1486html_component!(H5Renderer, "h5", NodeType::H5);
1487html_component!(H6Renderer, "h6", NodeType::H6);
1488html_component!(SpanRenderer, "span", NodeType::Span);
1489html_component!(PreRenderer, "pre", NodeType::Pre);
1490html_component!(CodeRenderer, "code", NodeType::Code);
1491html_component!(BlockquoteRenderer, "blockquote", NodeType::BlockQuote);
1492html_component!(UlRenderer, "ul", NodeType::Ul);
1493html_component!(OlRenderer, "ol", NodeType::Ol);
1494html_component!(LiRenderer, "li", NodeType::Li);
1495html_component!(DlRenderer, "dl", NodeType::Dl);
1496html_component!(DtRenderer, "dt", NodeType::Dt);
1497html_component!(DdRenderer, "dd", NodeType::Dd);
1498html_component!(TableRenderer, "table", NodeType::Table);
1499html_component!(TheadRenderer, "thead", NodeType::THead);
1500html_component!(TbodyRenderer, "tbody", NodeType::TBody);
1501html_component!(TfootRenderer, "tfoot", NodeType::TFoot);
1502html_component!(TrRenderer, "tr", NodeType::Tr);
1503html_component!(ThRenderer, "th", NodeType::Th);
1504html_component!(TdRenderer, "td", NodeType::Td);
1505html_component!(ARenderer, "a", NodeType::A);
1506html_component!(StrongRenderer, "strong", NodeType::Strong);
1507html_component!(EmRenderer, "em", NodeType::Em);
1508html_component!(BRenderer, "b", NodeType::B);
1509html_component!(IRenderer, "i", NodeType::I);
1510html_component!(URenderer, "u", NodeType::U);
1511html_component!(SmallRenderer, "small", NodeType::Small);
1512html_component!(MarkRenderer, "mark", NodeType::Mark);
1513html_component!(SubRenderer, "sub", NodeType::Sub);
1514html_component!(SupRenderer, "sup", NodeType::Sup);
1515html_component!(FormRenderer, "form", NodeType::Form);
1516html_component!(LabelRenderer, "label", NodeType::Label);
1517html_component!(ButtonRenderer, "button", NodeType::Button);
1518html_component!(HrRenderer, "hr", NodeType::Hr);
1519
1520#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1522pub struct DivRenderer {
1523 node: XmlNode,
1524}
1525
1526impl DivRenderer {
1527 pub fn new() -> Self {
1528 Self {
1529 node: XmlNode::create("div"),
1530 }
1531 }
1532}
1533
1534impl XmlComponentTrait for DivRenderer {
1535 fn get_available_arguments(&self) -> ComponentArguments {
1536 ComponentArguments::new()
1537 }
1538
1539 fn render_dom(
1540 &self,
1541 _: &XmlComponentMap,
1542 _: &FilteredComponentArguments,
1543 _: &XmlTextContent,
1544 ) -> Result<StyledDom, RenderDomError> {
1545 Ok(Dom::create_div().style(Css::empty()))
1546 }
1547
1548 fn compile_to_rust_code(
1549 &self,
1550 _: &XmlComponentMap,
1551 _: &ComponentArguments,
1552 _: &XmlTextContent,
1553 ) -> Result<String, CompileError> {
1554 Ok("Dom::create_div()".into())
1555 }
1556
1557 fn get_xml_node(&self) -> XmlNode {
1558 self.node.clone()
1559 }
1560}
1561
1562#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1564pub struct BodyRenderer {
1565 node: XmlNode,
1566}
1567
1568impl BodyRenderer {
1569 pub fn new() -> Self {
1570 Self {
1571 node: XmlNode::create("body"),
1572 }
1573 }
1574}
1575
1576impl XmlComponentTrait for BodyRenderer {
1577 fn get_available_arguments(&self) -> ComponentArguments {
1578 ComponentArguments::new()
1579 }
1580
1581 fn render_dom(
1582 &self,
1583 _: &XmlComponentMap,
1584 _: &FilteredComponentArguments,
1585 _: &XmlTextContent,
1586 ) -> Result<StyledDom, RenderDomError> {
1587 Ok(Dom::create_body().style(Css::empty()))
1588 }
1589
1590 fn compile_to_rust_code(
1591 &self,
1592 _: &XmlComponentMap,
1593 _: &ComponentArguments,
1594 _: &XmlTextContent,
1595 ) -> Result<String, CompileError> {
1596 Ok("Dom::create_body()".into())
1597 }
1598
1599 fn get_xml_node(&self) -> XmlNode {
1600 self.node.clone()
1601 }
1602}
1603
1604#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1606pub struct BrRenderer {
1607 node: XmlNode,
1608}
1609
1610impl BrRenderer {
1611 pub fn new() -> Self {
1612 Self {
1613 node: XmlNode::create("br"),
1614 }
1615 }
1616}
1617
1618impl XmlComponentTrait for BrRenderer {
1619 fn get_available_arguments(&self) -> ComponentArguments {
1620 ComponentArguments::new()
1621 }
1622
1623 fn render_dom(
1624 &self,
1625 _: &XmlComponentMap,
1626 _: &FilteredComponentArguments,
1627 _: &XmlTextContent,
1628 ) -> Result<StyledDom, RenderDomError> {
1629 Ok(Dom::create_node(NodeType::Br).style(Css::empty()))
1630 }
1631
1632 fn compile_to_rust_code(
1633 &self,
1634 _: &XmlComponentMap,
1635 _: &ComponentArguments,
1636 _: &XmlTextContent,
1637 ) -> Result<String, CompileError> {
1638 Ok("Dom::create_node(NodeType::Br)".into())
1639 }
1640
1641 fn get_xml_node(&self) -> XmlNode {
1642 self.node.clone()
1643 }
1644}
1645
1646#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1657pub struct IconRenderer {
1658 node: XmlNode,
1659}
1660
1661impl IconRenderer {
1662 pub fn new() -> Self {
1663 Self {
1664 node: XmlNode::create("icon"),
1665 }
1666 }
1667}
1668
1669impl XmlComponentTrait for IconRenderer {
1670 fn get_available_arguments(&self) -> ComponentArguments {
1671 let mut args = ComponentArgumentTypes::default();
1672 args.push(("name".to_string(), "String".to_string()));
1673 ComponentArguments {
1674 args,
1675 accepts_text: true, }
1677 }
1678
1679 fn render_dom(
1680 &self,
1681 _: &XmlComponentMap,
1682 args: &FilteredComponentArguments,
1683 content: &XmlTextContent,
1684 ) -> Result<StyledDom, RenderDomError> {
1685 let icon_name = args.values.get("name")
1687 .map(|s| s.to_string())
1688 .or_else(|| content.as_ref().map(|s| prepare_string(&s)))
1689 .unwrap_or_else(|| "invalid-icon".to_string());
1690
1691 Ok(Dom::create_node(NodeType::Icon(AzString::from(icon_name))).style(Css::empty()))
1692 }
1693
1694 fn compile_to_rust_code(
1695 &self,
1696 _: &XmlComponentMap,
1697 args: &ComponentArguments,
1698 content: &XmlTextContent,
1699 ) -> Result<String, CompileError> {
1700 let icon_name = args.args.iter()
1701 .find(|(name, _)| name == "name")
1702 .map(|(_, value)| value.to_string())
1703 .or_else(|| content.as_ref().map(|s| s.to_string()))
1704 .unwrap_or_else(|| "invalid-icon".to_string());
1705
1706 Ok(format!("Dom::create_node(NodeType::Icon(AzString::from(\"{}\")))", icon_name))
1707 }
1708
1709 fn get_xml_node(&self) -> XmlNode {
1710 self.node.clone()
1711 }
1712}
1713
1714#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1716pub struct TextRenderer {
1717 node: XmlNode,
1718}
1719
1720impl TextRenderer {
1721 pub fn new() -> Self {
1722 Self {
1723 node: XmlNode::create("p"),
1724 }
1725 }
1726}
1727
1728impl XmlComponentTrait for TextRenderer {
1729 fn get_available_arguments(&self) -> ComponentArguments {
1730 ComponentArguments {
1731 args: ComponentArgumentTypes::default(),
1732 accepts_text: true, }
1734 }
1735
1736 fn render_dom(
1737 &self,
1738 _: &XmlComponentMap,
1739 _: &FilteredComponentArguments,
1740 content: &XmlTextContent,
1741 ) -> Result<StyledDom, RenderDomError> {
1742 let content = content
1743 .as_ref()
1744 .map(|s| prepare_string(&s))
1745 .unwrap_or_default();
1746 Ok(Dom::create_node(NodeType::P)
1747 .with_children(vec![Dom::create_text(content)].into())
1748 .style(Css::empty()))
1749 }
1750
1751 fn compile_to_rust_code(
1752 &self,
1753 _: &XmlComponentMap,
1754 args: &ComponentArguments,
1755 content: &XmlTextContent,
1756 ) -> Result<String, CompileError> {
1757 Ok(String::from(
1758 "Dom::create_node(NodeType::P).with_children(vec![Dom::create_text(content)].into())",
1759 ))
1760 }
1761
1762 fn get_xml_node(&self) -> XmlNode {
1763 self.node.clone()
1764 }
1765}
1766
1767pub fn parse_component_arguments<'a>(
1769 input: &'a str,
1770) -> Result<ComponentArgumentTypes, ComponentParseError> {
1771 use self::ComponentParseError::*;
1772
1773 let mut args = ComponentArgumentTypes::default();
1774
1775 for (arg_idx, arg) in input.split(",").enumerate() {
1776 let mut colon_iterator = arg.split(":");
1777
1778 let arg_name = colon_iterator.next().ok_or(MissingName(arg_idx))?;
1779 let arg_name = arg_name.trim();
1780
1781 if arg_name.is_empty() {
1782 return Err(MissingName(arg_idx));
1783 }
1784 if arg_name.chars().any(char::is_whitespace) {
1785 return Err(WhiteSpaceInComponentName(arg_idx, arg_name.into()));
1786 }
1787
1788 let arg_type = colon_iterator
1789 .next()
1790 .ok_or(MissingType(arg_idx, arg_name.into()))?;
1791 let arg_type = arg_type.trim();
1792
1793 if arg_type.is_empty() {
1794 return Err(MissingType(arg_idx, arg_name.into()));
1795 }
1796
1797 if arg_type.chars().any(char::is_whitespace) {
1798 return Err(WhiteSpaceInComponentType(
1799 arg_idx,
1800 arg_name.into(),
1801 arg_type.into(),
1802 ));
1803 }
1804
1805 let arg_name = normalize_casing(arg_name);
1806 let arg_type = arg_type.to_string();
1807
1808 args.push((arg_name, arg_type));
1809 }
1810
1811 Ok(args)
1812}
1813
1814pub fn validate_and_filter_component_args(
1816 xml_attributes: &XmlAttributeMap,
1817 valid_args: &ComponentArguments,
1818) -> Result<FilteredComponentArguments, ComponentError> {
1819 let mut map = FilteredComponentArguments {
1820 types: ComponentArgumentTypes::default(),
1821 values: BTreeMap::new(),
1822 accepts_text: valid_args.accepts_text,
1823 };
1824
1825 for AzStringPair { key, value } in xml_attributes.as_ref().iter() {
1826 let xml_attribute_name = key;
1827 let xml_attribute_value = value;
1828 if let Some(valid_arg_type) = valid_args
1829 .args
1830 .iter()
1831 .find(|s| s.0 == xml_attribute_name.as_str())
1832 .map(|q| &q.1)
1833 {
1834 map.types.push((
1835 xml_attribute_name.as_str().to_string(),
1836 valid_arg_type.clone(),
1837 ));
1838 map.values.insert(
1839 xml_attribute_name.as_str().to_string(),
1840 xml_attribute_value.as_str().to_string(),
1841 );
1842 } else if DEFAULT_ARGS.contains(&xml_attribute_name.as_str()) {
1843 map.values.insert(
1845 xml_attribute_name.as_str().to_string(),
1846 xml_attribute_value.as_str().to_string(),
1847 );
1848 } else {
1849 #[cfg(feature = "std")]
1853 eprintln!(
1854 "Warning: Useless component argument \"{}\": \"{}\" for component with args: {:?}",
1855 xml_attribute_name,
1856 xml_attribute_value,
1857 valid_args.args.iter().map(|s| &s.0).collect::<Vec<_>>()
1858 );
1859
1860 map.values.insert(
1862 xml_attribute_name.as_str().to_string(),
1863 xml_attribute_value.as_str().to_string(),
1864 );
1865 }
1866 }
1867
1868 Ok(map)
1869}
1870
1871pub fn get_html_node<'a>(root_nodes: &'a [XmlNodeChild]) -> Result<&'a XmlNode, DomXmlParseError> {
1874 let mut html_node_iterator = root_nodes.iter().filter_map(|child| {
1875 if let XmlNodeChild::Element(node) = child {
1876 let node_type_normalized = normalize_casing(&node.node_type);
1877 if &node_type_normalized == "html" {
1878 Some(node)
1879 } else {
1880 None
1881 }
1882 } else {
1883 None
1884 }
1885 });
1886
1887 let html_node = html_node_iterator
1888 .next()
1889 .ok_or(DomXmlParseError::NoHtmlNode)?;
1890 if html_node_iterator.next().is_some() {
1891 Err(DomXmlParseError::MultipleHtmlRootNodes)
1892 } else {
1893 Ok(html_node)
1894 }
1895}
1896
1897pub fn get_body_node<'a>(root_nodes: &'a [XmlNodeChild]) -> Result<&'a XmlNode, DomXmlParseError> {
1900 let mut body_node_iterator = root_nodes.iter().filter_map(|child| {
1901 if let XmlNodeChild::Element(node) = child {
1902 let node_type_normalized = normalize_casing(&node.node_type);
1903 if &node_type_normalized == "body" {
1904 Some(node)
1905 } else {
1906 None
1907 }
1908 } else {
1909 None
1910 }
1911 });
1912
1913 let body_node = body_node_iterator
1914 .next()
1915 .ok_or(DomXmlParseError::NoBodyInHtml)?;
1916 if body_node_iterator.next().is_some() {
1917 Err(DomXmlParseError::MultipleBodyNodes)
1918 } else {
1919 Ok(body_node)
1920 }
1921}
1922
1923static DEFAULT_STR: &str = "";
1924
1925pub fn find_node_by_type<'a>(
1928 root_nodes: &'a [XmlNodeChild],
1929 node_type: &str,
1930) -> Option<&'a XmlNode> {
1931 root_nodes
1932 .iter()
1933 .filter_map(|child| {
1934 if let XmlNodeChild::Element(node) = child {
1935 if normalize_casing(&node.node_type).as_str() == node_type {
1936 Some(node)
1937 } else {
1938 None
1939 }
1940 } else {
1941 None
1942 }
1943 })
1944 .next()
1945}
1946
1947pub fn find_attribute<'a>(node: &'a XmlNode, attribute: &str) -> Option<&'a AzString> {
1948 node.attributes
1949 .iter()
1950 .find(|n| normalize_casing(&n.key.as_str()).as_str() == attribute)
1951 .map(|s| &s.value)
1952}
1953
1954pub fn normalize_casing(input: &str) -> String {
1956 let mut words: Vec<String> = Vec::new();
1957 let mut cur_str = Vec::new();
1958
1959 for ch in input.chars() {
1960 if ch.is_uppercase() || ch == '_' || ch == '-' {
1961 if !cur_str.is_empty() {
1962 words.push(cur_str.iter().collect());
1963 cur_str.clear();
1964 }
1965 if ch.is_uppercase() {
1966 cur_str.extend(ch.to_lowercase());
1967 }
1968 } else {
1969 cur_str.extend(ch.to_lowercase());
1970 }
1971 }
1972
1973 if !cur_str.is_empty() {
1974 words.push(cur_str.iter().collect());
1975 cur_str.clear();
1976 }
1977
1978 words.join("_")
1979}
1980
1981#[allow(trivial_casts)]
1984pub fn get_item<'a>(hierarchy: &[usize], root_node: &'a mut XmlNode) -> Option<&'a mut XmlNode> {
1985 let mut hierarchy = hierarchy.to_vec();
1986 hierarchy.reverse();
1987 let item = match hierarchy.pop() {
1988 Some(s) => s,
1989 None => return Some(root_node),
1990 };
1991 let child = root_node.children.as_mut().get_mut(item)?;
1992 match child {
1993 XmlNodeChild::Element(node) => get_item_internal(&mut hierarchy, node),
1994 XmlNodeChild::Text(_) => None, }
1996}
1997
1998fn get_item_internal<'a>(
1999 hierarchy: &mut Vec<usize>,
2000 root_node: &'a mut XmlNode,
2001) -> Option<&'a mut XmlNode> {
2002 if hierarchy.is_empty() {
2003 return Some(root_node);
2004 }
2005 let cur_item = match hierarchy.pop() {
2006 Some(s) => s,
2007 None => return Some(root_node),
2008 };
2009 let child = root_node.children.as_mut().get_mut(cur_item)?;
2010 match child {
2011 XmlNodeChild::Element(node) => get_item_internal(hierarchy, node),
2012 XmlNodeChild::Text(_) => None, }
2014}
2015
2016pub fn str_to_dom<'a>(
2019 root_nodes: &'a [XmlNodeChild],
2020 component_map: &'a mut XmlComponentMap,
2021 max_width: Option<f32>,
2022) -> Result<StyledDom, DomXmlParseError> {
2023 let html_node = get_html_node(root_nodes)?;
2024 let body_node = get_body_node(html_node.children.as_ref())?;
2025
2026 let mut global_style = None;
2027
2028 if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
2029 for child in head_node.children.as_ref() {
2031 if let XmlNodeChild::Element(node) = child {
2032 match DynamicXmlComponent::new(node) {
2033 Ok(comp) => {
2034 let node_name = comp.name.clone();
2035 component_map.register_component(XmlComponent {
2036 id: normalize_casing(&node_name),
2037 renderer: Box::new(comp),
2038 inherit_vars: false,
2039 });
2040 }
2041 Err(ComponentParseError::NotAComponent) => {} Err(e) => return Err(e.into()), }
2046 }
2047 }
2048
2049 if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
2051 let text = style_node.get_text_content();
2052 if !text.is_empty() {
2053 let parsed_css = Css::from_string(text.into());
2054 global_style = Some(parsed_css);
2055 }
2056 }
2057 }
2058
2059 render_dom_from_body_node(&body_node, global_style, component_map, max_width)
2060 .map_err(|e| e.into())
2061}
2062
2063pub fn str_to_rust_code<'a>(
2066 root_nodes: &'a [XmlNodeChild],
2067 imports: &str,
2068 component_map: &'a mut XmlComponentMap,
2069) -> Result<String, CompileError> {
2070 let html_node = get_html_node(&root_nodes)?;
2071 let body_node = get_body_node(html_node.children.as_ref())?;
2072 let mut global_style = Css::empty();
2073
2074 if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
2075 for child in head_node.children.as_ref() {
2076 if let XmlNodeChild::Element(node) = child {
2077 match DynamicXmlComponent::new(node) {
2078 Ok(node) => {
2079 let node_name = node.name.clone();
2080 component_map.register_component(XmlComponent {
2081 id: normalize_casing(&node_name),
2082 renderer: Box::new(node),
2083 inherit_vars: false,
2084 });
2085 }
2086 Err(ComponentParseError::NotAComponent) => {} Err(e) => return Err(CompileError::Xml(e.into())), }
2092 }
2093 }
2094
2095 if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
2096 let text = style_node.get_text_content();
2097 if !text.is_empty() {
2098 let parsed_css = azul_css::parser2::new_from_str(&text).0;
2099 global_style = parsed_css;
2100 }
2101 }
2102 }
2103
2104 global_style.sort_by_specificity();
2105
2106 let mut css_blocks = BTreeMap::new();
2107 let mut extra_blocks = VecContents::default();
2108 let app_source = compile_body_node_to_rust_code(
2109 &body_node,
2110 component_map,
2111 &mut extra_blocks,
2112 &mut css_blocks,
2113 &global_style,
2114 CssMatcher {
2115 path: Vec::new(),
2116 indices_in_parent: vec![0],
2117 children_length: vec![body_node.children.as_ref().len()],
2118 },
2119 )?;
2120
2121 let app_source = app_source
2122 .lines()
2123 .map(|l| format!(" {}", l))
2124 .collect::<Vec<String>>()
2125 .join("\r\n");
2126
2127 let t = " ";
2128 let css_blocks = css_blocks
2129 .iter()
2130 .map(|(k, v)| {
2131 let v = v
2132 .lines()
2133 .map(|l| format!("{}{}{}", t, t, l))
2134 .collect::<Vec<String>>()
2135 .join("\r\n");
2136
2137 format!(
2138 " const {}_PROPERTIES: &[NodeDataInlineCssProperty] = \
2139 &[\r\n{}\r\n{}];\r\n{}const {}: NodeDataInlineCssPropertyVec = \
2140 NodeDataInlineCssPropertyVec::from_const_slice({}_PROPERTIES);",
2141 k, v, t, t, k, k
2142 )
2143 })
2144 .collect::<Vec<_>>()
2145 .join(&format!("{}\r\n\r\n", t));
2146
2147 let mut extra_block_string = extra_blocks.format(1);
2148
2149 let main_func = "
2150
2151use azul::{
2152 app::{App, AppConfig, LayoutSolver},
2153 css::Css,
2154 style::StyledDom,
2155 callbacks::{RefAny, LayoutCallbackInfo},
2156 window::{WindowCreateOptions, WindowFrame},
2157};
2158
2159struct Data { }
2160
2161extern \"C\" fn render(_: RefAny, _: LayoutCallbackInfo) -> StyledDom {
2162 crate::ui::render()
2163 .style(Css::empty()) // styles are applied inline
2164}
2165
2166fn main() {
2167 let app = App::new(RefAny::new(Data { }), AppConfig::new(LayoutSolver::Default));
2168 let mut window = WindowCreateOptions::new(render);
2169 window.state.flags.frame = WindowFrame::Maximized;
2170 app.run(window);
2171}";
2172
2173 let source_code = format!(
2174 "#![windows_subsystem = \"windows\"]\r\n//! Auto-generated UI source \
2175 code\r\n{}\r\n{}\r\n\r\n{}{}",
2176 imports,
2177 compile_components(compile_components_to_rust_code(component_map)?),
2178 format!(
2179 "#[allow(unused_imports)]\r\npub mod ui {{
2180
2181 pub use crate::components::*;
2182
2183 use azul::css::*;
2184 use azul::str::String as AzString;
2185 use azul::vec::{{
2186 DomVec, IdOrClassVec, NodeDataInlineCssPropertyVec,
2187 StyleBackgroundSizeVec, StyleBackgroundRepeatVec,
2188 StyleBackgroundContentVec, StyleTransformVec,
2189 StyleFontFamilyVec, StyleBackgroundPositionVec,
2190 NormalizedLinearColorStopVec, NormalizedRadialColorStopVec,
2191 }};
2192 use azul::dom::{{
2193 Dom, IdOrClass, TabIndex,
2194 IdOrClass::{{Id, Class}},
2195 NodeDataInlineCssProperty,
2196 }};\r\n\r\n{}\r\n\r\n{}
2197
2198 pub fn render() -> Dom {{\r\n{}\r\n }}\r\n}}",
2199 extra_block_string, css_blocks, app_source
2200 ),
2201 main_func,
2202 );
2203
2204 Ok(source_code)
2205}
2206
2207pub fn compile_components(
2209 components: Vec<(
2210 ComponentName,
2211 CompiledComponent,
2212 ComponentArguments,
2213 BTreeMap<String, String>,
2214 )>,
2215) -> String {
2216 let cs = components
2217 .iter()
2218 .map(|(name, function_body, function_args, css_blocks)| {
2219 let name = &normalize_casing(&name);
2220 let f = compile_component(name, function_args, function_body)
2221 .lines()
2222 .map(|l| format!(" {}", l))
2223 .collect::<Vec<String>>()
2224 .join("\r\n");
2225
2226 format!(
2229 "#[allow(unused_imports)]\r\npub mod {} {{\r\n use azul::dom::Dom;\r\n use \
2230 azul::str::String as AzString;\r\n{}\r\n}}",
2231 name, f
2232 )
2233 })
2234 .collect::<Vec<String>>()
2235 .join("\r\n\r\n");
2236
2237 let cs = cs
2238 .lines()
2239 .map(|l| format!(" {}", l))
2240 .collect::<Vec<String>>()
2241 .join("\r\n");
2242
2243 if cs.is_empty() {
2244 cs
2245 } else {
2246 format!("pub mod components {{\r\n{}\r\n}}", cs)
2247 }
2248}
2249
2250pub fn format_component_args(component_args: &ComponentArgumentTypes) -> String {
2251 let mut args = component_args
2252 .iter()
2253 .map(|(arg_name, arg_type)| format!("{}: {}", arg_name, arg_type))
2254 .collect::<Vec<String>>();
2255
2256 args.sort_by(|a, b| b.cmp(&a));
2257
2258 args.join(", ")
2259}
2260
2261pub fn compile_component(
2262 component_name: &str,
2263 component_args: &ComponentArguments,
2264 component_function_body: &str,
2265) -> String {
2266 let component_name = &normalize_casing(&component_name);
2267 let function_args = format_component_args(&component_args.args);
2268 let component_function_body = component_function_body
2269 .lines()
2270 .map(|l| format!(" {}", l))
2271 .collect::<Vec<String>>()
2272 .join("\r\n");
2273 let should_inline = component_function_body.lines().count() == 1;
2274 format!(
2275 "{}pub fn render({}{}{}) -> Dom {{\r\n{}\r\n}}",
2276 if should_inline { "#[inline]\r\n" } else { "" },
2277 if component_args.accepts_text {
2279 "text: AzString"
2280 } else {
2281 ""
2282 },
2283 if function_args.is_empty() || !component_args.accepts_text {
2284 ""
2285 } else {
2286 ", "
2287 },
2288 function_args,
2289 component_function_body,
2290 )
2291}
2292
2293pub fn render_dom_from_body_node<'a>(
2294 body_node: &'a XmlNode,
2295 mut global_css: Option<Css>,
2296 component_map: &'a XmlComponentMap,
2297 max_width: Option<f32>,
2298) -> Result<StyledDom, RenderDomError> {
2299 let mut body_styled = render_dom_from_body_node_inner(
2301 body_node,
2302 component_map,
2303 &FilteredComponentArguments::default(),
2304 )?;
2305
2306 let root_node_id = match body_styled.root.into_crate_internal() {
2308 Some(id) => id,
2309 None => {
2310 let mut html_dom = Dom::create_html()
2312 .with_child(Dom::create_body())
2313 .style(Css::empty());
2314
2315 if let Some(max_width) = max_width {
2316 html_dom.restyle(Css::from_string(
2317 format!("html {{ max-width: {max_width}px; }}").into(),
2318 ));
2319 }
2320
2321 if let Some(global_css) = global_css.clone() {
2322 html_dom.restyle(global_css);
2323 }
2324
2325 return Ok(html_dom);
2326 }
2327 };
2328
2329 let root_node_data = &body_styled.node_data.as_ref()[root_node_id.index()];
2330 let root_node_type = &root_node_data.node_type;
2331
2332 use crate::dom::NodeType;
2333
2334 let mut dom = match root_node_type {
2336 NodeType::Html => {
2337 body_styled
2339 }
2340 NodeType::Body => {
2341 let mut html_dom = Dom::create_html().style(Css::empty());
2343 html_dom.append_child(body_styled);
2344 html_dom
2345 }
2346 _ => {
2347 let mut body_dom = Dom::create_body().style(Css::empty());
2349 body_dom.append_child(body_styled);
2350 let mut html_dom = Dom::create_html().style(Css::empty());
2351 html_dom.append_child(body_dom);
2352 html_dom
2353 }
2354 };
2355
2356 if let Some(max_width) = max_width {
2357 dom.restyle(Css::from_string(
2358 format!("html {{ max-width: {max_width}px; }}").into(),
2359 ));
2360 }
2361
2362 if let Some(global_css) = global_css.clone() {
2363 dom.restyle(global_css); }
2365
2366 Ok(dom)
2367}
2368
2369pub fn render_dom_from_body_node_inner<'a>(
2371 xml_node: &'a XmlNode,
2372 component_map: &'a XmlComponentMap,
2373 parent_xml_attributes: &FilteredComponentArguments,
2374) -> Result<StyledDom, RenderDomError> {
2375 let component_name = normalize_casing(&xml_node.node_type);
2376
2377 let xml_component = component_map
2378 .components
2379 .iter()
2380 .find(|s| normalize_casing(&s.id) == component_name)
2381 .ok_or(ComponentError::UnknownComponent(
2382 component_name.clone().into(),
2383 ))?;
2384
2385 let available_function_args = xml_component.renderer.get_available_arguments();
2387 let mut filtered_xml_attributes =
2388 validate_and_filter_component_args(&xml_node.attributes, &available_function_args)?;
2389
2390 if xml_component.inherit_vars {
2391 filtered_xml_attributes
2393 .types
2394 .extend(parent_xml_attributes.types.clone().into_iter());
2395 }
2396
2397 for v in filtered_xml_attributes.types.iter_mut() {
2399 v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.types).to_string();
2400 }
2401
2402 let mut dom = xml_component.renderer.render_dom(
2404 component_map,
2405 &filtered_xml_attributes,
2406 &OptionString::None,
2407 )?;
2408 set_attributes(&mut dom, &xml_node.attributes, &filtered_xml_attributes);
2409
2410 for child in xml_node.children.as_ref().iter() {
2411 match child {
2412 XmlNodeChild::Element(child_node) => {
2413 let child_dom = render_dom_from_body_node_inner(
2414 child_node,
2415 component_map,
2416 &filtered_xml_attributes,
2417 )?;
2418 dom.append_child(child_dom);
2419 }
2420 XmlNodeChild::Text(text) => {
2421 let text_dom = Dom::create_text(AzString::from(text.as_str())).style(Css::empty());
2423 dom.append_child(text_dom);
2424 }
2425 }
2426 }
2427
2428 Ok(dom)
2429}
2430
2431pub fn set_attributes(
2432 dom: &mut StyledDom,
2433 xml_attributes: &XmlAttributeMap,
2434 filtered_xml_attributes: &FilteredComponentArguments,
2435) {
2436 use crate::dom::{
2437 IdOrClass::{Class, Id},
2438 TabIndex,
2439 };
2440
2441 let mut ids_and_classes = Vec::new();
2442 let dom_root = match dom.root.into_crate_internal() {
2443 Some(s) => s,
2444 None => return,
2445 };
2446 let node_data = &mut dom.node_data.as_container_mut()[dom_root];
2447
2448 if let Some(ids) = xml_attributes.get_key("id") {
2449 for id in ids.split_whitespace() {
2450 ids_and_classes.push(Id(
2451 format_args_dynamic(id, &filtered_xml_attributes.types).into()
2452 ));
2453 }
2454 }
2455
2456 if let Some(classes) = xml_attributes.get_key("class") {
2457 for class in classes.split_whitespace() {
2458 ids_and_classes.push(Class(
2459 format_args_dynamic(class, &filtered_xml_attributes.types).into(),
2460 ));
2461 }
2462 }
2463
2464 node_data.set_ids_and_classes(ids_and_classes.into());
2465
2466 if let Some(focusable) = xml_attributes
2467 .get_key("focusable")
2468 .map(|f| format_args_dynamic(f.as_str(), &filtered_xml_attributes.types))
2469 .and_then(|f| parse_bool(&f))
2470 {
2471 match focusable {
2472 true => node_data.set_tab_index(TabIndex::Auto),
2473 false => node_data.set_tab_index(TabIndex::NoKeyboardFocus.into()),
2474 }
2475 }
2476
2477 if let Some(tab_index) = xml_attributes
2478 .get_key("tabindex")
2479 .map(|val| format_args_dynamic(val, &filtered_xml_attributes.types))
2480 .and_then(|val| val.parse::<isize>().ok())
2481 {
2482 match tab_index {
2483 0 => node_data.set_tab_index(TabIndex::Auto),
2484 i if i > 0 => node_data.set_tab_index(TabIndex::OverrideInParent(i as u32)),
2485 _ => node_data.set_tab_index(TabIndex::NoKeyboardFocus),
2486 }
2487 }
2488
2489 if let Some(style) = xml_attributes.get_key("style") {
2490 let css_key_map = azul_css::props::property::get_css_key_map();
2491 let mut attributes = Vec::new();
2492 for s in style.as_str().split(";") {
2493 let mut s = s.split(":");
2494 let key = match s.next() {
2495 Some(s) => s,
2496 None => continue,
2497 };
2498 let value = match s.next() {
2499 Some(s) => s,
2500 None => continue,
2501 };
2502 azul_css::parser2::parse_css_declaration(
2503 key.trim(),
2504 value.trim(),
2505 (ErrorLocation::default(), ErrorLocation::default()),
2506 &css_key_map,
2507 &mut Vec::new(),
2508 &mut attributes,
2509 );
2510 }
2511
2512 let props = attributes
2513 .into_iter()
2514 .filter_map(|s| {
2515 use azul_css::dynamic_selector::CssPropertyWithConditions;
2516 match s {
2517 CssDeclaration::Static(s) => Some(CssPropertyWithConditions::simple(s)),
2518 _ => return None,
2519 }
2520 })
2521 .collect::<Vec<_>>();
2522
2523 node_data.set_css_props(props.into());
2524 }
2525}
2526
2527pub fn set_stringified_attributes(
2528 dom_string: &mut String,
2529 xml_attributes: &XmlAttributeMap,
2530 filtered_xml_attributes: &ComponentArgumentTypes,
2531 tabs: usize,
2532) {
2533 let t0 = String::from(" ").repeat(tabs);
2534 let t = String::from(" ").repeat(tabs + 1);
2535
2536 let mut ids_and_classes = String::new();
2538
2539 for id in xml_attributes
2540 .get_key("id")
2541 .map(|s| s.split_whitespace().collect::<Vec<_>>())
2542 .unwrap_or_default()
2543 {
2544 ids_and_classes.push_str(&format!(
2545 "{} Id(AzString::from_const_str(\"{}\")),\r\n",
2546 t0,
2547 format_args_dynamic(id, &filtered_xml_attributes)
2548 ));
2549 }
2550
2551 for class in xml_attributes
2552 .get_key("class")
2553 .map(|s| s.split_whitespace().collect::<Vec<_>>())
2554 .unwrap_or_default()
2555 {
2556 ids_and_classes.push_str(&format!(
2557 "{} Class(AzString::from_const_str(\"{}\")),\r\n",
2558 t0,
2559 format_args_dynamic(class, &filtered_xml_attributes)
2560 ));
2561 }
2562
2563 if !ids_and_classes.is_empty() {
2564 use azul_css::format_rust_code::GetHash;
2565 let id = ids_and_classes.get_hash();
2566 dom_string.push_str(&format!(
2567 "\r\n{t0}.with_ids_and_classes({{\r\n{t}const IDS_AND_CLASSES_{id}: &[IdOrClass] = \
2568 &[\r\n{t}{ids_and_classes}\r\n{t}];\r\\
2569 n{t}IdOrClassVec::from_const_slice(IDS_AND_CLASSES_{id})\r\n{t0}}})",
2570 t0 = t0,
2571 t = t,
2572 ids_and_classes = ids_and_classes,
2573 id = id
2574 ));
2575 }
2576
2577 if let Some(focusable) = xml_attributes
2578 .get_key("focusable")
2579 .map(|f| format_args_dynamic(f, &filtered_xml_attributes))
2580 .and_then(|f| parse_bool(&f))
2581 {
2582 match focusable {
2583 true => dom_string.push_str(&format!("\r\n{}.with_tab_index(TabIndex::Auto)", t)),
2584 false => dom_string.push_str(&format!(
2585 "\r\n{}.with_tab_index(TabIndex::NoKeyboardFocus)",
2586 t
2587 )),
2588 }
2589 }
2590
2591 if let Some(tab_index) = xml_attributes
2592 .get_key("tabindex")
2593 .map(|val| format_args_dynamic(val, &filtered_xml_attributes))
2594 .and_then(|val| val.parse::<isize>().ok())
2595 {
2596 match tab_index {
2597 0 => dom_string.push_str(&format!("\r\n{}.with_tab_index(TabIndex::Auto)", t)),
2598 i if i > 0 => dom_string.push_str(&format!(
2599 "\r\n{}.with_tab_index(TabIndex::OverrideInParent({}))",
2600 t, i as usize
2601 )),
2602 _ => dom_string.push_str(&format!(
2603 "\r\n{}.with_tab_index(TabIndex::NoKeyboardFocus)",
2604 t
2605 )),
2606 }
2607 }
2608}
2609
2610#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
2612pub enum DynamicItem {
2613 Var(String),
2614 Str(String),
2615}
2616
2617pub fn split_dynamic_string(input: &str) -> Vec<DynamicItem> {
2635 use self::DynamicItem::*;
2636
2637 let input: Vec<char> = input.chars().collect();
2638 let input_chars_len = input.len();
2639
2640 let mut items = Vec::new();
2641 let mut current_idx = 0;
2642 let mut last_idx = 0;
2643
2644 while current_idx < input_chars_len {
2645 let c = input[current_idx];
2646 match c {
2647 '{' if input.get(current_idx + 1).copied() != Some('{') => {
2648 let mut start_offset = 1;
2650 let mut has_found_variable = false;
2651 while let Some(c) = input.get(current_idx + start_offset) {
2652 if c.is_whitespace() {
2653 break;
2654 }
2655 if *c == '}' && input.get(current_idx + start_offset + 1).copied() != Some('}')
2656 {
2657 start_offset += 1;
2658 has_found_variable = true;
2659 break;
2660 }
2661 start_offset += 1;
2662 }
2663
2664 if has_found_variable {
2668 if last_idx != current_idx {
2669 items.push(Str(input[last_idx..current_idx].iter().collect()));
2670 }
2671
2672 items.push(Var(input
2674 [(current_idx + 1)..(current_idx + start_offset - 1)]
2675 .iter()
2676 .collect()));
2677 current_idx = current_idx + start_offset;
2678 last_idx = current_idx;
2679 } else {
2680 current_idx += start_offset;
2681 }
2682 }
2683 _ => {
2684 current_idx += 1;
2685 }
2686 }
2687 }
2688
2689 if current_idx != last_idx {
2690 items.push(Str(input[last_idx..].iter().collect()));
2691 }
2692
2693 for item in &mut items {
2694 if let Str(s) = item {
2696 *s = s.replace("{{", "{").replace("}}", "}");
2697 }
2698 }
2699
2700 items
2701}
2702
2703pub fn combine_and_replace_dynamic_items(
2710 input: &[DynamicItem],
2711 variables: &ComponentArgumentTypes,
2712) -> String {
2713 let mut s = String::new();
2714
2715 for item in input {
2716 match item {
2717 DynamicItem::Var(v) => {
2718 let variable_name = normalize_casing(v.trim());
2719 match variables
2720 .iter()
2721 .find(|s| s.0 == variable_name)
2722 .map(|q| &q.1)
2723 {
2724 Some(resolved_var) => {
2725 s.push_str(&resolved_var);
2726 }
2727 None => {
2728 s.push('{');
2729 s.push_str(v);
2730 s.push('}');
2731 }
2732 }
2733 }
2734 DynamicItem::Str(dynamic_str) => {
2735 s.push_str(&dynamic_str);
2736 }
2737 }
2738 }
2739
2740 s
2741}
2742
2743pub fn format_args_dynamic(input: &str, variables: &ComponentArgumentTypes) -> String {
2762 let dynamic_str_items = split_dynamic_string(input);
2763 combine_and_replace_dynamic_items(&dynamic_str_items, variables)
2764}
2765
2766pub fn prepare_string(input: &str) -> String {
2768 const SPACE: &str = " ";
2769 const RETURN: &str = "\n";
2770
2771 let input = input.trim();
2772
2773 if input.is_empty() {
2774 return String::new();
2775 }
2776
2777 let input = input.replace("<", "<");
2778 let input = input.replace(">", ">");
2779
2780 let input_len = input.len();
2781 let mut final_lines: Vec<String> = Vec::new();
2782 let mut last_line_was_empty = false;
2783
2784 for line in input.lines() {
2785 let line = line.trim();
2786 let line = line.replace(" ", " ");
2787 let current_line_is_empty = line.is_empty();
2788
2789 if !current_line_is_empty {
2790 if last_line_was_empty {
2791 final_lines.push(format!("{}{}", RETURN, line));
2792 } else {
2793 final_lines.push(line.to_string());
2794 }
2795 }
2796
2797 last_line_was_empty = current_line_is_empty;
2798 }
2799
2800 let line_len = final_lines.len();
2801 let mut target = String::with_capacity(input_len);
2802 for (line_idx, line) in final_lines.iter().enumerate() {
2803 if !(line.starts_with(RETURN) || line_idx == 0 || line_idx == line_len.saturating_sub(1)) {
2804 target.push_str(SPACE);
2805 }
2806 target.push_str(line);
2807 }
2808 target
2809}
2810
2811pub fn parse_bool(input: &str) -> Option<bool> {
2813 match input {
2814 "true" => Some(true),
2815 "false" => Some(false),
2816 _ => None,
2817 }
2818}
2819
2820pub fn render_component_inner<'a>(
2821 map: &mut Vec<(
2822 ComponentName,
2823 CompiledComponent,
2824 ComponentArguments,
2825 BTreeMap<String, String>,
2826 )>,
2827 component_name: String,
2828 xml_component: &'a XmlComponent,
2829 component_map: &'a XmlComponentMap,
2830 parent_xml_attributes: &ComponentArguments,
2831 tabs: usize,
2832) -> Result<(), CompileError> {
2833 let t = String::from(" ").repeat(tabs - 1);
2834 let t1 = String::from(" ").repeat(tabs);
2835
2836 let component_name = normalize_casing(&component_name);
2837 let xml_node = xml_component.renderer.get_xml_node();
2838
2839 let mut css = match find_node_by_type(xml_node.children.as_ref(), "style") {
2840 Some(style_node) => {
2841 let text = style_node.get_text_content();
2842 if !text.is_empty() {
2843 Some(text)
2844 } else {
2845 None
2846 }
2847 }
2848 None => None,
2849 };
2850 let mut css = match css {
2851 Some(text) => azul_css::parser2::new_from_str(&text).0,
2852 None => Css::empty(),
2853 };
2854
2855 css.sort_by_specificity();
2856
2857 let available_function_arg_types = xml_component.renderer.get_available_arguments();
2859 let mut filtered_xml_attributes = available_function_arg_types.clone();
2861
2862 if xml_component.inherit_vars {
2863 filtered_xml_attributes
2865 .args
2866 .extend(parent_xml_attributes.args.clone().into_iter());
2867 }
2868
2869 for v in filtered_xml_attributes.args.iter_mut() {
2871 v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.args).to_string();
2872 }
2873
2874 let text_content = xml_node.get_text_content();
2875 let text = if !text_content.is_empty() {
2876 Some(AzString::from(format_args_dynamic(
2877 &text_content,
2878 &filtered_xml_attributes.args,
2879 )))
2880 } else {
2881 None
2882 };
2883
2884 let mut dom_string = xml_component.renderer.compile_to_rust_code(
2885 component_map,
2886 &filtered_xml_attributes,
2887 &text.into(),
2888 )?;
2889
2890 set_stringified_attributes(
2891 &mut dom_string,
2892 &xml_node.attributes,
2893 &filtered_xml_attributes.args,
2894 tabs,
2895 );
2896
2897 let matcher = CssMatcher {
2899 path: vec![CssPathSelector::Type(NodeTypeTag::Body)],
2900 indices_in_parent: Vec::new(),
2901 children_length: Vec::new(),
2902 };
2903
2904 let mut css_blocks = BTreeMap::new();
2905 let mut extra_blocks = VecContents::default();
2906
2907 if !xml_node.children.as_ref().is_empty() {
2908 dom_string.push_str(&format!(
2909 "\r\n{}.with_children(DomVec::from_vec(vec![\r\n",
2910 t
2911 ));
2912 for (child_idx, child) in xml_node.children.as_ref().iter().enumerate() {
2913 if let XmlNodeChild::Element(child_node) = child {
2914 let mut matcher = matcher.clone();
2915 matcher.indices_in_parent.push(child_idx);
2916 matcher
2917 .children_length
2918 .push(xml_node.children.as_ref().len());
2919
2920 dom_string.push_str(&format!(
2921 "{}{},",
2922 t1,
2923 compile_node_to_rust_code_inner(
2924 child_node,
2925 component_map,
2926 &filtered_xml_attributes,
2927 tabs + 1,
2928 &mut extra_blocks,
2929 &mut css_blocks,
2930 &css,
2931 matcher,
2932 )?
2933 ));
2934 }
2935 }
2936 dom_string.push_str(&format!("\r\n{}]))", t));
2937 }
2938
2939 map.push((
2940 component_name,
2941 dom_string,
2942 filtered_xml_attributes,
2943 css_blocks,
2944 ));
2945
2946 Ok(())
2947}
2948
2949pub fn compile_components_to_rust_code(
2951 components: &XmlComponentMap,
2952) -> Result<
2953 Vec<(
2954 ComponentName,
2955 CompiledComponent,
2956 ComponentArguments,
2957 BTreeMap<String, String>,
2958 )>,
2959 CompileError,
2960> {
2961 let mut map = Vec::new();
2962
2963 for xml_component in &components.components {
2964 render_component_inner(
2965 &mut map,
2966 normalize_casing(&xml_component.id),
2967 xml_component,
2968 &components,
2969 &ComponentArguments::default(),
2970 1,
2971 )?;
2972 }
2973
2974 Ok(map)
2975}
2976
2977#[derive(Clone)]
2978pub struct CssMatcher {
2979 path: Vec<CssPathSelector>,
2980 indices_in_parent: Vec<usize>,
2981 children_length: Vec<usize>,
2982}
2983
2984impl CssMatcher {
2985 fn get_hash(&self) -> u64 {
2986 use core::hash::Hash;
2987
2988 use highway::{HighwayHash, HighwayHasher, Key};
2989
2990 let mut hasher = HighwayHasher::new(Key([0; 4]));
2991 for p in self.path.iter() {
2992 p.hash(&mut hasher);
2993 }
2994 hasher.finalize64()
2995 }
2996}
2997
2998impl CssMatcher {
2999 fn matches(&self, path: &CssPath) -> bool {
3000 use azul_css::css::CssPathSelector::*;
3001
3002 use crate::style::{CssGroupIterator, CssGroupSplitReason};
3003
3004 if self.path.is_empty() {
3005 return false;
3006 }
3007 if path.selectors.as_ref().is_empty() {
3008 return false;
3009 }
3010
3011 let mut path_groups = CssGroupIterator::new(path.selectors.as_ref()).collect::<Vec<_>>();
3013 path_groups.reverse();
3014
3015 if path_groups.is_empty() {
3016 return false;
3017 }
3018 let mut self_groups = CssGroupIterator::new(self.path.as_ref()).collect::<Vec<_>>();
3019 self_groups.reverse();
3020 if self_groups.is_empty() {
3021 return false;
3022 }
3023
3024 if self.indices_in_parent.len() != self_groups.len() {
3025 return false;
3026 }
3027 if self.children_length.len() != self_groups.len() {
3028 return false;
3029 }
3030
3031 let mut cur_selfgroup_scan = 0;
3045 let mut cur_pathgroup_scan = 0;
3046 let mut valid = false;
3047 let mut path_group = path_groups[cur_pathgroup_scan].clone();
3048
3049 while cur_selfgroup_scan < self_groups.len() {
3050 let mut advance = None;
3051
3052 for (id, cg) in self_groups[cur_selfgroup_scan..].iter().enumerate() {
3054 let gm = group_matches(
3055 &path_group.0,
3056 &self_groups[cur_selfgroup_scan + id].0,
3057 self.indices_in_parent[cur_selfgroup_scan + id],
3058 self.children_length[cur_selfgroup_scan + id],
3059 );
3060
3061 if gm {
3062 advance = Some(id);
3065 break;
3066 }
3067 }
3068
3069 match advance {
3070 Some(n) => {
3071 if cur_pathgroup_scan == path_groups.len() - 1 {
3074 return cur_selfgroup_scan + n == self_groups.len() - 1;
3076 } else {
3077 cur_pathgroup_scan += 1;
3078 cur_selfgroup_scan += n;
3079 path_group = path_groups[cur_pathgroup_scan].clone();
3080 }
3081 }
3082 None => return false, }
3084 }
3085
3086 return cur_pathgroup_scan == path_groups.len() - 1;
3088 }
3089}
3090
3091fn group_matches(
3094 a: &[&CssPathSelector],
3095 b: &[&CssPathSelector],
3096 idx_in_parent: usize,
3097 parent_children: usize,
3098) -> bool {
3099 use azul_css::css::{CssNthChildSelector, CssPathPseudoSelector, CssPathSelector::*};
3100
3101 for selector in a {
3102 match selector {
3103 Global => {}
3105 PseudoSelector(CssPathPseudoSelector::Hover) => {}
3106 PseudoSelector(CssPathPseudoSelector::Active) => {}
3107 PseudoSelector(CssPathPseudoSelector::Focus) => {}
3108
3109 Type(tag) => {
3110 if !b.iter().any(|t| **t == Type(tag.clone())) {
3111 return false;
3112 }
3113 }
3114 Class(class) => {
3115 if !b.iter().any(|t| **t == Class(class.clone())) {
3116 return false;
3117 }
3118 }
3119 Id(id) => {
3120 if !b.iter().any(|t| **t == Id(id.clone())) {
3121 return false;
3122 }
3123 }
3124 PseudoSelector(CssPathPseudoSelector::First) => {
3125 if idx_in_parent != 0 {
3126 return false;
3127 }
3128 }
3129 PseudoSelector(CssPathPseudoSelector::Last) => {
3130 if idx_in_parent != parent_children.saturating_sub(1) {
3131 return false;
3132 }
3133 }
3134 PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Number(i))) => {
3135 if idx_in_parent != *i as usize {
3136 return false;
3137 }
3138 }
3139 PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Even)) => {
3140 if idx_in_parent % 2 != 0 {
3141 return false;
3142 }
3143 }
3144 PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Odd)) => {
3145 if idx_in_parent % 2 == 0 {
3146 return false;
3147 }
3148 }
3149 PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Pattern(p))) => {
3150 if idx_in_parent.saturating_sub(p.offset as usize) % p.pattern_repeat as usize != 0
3151 {
3152 return false;
3153 }
3154 }
3155
3156 _ => return false, }
3158 }
3159
3160 true
3161}
3162
3163struct CssBlock {
3164 ending: Option<CssPathPseudoSelector>,
3165 block: CssRuleBlock,
3166}
3167
3168pub fn compile_body_node_to_rust_code<'a>(
3169 body_node: &'a XmlNode,
3170 component_map: &'a XmlComponentMap,
3171 extra_blocks: &mut VecContents,
3172 css_blocks: &mut BTreeMap<String, String>,
3173 css: &Css,
3174 mut matcher: CssMatcher,
3175) -> Result<String, CompileError> {
3176 use azul_css::css::CssDeclaration;
3177
3178 let t = "";
3179 let t2 = " ";
3180 let mut dom_string = String::from("Dom::create_body()");
3181 let node_type = CssPathSelector::Type(NodeTypeTag::Body);
3182 matcher.path.push(node_type);
3183
3184 let ids = body_node
3185 .attributes
3186 .get_key("id")
3187 .map(|s| s.split_whitespace().collect::<Vec<_>>())
3188 .unwrap_or_default();
3189 matcher.path.extend(
3190 ids.into_iter()
3191 .map(|id| CssPathSelector::Id(id.to_string().into())),
3192 );
3193 let classes = body_node
3194 .attributes
3195 .get_key("class")
3196 .map(|s| s.split_whitespace().collect::<Vec<_>>())
3197 .unwrap_or_default();
3198 matcher.path.extend(
3199 classes
3200 .into_iter()
3201 .map(|class| CssPathSelector::Class(class.to_string().into())),
3202 );
3203
3204 let matcher_hash = matcher.get_hash();
3205 let css_blocks_for_this_node = get_css_blocks(css, &matcher);
3206 if !css_blocks_for_this_node.is_empty() {
3207 use azul_css::props::property::format_static_css_prop;
3208
3209 let css_strings = css_blocks_for_this_node
3210 .iter()
3211 .rev()
3212 .map(|css_block| {
3213 let wrapper = match css_block.ending {
3214 Some(CssPathPseudoSelector::Hover) => "Hover",
3215 Some(CssPathPseudoSelector::Active) => "Active",
3216 Some(CssPathPseudoSelector::Focus) => "Focus",
3217 _ => "Normal",
3218 };
3219
3220 for declaration in css_block.block.declarations.as_ref().iter() {
3221 let prop = match declaration {
3222 CssDeclaration::Static(s) => s,
3223 CssDeclaration::Dynamic(d) => &d.default_value,
3224 };
3225 extra_blocks.insert_from_css_property(prop);
3226 }
3227
3228 let formatted = css_block
3229 .block
3230 .declarations
3231 .as_ref()
3232 .iter()
3233 .rev()
3234 .map(|s| match &s {
3235 CssDeclaration::Static(s) => format!(
3236 "NodeDataInlineCssProperty::{}({})",
3237 wrapper,
3238 format_static_css_prop(s, 1)
3239 ),
3240 CssDeclaration::Dynamic(d) => format!(
3241 "NodeDataInlineCssProperty::{}({})",
3242 wrapper,
3243 format_static_css_prop(&d.default_value, 1)
3244 ),
3245 })
3246 .collect::<Vec<String>>();
3247
3248 format!("// {}\r\n{}", css_block.block.path, formatted.join(",\r\n"))
3249 })
3250 .collect::<Vec<_>>()
3251 .join(",\r\n");
3252
3253 css_blocks.insert(format!("CSS_MATCH_{:09}", matcher_hash), css_strings);
3254 dom_string.push_str(&format!(
3255 "\r\n{}.with_inline_css_props(CSS_MATCH_{:09})",
3256 t2, matcher_hash
3257 ));
3258 }
3259
3260 if !body_node.children.as_ref().is_empty() {
3261 use azul_css::format_rust_code::GetHash;
3262 let children_hash = body_node.children.as_ref().get_hash();
3263 dom_string.push_str(&format!("\r\n.with_children(DomVec::from_vec(vec![\r\n"));
3264
3265 for (child_idx, child) in body_node.children.as_ref().iter().enumerate() {
3266 match child {
3267 XmlNodeChild::Element(child_node) => {
3268 let mut matcher = matcher.clone();
3269 matcher.path.push(CssPathSelector::Children);
3270 matcher.indices_in_parent.push(child_idx);
3271 matcher.children_length.push(body_node.children.len());
3272
3273 dom_string.push_str(&format!(
3274 "{}{},\r\n",
3275 t,
3276 compile_node_to_rust_code_inner(
3277 child_node,
3278 component_map,
3279 &ComponentArguments::default(),
3280 1,
3281 extra_blocks,
3282 css_blocks,
3283 css,
3284 matcher,
3285 )?
3286 ));
3287 }
3288 XmlNodeChild::Text(text) => {
3289 let text = text.trim();
3290 if !text.is_empty() {
3291 let escaped = text.replace("\\", "\\\\").replace("\"", "\\\"");
3292 dom_string
3293 .push_str(&format!("{}Dom::text(\"{}\".into()),\r\n", t, escaped));
3294 }
3295 }
3296 }
3297 }
3298 dom_string.push_str(&format!("\r\n{}]))", t));
3299 }
3300
3301 let dom_string = dom_string.trim();
3302 Ok(dom_string.to_string())
3303}
3304
3305fn get_css_blocks(css: &Css, matcher: &CssMatcher) -> Vec<CssBlock> {
3306 let mut blocks = Vec::new();
3307
3308 for stylesheet in css.stylesheets.as_ref() {
3309 for css_block in stylesheet.rules.as_ref() {
3310 if matcher.matches(&css_block.path) {
3311 let mut ending = None;
3312
3313 if let Some(CssPathSelector::PseudoSelector(p)) =
3314 css_block.path.selectors.as_ref().last()
3315 {
3316 ending = Some(p.clone());
3317 }
3318
3319 blocks.push(CssBlock {
3320 ending,
3321 block: css_block.clone(),
3322 });
3323 }
3324 }
3325 }
3326
3327 blocks
3328}
3329
3330fn compile_and_format_dynamic_items(input: &[DynamicItem]) -> String {
3331 use self::DynamicItem::*;
3332 if input.is_empty() {
3333 String::from("AzString::from_const_str(\"\")")
3334 } else if input.len() == 1 {
3335 match &input[0] {
3337 Var(v) => normalize_casing(v.trim()),
3338 Str(s) => format!("AzString::from_const_str(\"{}\")", s),
3339 }
3340 } else {
3341 let mut formatted_str = String::from("format!(\"");
3343 let mut variables = Vec::new();
3344 for item in input {
3345 match item {
3346 Var(v) => {
3347 let variable_name = normalize_casing(v.trim());
3348 formatted_str.push_str(&format!("{{{}}}", variable_name));
3349 variables.push(variable_name.clone());
3350 }
3351 Str(s) => {
3352 let s = s.replace("\"", "\\\"");
3353 formatted_str.push_str(&s);
3354 }
3355 }
3356 }
3357
3358 formatted_str.push('\"');
3359 if !variables.is_empty() {
3360 formatted_str.push_str(", ");
3361 }
3362
3363 formatted_str.push_str(&variables.join(", "));
3364 formatted_str.push_str(").into()");
3365 formatted_str
3366 }
3367}
3368
3369fn format_args_for_rust_code(input: &str) -> String {
3370 let dynamic_str_items = split_dynamic_string(input);
3371 compile_and_format_dynamic_items(&dynamic_str_items)
3372}
3373
3374pub fn compile_node_to_rust_code_inner<'a>(
3375 node: &XmlNode,
3376 component_map: &'a XmlComponentMap,
3377 parent_xml_attributes: &ComponentArguments,
3378 tabs: usize,
3379 extra_blocks: &mut VecContents,
3380 css_blocks: &mut BTreeMap<String, String>,
3381 css: &Css,
3382 mut matcher: CssMatcher,
3383) -> Result<String, CompileError> {
3384 use azul_css::css::CssDeclaration;
3385
3386 let t = String::from(" ").repeat(tabs - 1);
3387 let t2 = String::from(" ").repeat(tabs);
3388
3389 let component_name = normalize_casing(&node.node_type);
3390
3391 let xml_component = component_map
3392 .components
3393 .iter()
3394 .find(|s| normalize_casing(&s.id) == component_name)
3395 .ok_or(ComponentError::UnknownComponent(
3396 component_name.clone().into(),
3397 ))?;
3398
3399 let available_function_args = xml_component.renderer.get_available_arguments();
3401 let mut filtered_xml_attributes =
3402 validate_and_filter_component_args(&node.attributes, &available_function_args)?;
3403
3404 if xml_component.inherit_vars {
3405 filtered_xml_attributes
3407 .types
3408 .extend(parent_xml_attributes.args.clone().into_iter());
3409 }
3410
3411 for v in filtered_xml_attributes.types.iter_mut() {
3413 v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.args).to_string();
3414 }
3415
3416 let instantiated_function_arguments = {
3417 let mut args = filtered_xml_attributes
3418 .types
3419 .iter()
3420 .filter_map(|(xml_attribute_key, _xml_attribute_type)| {
3421 match node.attributes.get_key(xml_attribute_key).cloned() {
3422 Some(s) => Some(format_args_for_rust_code(&s)),
3423 None => {
3424 None
3430 }
3431 }
3432 })
3433 .collect::<Vec<String>>();
3434
3435 args.sort_by(|a, b| a.cmp(&b));
3436
3437 args.join(", ")
3438 };
3439
3440 let text_as_first_arg = if filtered_xml_attributes.accepts_text {
3441 let node_text = node.get_text_content();
3442 let node_text = format_args_for_rust_code(node_text.trim());
3443 let trailing_comma = if !instantiated_function_arguments.is_empty() {
3444 ", "
3445 } else {
3446 ""
3447 };
3448
3449 format!("{}{}", node_text, trailing_comma)
3456 } else {
3457 String::new()
3458 };
3459
3460 let node_type = CssPathSelector::Type(match component_name.as_str() {
3461 "body" => NodeTypeTag::Body,
3462 "div" => NodeTypeTag::Div,
3463 "br" => NodeTypeTag::Br,
3464 "p" => NodeTypeTag::P,
3465 "img" => NodeTypeTag::Img,
3466 "br" => NodeTypeTag::Br,
3467 other => {
3468 return Err(CompileError::Dom(RenderDomError::Component(
3469 ComponentError::UnknownComponent(other.to_string().into()),
3470 )));
3471 }
3472 });
3473
3474 let mut dom_string = format!(
3476 "{}{}::render({}{})",
3477 t2, component_name, text_as_first_arg, instantiated_function_arguments
3478 );
3479
3480 matcher.path.push(node_type);
3481 let ids = node
3482 .attributes
3483 .get_key("id")
3484 .map(|s| s.split_whitespace().collect::<Vec<_>>())
3485 .unwrap_or_default();
3486
3487 matcher.path.extend(
3488 ids.into_iter()
3489 .map(|id| CssPathSelector::Id(id.to_string().into())),
3490 );
3491
3492 let classes = node
3493 .attributes
3494 .get_key("class")
3495 .map(|s| s.split_whitespace().collect::<Vec<_>>())
3496 .unwrap_or_default();
3497
3498 matcher.path.extend(
3499 classes
3500 .into_iter()
3501 .map(|class| CssPathSelector::Class(class.to_string().into())),
3502 );
3503
3504 let matcher_hash = matcher.get_hash();
3505 let css_blocks_for_this_node = get_css_blocks(css, &matcher);
3506 if !css_blocks_for_this_node.is_empty() {
3507 use azul_css::props::property::format_static_css_prop;
3508
3509 let css_strings = css_blocks_for_this_node
3510 .iter()
3511 .rev()
3512 .map(|css_block| {
3513 let wrapper = match css_block.ending {
3514 Some(CssPathPseudoSelector::Hover) => "Hover",
3515 Some(CssPathPseudoSelector::Active) => "Active",
3516 Some(CssPathPseudoSelector::Focus) => "Focus",
3517 _ => "Normal",
3518 };
3519
3520 for declaration in css_block.block.declarations.as_ref().iter() {
3521 let prop = match declaration {
3522 CssDeclaration::Static(s) => s,
3523 CssDeclaration::Dynamic(d) => &d.default_value,
3524 };
3525 extra_blocks.insert_from_css_property(prop);
3526 }
3527
3528 let formatted = css_block
3529 .block
3530 .declarations
3531 .as_ref()
3532 .iter()
3533 .rev()
3534 .map(|s| match &s {
3535 CssDeclaration::Static(s) => format!(
3536 "NodeDataInlineCssProperty::{}({})",
3537 wrapper,
3538 format_static_css_prop(s, 1)
3539 ),
3540 CssDeclaration::Dynamic(d) => format!(
3541 "NodeDataInlineCssProperty::{}({})",
3542 wrapper,
3543 format_static_css_prop(&d.default_value, 1)
3544 ),
3545 })
3546 .collect::<Vec<String>>();
3547
3548 format!("// {}\r\n{}", css_block.block.path, formatted.join(",\r\n"))
3549 })
3550 .collect::<Vec<_>>()
3551 .join(",\r\n");
3552
3553 css_blocks.insert(format!("CSS_MATCH_{:09}", matcher_hash), css_strings);
3554 dom_string.push_str(&format!(
3555 "\r\n{}.with_inline_css_props(CSS_MATCH_{:09})",
3556 t2, matcher_hash
3557 ));
3558 }
3559
3560 set_stringified_attributes(
3561 &mut dom_string,
3562 &node.attributes,
3563 &filtered_xml_attributes.types,
3564 tabs,
3565 );
3566
3567 let mut children_string = node
3568 .children
3569 .as_ref()
3570 .iter()
3571 .enumerate()
3572 .filter_map(|(child_idx, c)| match c {
3573 XmlNodeChild::Element(child_node) => {
3574 let mut matcher = matcher.clone();
3575 matcher.path.push(CssPathSelector::Children);
3576 matcher.indices_in_parent.push(child_idx);
3577 matcher.children_length.push(node.children.len());
3578
3579 Some(compile_node_to_rust_code_inner(
3580 child_node,
3581 component_map,
3582 &ComponentArguments {
3583 args: filtered_xml_attributes.types.clone(),
3584 accepts_text: filtered_xml_attributes.accepts_text,
3585 },
3586 tabs + 1,
3587 extra_blocks,
3588 css_blocks,
3589 css,
3590 matcher,
3591 ))
3592 }
3593 XmlNodeChild::Text(text) => {
3594 let text = text.trim();
3595 if text.is_empty() {
3596 None
3597 } else {
3598 let t2 = String::from(" ").repeat(tabs);
3599 let escaped = text.replace("\\", "\\\\").replace("\"", "\\\"");
3600 Some(Ok(format!("{}Dom::text(\"{}\".into())", t2, escaped)))
3601 }
3602 }
3603 })
3604 .collect::<Result<Vec<_>, _>>()?
3605 .join(&format!(",\r\n"));
3606
3607 if !children_string.is_empty() {
3608 dom_string.push_str(&format!(
3609 "\r\n{}.with_children(DomVec::from_vec(vec![\r\n{}\r\n{}]))",
3610 t2, children_string, t2
3611 ));
3612 }
3613
3614 Ok(dom_string)
3615}
3616
3617pub struct DynamicXmlComponent {
3620 pub name: String,
3622 pub arguments: ComponentArguments,
3624 pub root: XmlNode,
3626}
3627
3628impl DynamicXmlComponent {
3629 pub fn new<'a>(root: &'a XmlNode) -> Result<Self, ComponentParseError> {
3631 let node_type = normalize_casing(&root.node_type);
3632
3633 if node_type.as_str() != "component" {
3634 return Err(ComponentParseError::NotAComponent);
3635 }
3636
3637 let name = root
3638 .attributes
3639 .get_key("name")
3640 .cloned()
3641 .ok_or(ComponentParseError::NotAComponent)?;
3642 let accepts_text = root
3643 .attributes
3644 .get_key("accepts_text")
3645 .and_then(|p| parse_bool(p.as_str()))
3646 .unwrap_or(false);
3647
3648 let args = match root.attributes.get_key("args") {
3649 Some(s) => parse_component_arguments(s)?,
3650 None => ComponentArgumentTypes::default(),
3651 };
3652
3653 Ok(Self {
3654 name: normalize_casing(&name),
3655 arguments: ComponentArguments { args, accepts_text },
3656 root: root.clone(),
3657 })
3658 }
3659}
3660
3661impl XmlComponentTrait for DynamicXmlComponent {
3662 fn get_available_arguments(&self) -> ComponentArguments {
3663 self.arguments.clone()
3664 }
3665
3666 fn get_xml_node(&self) -> XmlNode {
3667 self.root.clone()
3668 }
3669
3670 fn render_dom<'a>(
3671 &'a self,
3672 components: &'a XmlComponentMap,
3673 arguments: &FilteredComponentArguments,
3674 content: &XmlTextContent,
3675 ) -> Result<StyledDom, RenderDomError> {
3676 let mut component_css = match find_node_by_type(self.root.children.as_ref(), "style") {
3677 Some(style_node) => {
3678 let text = style_node.get_text_content();
3679 if !text.is_empty() {
3680 let parsed_css = Css::from_string(text.into());
3681 Some(parsed_css)
3682 } else {
3683 None
3684 }
3685 }
3686 None => None,
3687 };
3688
3689 let mut dom = StyledDom::default();
3690
3691 for child in self.root.children.as_ref() {
3692 if let XmlNodeChild::Element(child_node) = child {
3693 dom.append_child(render_dom_from_body_node_inner(
3694 child_node, components, arguments,
3695 )?);
3696 }
3697 }
3698
3699 if let Some(css) = component_css.clone() {
3700 dom.restyle(css);
3701 }
3702
3703 Ok(dom)
3704 }
3705
3706 fn compile_to_rust_code(
3707 &self,
3708 components: &XmlComponentMap,
3709 attributes: &ComponentArguments,
3710 content: &XmlTextContent,
3711 ) -> Result<String, CompileError> {
3712 Ok("Dom::create_div()".into()) }
3714}
3715
3716#[cfg(test)]
3717mod tests {
3718 use super::*;
3719 use crate::dom::{Dom, NodeType};
3720
3721 #[test]
3722 fn test_inline_span_parsing() {
3723 let html = r#"<p>Text before <span class="highlight">inline text</span> text after.</p>"#;
3727
3728 let expected_dom = Dom::create_p().with_children(
3738 vec![
3739 Dom::create_text("Text before "),
3740 Dom::create_node(NodeType::Span)
3741 .with_children(vec![Dom::create_text("inline text")].into()),
3742 Dom::create_text(" text after."),
3743 ]
3744 .into(),
3745 );
3746
3747 assert_eq!(expected_dom.children.as_ref().len(), 3);
3749
3750 match &expected_dom.children.as_ref()[1].root.node_type {
3752 NodeType::Span => {}
3753 other => panic!("Expected Span, got {:?}", other),
3754 }
3755
3756 assert_eq!(expected_dom.children.as_ref()[1].children.as_ref().len(), 1);
3758
3759 println!("Test passed: Inline span parsing structure is correct");
3760 }
3761
3762 #[test]
3763 fn test_xml_node_structure() {
3764 let node = XmlNode {
3768 node_type: "p".into(),
3769 attributes: XmlAttributeMap {
3770 inner: StringPairVec::from_const_slice(&[]),
3771 },
3772 children: vec![
3773 XmlNodeChild::Text("Before ".into()),
3774 XmlNodeChild::Element(XmlNode {
3775 node_type: "span".into(),
3776 children: vec![XmlNodeChild::Text("inline".into())].into(),
3777 ..Default::default()
3778 }),
3779 XmlNodeChild::Text(" after".into()),
3780 ]
3781 .into(),
3782 };
3783
3784 assert_eq!(node.children.as_ref().len(), 3);
3786 assert_eq!(node.children.as_ref()[0].as_text(), Some("Before "));
3787 assert_eq!(
3788 node.children.as_ref()[1]
3789 .as_element()
3790 .unwrap()
3791 .node_type
3792 .as_str(),
3793 "span"
3794 );
3795 assert_eq!(node.children.as_ref()[2].as_text(), Some(" after"));
3796
3797 let span = node.children.as_ref()[1].as_element().unwrap();
3799 assert_eq!(span.children.as_ref().len(), 1);
3800 assert_eq!(span.children.as_ref()[0].as_text(), Some("inline"));
3801
3802 println!("Test passed: XmlNode structure preserves text nodes correctly");
3803 }
3804}