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, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
158#[repr(C)]
159pub enum ExternalResourceKind {
160 Image,
162 Font,
164 Stylesheet,
166 Script,
168 Icon,
170 Video,
172 Audio,
174 Unknown,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
180#[repr(C)]
181pub struct MimeTypeHint {
182 pub inner: AzString,
183}
184
185impl MimeTypeHint {
186 pub fn new(s: &str) -> Self {
187 Self { inner: AzString::from(s) }
188 }
189
190 pub fn from_extension(ext: &str) -> Self {
191 let mime = match ext.to_lowercase().as_str() {
192 "png" => "image/png",
194 "jpg" | "jpeg" => "image/jpeg",
195 "gif" => "image/gif",
196 "webp" => "image/webp",
197 "svg" => "image/svg+xml",
198 "ico" => "image/x-icon",
199 "bmp" => "image/bmp",
200 "avif" => "image/avif",
201 "ttf" => "font/ttf",
203 "otf" => "font/otf",
204 "woff" => "font/woff",
205 "woff2" => "font/woff2",
206 "eot" => "application/vnd.ms-fontobject",
207 "css" => "text/css",
209 "js" => "application/javascript",
211 "mjs" => "application/javascript",
212 "mp4" => "video/mp4",
214 "webm" => "video/webm",
215 "ogg" => "video/ogg",
216 "mp3" => "audio/mpeg",
218 "wav" => "audio/wav",
219 "flac" => "audio/flac",
220 _ => "application/octet-stream",
222 };
223 Self { inner: AzString::from(mime) }
224 }
225}
226
227impl_option!(
228 MimeTypeHint,
229 OptionMimeTypeHint,
230 copy = false,
231 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
232);
233
234#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
236#[repr(C)]
237pub struct ExternalResource {
238 pub url: AzString,
240 pub kind: ExternalResourceKind,
242 pub mime_type: OptionMimeTypeHint,
244 pub source_element: AzString,
246 pub source_attribute: AzString,
248}
249
250impl_option!(
251 ExternalResource,
252 OptionExternalResource,
253 copy = false,
254 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
255);
256
257impl_vec!(ExternalResource, ExternalResourceVec, ExternalResourceVecDestructor, ExternalResourceVecDestructorType, ExternalResourceVecSlice, OptionExternalResource);
258impl_vec_mut!(ExternalResource, ExternalResourceVec);
259impl_vec_debug!(ExternalResource, ExternalResourceVec);
260impl_vec_partialeq!(ExternalResource, ExternalResourceVec);
261impl_vec_eq!(ExternalResource, ExternalResourceVec);
262impl_vec_partialord!(ExternalResource, ExternalResourceVec);
263impl_vec_ord!(ExternalResource, ExternalResourceVec);
264impl_vec_hash!(ExternalResource, ExternalResourceVec);
265impl_vec_clone!(ExternalResource, ExternalResourceVec, ExternalResourceVecDestructor);
266
267#[derive(Debug, PartialEq, PartialOrd, Clone)]
268#[repr(C)]
269pub struct Xml {
270 pub root: XmlNodeChildVec,
271}
272
273impl Xml {
274 pub fn scan_external_resources(&self) -> ExternalResourceVec {
286 let mut resources = Vec::new();
287
288 for child in self.root.as_ref().iter() {
289 Self::scan_node_child(child, &mut resources);
290 }
291
292 resources.into()
293 }
294
295 fn scan_node_child(child: &XmlNodeChild, resources: &mut Vec<ExternalResource>) {
296 match child {
297 XmlNodeChild::Text(text) => {
298 Self::extract_css_urls(text.as_str(), resources);
300 }
301 XmlNodeChild::Element(node) => {
302 Self::scan_node(node, resources);
303 }
304 }
305 }
306
307 fn scan_node(node: &XmlNode, resources: &mut Vec<ExternalResource>) {
308 let tag_name = node.node_type.inner.as_str().to_lowercase();
309
310 let get_attr = |name: &str| -> Option<String> {
312 node.attributes.inner.as_ref().iter()
313 .find(|pair| pair.key.as_str().eq_ignore_ascii_case(name))
314 .map(|pair| pair.value.as_str().to_string())
315 };
316
317 match tag_name.as_str() {
318 "img" => {
319 if let Some(src) = get_attr("src") {
320 let mime = Self::guess_mime_from_url(&src, "image");
321 resources.push(ExternalResource {
322 url: AzString::from(src),
323 kind: ExternalResourceKind::Image,
324 mime_type: mime.into(),
325 source_element: AzString::from("img"),
326 source_attribute: AzString::from("src"),
327 });
328 }
329 if let Some(srcset) = get_attr("srcset") {
331 for src in Self::parse_srcset(&srcset) {
332 let mime = Self::guess_mime_from_url(&src, "image");
333 resources.push(ExternalResource {
334 url: AzString::from(src),
335 kind: ExternalResourceKind::Image,
336 mime_type: mime.into(),
337 source_element: AzString::from("img"),
338 source_attribute: AzString::from("srcset"),
339 });
340 }
341 }
342 }
343 "link" => {
344 if let Some(href) = get_attr("href") {
345 let rel = get_attr("rel").unwrap_or_default().to_lowercase();
346 let type_attr = get_attr("type");
347 let as_attr = get_attr("as").unwrap_or_default().to_lowercase();
348
349 let (kind, category) = if rel.contains("stylesheet") {
350 (ExternalResourceKind::Stylesheet, "stylesheet")
351 } else if rel.contains("icon") || rel.contains("apple-touch-icon") {
352 (ExternalResourceKind::Icon, "image")
353 } else if as_attr == "font" || rel.contains("preload") && as_attr == "font" {
354 (ExternalResourceKind::Font, "font")
355 } else if as_attr == "script" {
356 (ExternalResourceKind::Script, "script")
357 } else if as_attr == "image" {
358 (ExternalResourceKind::Image, "image")
359 } else {
360 (ExternalResourceKind::Unknown, "")
361 };
362
363 let mime = type_attr.map(|t| MimeTypeHint::new(&t))
364 .or_else(|| Self::guess_mime_from_url(&href, category));
365
366 resources.push(ExternalResource {
367 url: AzString::from(href),
368 kind,
369 mime_type: mime.into(),
370 source_element: AzString::from("link"),
371 source_attribute: AzString::from("href"),
372 });
373 }
374 }
375 "script" => {
376 if let Some(src) = get_attr("src") {
377 let type_attr = get_attr("type");
378 let mime = type_attr.map(|t| MimeTypeHint::new(&t))
379 .or_else(|| Some(MimeTypeHint::new("application/javascript")));
380
381 resources.push(ExternalResource {
382 url: AzString::from(src),
383 kind: ExternalResourceKind::Script,
384 mime_type: mime.into(),
385 source_element: AzString::from("script"),
386 source_attribute: AzString::from("src"),
387 });
388 }
389 }
390 "video" => {
391 if let Some(src) = get_attr("src") {
392 let mime = Self::guess_mime_from_url(&src, "video");
393 resources.push(ExternalResource {
394 url: AzString::from(src),
395 kind: ExternalResourceKind::Video,
396 mime_type: mime.into(),
397 source_element: AzString::from("video"),
398 source_attribute: AzString::from("src"),
399 });
400 }
401 if let Some(poster) = get_attr("poster") {
402 let mime = Self::guess_mime_from_url(&poster, "image");
403 resources.push(ExternalResource {
404 url: AzString::from(poster),
405 kind: ExternalResourceKind::Image,
406 mime_type: mime.into(),
407 source_element: AzString::from("video"),
408 source_attribute: AzString::from("poster"),
409 });
410 }
411 }
412 "audio" => {
413 if let Some(src) = get_attr("src") {
414 let mime = Self::guess_mime_from_url(&src, "audio");
415 resources.push(ExternalResource {
416 url: AzString::from(src),
417 kind: ExternalResourceKind::Audio,
418 mime_type: mime.into(),
419 source_element: AzString::from("audio"),
420 source_attribute: AzString::from("src"),
421 });
422 }
423 }
424 "source" => {
425 if let Some(src) = get_attr("src") {
426 let type_attr = get_attr("type");
427 let kind = if type_attr.as_ref().map(|t| t.starts_with("audio")).unwrap_or(false) {
429 ExternalResourceKind::Audio
430 } else {
431 ExternalResourceKind::Video
432 };
433 let mime = type_attr.map(|t| MimeTypeHint::new(&t))
434 .or_else(|| Self::guess_mime_from_url(&src, if kind == ExternalResourceKind::Audio { "audio" } else { "video" }));
435
436 resources.push(ExternalResource {
437 url: AzString::from(src),
438 kind,
439 mime_type: mime.into(),
440 source_element: AzString::from("source"),
441 source_attribute: AzString::from("src"),
442 });
443 }
444 if let Some(srcset) = get_attr("srcset") {
446 for src in Self::parse_srcset(&srcset) {
447 let mime = Self::guess_mime_from_url(&src, "image");
448 resources.push(ExternalResource {
449 url: AzString::from(src),
450 kind: ExternalResourceKind::Image,
451 mime_type: mime.into(),
452 source_element: AzString::from("source"),
453 source_attribute: AzString::from("srcset"),
454 });
455 }
456 }
457 }
458 "a" => {
459 if let Some(href) = get_attr("href") {
460 if Self::looks_like_resource(&href) {
462 let mime = Self::guess_mime_from_url(&href, "");
463 resources.push(ExternalResource {
464 url: AzString::from(href),
465 kind: ExternalResourceKind::Unknown,
466 mime_type: mime.into(),
467 source_element: AzString::from("a"),
468 source_attribute: AzString::from("href"),
469 });
470 }
471 }
472 }
473 "iframe" | "embed" | "object" => {
474 let src_attr = if tag_name == "object" { "data" } else { "src" };
475 if let Some(src) = get_attr(src_attr) {
476 resources.push(ExternalResource {
477 url: AzString::from(src),
478 kind: ExternalResourceKind::Unknown,
479 mime_type: OptionMimeTypeHint::None,
480 source_element: AzString::from(tag_name.clone()),
481 source_attribute: AzString::from(src_attr),
482 });
483 }
484 }
485 "style" => {
486 for child in node.children.as_ref().iter() {
488 if let XmlNodeChild::Text(text) = child {
489 Self::extract_css_urls(text.as_str(), resources);
490 }
491 }
492 }
493 _ => {}
494 }
495
496 if let Some(style) = get_attr("style") {
498 Self::extract_css_urls(&style, resources);
499 }
500
501 if let Some(bg) = get_attr("background") {
503 let mime = Self::guess_mime_from_url(&bg, "image");
504 resources.push(ExternalResource {
505 url: AzString::from(bg),
506 kind: ExternalResourceKind::Image,
507 mime_type: mime.into(),
508 source_element: AzString::from(tag_name),
509 source_attribute: AzString::from("background"),
510 });
511 }
512
513 for child in node.children.as_ref().iter() {
515 Self::scan_node_child(child, resources);
516 }
517 }
518
519 fn extract_css_urls(css: &str, resources: &mut Vec<ExternalResource>) {
521 let mut remaining = css;
523
524 while let Some(pos) = remaining.find("url(") {
525 let after_url = &remaining[pos + 4..];
526 if let Some(url) = Self::extract_url_value(after_url) {
527 let mime = Self::guess_mime_from_url(&url, "");
528 let kind = Self::guess_kind_from_url(&url);
529 resources.push(ExternalResource {
530 url: AzString::from(url),
531 kind,
532 mime_type: mime.into(),
533 source_element: AzString::from("style"),
534 source_attribute: AzString::from("url()"),
535 });
536 }
537 remaining = after_url;
538 }
539
540 remaining = css;
542 while let Some(pos) = remaining.to_lowercase().find("@import") {
543 let after_import = &remaining[pos + 7..];
544 let trimmed = after_import.trim_start();
545
546 if trimmed.starts_with("url(") {
547 if let Some(url) = Self::extract_url_value(&trimmed[4..]) {
548 resources.push(ExternalResource {
549 url: AzString::from(url),
550 kind: ExternalResourceKind::Stylesheet,
551 mime_type: Some(MimeTypeHint::new("text/css")).into(),
552 source_element: AzString::from("style"),
553 source_attribute: AzString::from("@import"),
554 });
555 }
556 } else if let Some(url) = Self::extract_quoted_string(trimmed) {
557 resources.push(ExternalResource {
558 url: AzString::from(url),
559 kind: ExternalResourceKind::Stylesheet,
560 mime_type: Some(MimeTypeHint::new("text/css")).into(),
561 source_element: AzString::from("style"),
562 source_attribute: AzString::from("@import"),
563 });
564 }
565
566 remaining = after_import;
567 }
568 }
569
570 fn extract_url_value(s: &str) -> Option<String> {
572 let trimmed = s.trim_start();
573 if trimmed.starts_with('"') {
574 Self::extract_quoted_string(trimmed)
575 } else if trimmed.starts_with('\'') {
576 let end = trimmed[1..].find('\'')?;
577 Some(trimmed[1..1+end].to_string())
578 } else {
579 let end = trimmed.find(')')?;
580 Some(trimmed[..end].trim().to_string())
581 }
582 }
583
584 fn extract_quoted_string(s: &str) -> Option<String> {
586 if s.starts_with('"') {
587 let end = s[1..].find('"')?;
588 Some(s[1..1+end].to_string())
589 } else if s.starts_with('\'') {
590 let end = s[1..].find('\'')?;
591 Some(s[1..1+end].to_string())
592 } else {
593 None
594 }
595 }
596
597 fn parse_srcset(srcset: &str) -> Vec<String> {
599 srcset.split(',')
600 .filter_map(|entry| {
601 let trimmed = entry.trim();
602 trimmed.split_whitespace().next().map(|s| s.to_string())
604 })
605 .filter(|url| !url.is_empty())
606 .collect()
607 }
608
609 fn looks_like_resource(url: &str) -> bool {
611 let lower = url.to_lowercase();
612 let resource_exts = [
614 ".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico", ".bmp",
615 ".ttf", ".otf", ".woff", ".woff2", ".eot",
616 ".css", ".js",
617 ".mp4", ".webm", ".ogg", ".mp3", ".wav",
618 ".pdf", ".zip", ".tar", ".gz",
619 ];
620 resource_exts.iter().any(|ext| lower.ends_with(ext))
621 }
622
623 fn guess_kind_from_url(url: &str) -> ExternalResourceKind {
625 let lower = url.to_lowercase();
626 if lower.contains(".png") || lower.contains(".jpg") || lower.contains(".jpeg")
627 || lower.contains(".gif") || lower.contains(".webp") || lower.contains(".svg")
628 || lower.contains(".bmp") || lower.contains(".avif") {
629 ExternalResourceKind::Image
630 } else if lower.contains(".ttf") || lower.contains(".otf") || lower.contains(".woff")
631 || lower.contains(".eot") {
632 ExternalResourceKind::Font
633 } else if lower.contains(".css") {
634 ExternalResourceKind::Stylesheet
635 } else if lower.contains(".js") {
636 ExternalResourceKind::Script
637 } else if lower.contains(".mp4") || lower.contains(".webm") || lower.contains(".ogg") {
638 ExternalResourceKind::Video
639 } else if lower.contains(".mp3") || lower.contains(".wav") || lower.contains(".flac") {
640 ExternalResourceKind::Audio
641 } else if lower.contains(".ico") {
642 ExternalResourceKind::Icon
643 } else {
644 ExternalResourceKind::Unknown
645 }
646 }
647
648 fn guess_mime_from_url(url: &str, category: &str) -> Option<MimeTypeHint> {
650 let lower = url.to_lowercase();
651 let ext = lower.rsplit('.').next()?;
653 let ext = ext.split('?').next()?;
655
656 let valid_exts = [
658 "png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "bmp", "avif",
659 "ttf", "otf", "woff", "woff2", "eot",
660 "css", "js", "mjs",
661 "mp4", "webm", "ogg", "mp3", "wav", "flac",
662 ];
663
664 if valid_exts.contains(&ext) {
665 Some(MimeTypeHint::from_extension(ext))
666 } else if !category.is_empty() {
667 match category {
669 "image" => Some(MimeTypeHint::new("image/*")),
670 "font" => Some(MimeTypeHint::new("font/*")),
671 "stylesheet" => Some(MimeTypeHint::new("text/css")),
672 "script" => Some(MimeTypeHint::new("application/javascript")),
673 "video" => Some(MimeTypeHint::new("video/*")),
674 "audio" => Some(MimeTypeHint::new("audio/*")),
675 _ => None,
676 }
677 } else {
678 None
679 }
680 }
681}
682
683#[derive(Debug, PartialEq, PartialOrd, Clone)]
684#[repr(C)]
685pub struct NonXmlCharError {
686 pub ch: u32, pub pos: XmlTextPos,
688}
689
690#[derive(Debug, PartialEq, PartialOrd, Clone)]
691#[repr(C)]
692pub struct InvalidCharError {
693 pub expected: u8,
694 pub got: u8,
695 pub pos: XmlTextPos,
696}
697
698#[derive(Debug, PartialEq, PartialOrd, Clone)]
699#[repr(C)]
700pub struct InvalidCharMultipleError {
701 pub expected: u8,
702 pub got: U8Vec,
703 pub pos: XmlTextPos,
704}
705
706#[derive(Debug, PartialEq, PartialOrd, Clone)]
707#[repr(C)]
708pub struct InvalidQuoteError {
709 pub got: u8,
710 pub pos: XmlTextPos,
711}
712
713#[derive(Debug, PartialEq, PartialOrd, Clone)]
714#[repr(C)]
715pub struct InvalidSpaceError {
716 pub got: u8,
717 pub pos: XmlTextPos,
718}
719
720#[derive(Debug, PartialEq, PartialOrd, Clone)]
721#[repr(C)]
722pub struct InvalidStringError {
723 pub got: AzString,
724 pub pos: XmlTextPos,
725}
726
727#[derive(Debug, PartialEq, PartialOrd, Clone)]
728#[repr(C, u8)]
729pub enum XmlStreamError {
730 UnexpectedEndOfStream,
731 InvalidName,
732 NonXmlChar(NonXmlCharError),
733 InvalidChar(InvalidCharError),
734 InvalidCharMultiple(InvalidCharMultipleError),
735 InvalidQuote(InvalidQuoteError),
736 InvalidSpace(InvalidSpaceError),
737 InvalidString(InvalidStringError),
738 InvalidReference,
739 InvalidExternalID,
740 InvalidCommentData,
741 InvalidCommentEnd,
742 InvalidCharacterData,
743}
744
745impl fmt::Display for XmlStreamError {
746 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
747 use self::XmlStreamError::*;
748 match self {
749 UnexpectedEndOfStream => write!(f, "Unexpected end of stream"),
750 InvalidName => write!(f, "Invalid name"),
751 NonXmlChar(nx) => write!(
752 f,
753 "Non-XML character: {:?} at {}",
754 core::char::from_u32(nx.ch),
755 nx.pos
756 ),
757 InvalidChar(ic) => write!(
758 f,
759 "Invalid character: expected: {}, got: {} at {}",
760 ic.expected as char, ic.got as char, ic.pos
761 ),
762 InvalidCharMultiple(imc) => write!(
763 f,
764 "Multiple invalid characters: expected: {}, got: {:?} at {}",
765 imc.expected,
766 imc.got.as_ref(),
767 imc.pos
768 ),
769 InvalidQuote(iq) => write!(f, "Invalid quote: got {} at {}", iq.got as char, iq.pos),
770 InvalidSpace(is) => write!(f, "Invalid space: got {} at {}", is.got as char, is.pos),
771 InvalidString(ise) => write!(
772 f,
773 "Invalid string: got \"{}\" at {}",
774 ise.got.as_str(),
775 ise.pos
776 ),
777 InvalidReference => write!(f, "Invalid reference"),
778 InvalidExternalID => write!(f, "Invalid external ID"),
779 InvalidCommentData => write!(f, "Invalid comment data"),
780 InvalidCommentEnd => write!(f, "Invalid comment end"),
781 InvalidCharacterData => write!(f, "Invalid character data"),
782 }
783 }
784}
785
786#[derive(Debug, PartialEq, PartialOrd, Clone, Ord, Hash, Eq)]
787#[repr(C)]
788pub struct XmlTextPos {
789 pub row: u32,
790 pub col: u32,
791}
792
793impl fmt::Display for XmlTextPos {
794 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
795 write!(f, "line {}:{}", self.row, self.col)
796 }
797}
798
799#[derive(Debug, PartialEq, PartialOrd, Clone)]
800#[repr(C)]
801pub struct XmlTextError {
802 pub stream_error: XmlStreamError,
803 pub pos: XmlTextPos,
804}
805
806#[derive(Debug, PartialEq, PartialOrd, Clone)]
807#[repr(C, u8)]
808pub enum XmlParseError {
809 InvalidDeclaration(XmlTextError),
810 InvalidComment(XmlTextError),
811 InvalidPI(XmlTextError),
812 InvalidDoctype(XmlTextError),
813 InvalidEntity(XmlTextError),
814 InvalidElement(XmlTextError),
815 InvalidAttribute(XmlTextError),
816 InvalidCdata(XmlTextError),
817 InvalidCharData(XmlTextError),
818 UnknownToken(XmlTextPos),
819}
820
821impl fmt::Display for XmlParseError {
822 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
823 use self::XmlParseError::*;
824 match self {
825 InvalidDeclaration(e) => {
826 write!(f, "Invalid declaraction: {} at {}", e.stream_error, e.pos)
827 }
828 InvalidComment(e) => write!(f, "Invalid comment: {} at {}", e.stream_error, e.pos),
829 InvalidPI(e) => write!(
830 f,
831 "Invalid processing instruction: {} at {}",
832 e.stream_error, e.pos
833 ),
834 InvalidDoctype(e) => write!(f, "Invalid doctype: {} at {}", e.stream_error, e.pos),
835 InvalidEntity(e) => write!(f, "Invalid entity: {} at {}", e.stream_error, e.pos),
836 InvalidElement(e) => write!(f, "Invalid element: {} at {}", e.stream_error, e.pos),
837 InvalidAttribute(e) => write!(f, "Invalid attribute: {} at {}", e.stream_error, e.pos),
838 InvalidCdata(e) => write!(f, "Invalid CDATA: {} at {}", e.stream_error, e.pos),
839 InvalidCharData(e) => write!(f, "Invalid char data: {} at {}", e.stream_error, e.pos),
840 UnknownToken(e) => write!(f, "Unknown token at {}", e),
841 }
842 }
843}
844
845impl_result!(
846 Xml,
847 XmlError,
848 ResultXmlXmlError,
849 copy = false,
850 [Debug, PartialEq, PartialOrd, Clone]
851);
852
853#[derive(Debug, PartialEq, PartialOrd, Clone)]
854#[repr(C)]
855pub struct DuplicatedNamespaceError {
856 pub ns: AzString,
857 pub pos: XmlTextPos,
858}
859
860#[derive(Debug, PartialEq, PartialOrd, Clone)]
861#[repr(C)]
862pub struct UnknownNamespaceError {
863 pub ns: AzString,
864 pub pos: XmlTextPos,
865}
866
867#[derive(Debug, PartialEq, PartialOrd, Clone)]
868#[repr(C)]
869pub struct UnexpectedCloseTagError {
870 pub expected: AzString,
871 pub actual: AzString,
872 pub pos: XmlTextPos,
873}
874
875#[derive(Debug, PartialEq, PartialOrd, Clone)]
876#[repr(C)]
877pub struct UnknownEntityReferenceError {
878 pub entity: AzString,
879 pub pos: XmlTextPos,
880}
881
882#[derive(Debug, PartialEq, PartialOrd, Clone)]
883#[repr(C)]
884pub struct DuplicatedAttributeError {
885 pub attribute: AzString,
886 pub pos: XmlTextPos,
887}
888
889#[derive(Debug, PartialEq, PartialOrd, Clone)]
891#[repr(C)]
892pub struct MalformedHierarchyError {
893 pub expected: AzString,
895 pub got: AzString,
897}
898
899#[derive(Debug, PartialEq, PartialOrd, Clone)]
900#[repr(C, u8)]
901pub enum XmlError {
902 NoParserAvailable,
903 InvalidXmlPrefixUri(XmlTextPos),
904 UnexpectedXmlUri(XmlTextPos),
905 UnexpectedXmlnsUri(XmlTextPos),
906 InvalidElementNamePrefix(XmlTextPos),
907 DuplicatedNamespace(DuplicatedNamespaceError),
908 UnknownNamespace(UnknownNamespaceError),
909 UnexpectedCloseTag(UnexpectedCloseTagError),
910 UnexpectedEntityCloseTag(XmlTextPos),
911 UnknownEntityReference(UnknownEntityReferenceError),
912 MalformedEntityReference(XmlTextPos),
913 EntityReferenceLoop(XmlTextPos),
914 InvalidAttributeValue(XmlTextPos),
915 DuplicatedAttribute(DuplicatedAttributeError),
916 NoRootNode,
917 SizeLimit,
918 DtdDetected,
919 MalformedHierarchy(MalformedHierarchyError),
921 ParserError(XmlParseError),
922 UnclosedRootNode,
923 UnexpectedDeclaration(XmlTextPos),
924 NodesLimitReached,
925 AttributesLimitReached,
926 NamespacesLimitReached,
927 InvalidName(XmlTextPos),
928 NonXmlChar(XmlTextPos),
929 InvalidChar(XmlTextPos),
930 InvalidChar2(XmlTextPos),
931 InvalidString(XmlTextPos),
932 InvalidExternalID(XmlTextPos),
933 InvalidComment(XmlTextPos),
934 InvalidCharacterData(XmlTextPos),
935 UnknownToken(XmlTextPos),
936 UnexpectedEndOfStream,
937}
938
939impl fmt::Display for XmlError {
940 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
941 use self::XmlError::*;
942 match self {
943 NoParserAvailable => write!(
944 f,
945 "Library was compiled without XML parser (XML parser not available)"
946 ),
947 InvalidXmlPrefixUri(pos) => {
948 write!(f, "Invalid XML Prefix URI at line {}:{}", pos.row, pos.col)
949 }
950 UnexpectedXmlUri(pos) => {
951 write!(f, "Unexpected XML URI at at line {}:{}", pos.row, pos.col)
952 }
953 UnexpectedXmlnsUri(pos) => write!(
954 f,
955 "Unexpected XML namespace URI at line {}:{}",
956 pos.row, pos.col
957 ),
958 InvalidElementNamePrefix(pos) => write!(
959 f,
960 "Invalid element name prefix at line {}:{}",
961 pos.row, pos.col
962 ),
963 DuplicatedNamespace(ns) => write!(
964 f,
965 "Duplicated namespace: \"{}\" at {}",
966 ns.ns.as_str(),
967 ns.pos
968 ),
969 UnknownNamespace(uns) => write!(
970 f,
971 "Unknown namespace: \"{}\" at {}",
972 uns.ns.as_str(),
973 uns.pos
974 ),
975 UnexpectedCloseTag(ct) => write!(
976 f,
977 "Unexpected close tag: expected \"{}\", got \"{}\" at {}",
978 ct.expected.as_str(),
979 ct.actual.as_str(),
980 ct.pos
981 ),
982 UnexpectedEntityCloseTag(pos) => write!(
983 f,
984 "Unexpected entity close tag at line {}:{}",
985 pos.row, pos.col
986 ),
987 UnknownEntityReference(uer) => write!(
988 f,
989 "Unexpected entity reference: \"{}\" at {}",
990 uer.entity, uer.pos
991 ),
992 MalformedEntityReference(pos) => write!(
993 f,
994 "Malformed entity reference at line {}:{}",
995 pos.row, pos.col
996 ),
997 EntityReferenceLoop(pos) => write!(
998 f,
999 "Entity reference loop (recursive entity reference) at line {}:{}",
1000 pos.row, pos.col
1001 ),
1002 InvalidAttributeValue(pos) => {
1003 write!(f, "Invalid attribute value at line {}:{}", pos.row, pos.col)
1004 }
1005 DuplicatedAttribute(ae) => write!(
1006 f,
1007 "Duplicated attribute \"{}\" at line {}:{}",
1008 ae.attribute.as_str(),
1009 ae.pos.row,
1010 ae.pos.col
1011 ),
1012 NoRootNode => write!(f, "No root node found"),
1013 SizeLimit => write!(f, "XML file too large (size limit reached)"),
1014 DtdDetected => write!(f, "Document type descriptor detected"),
1015 MalformedHierarchy(e) => write!(
1016 f,
1017 "Malformed hierarchy: expected <{}/> closing tag, got <{}/>",
1018 e.expected.as_str(),
1019 e.got.as_str()
1020 ),
1021 ParserError(p) => write!(f, "{}", p),
1022 UnclosedRootNode => write!(f, "unclosed root node"),
1023 UnexpectedDeclaration(tp) => write!(f, "unexpected declaration at {tp}"),
1024 NodesLimitReached => write!(f, "nodes limit reached"),
1025 AttributesLimitReached => write!(f, "attributes limit reached"),
1026 NamespacesLimitReached => write!(f, "namespaces limit reached"),
1027 InvalidName(tp) => write!(f, "invalid name at {tp}"),
1028 NonXmlChar(tp) => write!(f, "non xml char at {tp}"),
1029 InvalidChar(tp) => write!(f, "invalid char at {tp}"),
1030 InvalidChar2(tp) => write!(f, "invalid char2 at {tp}"),
1031 InvalidString(tp) => write!(f, "invalid string at {tp}"),
1032 InvalidExternalID(tp) => write!(f, "invalid externalid at {tp}"),
1033 InvalidComment(tp) => write!(f, "invalid comment at {tp}"),
1034 InvalidCharacterData(tp) => write!(f, "invalid character data at {tp}"),
1035 UnknownToken(tp) => write!(f, "unknown token at {tp}"),
1036 UnexpectedEndOfStream => write!(f, "unexpected end of stream"),
1037 }
1038 }
1039}
1040
1041#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1068pub struct ComponentArguments {
1069 pub args: ComponentArgumentTypes,
1071 pub accepts_text: bool,
1074}
1075
1076impl Default for ComponentArguments {
1077 fn default() -> Self {
1078 Self {
1079 args: ComponentArgumentTypes::default(),
1080 accepts_text: false,
1081 }
1082 }
1083}
1084
1085impl ComponentArguments {
1086 pub fn new() -> Self {
1087 Self::default()
1088 }
1089}
1090
1091#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1092pub struct FilteredComponentArguments {
1093 pub types: ComponentArgumentTypes,
1095 pub values: BTreeMap<String, String>,
1097 pub accepts_text: bool,
1100}
1101
1102impl Default for FilteredComponentArguments {
1103 fn default() -> Self {
1104 Self {
1105 types: Vec::new(),
1106 values: BTreeMap::default(),
1107 accepts_text: false,
1108 }
1109 }
1110}
1111
1112impl FilteredComponentArguments {
1113 fn new() -> Self {
1114 Self::default()
1115 }
1116}
1117
1118pub trait XmlComponentTrait {
1120 fn get_type_id(&self) -> String {
1122 "div".to_string()
1123 }
1124
1125 fn get_xml_node(&self) -> XmlNode {
1128 XmlNode::create(self.get_type_id())
1129 }
1130
1131 fn get_available_arguments(&self) -> ComponentArguments {
1184 ComponentArguments::new()
1185 }
1186
1187 fn render_dom(
1191 &self,
1192 components: &XmlComponentMap,
1193 arguments: &FilteredComponentArguments,
1194 content: &XmlTextContent,
1195 ) -> Result<StyledDom, RenderDomError>;
1196
1197 fn compile_to_rust_code(
1199 &self,
1200 components: &XmlComponentMap,
1201 attributes: &ComponentArguments,
1202 content: &XmlTextContent,
1203 ) -> Result<String, CompileError> {
1204 Ok(String::new())
1205 }
1206}
1207
1208#[derive(Default)]
1211pub struct DomXml {
1212 pub parsed_dom: StyledDom,
1213}
1214
1215impl DomXml {
1216 #[cfg(test)]
1229 pub fn assert_eq(self, other: StyledDom) {
1230 let mut fixed = Dom::create_body().style(Css::empty());
1231 fixed.append_child(other);
1232 if self.parsed_dom != fixed {
1233 panic!(
1234 "\r\nExpected DOM did not match:\r\n\r\nexpected: ----------\r\n{}\r\ngot: \
1235 ----------\r\n{}\r\n",
1236 self.parsed_dom.get_html_string("", "", true),
1237 fixed.get_html_string("", "", true)
1238 );
1239 }
1240 }
1241
1242 pub fn into_styled_dom(self) -> StyledDom {
1243 self.into()
1244 }
1245}
1246
1247impl Into<StyledDom> for DomXml {
1248 fn into(self) -> StyledDom {
1249 self.parsed_dom
1250 }
1251}
1252
1253#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1255#[repr(C, u8)]
1256pub enum XmlNodeChild {
1257 Text(AzString),
1259 Element(XmlNode),
1261}
1262
1263impl_option!(
1264 XmlNodeChild,
1265 OptionXmlNodeChild,
1266 copy = false,
1267 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1268);
1269
1270impl XmlNodeChild {
1271 pub fn as_text(&self) -> Option<&str> {
1273 match self {
1274 XmlNodeChild::Text(s) => Some(s.as_str()),
1275 XmlNodeChild::Element(_) => None,
1276 }
1277 }
1278
1279 pub fn as_element(&self) -> Option<&XmlNode> {
1281 match self {
1282 XmlNodeChild::Text(_) => None,
1283 XmlNodeChild::Element(node) => Some(node),
1284 }
1285 }
1286
1287 pub fn as_element_mut(&mut self) -> Option<&mut XmlNode> {
1289 match self {
1290 XmlNodeChild::Text(_) => None,
1291 XmlNodeChild::Element(node) => Some(node),
1292 }
1293 }
1294}
1295
1296impl_vec!(XmlNodeChild, XmlNodeChildVec, XmlNodeChildVecDestructor, XmlNodeChildVecDestructorType, XmlNodeChildVecSlice, OptionXmlNodeChild);
1297impl_vec_mut!(XmlNodeChild, XmlNodeChildVec);
1298impl_vec_debug!(XmlNodeChild, XmlNodeChildVec);
1299impl_vec_partialeq!(XmlNodeChild, XmlNodeChildVec);
1300impl_vec_eq!(XmlNodeChild, XmlNodeChildVec);
1301impl_vec_partialord!(XmlNodeChild, XmlNodeChildVec);
1302impl_vec_ord!(XmlNodeChild, XmlNodeChildVec);
1303impl_vec_hash!(XmlNodeChild, XmlNodeChildVec);
1304impl_vec_clone!(XmlNodeChild, XmlNodeChildVec, XmlNodeChildVecDestructor);
1305
1306#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1308#[repr(C)]
1309pub struct XmlNode {
1310 pub node_type: XmlTagName,
1312 pub attributes: XmlAttributeMap,
1314 pub children: XmlNodeChildVec,
1316}
1317
1318impl_option!(
1319 XmlNode,
1320 OptionXmlNode,
1321 copy = false,
1322 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1323);
1324
1325impl XmlNode {
1326 pub fn create<I: Into<XmlTagName>>(node_type: I) -> Self {
1327 XmlNode {
1328 node_type: node_type.into(),
1329 ..Default::default()
1330 }
1331 }
1332 pub fn with_children(mut self, v: Vec<XmlNodeChild>) -> Self {
1333 Self {
1334 children: v.into(),
1335 ..self
1336 }
1337 }
1338
1339 pub fn get_text_content(&self) -> String {
1341 self.children
1342 .as_ref()
1343 .iter()
1344 .filter_map(|child| child.as_text())
1345 .collect::<Vec<_>>()
1346 .join("")
1347 }
1348
1349 pub fn has_only_text_children(&self) -> bool {
1351 self.children
1352 .as_ref()
1353 .iter()
1354 .all(|child| matches!(child, XmlNodeChild::Text(_)))
1355 }
1356}
1357
1358impl_vec!(XmlNode, XmlNodeVec, XmlNodeVecDestructor, XmlNodeVecDestructorType, XmlNodeVecSlice, OptionXmlNode);
1359impl_vec_mut!(XmlNode, XmlNodeVec);
1360impl_vec_debug!(XmlNode, XmlNodeVec);
1361impl_vec_partialeq!(XmlNode, XmlNodeVec);
1362impl_vec_eq!(XmlNode, XmlNodeVec);
1363impl_vec_partialord!(XmlNode, XmlNodeVec);
1364impl_vec_ord!(XmlNode, XmlNodeVec);
1365impl_vec_hash!(XmlNode, XmlNodeVec);
1366impl_vec_clone!(XmlNode, XmlNodeVec, XmlNodeVecDestructor);
1367
1368pub struct XmlComponent {
1369 pub id: String,
1370 pub renderer: Box<dyn XmlComponentTrait>,
1372 pub inherit_vars: bool,
1374}
1375
1376impl core::fmt::Debug for XmlComponent {
1377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1378 f.debug_struct("XmlComponent")
1379 .field("id", &self.id)
1380 .field("args", &self.renderer.get_available_arguments())
1381 .field("inherit_vars", &self.inherit_vars)
1382 .finish()
1383 }
1384}
1385
1386pub struct XmlComponentMap {
1388 pub components: BTreeMap<String, XmlComponent>,
1392}
1393
1394impl Default for XmlComponentMap {
1395 fn default() -> Self {
1396 let mut map = Self {
1397 components: BTreeMap::new(),
1398 };
1399
1400 map.register_component(XmlComponent {
1402 id: normalize_casing("html"),
1403 renderer: Box::new(HtmlRenderer::new()),
1404 inherit_vars: true,
1405 });
1406 map.register_component(XmlComponent {
1407 id: normalize_casing("head"),
1408 renderer: Box::new(HeadRenderer::new()),
1409 inherit_vars: true,
1410 });
1411 map.register_component(XmlComponent {
1412 id: normalize_casing("title"),
1413 renderer: Box::new(TitleRenderer::new()),
1414 inherit_vars: true,
1415 });
1416 map.register_component(XmlComponent {
1417 id: normalize_casing("body"),
1418 renderer: Box::new(BodyRenderer::new()),
1419 inherit_vars: true,
1420 });
1421
1422 map.register_component(XmlComponent {
1424 id: normalize_casing("div"),
1425 renderer: Box::new(DivRenderer::new()),
1426 inherit_vars: true,
1427 });
1428 map.register_component(XmlComponent {
1429 id: normalize_casing("header"),
1430 renderer: Box::new(HeaderRenderer::new()),
1431 inherit_vars: true,
1432 });
1433 map.register_component(XmlComponent {
1434 id: normalize_casing("footer"),
1435 renderer: Box::new(FooterRenderer::new()),
1436 inherit_vars: true,
1437 });
1438 map.register_component(XmlComponent {
1439 id: normalize_casing("section"),
1440 renderer: Box::new(SectionRenderer::new()),
1441 inherit_vars: true,
1442 });
1443 map.register_component(XmlComponent {
1444 id: normalize_casing("article"),
1445 renderer: Box::new(ArticleRenderer::new()),
1446 inherit_vars: true,
1447 });
1448 map.register_component(XmlComponent {
1449 id: normalize_casing("aside"),
1450 renderer: Box::new(AsideRenderer::new()),
1451 inherit_vars: true,
1452 });
1453 map.register_component(XmlComponent {
1454 id: normalize_casing("nav"),
1455 renderer: Box::new(NavRenderer::new()),
1456 inherit_vars: true,
1457 });
1458 map.register_component(XmlComponent {
1459 id: normalize_casing("main"),
1460 renderer: Box::new(MainRenderer::new()),
1461 inherit_vars: true,
1462 });
1463
1464 map.register_component(XmlComponent {
1466 id: normalize_casing("h1"),
1467 renderer: Box::new(H1Renderer::new()),
1468 inherit_vars: true,
1469 });
1470 map.register_component(XmlComponent {
1471 id: normalize_casing("h2"),
1472 renderer: Box::new(H2Renderer::new()),
1473 inherit_vars: true,
1474 });
1475 map.register_component(XmlComponent {
1476 id: normalize_casing("h3"),
1477 renderer: Box::new(H3Renderer::new()),
1478 inherit_vars: true,
1479 });
1480 map.register_component(XmlComponent {
1481 id: normalize_casing("h4"),
1482 renderer: Box::new(H4Renderer::new()),
1483 inherit_vars: true,
1484 });
1485 map.register_component(XmlComponent {
1486 id: normalize_casing("h5"),
1487 renderer: Box::new(H5Renderer::new()),
1488 inherit_vars: true,
1489 });
1490 map.register_component(XmlComponent {
1491 id: normalize_casing("h6"),
1492 renderer: Box::new(H6Renderer::new()),
1493 inherit_vars: true,
1494 });
1495
1496 map.register_component(XmlComponent {
1498 id: normalize_casing("p"),
1499 renderer: Box::new(TextRenderer::new()),
1500 inherit_vars: true,
1501 });
1502 map.register_component(XmlComponent {
1503 id: normalize_casing("span"),
1504 renderer: Box::new(SpanRenderer::new()),
1505 inherit_vars: true,
1506 });
1507 map.register_component(XmlComponent {
1508 id: normalize_casing("pre"),
1509 renderer: Box::new(PreRenderer::new()),
1510 inherit_vars: true,
1511 });
1512 map.register_component(XmlComponent {
1513 id: normalize_casing("code"),
1514 renderer: Box::new(CodeRenderer::new()),
1515 inherit_vars: true,
1516 });
1517 map.register_component(XmlComponent {
1518 id: normalize_casing("blockquote"),
1519 renderer: Box::new(BlockquoteRenderer::new()),
1520 inherit_vars: true,
1521 });
1522 map.register_component(XmlComponent {
1523 id: normalize_casing("br"),
1524 renderer: Box::new(BrRenderer::new()),
1525 inherit_vars: true,
1526 });
1527 map.register_component(XmlComponent {
1528 id: normalize_casing("hr"),
1529 renderer: Box::new(HrRenderer::new()),
1530 inherit_vars: true,
1531 });
1532 map.register_component(XmlComponent {
1533 id: normalize_casing("icon"),
1534 renderer: Box::new(IconRenderer::new()),
1535 inherit_vars: true,
1536 });
1537
1538 map.register_component(XmlComponent {
1540 id: normalize_casing("ul"),
1541 renderer: Box::new(UlRenderer::new()),
1542 inherit_vars: true,
1543 });
1544 map.register_component(XmlComponent {
1545 id: normalize_casing("ol"),
1546 renderer: Box::new(OlRenderer::new()),
1547 inherit_vars: true,
1548 });
1549 map.register_component(XmlComponent {
1550 id: normalize_casing("li"),
1551 renderer: Box::new(LiRenderer::new()),
1552 inherit_vars: true,
1553 });
1554 map.register_component(XmlComponent {
1555 id: normalize_casing("dl"),
1556 renderer: Box::new(DlRenderer::new()),
1557 inherit_vars: true,
1558 });
1559 map.register_component(XmlComponent {
1560 id: normalize_casing("dt"),
1561 renderer: Box::new(DtRenderer::new()),
1562 inherit_vars: true,
1563 });
1564 map.register_component(XmlComponent {
1565 id: normalize_casing("dd"),
1566 renderer: Box::new(DdRenderer::new()),
1567 inherit_vars: true,
1568 });
1569
1570 map.register_component(XmlComponent {
1572 id: normalize_casing("table"),
1573 renderer: Box::new(TableRenderer::new()),
1574 inherit_vars: true,
1575 });
1576 map.register_component(XmlComponent {
1577 id: normalize_casing("thead"),
1578 renderer: Box::new(TheadRenderer::new()),
1579 inherit_vars: true,
1580 });
1581 map.register_component(XmlComponent {
1582 id: normalize_casing("tbody"),
1583 renderer: Box::new(TbodyRenderer::new()),
1584 inherit_vars: true,
1585 });
1586 map.register_component(XmlComponent {
1587 id: normalize_casing("tfoot"),
1588 renderer: Box::new(TfootRenderer::new()),
1589 inherit_vars: true,
1590 });
1591 map.register_component(XmlComponent {
1592 id: normalize_casing("tr"),
1593 renderer: Box::new(TrRenderer::new()),
1594 inherit_vars: true,
1595 });
1596 map.register_component(XmlComponent {
1597 id: normalize_casing("th"),
1598 renderer: Box::new(ThRenderer::new()),
1599 inherit_vars: true,
1600 });
1601 map.register_component(XmlComponent {
1602 id: normalize_casing("td"),
1603 renderer: Box::new(TdRenderer::new()),
1604 inherit_vars: true,
1605 });
1606
1607 map.register_component(XmlComponent {
1609 id: normalize_casing("a"),
1610 renderer: Box::new(ARenderer::new()),
1611 inherit_vars: true,
1612 });
1613 map.register_component(XmlComponent {
1614 id: normalize_casing("strong"),
1615 renderer: Box::new(StrongRenderer::new()),
1616 inherit_vars: true,
1617 });
1618 map.register_component(XmlComponent {
1619 id: normalize_casing("em"),
1620 renderer: Box::new(EmRenderer::new()),
1621 inherit_vars: true,
1622 });
1623 map.register_component(XmlComponent {
1624 id: normalize_casing("b"),
1625 renderer: Box::new(BRenderer::new()),
1626 inherit_vars: true,
1627 });
1628 map.register_component(XmlComponent {
1629 id: normalize_casing("i"),
1630 renderer: Box::new(IRenderer::new()),
1631 inherit_vars: true,
1632 });
1633 map.register_component(XmlComponent {
1634 id: normalize_casing("u"),
1635 renderer: Box::new(URenderer::new()),
1636 inherit_vars: true,
1637 });
1638 map.register_component(XmlComponent {
1639 id: normalize_casing("small"),
1640 renderer: Box::new(SmallRenderer::new()),
1641 inherit_vars: true,
1642 });
1643 map.register_component(XmlComponent {
1644 id: normalize_casing("mark"),
1645 renderer: Box::new(MarkRenderer::new()),
1646 inherit_vars: true,
1647 });
1648 map.register_component(XmlComponent {
1649 id: normalize_casing("sub"),
1650 renderer: Box::new(SubRenderer::new()),
1651 inherit_vars: true,
1652 });
1653 map.register_component(XmlComponent {
1654 id: normalize_casing("sup"),
1655 renderer: Box::new(SupRenderer::new()),
1656 inherit_vars: true,
1657 });
1658
1659 map.register_component(XmlComponent {
1661 id: normalize_casing("form"),
1662 renderer: Box::new(FormRenderer::new()),
1663 inherit_vars: true,
1664 });
1665 map.register_component(XmlComponent {
1666 id: normalize_casing("label"),
1667 renderer: Box::new(LabelRenderer::new()),
1668 inherit_vars: true,
1669 });
1670 map.register_component(XmlComponent {
1671 id: normalize_casing("button"),
1672 renderer: Box::new(ButtonRenderer::new()),
1673 inherit_vars: true,
1674 });
1675
1676 map
1677 }
1678}
1679
1680impl XmlComponentMap {
1681 pub fn register_component(&mut self, comp: XmlComponent) {
1682 self.components.insert(comp.id.clone(), comp);
1683 }
1684
1685 pub fn get(&self, name: &str) -> Option<&XmlComponent> {
1687 self.components.get(name)
1688 }
1689}
1690
1691#[derive(Debug, Clone, PartialEq)]
1692pub enum DomXmlParseError {
1693 NoHtmlNode,
1695 MultipleHtmlRootNodes,
1697 NoBodyInHtml,
1699 MultipleBodyNodes,
1701 Xml(XmlError),
1706 MalformedHierarchy(MalformedHierarchyError),
1708 RenderDom(RenderDomError),
1711 Component(ComponentParseError),
1713 Css(CssParseErrorOwned),
1715}
1716
1717impl From<XmlError> for DomXmlParseError {
1718 fn from(e: XmlError) -> Self {
1719 Self::Xml(e)
1720 }
1721}
1722
1723impl From<ComponentParseError> for DomXmlParseError {
1724 fn from(e: ComponentParseError) -> Self {
1725 Self::Component(e)
1726 }
1727}
1728
1729impl From<RenderDomError> for DomXmlParseError {
1730 fn from(e: RenderDomError) -> Self {
1731 Self::RenderDom(e)
1732 }
1733}
1734
1735impl From<CssParseErrorOwned> for DomXmlParseError {
1736 fn from(e: CssParseErrorOwned) -> Self {
1737 Self::Css(e)
1738 }
1739}
1740
1741#[derive(Debug, Clone, PartialEq)]
1744pub enum CompileError {
1745 Dom(RenderDomError),
1746 Xml(DomXmlParseError),
1747 Css(CssParseErrorOwned),
1748}
1749
1750impl From<ComponentError> for CompileError {
1751 fn from(e: ComponentError) -> Self {
1752 CompileError::Dom(RenderDomError::Component(e))
1753 }
1754}
1755
1756impl From<CssParseErrorOwned> for CompileError {
1757 fn from(e: CssParseErrorOwned) -> Self {
1758 CompileError::Css(e)
1759 }
1760}
1761
1762impl<'a> fmt::Display for CompileError {
1763 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1764 use self::CompileError::*;
1765 match self {
1766 Dom(d) => write!(f, "{}", d),
1767 Xml(s) => write!(f, "{}", s),
1768 Css(s) => write!(f, "{}", s.to_shared()),
1769 }
1770 }
1771}
1772
1773impl From<RenderDomError> for CompileError {
1774 fn from(e: RenderDomError) -> Self {
1775 CompileError::Dom(e)
1776 }
1777}
1778
1779impl From<DomXmlParseError> for CompileError {
1780 fn from(e: DomXmlParseError) -> Self {
1781 CompileError::Xml(e)
1782 }
1783}
1784
1785#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1786pub enum ComponentError {
1787 UselessFunctionArgument(AzString, AzString, Vec<String>),
1790 UnknownComponent(AzString),
1795}
1796
1797#[derive(Debug, Clone, PartialEq)]
1798pub enum RenderDomError {
1799 Component(ComponentError),
1800 CssError(CssParseErrorOwned),
1802}
1803
1804impl From<ComponentError> for RenderDomError {
1805 fn from(e: ComponentError) -> Self {
1806 Self::Component(e)
1807 }
1808}
1809
1810impl From<CssParseErrorOwned> for RenderDomError {
1811 fn from(e: CssParseErrorOwned) -> Self {
1812 Self::CssError(e)
1813 }
1814}
1815
1816#[derive(Debug, Clone, PartialEq)]
1817pub enum ComponentParseError {
1818 NotAComponent,
1820 UnnamedComponent,
1822 MissingName(usize),
1824 MissingType(usize, AzString),
1827 WhiteSpaceInComponentName(usize, AzString),
1830 WhiteSpaceInComponentType(usize, AzString, AzString),
1833 CssError(CssParseErrorOwned),
1835}
1836
1837impl<'a> fmt::Display for DomXmlParseError {
1838 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1839 use self::DomXmlParseError::*;
1840 match self {
1841 NoHtmlNode => write!(
1842 f,
1843 "No <html> node found as the root of the file - empty file?"
1844 ),
1845 MultipleHtmlRootNodes => write!(
1846 f,
1847 "Multiple <html> nodes found as the root of the file - only one root node allowed"
1848 ),
1849 NoBodyInHtml => write!(
1850 f,
1851 "No <body> node found as a direct child of an <html> node - malformed DOM \
1852 hierarchy?"
1853 ),
1854 MultipleBodyNodes => write!(
1855 f,
1856 "Multiple <body> nodes present, only one <body> node is allowed"
1857 ),
1858 Xml(e) => write!(f, "Error parsing XML: {}", e),
1859 MalformedHierarchy(e) => write!(
1860 f,
1861 "Invalid </{}> tag: expected </{}>",
1862 e.got.as_str(),
1863 e.expected.as_str()
1864 ),
1865 RenderDom(e) => write!(f, "Error rendering DOM: {}", e),
1866 Component(c) => write!(f, "Error parsing component in <head> node:\r\n{}", c),
1867 Css(c) => write!(f, "Error parsing CSS in <head> node:\r\n{}", c.to_shared()),
1868 }
1869 }
1870}
1871
1872impl<'a> fmt::Display for ComponentParseError {
1873 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1874 use self::ComponentParseError::*;
1875 match self {
1876 NotAComponent => write!(f, "Expected <component/> node, found no such node"),
1877 UnnamedComponent => write!(
1878 f,
1879 "Found <component/> tag with out a \"name\" attribute, component must have a name"
1880 ),
1881 MissingName(arg_pos) => write!(
1882 f,
1883 "Argument at position {} is either empty or has no name",
1884 arg_pos
1885 ),
1886 MissingType(arg_pos, arg_name) => write!(
1887 f,
1888 "Argument \"{}\" at position {} doesn't have a `: type`",
1889 arg_pos, arg_name
1890 ),
1891 WhiteSpaceInComponentName(arg_pos, arg_name_unparsed) => {
1892 write!(
1893 f,
1894 "Missing `:` between the name and the type in argument {} (around \"{}\")",
1895 arg_pos, arg_name_unparsed
1896 )
1897 }
1898 WhiteSpaceInComponentType(arg_pos, arg_name, arg_type_unparsed) => {
1899 write!(
1900 f,
1901 "Missing `,` between two arguments (in argument {}, position {}, around \
1902 \"{}\")",
1903 arg_name, arg_pos, arg_type_unparsed
1904 )
1905 }
1906 CssError(lsf) => write!(f, "Error parsing <style> tag: {}", lsf.to_shared()),
1907 }
1908 }
1909}
1910
1911impl fmt::Display for ComponentError {
1912 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1913 use self::ComponentError::*;
1914 match self {
1915 UselessFunctionArgument(k, v, available_args) => {
1916 write!(
1917 f,
1918 "Useless component argument \"{}\": \"{}\" - available args are: {:#?}",
1919 k, v, available_args
1920 )
1921 }
1922 UnknownComponent(name) => write!(f, "Unknown component: \"{}\"", name),
1923 }
1924 }
1925}
1926
1927impl<'a> fmt::Display for RenderDomError {
1928 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1929 use self::RenderDomError::*;
1930 match self {
1931 Component(c) => write!(f, "{}", c),
1932 CssError(e) => write!(f, "Error parsing CSS in component: {}", e.to_shared()),
1933 }
1934 }
1935}
1936
1937macro_rules! html_component {
1942 ($name:ident, $tag:expr, $node_type:expr) => {
1943 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1944 pub struct $name {
1945 node: XmlNode,
1946 }
1947
1948 impl $name {
1949 pub fn new() -> Self {
1950 Self {
1951 node: XmlNode::create($tag),
1952 }
1953 }
1954 }
1955
1956 impl XmlComponentTrait for $name {
1957 fn get_available_arguments(&self) -> ComponentArguments {
1958 ComponentArguments {
1959 args: ComponentArgumentTypes::default(),
1960 accepts_text: true,
1961 }
1962 }
1963
1964 fn render_dom(
1965 &self,
1966 _: &XmlComponentMap,
1967 _: &FilteredComponentArguments,
1968 text: &XmlTextContent,
1969 ) -> Result<StyledDom, RenderDomError> {
1970 let mut dom = Dom::create_node($node_type);
1971
1972 if let Some(text_str) = text.as_ref() {
1974 let prepared = prepare_string(text_str);
1975 if !prepared.is_empty() {
1976 dom = dom.with_children(alloc::vec![Dom::create_text(prepared)].into());
1977 }
1978 }
1979
1980 Ok(dom.style(Css::empty()))
1981 }
1982
1983 fn compile_to_rust_code(
1984 &self,
1985 _: &XmlComponentMap,
1986 _: &ComponentArguments,
1987 _: &XmlTextContent,
1988 ) -> Result<String, CompileError> {
1989 Ok(format!(
1990 "Dom::create_node(NodeType::{})",
1991 stringify!($node_type)
1992 ))
1993 }
1994
1995 fn get_xml_node(&self) -> XmlNode {
1996 self.node.clone()
1997 }
1998 }
1999 };
2000}
2001
2002html_component!(HtmlRenderer, "html", NodeType::Html);
2004html_component!(HeadRenderer, "head", NodeType::Head);
2005html_component!(TitleRenderer, "title", NodeType::Title);
2006html_component!(HeaderRenderer, "header", NodeType::Header);
2007html_component!(FooterRenderer, "footer", NodeType::Footer);
2008html_component!(SectionRenderer, "section", NodeType::Section);
2009html_component!(ArticleRenderer, "article", NodeType::Article);
2010html_component!(AsideRenderer, "aside", NodeType::Aside);
2011html_component!(NavRenderer, "nav", NodeType::Nav);
2012html_component!(MainRenderer, "main", NodeType::Main);
2013html_component!(H1Renderer, "h1", NodeType::H1);
2014html_component!(H2Renderer, "h2", NodeType::H2);
2015html_component!(H3Renderer, "h3", NodeType::H3);
2016html_component!(H4Renderer, "h4", NodeType::H4);
2017html_component!(H5Renderer, "h5", NodeType::H5);
2018html_component!(H6Renderer, "h6", NodeType::H6);
2019html_component!(SpanRenderer, "span", NodeType::Span);
2020html_component!(PreRenderer, "pre", NodeType::Pre);
2021html_component!(CodeRenderer, "code", NodeType::Code);
2022html_component!(BlockquoteRenderer, "blockquote", NodeType::BlockQuote);
2023html_component!(UlRenderer, "ul", NodeType::Ul);
2024html_component!(OlRenderer, "ol", NodeType::Ol);
2025html_component!(LiRenderer, "li", NodeType::Li);
2026html_component!(DlRenderer, "dl", NodeType::Dl);
2027html_component!(DtRenderer, "dt", NodeType::Dt);
2028html_component!(DdRenderer, "dd", NodeType::Dd);
2029html_component!(TableRenderer, "table", NodeType::Table);
2030html_component!(TheadRenderer, "thead", NodeType::THead);
2031html_component!(TbodyRenderer, "tbody", NodeType::TBody);
2032html_component!(TfootRenderer, "tfoot", NodeType::TFoot);
2033html_component!(TrRenderer, "tr", NodeType::Tr);
2034html_component!(ThRenderer, "th", NodeType::Th);
2035html_component!(TdRenderer, "td", NodeType::Td);
2036html_component!(ARenderer, "a", NodeType::A);
2037html_component!(StrongRenderer, "strong", NodeType::Strong);
2038html_component!(EmRenderer, "em", NodeType::Em);
2039html_component!(BRenderer, "b", NodeType::B);
2040html_component!(IRenderer, "i", NodeType::I);
2041html_component!(URenderer, "u", NodeType::U);
2042html_component!(SmallRenderer, "small", NodeType::Small);
2043html_component!(MarkRenderer, "mark", NodeType::Mark);
2044html_component!(SubRenderer, "sub", NodeType::Sub);
2045html_component!(SupRenderer, "sup", NodeType::Sup);
2046html_component!(FormRenderer, "form", NodeType::Form);
2047html_component!(LabelRenderer, "label", NodeType::Label);
2048html_component!(ButtonRenderer, "button", NodeType::Button);
2049html_component!(HrRenderer, "hr", NodeType::Hr);
2050
2051#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2053pub struct DivRenderer {
2054 node: XmlNode,
2055}
2056
2057impl DivRenderer {
2058 pub fn new() -> Self {
2059 Self {
2060 node: XmlNode::create("div"),
2061 }
2062 }
2063}
2064
2065impl XmlComponentTrait for DivRenderer {
2066 fn get_available_arguments(&self) -> ComponentArguments {
2067 ComponentArguments::new()
2068 }
2069
2070 fn render_dom(
2071 &self,
2072 _: &XmlComponentMap,
2073 _: &FilteredComponentArguments,
2074 _: &XmlTextContent,
2075 ) -> Result<StyledDom, RenderDomError> {
2076 Ok(Dom::create_div().style(Css::empty()))
2077 }
2078
2079 fn compile_to_rust_code(
2080 &self,
2081 _: &XmlComponentMap,
2082 _: &ComponentArguments,
2083 _: &XmlTextContent,
2084 ) -> Result<String, CompileError> {
2085 Ok("Dom::create_div()".into())
2086 }
2087
2088 fn get_xml_node(&self) -> XmlNode {
2089 self.node.clone()
2090 }
2091}
2092
2093#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2095pub struct BodyRenderer {
2096 node: XmlNode,
2097}
2098
2099impl BodyRenderer {
2100 pub fn new() -> Self {
2101 Self {
2102 node: XmlNode::create("body"),
2103 }
2104 }
2105}
2106
2107impl XmlComponentTrait for BodyRenderer {
2108 fn get_available_arguments(&self) -> ComponentArguments {
2109 ComponentArguments::new()
2110 }
2111
2112 fn render_dom(
2113 &self,
2114 _: &XmlComponentMap,
2115 _: &FilteredComponentArguments,
2116 _: &XmlTextContent,
2117 ) -> Result<StyledDom, RenderDomError> {
2118 Ok(Dom::create_body().style(Css::empty()))
2119 }
2120
2121 fn compile_to_rust_code(
2122 &self,
2123 _: &XmlComponentMap,
2124 _: &ComponentArguments,
2125 _: &XmlTextContent,
2126 ) -> Result<String, CompileError> {
2127 Ok("Dom::create_body()".into())
2128 }
2129
2130 fn get_xml_node(&self) -> XmlNode {
2131 self.node.clone()
2132 }
2133}
2134
2135#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2137pub struct BrRenderer {
2138 node: XmlNode,
2139}
2140
2141impl BrRenderer {
2142 pub fn new() -> Self {
2143 Self {
2144 node: XmlNode::create("br"),
2145 }
2146 }
2147}
2148
2149impl XmlComponentTrait for BrRenderer {
2150 fn get_available_arguments(&self) -> ComponentArguments {
2151 ComponentArguments::new()
2152 }
2153
2154 fn render_dom(
2155 &self,
2156 _: &XmlComponentMap,
2157 _: &FilteredComponentArguments,
2158 _: &XmlTextContent,
2159 ) -> Result<StyledDom, RenderDomError> {
2160 Ok(Dom::create_node(NodeType::Br).style(Css::empty()))
2161 }
2162
2163 fn compile_to_rust_code(
2164 &self,
2165 _: &XmlComponentMap,
2166 _: &ComponentArguments,
2167 _: &XmlTextContent,
2168 ) -> Result<String, CompileError> {
2169 Ok("Dom::create_node(NodeType::Br)".into())
2170 }
2171
2172 fn get_xml_node(&self) -> XmlNode {
2173 self.node.clone()
2174 }
2175}
2176
2177#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2188pub struct IconRenderer {
2189 node: XmlNode,
2190}
2191
2192impl IconRenderer {
2193 pub fn new() -> Self {
2194 Self {
2195 node: XmlNode::create("icon"),
2196 }
2197 }
2198}
2199
2200impl XmlComponentTrait for IconRenderer {
2201 fn get_available_arguments(&self) -> ComponentArguments {
2202 let mut args = ComponentArgumentTypes::default();
2203 args.push(("name".to_string(), "String".to_string()));
2204 ComponentArguments {
2205 args,
2206 accepts_text: true, }
2208 }
2209
2210 fn render_dom(
2211 &self,
2212 _: &XmlComponentMap,
2213 args: &FilteredComponentArguments,
2214 content: &XmlTextContent,
2215 ) -> Result<StyledDom, RenderDomError> {
2216 let icon_name = args.values.get("name")
2218 .map(|s| s.to_string())
2219 .or_else(|| content.as_ref().map(|s| prepare_string(&s)))
2220 .unwrap_or_else(|| "invalid-icon".to_string());
2221
2222 Ok(Dom::create_node(NodeType::Icon(AzString::from(icon_name))).style(Css::empty()))
2223 }
2224
2225 fn compile_to_rust_code(
2226 &self,
2227 _: &XmlComponentMap,
2228 args: &ComponentArguments,
2229 content: &XmlTextContent,
2230 ) -> Result<String, CompileError> {
2231 let icon_name = args.args.iter()
2232 .find(|(name, _)| name == "name")
2233 .map(|(_, value)| value.to_string())
2234 .or_else(|| content.as_ref().map(|s| s.to_string()))
2235 .unwrap_or_else(|| "invalid-icon".to_string());
2236
2237 Ok(format!("Dom::create_node(NodeType::Icon(AzString::from(\"{}\")))", icon_name))
2238 }
2239
2240 fn get_xml_node(&self) -> XmlNode {
2241 self.node.clone()
2242 }
2243}
2244
2245#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
2247pub struct TextRenderer {
2248 node: XmlNode,
2249}
2250
2251impl TextRenderer {
2252 pub fn new() -> Self {
2253 Self {
2254 node: XmlNode::create("p"),
2255 }
2256 }
2257}
2258
2259impl XmlComponentTrait for TextRenderer {
2260 fn get_available_arguments(&self) -> ComponentArguments {
2261 ComponentArguments {
2262 args: ComponentArgumentTypes::default(),
2263 accepts_text: true, }
2265 }
2266
2267 fn render_dom(
2268 &self,
2269 _: &XmlComponentMap,
2270 _: &FilteredComponentArguments,
2271 content: &XmlTextContent,
2272 ) -> Result<StyledDom, RenderDomError> {
2273 let content = content
2274 .as_ref()
2275 .map(|s| prepare_string(&s))
2276 .unwrap_or_default();
2277 Ok(Dom::create_node(NodeType::P)
2278 .with_children(vec![Dom::create_text(content)].into())
2279 .style(Css::empty()))
2280 }
2281
2282 fn compile_to_rust_code(
2283 &self,
2284 _: &XmlComponentMap,
2285 args: &ComponentArguments,
2286 content: &XmlTextContent,
2287 ) -> Result<String, CompileError> {
2288 Ok(String::from(
2289 "Dom::create_node(NodeType::P).with_children(vec![Dom::create_text(content)].into())",
2290 ))
2291 }
2292
2293 fn get_xml_node(&self) -> XmlNode {
2294 self.node.clone()
2295 }
2296}
2297
2298pub fn parse_component_arguments<'a>(
2300 input: &'a str,
2301) -> Result<ComponentArgumentTypes, ComponentParseError> {
2302 use self::ComponentParseError::*;
2303
2304 let mut args = ComponentArgumentTypes::default();
2305
2306 for (arg_idx, arg) in input.split(",").enumerate() {
2307 let mut colon_iterator = arg.split(":");
2308
2309 let arg_name = colon_iterator.next().ok_or(MissingName(arg_idx))?;
2310 let arg_name = arg_name.trim();
2311
2312 if arg_name.is_empty() {
2313 return Err(MissingName(arg_idx));
2314 }
2315 if arg_name.chars().any(char::is_whitespace) {
2316 return Err(WhiteSpaceInComponentName(arg_idx, arg_name.into()));
2317 }
2318
2319 let arg_type = colon_iterator
2320 .next()
2321 .ok_or(MissingType(arg_idx, arg_name.into()))?;
2322 let arg_type = arg_type.trim();
2323
2324 if arg_type.is_empty() {
2325 return Err(MissingType(arg_idx, arg_name.into()));
2326 }
2327
2328 if arg_type.chars().any(char::is_whitespace) {
2329 return Err(WhiteSpaceInComponentType(
2330 arg_idx,
2331 arg_name.into(),
2332 arg_type.into(),
2333 ));
2334 }
2335
2336 let arg_name = normalize_casing(arg_name);
2337 let arg_type = arg_type.to_string();
2338
2339 args.push((arg_name, arg_type));
2340 }
2341
2342 Ok(args)
2343}
2344
2345pub fn validate_and_filter_component_args(
2347 xml_attributes: &XmlAttributeMap,
2348 valid_args: &ComponentArguments,
2349) -> Result<FilteredComponentArguments, ComponentError> {
2350 let mut map = FilteredComponentArguments {
2351 types: ComponentArgumentTypes::default(),
2352 values: BTreeMap::new(),
2353 accepts_text: valid_args.accepts_text,
2354 };
2355
2356 for AzStringPair { key, value } in xml_attributes.as_ref().iter() {
2357 let xml_attribute_name = key;
2358 let xml_attribute_value = value;
2359 if let Some(valid_arg_type) = valid_args
2360 .args
2361 .iter()
2362 .find(|s| s.0 == xml_attribute_name.as_str())
2363 .map(|q| &q.1)
2364 {
2365 map.types.push((
2366 xml_attribute_name.as_str().to_string(),
2367 valid_arg_type.clone(),
2368 ));
2369 map.values.insert(
2370 xml_attribute_name.as_str().to_string(),
2371 xml_attribute_value.as_str().to_string(),
2372 );
2373 } else if DEFAULT_ARGS.contains(&xml_attribute_name.as_str()) {
2374 map.values.insert(
2376 xml_attribute_name.as_str().to_string(),
2377 xml_attribute_value.as_str().to_string(),
2378 );
2379 } else {
2380 #[cfg(feature = "std")]
2384 eprintln!(
2385 "Warning: Useless component argument \"{}\": \"{}\" for component with args: {:?}",
2386 xml_attribute_name,
2387 xml_attribute_value,
2388 valid_args.args.iter().map(|s| &s.0).collect::<Vec<_>>()
2389 );
2390
2391 map.values.insert(
2393 xml_attribute_name.as_str().to_string(),
2394 xml_attribute_value.as_str().to_string(),
2395 );
2396 }
2397 }
2398
2399 Ok(map)
2400}
2401
2402pub fn get_html_node<'a>(root_nodes: &'a [XmlNodeChild]) -> Result<&'a XmlNode, DomXmlParseError> {
2405 let mut html_node_iterator = root_nodes.iter().filter_map(|child| {
2406 if let XmlNodeChild::Element(node) = child {
2407 let node_type_normalized = normalize_casing(&node.node_type);
2408 if &node_type_normalized == "html" {
2409 Some(node)
2410 } else {
2411 None
2412 }
2413 } else {
2414 None
2415 }
2416 });
2417
2418 let html_node = html_node_iterator
2419 .next()
2420 .ok_or(DomXmlParseError::NoHtmlNode)?;
2421 if html_node_iterator.next().is_some() {
2422 Err(DomXmlParseError::MultipleHtmlRootNodes)
2423 } else {
2424 Ok(html_node)
2425 }
2426}
2427
2428pub fn get_body_node<'a>(root_nodes: &'a [XmlNodeChild]) -> Result<&'a XmlNode, DomXmlParseError> {
2431 let direct_body = root_nodes.iter().filter_map(|child| {
2433 if let XmlNodeChild::Element(node) = child {
2434 let node_type_normalized = normalize_casing(&node.node_type);
2435 if &node_type_normalized == "body" {
2436 Some(node)
2437 } else {
2438 None
2439 }
2440 } else {
2441 None
2442 }
2443 }).next();
2444
2445 if let Some(body) = direct_body {
2446 return Ok(body);
2447 }
2448
2449 fn find_body_recursive<'a>(nodes: &'a [XmlNodeChild]) -> Option<&'a XmlNode> {
2452 for child in nodes {
2453 if let XmlNodeChild::Element(node) = child {
2454 let node_type_normalized = normalize_casing(&node.node_type);
2455 if &node_type_normalized == "body" {
2456 return Some(node);
2457 }
2458 if let Some(found) = find_body_recursive(node.children.as_ref()) {
2460 return Some(found);
2461 }
2462 }
2463 }
2464 None
2465 }
2466
2467 find_body_recursive(root_nodes).ok_or(DomXmlParseError::NoBodyInHtml)
2468}
2469
2470static DEFAULT_STR: &str = "";
2471
2472pub fn find_node_by_type<'a>(
2476 root_nodes: &'a [XmlNodeChild],
2477 node_type: &str,
2478) -> Option<&'a XmlNode> {
2479 for child in root_nodes {
2481 if let XmlNodeChild::Element(node) = child {
2482 if normalize_casing(&node.node_type).as_str() == node_type {
2483 return Some(node);
2484 }
2485 }
2486 }
2487
2488 for child in root_nodes {
2490 if let XmlNodeChild::Element(node) = child {
2491 if let Some(found) = find_node_by_type(node.children.as_ref(), node_type) {
2492 return Some(found);
2493 }
2494 }
2495 }
2496
2497 None
2498}
2499
2500pub fn find_attribute<'a>(node: &'a XmlNode, attribute: &str) -> Option<&'a AzString> {
2501 node.attributes
2502 .iter()
2503 .find(|n| normalize_casing(&n.key.as_str()).as_str() == attribute)
2504 .map(|s| &s.value)
2505}
2506
2507pub fn normalize_casing(input: &str) -> String {
2509 let mut words: Vec<String> = Vec::new();
2510 let mut cur_str = Vec::new();
2511
2512 for ch in input.chars() {
2513 if ch.is_uppercase() || ch == '_' || ch == '-' {
2514 if !cur_str.is_empty() {
2515 words.push(cur_str.iter().collect());
2516 cur_str.clear();
2517 }
2518 if ch.is_uppercase() {
2519 cur_str.extend(ch.to_lowercase());
2520 }
2521 } else {
2522 cur_str.extend(ch.to_lowercase());
2523 }
2524 }
2525
2526 if !cur_str.is_empty() {
2527 words.push(cur_str.iter().collect());
2528 cur_str.clear();
2529 }
2530
2531 words.join("_")
2532}
2533
2534#[allow(trivial_casts)]
2537pub fn get_item<'a>(hierarchy: &[usize], root_node: &'a mut XmlNode) -> Option<&'a mut XmlNode> {
2538 let mut hierarchy = hierarchy.to_vec();
2539 hierarchy.reverse();
2540 let item = match hierarchy.pop() {
2541 Some(s) => s,
2542 None => return Some(root_node),
2543 };
2544 let child = root_node.children.as_mut().get_mut(item)?;
2545 match child {
2546 XmlNodeChild::Element(node) => get_item_internal(&mut hierarchy, node),
2547 XmlNodeChild::Text(_) => None, }
2549}
2550
2551fn get_item_internal<'a>(
2552 hierarchy: &mut Vec<usize>,
2553 root_node: &'a mut XmlNode,
2554) -> Option<&'a mut XmlNode> {
2555 if hierarchy.is_empty() {
2556 return Some(root_node);
2557 }
2558 let cur_item = match hierarchy.pop() {
2559 Some(s) => s,
2560 None => return Some(root_node),
2561 };
2562 let child = root_node.children.as_mut().get_mut(cur_item)?;
2563 match child {
2564 XmlNodeChild::Element(node) => get_item_internal(hierarchy, node),
2565 XmlNodeChild::Text(_) => None, }
2567}
2568
2569pub fn str_to_dom<'a>(
2572 root_nodes: &'a [XmlNodeChild],
2573 component_map: &'a mut XmlComponentMap,
2574 max_width: Option<f32>,
2575) -> Result<StyledDom, DomXmlParseError> {
2576 let html_node = get_html_node(root_nodes)?;
2577 let body_node = get_body_node(html_node.children.as_ref())?;
2578
2579 let mut global_style = None;
2580
2581 if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
2582 for child in head_node.children.as_ref() {
2584 if let XmlNodeChild::Element(node) = child {
2585 match DynamicXmlComponent::new(node) {
2586 Ok(comp) => {
2587 let node_name = comp.name.clone();
2588 component_map.register_component(XmlComponent {
2589 id: normalize_casing(&node_name),
2590 renderer: Box::new(comp),
2591 inherit_vars: false,
2592 });
2593 }
2594 Err(ComponentParseError::NotAComponent) => {} Err(e) => return Err(e.into()), }
2599 }
2600 }
2601
2602 if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
2604 let text = style_node.get_text_content();
2605 if !text.is_empty() {
2606 let parsed_css = Css::from_string(text.into());
2607 global_style = Some(parsed_css);
2608 }
2609 }
2610 }
2611
2612 render_dom_from_body_node(&body_node, global_style, component_map, max_width)
2613 .map_err(|e| e.into())
2614}
2615
2616pub fn str_to_rust_code<'a>(
2619 root_nodes: &'a [XmlNodeChild],
2620 imports: &str,
2621 component_map: &'a mut XmlComponentMap,
2622) -> Result<String, CompileError> {
2623 let html_node = get_html_node(&root_nodes)?;
2624 let body_node = get_body_node(html_node.children.as_ref())?;
2625 let mut global_style = Css::empty();
2626
2627 if let Some(head_node) = find_node_by_type(html_node.children.as_ref(), "head") {
2628 for child in head_node.children.as_ref() {
2629 if let XmlNodeChild::Element(node) = child {
2630 match DynamicXmlComponent::new(node) {
2631 Ok(node) => {
2632 let node_name = node.name.clone();
2633 component_map.register_component(XmlComponent {
2634 id: normalize_casing(&node_name),
2635 renderer: Box::new(node),
2636 inherit_vars: false,
2637 });
2638 }
2639 Err(ComponentParseError::NotAComponent) => {} Err(e) => return Err(CompileError::Xml(e.into())), }
2645 }
2646 }
2647
2648 if let Some(style_node) = find_node_by_type(head_node.children.as_ref(), "style") {
2649 let text = style_node.get_text_content();
2650 if !text.is_empty() {
2651 let parsed_css = azul_css::parser2::new_from_str(&text).0;
2652 global_style = parsed_css;
2653 }
2654 }
2655 }
2656
2657 global_style.sort_by_specificity();
2658
2659 let mut css_blocks = BTreeMap::new();
2660 let mut extra_blocks = VecContents::default();
2661 let app_source = compile_body_node_to_rust_code(
2662 &body_node,
2663 component_map,
2664 &mut extra_blocks,
2665 &mut css_blocks,
2666 &global_style,
2667 CssMatcher {
2668 path: Vec::new(),
2669 indices_in_parent: vec![0],
2670 children_length: vec![body_node.children.as_ref().len()],
2671 },
2672 )?;
2673
2674 let app_source = app_source
2675 .lines()
2676 .map(|l| format!(" {}", l))
2677 .collect::<Vec<String>>()
2678 .join("\r\n");
2679
2680 let t = " ";
2681 let css_blocks = css_blocks
2682 .iter()
2683 .map(|(k, v)| {
2684 let v = v
2685 .lines()
2686 .map(|l| format!("{}{}{}", t, t, l))
2687 .collect::<Vec<String>>()
2688 .join("\r\n");
2689
2690 format!(
2691 " const {}_PROPERTIES: &[NodeDataInlineCssProperty] = \
2692 &[\r\n{}\r\n{}];\r\n{}const {}: NodeDataInlineCssPropertyVec = \
2693 NodeDataInlineCssPropertyVec::from_const_slice({}_PROPERTIES);",
2694 k, v, t, t, k, k
2695 )
2696 })
2697 .collect::<Vec<_>>()
2698 .join(&format!("{}\r\n\r\n", t));
2699
2700 let mut extra_block_string = extra_blocks.format(1);
2701
2702 let main_func = "
2703
2704use azul::{
2705 app::{App, AppConfig, LayoutSolver},
2706 css::Css,
2707 style::StyledDom,
2708 callbacks::{RefAny, LayoutCallbackInfo},
2709 window::{WindowCreateOptions, WindowFrame},
2710};
2711
2712struct Data { }
2713
2714extern \"C\" fn render(_: RefAny, _: LayoutCallbackInfo) -> StyledDom {
2715 crate::ui::render()
2716 .style(Css::empty()) // styles are applied inline
2717}
2718
2719fn main() {
2720 let app = App::new(RefAny::new(Data { }), AppConfig::new(LayoutSolver::Default));
2721 let mut window = WindowCreateOptions::new(render);
2722 window.state.flags.frame = WindowFrame::Maximized;
2723 app.run(window);
2724}";
2725
2726 let source_code = format!(
2727 "#![windows_subsystem = \"windows\"]\r\n//! Auto-generated UI source \
2728 code\r\n{}\r\n{}\r\n\r\n{}{}",
2729 imports,
2730 compile_components(compile_components_to_rust_code(component_map)?),
2731 format!(
2732 "#[allow(unused_imports)]\r\npub mod ui {{
2733
2734 pub use crate::components::*;
2735
2736 use azul::css::*;
2737 use azul::str::String as AzString;
2738 use azul::vec::{{
2739 DomVec, IdOrClassVec, NodeDataInlineCssPropertyVec,
2740 StyleBackgroundSizeVec, StyleBackgroundRepeatVec,
2741 StyleBackgroundContentVec, StyleTransformVec,
2742 StyleFontFamilyVec, StyleBackgroundPositionVec,
2743 NormalizedLinearColorStopVec, NormalizedRadialColorStopVec,
2744 }};
2745 use azul::dom::{{
2746 Dom, IdOrClass, TabIndex,
2747 IdOrClass::{{Id, Class}},
2748 NodeDataInlineCssProperty,
2749 }};\r\n\r\n{}\r\n\r\n{}
2750
2751 pub fn render() -> Dom {{\r\n{}\r\n }}\r\n}}",
2752 extra_block_string, css_blocks, app_source
2753 ),
2754 main_func,
2755 );
2756
2757 Ok(source_code)
2758}
2759
2760pub fn compile_components(
2762 components: Vec<(
2763 ComponentName,
2764 CompiledComponent,
2765 ComponentArguments,
2766 BTreeMap<String, String>,
2767 )>,
2768) -> String {
2769 let cs = components
2770 .iter()
2771 .map(|(name, function_body, function_args, css_blocks)| {
2772 let name = &normalize_casing(&name);
2773 let f = compile_component(name, function_args, function_body)
2774 .lines()
2775 .map(|l| format!(" {}", l))
2776 .collect::<Vec<String>>()
2777 .join("\r\n");
2778
2779 format!(
2782 "#[allow(unused_imports)]\r\npub mod {} {{\r\n use azul::dom::Dom;\r\n use \
2783 azul::str::String as AzString;\r\n{}\r\n}}",
2784 name, f
2785 )
2786 })
2787 .collect::<Vec<String>>()
2788 .join("\r\n\r\n");
2789
2790 let cs = cs
2791 .lines()
2792 .map(|l| format!(" {}", l))
2793 .collect::<Vec<String>>()
2794 .join("\r\n");
2795
2796 if cs.is_empty() {
2797 cs
2798 } else {
2799 format!("pub mod components {{\r\n{}\r\n}}", cs)
2800 }
2801}
2802
2803pub fn format_component_args(component_args: &ComponentArgumentTypes) -> String {
2804 let mut args = component_args
2805 .iter()
2806 .map(|(arg_name, arg_type)| format!("{}: {}", arg_name, arg_type))
2807 .collect::<Vec<String>>();
2808
2809 args.sort_by(|a, b| b.cmp(&a));
2810
2811 args.join(", ")
2812}
2813
2814pub fn compile_component(
2815 component_name: &str,
2816 component_args: &ComponentArguments,
2817 component_function_body: &str,
2818) -> String {
2819 let component_name = &normalize_casing(&component_name);
2820 let function_args = format_component_args(&component_args.args);
2821 let component_function_body = component_function_body
2822 .lines()
2823 .map(|l| format!(" {}", l))
2824 .collect::<Vec<String>>()
2825 .join("\r\n");
2826 let should_inline = component_function_body.lines().count() == 1;
2827 format!(
2828 "{}pub fn render({}{}{}) -> Dom {{\r\n{}\r\n}}",
2829 if should_inline { "#[inline]\r\n" } else { "" },
2830 if component_args.accepts_text {
2832 "text: AzString"
2833 } else {
2834 ""
2835 },
2836 if function_args.is_empty() || !component_args.accepts_text {
2837 ""
2838 } else {
2839 ", "
2840 },
2841 function_args,
2842 component_function_body,
2843 )
2844}
2845
2846fn xml_node_to_dom_fast<'a>(
2849 xml_node: &'a XmlNode,
2850 component_map: &'a XmlComponentMap,
2851) -> Result<Dom, RenderDomError> {
2852 use crate::dom::{Dom, NodeType, IdOrClass};
2853
2854 let component_name = normalize_casing(&xml_node.node_type);
2855
2856 let xml_component = component_map
2858 .get(&component_name)
2859 .ok_or(ComponentError::UnknownComponent(component_name.clone().into()))?;
2860
2861 let node_type = get_node_type_for_component(&component_name);
2863 let mut dom = Dom::create_node(node_type);
2864
2865 let mut ids_and_classes = Vec::new();
2867 if let Some(id_str) = xml_node.attributes.get_key("id") {
2868 for id in id_str.split_whitespace() {
2869 ids_and_classes.push(IdOrClass::Id(id.into()));
2870 }
2871 }
2872 if let Some(class_str) = xml_node.attributes.get_key("class") {
2873 for class in class_str.split_whitespace() {
2874 ids_and_classes.push(IdOrClass::Class(class.into()));
2875 }
2876 }
2877 if !ids_and_classes.is_empty() {
2878 dom.root.set_ids_and_classes(ids_and_classes.into());
2879 }
2880
2881 let mut children = Vec::new();
2883 for child in xml_node.children.as_ref().iter() {
2884 match child {
2885 XmlNodeChild::Element(child_node) => {
2886 let child_dom = xml_node_to_dom_fast(child_node, component_map)?;
2887 children.push(child_dom);
2888 }
2889 XmlNodeChild::Text(text) => {
2890 let text_dom = Dom::create_text(AzString::from(text.as_str()));
2891 children.push(text_dom);
2892 }
2893 }
2894 }
2895
2896 if !children.is_empty() {
2897 dom = dom.with_children(children.into());
2898 }
2899
2900 Ok(dom)
2901}
2902
2903fn get_node_type_for_component(name: &str) -> crate::dom::NodeType {
2905 use crate::dom::NodeType;
2906 match name {
2907 "html" => NodeType::Html,
2908 "head" => NodeType::Head,
2909 "title" => NodeType::Title,
2910 "body" => NodeType::Body,
2911 "div" => NodeType::Div,
2912 "p" => NodeType::P,
2913 "span" => NodeType::Span,
2914 "br" => NodeType::Br,
2915 "h1" => NodeType::H1,
2916 "h2" => NodeType::H2,
2917 "h3" => NodeType::H3,
2918 "h4" => NodeType::H4,
2919 "h5" => NodeType::H5,
2920 "h6" => NodeType::H6,
2921 "header" => NodeType::Header,
2922 "footer" => NodeType::Footer,
2923 "section" => NodeType::Section,
2924 "article" => NodeType::Article,
2925 "aside" => NodeType::Aside,
2926 "nav" => NodeType::Nav,
2927 "main" => NodeType::Main,
2928 "pre" => NodeType::Pre,
2929 "code" => NodeType::Code,
2930 "blockquote" => NodeType::BlockQuote,
2931 "ul" => NodeType::Ul,
2932 "ol" => NodeType::Ol,
2933 "li" => NodeType::Li,
2934 "dl" => NodeType::Dl,
2935 "dt" => NodeType::Dt,
2936 "dd" => NodeType::Dd,
2937 "table" => NodeType::Table,
2938 "thead" => NodeType::THead,
2939 "tbody" => NodeType::TBody,
2940 "tfoot" => NodeType::TFoot,
2941 "tr" => NodeType::Tr,
2942 "th" => NodeType::Th,
2943 "td" => NodeType::Td,
2944 "a" => NodeType::A,
2945 "strong" => NodeType::Strong,
2946 "em" => NodeType::Em,
2947 "b" => NodeType::B,
2948 "i" => NodeType::I,
2949 "u" => NodeType::U,
2950 "small" => NodeType::Small,
2951 "mark" => NodeType::Mark,
2952 "sub" => NodeType::Sub,
2953 "sup" => NodeType::Sup,
2954 "form" => NodeType::Form,
2955 "label" => NodeType::Label,
2956 "button" => NodeType::Button,
2957 "hr" => NodeType::Hr,
2958 _ => NodeType::Div, }
2960}
2961
2962pub fn render_dom_from_body_node<'a>(
2963 body_node: &'a XmlNode,
2964 mut global_css: Option<Css>,
2965 component_map: &'a XmlComponentMap,
2966 max_width: Option<f32>,
2967) -> Result<StyledDom, RenderDomError> {
2968 let body_dom = xml_node_to_dom_fast(body_node, component_map)?;
2971
2972 let mut combined_stylesheets = Vec::new();
2975
2976 if let Some(max_width) = max_width {
2978 let max_width_css = Css::from_string(
2979 format!("html {{ max-width: {max_width}px; }}").into(),
2980 );
2981 for s in max_width_css.stylesheets.as_ref().iter() {
2982 combined_stylesheets.push(s.clone());
2983 }
2984 }
2985
2986 if let Some(css) = global_css.take() {
2988 for s in css.stylesheets.as_ref().iter() {
2989 combined_stylesheets.push(s.clone());
2990 }
2991 }
2992
2993 let combined_css = Css::new(combined_stylesheets);
2994
2995 use crate::dom::NodeType;
3003 let root_node_type = body_dom.root.node_type.clone();
3004
3005 let mut full_dom = match root_node_type {
3006 NodeType::Html => {
3007 body_dom
3009 }
3010 NodeType::Body => {
3011 Dom::create_html().with_child(body_dom)
3013 }
3014 _ => {
3015 let body_wrapper = Dom::create_body().with_child(body_dom);
3017 Dom::create_html().with_child(body_wrapper)
3018 }
3019 };
3020
3021 let styled = full_dom.style(combined_css);
3023
3024 Ok(styled)
3025}
3026
3027pub fn render_dom_from_body_node_inner<'a>(
3029 xml_node: &'a XmlNode,
3030 component_map: &'a XmlComponentMap,
3031 parent_xml_attributes: &FilteredComponentArguments,
3032) -> Result<StyledDom, RenderDomError> {
3033 let component_name = normalize_casing(&xml_node.node_type);
3034
3035 let xml_component = component_map
3036 .get(&component_name)
3037 .ok_or(ComponentError::UnknownComponent(
3038 component_name.clone().into(),
3039 ))?;
3040
3041 let available_function_args = xml_component.renderer.get_available_arguments();
3043 let mut filtered_xml_attributes =
3044 validate_and_filter_component_args(&xml_node.attributes, &available_function_args)?;
3045
3046 if xml_component.inherit_vars {
3047 filtered_xml_attributes
3049 .types
3050 .extend(parent_xml_attributes.types.clone().into_iter());
3051 }
3052
3053 for v in filtered_xml_attributes.types.iter_mut() {
3055 v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.types).to_string();
3056 }
3057
3058 let mut dom = xml_component.renderer.render_dom(
3060 component_map,
3061 &filtered_xml_attributes,
3062 &OptionString::None,
3063 )?;
3064 set_attributes(&mut dom, &xml_node.attributes, &filtered_xml_attributes);
3065
3066 let mut child_index = 0usize;
3068 for child in xml_node.children.as_ref().iter() {
3069 match child {
3070 XmlNodeChild::Element(child_node) => {
3071 let child_dom = render_dom_from_body_node_inner(
3072 child_node,
3073 component_map,
3074 &filtered_xml_attributes,
3075 )?;
3076 dom.append_child_with_index(child_dom, child_index);
3077 child_index += 1;
3078 }
3079 XmlNodeChild::Text(text) => {
3080 let text_dom = Dom::create_text(AzString::from(text.as_str())).style(Css::empty());
3082 dom.append_child_with_index(text_dom, child_index);
3083 child_index += 1;
3084 }
3085 }
3086 }
3087
3088 Ok(dom)
3089}
3090
3091pub fn set_attributes(
3092 dom: &mut StyledDom,
3093 xml_attributes: &XmlAttributeMap,
3094 filtered_xml_attributes: &FilteredComponentArguments,
3095) {
3096 use crate::dom::{
3097 IdOrClass::{Class, Id},
3098 TabIndex,
3099 };
3100
3101 let mut ids_and_classes = Vec::new();
3102 let dom_root = match dom.root.into_crate_internal() {
3103 Some(s) => s,
3104 None => return,
3105 };
3106 let node_data = &mut dom.node_data.as_container_mut()[dom_root];
3107
3108 if let Some(ids) = xml_attributes.get_key("id") {
3109 for id in ids.split_whitespace() {
3110 ids_and_classes.push(Id(
3111 format_args_dynamic(id, &filtered_xml_attributes.types).into()
3112 ));
3113 }
3114 }
3115
3116 if let Some(classes) = xml_attributes.get_key("class") {
3117 for class in classes.split_whitespace() {
3118 ids_and_classes.push(Class(
3119 format_args_dynamic(class, &filtered_xml_attributes.types).into(),
3120 ));
3121 }
3122 }
3123
3124 node_data.set_ids_and_classes(ids_and_classes.into());
3125
3126 if let Some(focusable) = xml_attributes
3127 .get_key("focusable")
3128 .map(|f| format_args_dynamic(f.as_str(), &filtered_xml_attributes.types))
3129 .and_then(|f| parse_bool(&f))
3130 {
3131 match focusable {
3132 true => node_data.set_tab_index(TabIndex::Auto),
3133 false => node_data.set_tab_index(TabIndex::NoKeyboardFocus.into()),
3134 }
3135 }
3136
3137 if let Some(tab_index) = xml_attributes
3138 .get_key("tabindex")
3139 .map(|val| format_args_dynamic(val, &filtered_xml_attributes.types))
3140 .and_then(|val| val.parse::<isize>().ok())
3141 {
3142 match tab_index {
3143 0 => node_data.set_tab_index(TabIndex::Auto),
3144 i if i > 0 => node_data.set_tab_index(TabIndex::OverrideInParent(i as u32)),
3145 _ => node_data.set_tab_index(TabIndex::NoKeyboardFocus),
3146 }
3147 }
3148
3149 if let Some(style) = xml_attributes.get_key("style") {
3150 let css_key_map = azul_css::props::property::get_css_key_map();
3151 let mut attributes = Vec::new();
3152 for s in style.as_str().split(";") {
3153 let mut s = s.split(":");
3154 let key = match s.next() {
3155 Some(s) => s,
3156 None => continue,
3157 };
3158 let value = match s.next() {
3159 Some(s) => s,
3160 None => continue,
3161 };
3162 azul_css::parser2::parse_css_declaration(
3163 key.trim(),
3164 value.trim(),
3165 (ErrorLocation::default(), ErrorLocation::default()),
3166 &css_key_map,
3167 &mut Vec::new(),
3168 &mut attributes,
3169 );
3170 }
3171
3172 let props = attributes
3173 .into_iter()
3174 .filter_map(|s| {
3175 use azul_css::dynamic_selector::CssPropertyWithConditions;
3176 match s {
3177 CssDeclaration::Static(s) => Some(CssPropertyWithConditions::simple(s)),
3178 _ => return None,
3179 }
3180 })
3181 .collect::<Vec<_>>();
3182
3183 node_data.set_css_props(props.into());
3184 }
3185}
3186
3187pub fn set_stringified_attributes(
3188 dom_string: &mut String,
3189 xml_attributes: &XmlAttributeMap,
3190 filtered_xml_attributes: &ComponentArgumentTypes,
3191 tabs: usize,
3192) {
3193 let t0 = String::from(" ").repeat(tabs);
3194 let t = String::from(" ").repeat(tabs + 1);
3195
3196 let mut ids_and_classes = String::new();
3198
3199 for id in xml_attributes
3200 .get_key("id")
3201 .map(|s| s.split_whitespace().collect::<Vec<_>>())
3202 .unwrap_or_default()
3203 {
3204 ids_and_classes.push_str(&format!(
3205 "{} Id(AzString::from_const_str(\"{}\")),\r\n",
3206 t0,
3207 format_args_dynamic(id, &filtered_xml_attributes)
3208 ));
3209 }
3210
3211 for class in xml_attributes
3212 .get_key("class")
3213 .map(|s| s.split_whitespace().collect::<Vec<_>>())
3214 .unwrap_or_default()
3215 {
3216 ids_and_classes.push_str(&format!(
3217 "{} Class(AzString::from_const_str(\"{}\")),\r\n",
3218 t0,
3219 format_args_dynamic(class, &filtered_xml_attributes)
3220 ));
3221 }
3222
3223 if !ids_and_classes.is_empty() {
3224 use azul_css::format_rust_code::GetHash;
3225 let id = ids_and_classes.get_hash();
3226 dom_string.push_str(&format!(
3227 "\r\n{t0}.with_ids_and_classes({{\r\n{t}const IDS_AND_CLASSES_{id}: &[IdOrClass] = \
3228 &[\r\n{t}{ids_and_classes}\r\n{t}];\r\\
3229 n{t}IdOrClassVec::from_const_slice(IDS_AND_CLASSES_{id})\r\n{t0}}})",
3230 t0 = t0,
3231 t = t,
3232 ids_and_classes = ids_and_classes,
3233 id = id
3234 ));
3235 }
3236
3237 if let Some(focusable) = xml_attributes
3238 .get_key("focusable")
3239 .map(|f| format_args_dynamic(f, &filtered_xml_attributes))
3240 .and_then(|f| parse_bool(&f))
3241 {
3242 match focusable {
3243 true => dom_string.push_str(&format!("\r\n{}.with_tab_index(TabIndex::Auto)", t)),
3244 false => dom_string.push_str(&format!(
3245 "\r\n{}.with_tab_index(TabIndex::NoKeyboardFocus)",
3246 t
3247 )),
3248 }
3249 }
3250
3251 if let Some(tab_index) = xml_attributes
3252 .get_key("tabindex")
3253 .map(|val| format_args_dynamic(val, &filtered_xml_attributes))
3254 .and_then(|val| val.parse::<isize>().ok())
3255 {
3256 match tab_index {
3257 0 => dom_string.push_str(&format!("\r\n{}.with_tab_index(TabIndex::Auto)", t)),
3258 i if i > 0 => dom_string.push_str(&format!(
3259 "\r\n{}.with_tab_index(TabIndex::OverrideInParent({}))",
3260 t, i as usize
3261 )),
3262 _ => dom_string.push_str(&format!(
3263 "\r\n{}.with_tab_index(TabIndex::NoKeyboardFocus)",
3264 t
3265 )),
3266 }
3267 }
3268}
3269
3270#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
3272pub enum DynamicItem {
3273 Var(String),
3274 Str(String),
3275}
3276
3277pub fn split_dynamic_string(input: &str) -> Vec<DynamicItem> {
3295 use self::DynamicItem::*;
3296
3297 let input: Vec<char> = input.chars().collect();
3298 let input_chars_len = input.len();
3299
3300 let mut items = Vec::new();
3301 let mut current_idx = 0;
3302 let mut last_idx = 0;
3303
3304 while current_idx < input_chars_len {
3305 let c = input[current_idx];
3306 match c {
3307 '{' if input.get(current_idx + 1).copied() != Some('{') => {
3308 let mut start_offset = 1;
3310 let mut has_found_variable = false;
3311 while let Some(c) = input.get(current_idx + start_offset) {
3312 if c.is_whitespace() {
3313 break;
3314 }
3315 if *c == '}' && input.get(current_idx + start_offset + 1).copied() != Some('}')
3316 {
3317 start_offset += 1;
3318 has_found_variable = true;
3319 break;
3320 }
3321 start_offset += 1;
3322 }
3323
3324 if has_found_variable {
3328 if last_idx != current_idx {
3329 items.push(Str(input[last_idx..current_idx].iter().collect()));
3330 }
3331
3332 items.push(Var(input
3334 [(current_idx + 1)..(current_idx + start_offset - 1)]
3335 .iter()
3336 .collect()));
3337 current_idx = current_idx + start_offset;
3338 last_idx = current_idx;
3339 } else {
3340 current_idx += start_offset;
3341 }
3342 }
3343 _ => {
3344 current_idx += 1;
3345 }
3346 }
3347 }
3348
3349 if current_idx != last_idx {
3350 items.push(Str(input[last_idx..].iter().collect()));
3351 }
3352
3353 for item in &mut items {
3354 if let Str(s) = item {
3356 *s = s.replace("{{", "{").replace("}}", "}");
3357 }
3358 }
3359
3360 items
3361}
3362
3363pub fn combine_and_replace_dynamic_items(
3370 input: &[DynamicItem],
3371 variables: &ComponentArgumentTypes,
3372) -> String {
3373 let mut s = String::new();
3374
3375 for item in input {
3376 match item {
3377 DynamicItem::Var(v) => {
3378 let variable_name = normalize_casing(v.trim());
3379 match variables
3380 .iter()
3381 .find(|s| s.0 == variable_name)
3382 .map(|q| &q.1)
3383 {
3384 Some(resolved_var) => {
3385 s.push_str(&resolved_var);
3386 }
3387 None => {
3388 s.push('{');
3389 s.push_str(v);
3390 s.push('}');
3391 }
3392 }
3393 }
3394 DynamicItem::Str(dynamic_str) => {
3395 s.push_str(&dynamic_str);
3396 }
3397 }
3398 }
3399
3400 s
3401}
3402
3403pub fn format_args_dynamic(input: &str, variables: &ComponentArgumentTypes) -> String {
3422 let dynamic_str_items = split_dynamic_string(input);
3423 combine_and_replace_dynamic_items(&dynamic_str_items, variables)
3424}
3425
3426pub fn prepare_string(input: &str) -> String {
3428 const SPACE: &str = " ";
3429 const RETURN: &str = "\n";
3430
3431 let input = input.trim();
3432
3433 if input.is_empty() {
3434 return String::new();
3435 }
3436
3437 let input = input.replace("<", "<");
3438 let input = input.replace(">", ">");
3439
3440 let input_len = input.len();
3441 let mut final_lines: Vec<String> = Vec::new();
3442 let mut last_line_was_empty = false;
3443
3444 for line in input.lines() {
3445 let line = line.trim();
3446 let line = line.replace(" ", " ");
3447 let current_line_is_empty = line.is_empty();
3448
3449 if !current_line_is_empty {
3450 if last_line_was_empty {
3451 final_lines.push(format!("{}{}", RETURN, line));
3452 } else {
3453 final_lines.push(line.to_string());
3454 }
3455 }
3456
3457 last_line_was_empty = current_line_is_empty;
3458 }
3459
3460 let line_len = final_lines.len();
3461 let mut target = String::with_capacity(input_len);
3462 for (line_idx, line) in final_lines.iter().enumerate() {
3463 if !(line.starts_with(RETURN) || line_idx == 0 || line_idx == line_len.saturating_sub(1)) {
3464 target.push_str(SPACE);
3465 }
3466 target.push_str(line);
3467 }
3468 target
3469}
3470
3471pub fn parse_bool(input: &str) -> Option<bool> {
3473 match input {
3474 "true" => Some(true),
3475 "false" => Some(false),
3476 _ => None,
3477 }
3478}
3479
3480pub fn render_component_inner<'a>(
3481 map: &mut Vec<(
3482 ComponentName,
3483 CompiledComponent,
3484 ComponentArguments,
3485 BTreeMap<String, String>,
3486 )>,
3487 component_name: String,
3488 xml_component: &'a XmlComponent,
3489 component_map: &'a XmlComponentMap,
3490 parent_xml_attributes: &ComponentArguments,
3491 tabs: usize,
3492) -> Result<(), CompileError> {
3493 let t = String::from(" ").repeat(tabs - 1);
3494 let t1 = String::from(" ").repeat(tabs);
3495
3496 let component_name = normalize_casing(&component_name);
3497 let xml_node = xml_component.renderer.get_xml_node();
3498
3499 let mut css = match find_node_by_type(xml_node.children.as_ref(), "style") {
3500 Some(style_node) => {
3501 let text = style_node.get_text_content();
3502 if !text.is_empty() {
3503 Some(text)
3504 } else {
3505 None
3506 }
3507 }
3508 None => None,
3509 };
3510 let mut css = match css {
3511 Some(text) => azul_css::parser2::new_from_str(&text).0,
3512 None => Css::empty(),
3513 };
3514
3515 css.sort_by_specificity();
3516
3517 let available_function_arg_types = xml_component.renderer.get_available_arguments();
3519 let mut filtered_xml_attributes = available_function_arg_types.clone();
3521
3522 if xml_component.inherit_vars {
3523 filtered_xml_attributes
3525 .args
3526 .extend(parent_xml_attributes.args.clone().into_iter());
3527 }
3528
3529 for v in filtered_xml_attributes.args.iter_mut() {
3531 v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.args).to_string();
3532 }
3533
3534 let text_content = xml_node.get_text_content();
3535 let text = if !text_content.is_empty() {
3536 Some(AzString::from(format_args_dynamic(
3537 &text_content,
3538 &filtered_xml_attributes.args,
3539 )))
3540 } else {
3541 None
3542 };
3543
3544 let mut dom_string = xml_component.renderer.compile_to_rust_code(
3545 component_map,
3546 &filtered_xml_attributes,
3547 &text.into(),
3548 )?;
3549
3550 set_stringified_attributes(
3551 &mut dom_string,
3552 &xml_node.attributes,
3553 &filtered_xml_attributes.args,
3554 tabs,
3555 );
3556
3557 let matcher = CssMatcher {
3559 path: vec![CssPathSelector::Type(NodeTypeTag::Body)],
3560 indices_in_parent: Vec::new(),
3561 children_length: Vec::new(),
3562 };
3563
3564 let mut css_blocks = BTreeMap::new();
3565 let mut extra_blocks = VecContents::default();
3566
3567 if !xml_node.children.as_ref().is_empty() {
3568 dom_string.push_str(&format!(
3569 "\r\n{}.with_children(DomVec::from_vec(vec![\r\n",
3570 t
3571 ));
3572 for (child_idx, child) in xml_node.children.as_ref().iter().enumerate() {
3573 if let XmlNodeChild::Element(child_node) = child {
3574 let mut matcher = matcher.clone();
3575 matcher.indices_in_parent.push(child_idx);
3576 matcher
3577 .children_length
3578 .push(xml_node.children.as_ref().len());
3579
3580 dom_string.push_str(&format!(
3581 "{}{},",
3582 t1,
3583 compile_node_to_rust_code_inner(
3584 child_node,
3585 component_map,
3586 &filtered_xml_attributes,
3587 tabs + 1,
3588 &mut extra_blocks,
3589 &mut css_blocks,
3590 &css,
3591 matcher,
3592 )?
3593 ));
3594 }
3595 }
3596 dom_string.push_str(&format!("\r\n{}]))", t));
3597 }
3598
3599 map.push((
3600 component_name,
3601 dom_string,
3602 filtered_xml_attributes,
3603 css_blocks,
3604 ));
3605
3606 Ok(())
3607}
3608
3609pub fn compile_components_to_rust_code(
3611 components: &XmlComponentMap,
3612) -> Result<
3613 Vec<(
3614 ComponentName,
3615 CompiledComponent,
3616 ComponentArguments,
3617 BTreeMap<String, String>,
3618 )>,
3619 CompileError,
3620> {
3621 let mut map = Vec::new();
3622
3623 for (id, xml_component) in &components.components {
3624 render_component_inner(
3625 &mut map,
3626 id.clone(),
3627 xml_component,
3628 &components,
3629 &ComponentArguments::default(),
3630 1,
3631 )?;
3632 }
3633
3634 Ok(map)
3635}
3636
3637#[derive(Clone)]
3638pub struct CssMatcher {
3639 path: Vec<CssPathSelector>,
3640 indices_in_parent: Vec<usize>,
3641 children_length: Vec<usize>,
3642}
3643
3644impl CssMatcher {
3645 fn get_hash(&self) -> u64 {
3646 use core::hash::Hash;
3647
3648 use highway::{HighwayHash, HighwayHasher, Key};
3649
3650 let mut hasher = HighwayHasher::new(Key([0; 4]));
3651 for p in self.path.iter() {
3652 p.hash(&mut hasher);
3653 }
3654 hasher.finalize64()
3655 }
3656}
3657
3658impl CssMatcher {
3659 fn matches(&self, path: &CssPath) -> bool {
3660 use azul_css::css::CssPathSelector::*;
3661
3662 use crate::style::{CssGroupIterator, CssGroupSplitReason};
3663
3664 if self.path.is_empty() {
3665 return false;
3666 }
3667 if path.selectors.as_ref().is_empty() {
3668 return false;
3669 }
3670
3671 let mut path_groups = CssGroupIterator::new(path.selectors.as_ref()).collect::<Vec<_>>();
3673 path_groups.reverse();
3674
3675 if path_groups.is_empty() {
3676 return false;
3677 }
3678 let mut self_groups = CssGroupIterator::new(self.path.as_ref()).collect::<Vec<_>>();
3679 self_groups.reverse();
3680 if self_groups.is_empty() {
3681 return false;
3682 }
3683
3684 if self.indices_in_parent.len() != self_groups.len() {
3685 return false;
3686 }
3687 if self.children_length.len() != self_groups.len() {
3688 return false;
3689 }
3690
3691 let mut cur_selfgroup_scan = 0;
3705 let mut cur_pathgroup_scan = 0;
3706 let mut valid = false;
3707 let mut path_group = path_groups[cur_pathgroup_scan].clone();
3708
3709 while cur_selfgroup_scan < self_groups.len() {
3710 let mut advance = None;
3711
3712 for (id, cg) in self_groups[cur_selfgroup_scan..].iter().enumerate() {
3714 let gm = group_matches(
3715 &path_group.0,
3716 &self_groups[cur_selfgroup_scan + id].0,
3717 self.indices_in_parent[cur_selfgroup_scan + id],
3718 self.children_length[cur_selfgroup_scan + id],
3719 );
3720
3721 if gm {
3722 advance = Some(id);
3725 break;
3726 }
3727 }
3728
3729 match advance {
3730 Some(n) => {
3731 if cur_pathgroup_scan == path_groups.len() - 1 {
3734 return cur_selfgroup_scan + n == self_groups.len() - 1;
3736 } else {
3737 cur_pathgroup_scan += 1;
3738 cur_selfgroup_scan += n;
3739 path_group = path_groups[cur_pathgroup_scan].clone();
3740 }
3741 }
3742 None => return false, }
3744 }
3745
3746 return cur_pathgroup_scan == path_groups.len() - 1;
3748 }
3749}
3750
3751fn group_matches(
3754 a: &[&CssPathSelector],
3755 b: &[&CssPathSelector],
3756 idx_in_parent: usize,
3757 parent_children: usize,
3758) -> bool {
3759 use azul_css::css::{CssNthChildSelector, CssPathPseudoSelector, CssPathSelector::*};
3760
3761 for selector in a {
3762 match selector {
3763 Global => {}
3765 PseudoSelector(CssPathPseudoSelector::Hover) => {}
3766 PseudoSelector(CssPathPseudoSelector::Active) => {}
3767 PseudoSelector(CssPathPseudoSelector::Focus) => {}
3768
3769 Type(tag) => {
3770 if !b.iter().any(|t| **t == Type(tag.clone())) {
3771 return false;
3772 }
3773 }
3774 Class(class) => {
3775 if !b.iter().any(|t| **t == Class(class.clone())) {
3776 return false;
3777 }
3778 }
3779 Id(id) => {
3780 if !b.iter().any(|t| **t == Id(id.clone())) {
3781 return false;
3782 }
3783 }
3784 PseudoSelector(CssPathPseudoSelector::First) => {
3785 if idx_in_parent != 0 {
3786 return false;
3787 }
3788 }
3789 PseudoSelector(CssPathPseudoSelector::Last) => {
3790 if idx_in_parent != parent_children.saturating_sub(1) {
3791 return false;
3792 }
3793 }
3794 PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Number(i))) => {
3795 if idx_in_parent != *i as usize {
3796 return false;
3797 }
3798 }
3799 PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Even)) => {
3800 if idx_in_parent % 2 != 0 {
3801 return false;
3802 }
3803 }
3804 PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Odd)) => {
3805 if idx_in_parent % 2 == 0 {
3806 return false;
3807 }
3808 }
3809 PseudoSelector(CssPathPseudoSelector::NthChild(CssNthChildSelector::Pattern(p))) => {
3810 if idx_in_parent.saturating_sub(p.offset as usize) % p.pattern_repeat as usize != 0
3811 {
3812 return false;
3813 }
3814 }
3815
3816 _ => return false, }
3818 }
3819
3820 true
3821}
3822
3823struct CssBlock {
3824 ending: Option<CssPathPseudoSelector>,
3825 block: CssRuleBlock,
3826}
3827
3828pub fn compile_body_node_to_rust_code<'a>(
3829 body_node: &'a XmlNode,
3830 component_map: &'a XmlComponentMap,
3831 extra_blocks: &mut VecContents,
3832 css_blocks: &mut BTreeMap<String, String>,
3833 css: &Css,
3834 mut matcher: CssMatcher,
3835) -> Result<String, CompileError> {
3836 use azul_css::css::CssDeclaration;
3837
3838 let t = "";
3839 let t2 = " ";
3840 let mut dom_string = String::from("Dom::create_body()");
3841 let node_type = CssPathSelector::Type(NodeTypeTag::Body);
3842 matcher.path.push(node_type);
3843
3844 let ids = body_node
3845 .attributes
3846 .get_key("id")
3847 .map(|s| s.split_whitespace().collect::<Vec<_>>())
3848 .unwrap_or_default();
3849 matcher.path.extend(
3850 ids.into_iter()
3851 .map(|id| CssPathSelector::Id(id.to_string().into())),
3852 );
3853 let classes = body_node
3854 .attributes
3855 .get_key("class")
3856 .map(|s| s.split_whitespace().collect::<Vec<_>>())
3857 .unwrap_or_default();
3858 matcher.path.extend(
3859 classes
3860 .into_iter()
3861 .map(|class| CssPathSelector::Class(class.to_string().into())),
3862 );
3863
3864 let matcher_hash = matcher.get_hash();
3865 let css_blocks_for_this_node = get_css_blocks(css, &matcher);
3866 if !css_blocks_for_this_node.is_empty() {
3867 use azul_css::props::property::format_static_css_prop;
3868
3869 let css_strings = css_blocks_for_this_node
3870 .iter()
3871 .rev()
3872 .map(|css_block| {
3873 let wrapper = match css_block.ending {
3874 Some(CssPathPseudoSelector::Hover) => "Hover",
3875 Some(CssPathPseudoSelector::Active) => "Active",
3876 Some(CssPathPseudoSelector::Focus) => "Focus",
3877 _ => "Normal",
3878 };
3879
3880 for declaration in css_block.block.declarations.as_ref().iter() {
3881 let prop = match declaration {
3882 CssDeclaration::Static(s) => s,
3883 CssDeclaration::Dynamic(d) => &d.default_value,
3884 };
3885 extra_blocks.insert_from_css_property(prop);
3886 }
3887
3888 let formatted = css_block
3889 .block
3890 .declarations
3891 .as_ref()
3892 .iter()
3893 .rev()
3894 .map(|s| match &s {
3895 CssDeclaration::Static(s) => format!(
3896 "NodeDataInlineCssProperty::{}({})",
3897 wrapper,
3898 format_static_css_prop(s, 1)
3899 ),
3900 CssDeclaration::Dynamic(d) => format!(
3901 "NodeDataInlineCssProperty::{}({})",
3902 wrapper,
3903 format_static_css_prop(&d.default_value, 1)
3904 ),
3905 })
3906 .collect::<Vec<String>>();
3907
3908 format!("// {}\r\n{}", css_block.block.path, formatted.join(",\r\n"))
3909 })
3910 .collect::<Vec<_>>()
3911 .join(",\r\n");
3912
3913 css_blocks.insert(format!("CSS_MATCH_{:09}", matcher_hash), css_strings);
3914 dom_string.push_str(&format!(
3915 "\r\n{}.with_inline_css_props(CSS_MATCH_{:09})",
3916 t2, matcher_hash
3917 ));
3918 }
3919
3920 if !body_node.children.as_ref().is_empty() {
3921 use azul_css::format_rust_code::GetHash;
3922 let children_hash = body_node.children.as_ref().get_hash();
3923 dom_string.push_str(&format!("\r\n.with_children(DomVec::from_vec(vec![\r\n"));
3924
3925 for (child_idx, child) in body_node.children.as_ref().iter().enumerate() {
3926 match child {
3927 XmlNodeChild::Element(child_node) => {
3928 let mut matcher = matcher.clone();
3929 matcher.path.push(CssPathSelector::Children);
3930 matcher.indices_in_parent.push(child_idx);
3931 matcher.children_length.push(body_node.children.len());
3932
3933 dom_string.push_str(&format!(
3934 "{}{},\r\n",
3935 t,
3936 compile_node_to_rust_code_inner(
3937 child_node,
3938 component_map,
3939 &ComponentArguments::default(),
3940 1,
3941 extra_blocks,
3942 css_blocks,
3943 css,
3944 matcher,
3945 )?
3946 ));
3947 }
3948 XmlNodeChild::Text(text) => {
3949 let text = text.trim();
3950 if !text.is_empty() {
3951 let escaped = text.replace("\\", "\\\\").replace("\"", "\\\"");
3952 dom_string
3953 .push_str(&format!("{}Dom::create_text(\"{}\".into()),\r\n", t, escaped));
3954 }
3955 }
3956 }
3957 }
3958 dom_string.push_str(&format!("\r\n{}]))", t));
3959 }
3960
3961 let dom_string = dom_string.trim();
3962 Ok(dom_string.to_string())
3963}
3964
3965fn get_css_blocks(css: &Css, matcher: &CssMatcher) -> Vec<CssBlock> {
3966 let mut blocks = Vec::new();
3967
3968 for stylesheet in css.stylesheets.as_ref() {
3969 for css_block in stylesheet.rules.as_ref() {
3970 if matcher.matches(&css_block.path) {
3971 let mut ending = None;
3972
3973 if let Some(CssPathSelector::PseudoSelector(p)) =
3974 css_block.path.selectors.as_ref().last()
3975 {
3976 ending = Some(p.clone());
3977 }
3978
3979 blocks.push(CssBlock {
3980 ending,
3981 block: css_block.clone(),
3982 });
3983 }
3984 }
3985 }
3986
3987 blocks
3988}
3989
3990fn compile_and_format_dynamic_items(input: &[DynamicItem]) -> String {
3991 use self::DynamicItem::*;
3992 if input.is_empty() {
3993 String::from("AzString::from_const_str(\"\")")
3994 } else if input.len() == 1 {
3995 match &input[0] {
3997 Var(v) => normalize_casing(v.trim()),
3998 Str(s) => format!("AzString::from_const_str(\"{}\")", s),
3999 }
4000 } else {
4001 let mut formatted_str = String::from("format!(\"");
4003 let mut variables = Vec::new();
4004 for item in input {
4005 match item {
4006 Var(v) => {
4007 let variable_name = normalize_casing(v.trim());
4008 formatted_str.push_str(&format!("{{{}}}", variable_name));
4009 variables.push(variable_name.clone());
4010 }
4011 Str(s) => {
4012 let s = s.replace("\"", "\\\"");
4013 formatted_str.push_str(&s);
4014 }
4015 }
4016 }
4017
4018 formatted_str.push('\"');
4019 if !variables.is_empty() {
4020 formatted_str.push_str(", ");
4021 }
4022
4023 formatted_str.push_str(&variables.join(", "));
4024 formatted_str.push_str(").into()");
4025 formatted_str
4026 }
4027}
4028
4029fn format_args_for_rust_code(input: &str) -> String {
4030 let dynamic_str_items = split_dynamic_string(input);
4031 compile_and_format_dynamic_items(&dynamic_str_items)
4032}
4033
4034pub fn compile_node_to_rust_code_inner<'a>(
4035 node: &XmlNode,
4036 component_map: &'a XmlComponentMap,
4037 parent_xml_attributes: &ComponentArguments,
4038 tabs: usize,
4039 extra_blocks: &mut VecContents,
4040 css_blocks: &mut BTreeMap<String, String>,
4041 css: &Css,
4042 mut matcher: CssMatcher,
4043) -> Result<String, CompileError> {
4044 use azul_css::css::CssDeclaration;
4045
4046 let t = String::from(" ").repeat(tabs - 1);
4047 let t2 = String::from(" ").repeat(tabs);
4048
4049 let component_name = normalize_casing(&node.node_type);
4050
4051 let xml_component = component_map
4052 .get(&component_name)
4053 .ok_or(ComponentError::UnknownComponent(
4054 component_name.clone().into(),
4055 ))?;
4056
4057 let available_function_args = xml_component.renderer.get_available_arguments();
4059 let mut filtered_xml_attributes =
4060 validate_and_filter_component_args(&node.attributes, &available_function_args)?;
4061
4062 if xml_component.inherit_vars {
4063 filtered_xml_attributes
4065 .types
4066 .extend(parent_xml_attributes.args.clone().into_iter());
4067 }
4068
4069 for v in filtered_xml_attributes.types.iter_mut() {
4071 v.1 = format_args_dynamic(&v.1, &parent_xml_attributes.args).to_string();
4072 }
4073
4074 let instantiated_function_arguments = {
4075 let mut args = filtered_xml_attributes
4076 .types
4077 .iter()
4078 .filter_map(|(xml_attribute_key, _xml_attribute_type)| {
4079 match node.attributes.get_key(xml_attribute_key).cloned() {
4080 Some(s) => Some(format_args_for_rust_code(&s)),
4081 None => {
4082 None
4088 }
4089 }
4090 })
4091 .collect::<Vec<String>>();
4092
4093 args.sort_by(|a, b| a.cmp(&b));
4094
4095 args.join(", ")
4096 };
4097
4098 let text_as_first_arg = if filtered_xml_attributes.accepts_text {
4099 let node_text = node.get_text_content();
4100 let node_text = format_args_for_rust_code(node_text.trim());
4101 let trailing_comma = if !instantiated_function_arguments.is_empty() {
4102 ", "
4103 } else {
4104 ""
4105 };
4106
4107 format!("{}{}", node_text, trailing_comma)
4114 } else {
4115 String::new()
4116 };
4117
4118 let node_type = CssPathSelector::Type(match component_name.as_str() {
4119 "body" => NodeTypeTag::Body,
4120 "div" => NodeTypeTag::Div,
4121 "br" => NodeTypeTag::Br,
4122 "p" => NodeTypeTag::P,
4123 "img" => NodeTypeTag::Img,
4124 "br" => NodeTypeTag::Br,
4125 other => {
4126 return Err(CompileError::Dom(RenderDomError::Component(
4127 ComponentError::UnknownComponent(other.to_string().into()),
4128 )));
4129 }
4130 });
4131
4132 let mut dom_string = format!(
4134 "{}{}::render({}{})",
4135 t2, component_name, text_as_first_arg, instantiated_function_arguments
4136 );
4137
4138 matcher.path.push(node_type);
4139 let ids = node
4140 .attributes
4141 .get_key("id")
4142 .map(|s| s.split_whitespace().collect::<Vec<_>>())
4143 .unwrap_or_default();
4144
4145 matcher.path.extend(
4146 ids.into_iter()
4147 .map(|id| CssPathSelector::Id(id.to_string().into())),
4148 );
4149
4150 let classes = node
4151 .attributes
4152 .get_key("class")
4153 .map(|s| s.split_whitespace().collect::<Vec<_>>())
4154 .unwrap_or_default();
4155
4156 matcher.path.extend(
4157 classes
4158 .into_iter()
4159 .map(|class| CssPathSelector::Class(class.to_string().into())),
4160 );
4161
4162 let matcher_hash = matcher.get_hash();
4163 let css_blocks_for_this_node = get_css_blocks(css, &matcher);
4164 if !css_blocks_for_this_node.is_empty() {
4165 use azul_css::props::property::format_static_css_prop;
4166
4167 let css_strings = css_blocks_for_this_node
4168 .iter()
4169 .rev()
4170 .map(|css_block| {
4171 let wrapper = match css_block.ending {
4172 Some(CssPathPseudoSelector::Hover) => "Hover",
4173 Some(CssPathPseudoSelector::Active) => "Active",
4174 Some(CssPathPseudoSelector::Focus) => "Focus",
4175 _ => "Normal",
4176 };
4177
4178 for declaration in css_block.block.declarations.as_ref().iter() {
4179 let prop = match declaration {
4180 CssDeclaration::Static(s) => s,
4181 CssDeclaration::Dynamic(d) => &d.default_value,
4182 };
4183 extra_blocks.insert_from_css_property(prop);
4184 }
4185
4186 let formatted = css_block
4187 .block
4188 .declarations
4189 .as_ref()
4190 .iter()
4191 .rev()
4192 .map(|s| match &s {
4193 CssDeclaration::Static(s) => format!(
4194 "NodeDataInlineCssProperty::{}({})",
4195 wrapper,
4196 format_static_css_prop(s, 1)
4197 ),
4198 CssDeclaration::Dynamic(d) => format!(
4199 "NodeDataInlineCssProperty::{}({})",
4200 wrapper,
4201 format_static_css_prop(&d.default_value, 1)
4202 ),
4203 })
4204 .collect::<Vec<String>>();
4205
4206 format!("// {}\r\n{}", css_block.block.path, formatted.join(",\r\n"))
4207 })
4208 .collect::<Vec<_>>()
4209 .join(",\r\n");
4210
4211 css_blocks.insert(format!("CSS_MATCH_{:09}", matcher_hash), css_strings);
4212 dom_string.push_str(&format!(
4213 "\r\n{}.with_inline_css_props(CSS_MATCH_{:09})",
4214 t2, matcher_hash
4215 ));
4216 }
4217
4218 set_stringified_attributes(
4219 &mut dom_string,
4220 &node.attributes,
4221 &filtered_xml_attributes.types,
4222 tabs,
4223 );
4224
4225 let mut children_string = node
4226 .children
4227 .as_ref()
4228 .iter()
4229 .enumerate()
4230 .filter_map(|(child_idx, c)| match c {
4231 XmlNodeChild::Element(child_node) => {
4232 let mut matcher = matcher.clone();
4233 matcher.path.push(CssPathSelector::Children);
4234 matcher.indices_in_parent.push(child_idx);
4235 matcher.children_length.push(node.children.len());
4236
4237 Some(compile_node_to_rust_code_inner(
4238 child_node,
4239 component_map,
4240 &ComponentArguments {
4241 args: filtered_xml_attributes.types.clone(),
4242 accepts_text: filtered_xml_attributes.accepts_text,
4243 },
4244 tabs + 1,
4245 extra_blocks,
4246 css_blocks,
4247 css,
4248 matcher,
4249 ))
4250 }
4251 XmlNodeChild::Text(text) => {
4252 let text = text.trim();
4253 if text.is_empty() {
4254 None
4255 } else {
4256 let t2 = String::from(" ").repeat(tabs);
4257 let escaped = text.replace("\\", "\\\\").replace("\"", "\\\"");
4258 Some(Ok(format!("{}Dom::create_text(\"{}\".into())", t2, escaped)))
4259 }
4260 }
4261 })
4262 .collect::<Result<Vec<_>, _>>()?
4263 .join(&format!(",\r\n"));
4264
4265 if !children_string.is_empty() {
4266 dom_string.push_str(&format!(
4267 "\r\n{}.with_children(DomVec::from_vec(vec![\r\n{}\r\n{}]))",
4268 t2, children_string, t2
4269 ));
4270 }
4271
4272 Ok(dom_string)
4273}
4274
4275pub struct DynamicXmlComponent {
4278 pub name: String,
4280 pub arguments: ComponentArguments,
4282 pub root: XmlNode,
4284}
4285
4286impl DynamicXmlComponent {
4287 pub fn new<'a>(root: &'a XmlNode) -> Result<Self, ComponentParseError> {
4289 let node_type = normalize_casing(&root.node_type);
4290
4291 if node_type.as_str() != "component" {
4292 return Err(ComponentParseError::NotAComponent);
4293 }
4294
4295 let name = root
4296 .attributes
4297 .get_key("name")
4298 .cloned()
4299 .ok_or(ComponentParseError::NotAComponent)?;
4300 let accepts_text = root
4301 .attributes
4302 .get_key("accepts_text")
4303 .and_then(|p| parse_bool(p.as_str()))
4304 .unwrap_or(false);
4305
4306 let args = match root.attributes.get_key("args") {
4307 Some(s) => parse_component_arguments(s)?,
4308 None => ComponentArgumentTypes::default(),
4309 };
4310
4311 Ok(Self {
4312 name: normalize_casing(&name),
4313 arguments: ComponentArguments { args, accepts_text },
4314 root: root.clone(),
4315 })
4316 }
4317}
4318
4319impl XmlComponentTrait for DynamicXmlComponent {
4320 fn get_available_arguments(&self) -> ComponentArguments {
4321 self.arguments.clone()
4322 }
4323
4324 fn get_xml_node(&self) -> XmlNode {
4325 self.root.clone()
4326 }
4327
4328 fn render_dom<'a>(
4329 &'a self,
4330 components: &'a XmlComponentMap,
4331 arguments: &FilteredComponentArguments,
4332 content: &XmlTextContent,
4333 ) -> Result<StyledDom, RenderDomError> {
4334 let mut component_css = match find_node_by_type(self.root.children.as_ref(), "style") {
4335 Some(style_node) => {
4336 let text = style_node.get_text_content();
4337 if !text.is_empty() {
4338 let parsed_css = Css::from_string(text.into());
4339 Some(parsed_css)
4340 } else {
4341 None
4342 }
4343 }
4344 None => None,
4345 };
4346
4347 let mut dom = StyledDom::default();
4348
4349 for child in self.root.children.as_ref() {
4350 if let XmlNodeChild::Element(child_node) = child {
4351 dom.append_child(render_dom_from_body_node_inner(
4352 child_node, components, arguments,
4353 )?);
4354 }
4355 }
4356
4357 if let Some(css) = component_css.clone() {
4358 dom.restyle(css);
4359 }
4360
4361 Ok(dom)
4362 }
4363
4364 fn compile_to_rust_code(
4365 &self,
4366 components: &XmlComponentMap,
4367 attributes: &ComponentArguments,
4368 content: &XmlTextContent,
4369 ) -> Result<String, CompileError> {
4370 Ok("Dom::create_div()".into()) }
4372}
4373
4374#[cfg(test)]
4375mod tests {
4376 use super::*;
4377 use crate::dom::{Dom, NodeType};
4378
4379 #[test]
4380 fn test_inline_span_parsing() {
4381 let html = r#"<p>Text before <span class="highlight">inline text</span> text after.</p>"#;
4385
4386 let expected_dom = Dom::create_p().with_children(
4396 vec![
4397 Dom::create_text("Text before "),
4398 Dom::create_node(NodeType::Span)
4399 .with_children(vec![Dom::create_text("inline text")].into()),
4400 Dom::create_text(" text after."),
4401 ]
4402 .into(),
4403 );
4404
4405 assert_eq!(expected_dom.children.as_ref().len(), 3);
4407
4408 match &expected_dom.children.as_ref()[1].root.node_type {
4410 NodeType::Span => {}
4411 other => panic!("Expected Span, got {:?}", other),
4412 }
4413
4414 assert_eq!(expected_dom.children.as_ref()[1].children.as_ref().len(), 1);
4416
4417 println!("Test passed: Inline span parsing structure is correct");
4418 }
4419
4420 #[test]
4421 fn test_xml_node_structure() {
4422 let node = XmlNode {
4426 node_type: "p".into(),
4427 attributes: XmlAttributeMap {
4428 inner: StringPairVec::from_const_slice(&[]),
4429 },
4430 children: vec![
4431 XmlNodeChild::Text("Before ".into()),
4432 XmlNodeChild::Element(XmlNode {
4433 node_type: "span".into(),
4434 children: vec![XmlNodeChild::Text("inline".into())].into(),
4435 ..Default::default()
4436 }),
4437 XmlNodeChild::Text(" after".into()),
4438 ]
4439 .into(),
4440 };
4441
4442 assert_eq!(node.children.as_ref().len(), 3);
4444 assert_eq!(node.children.as_ref()[0].as_text(), Some("Before "));
4445 assert_eq!(
4446 node.children.as_ref()[1]
4447 .as_element()
4448 .unwrap()
4449 .node_type
4450 .as_str(),
4451 "span"
4452 );
4453 assert_eq!(node.children.as_ref()[2].as_text(), Some(" after"));
4454
4455 let span = node.children.as_ref()[1].as_element().unwrap();
4457 assert_eq!(span.children.as_ref().len(), 1);
4458 assert_eq!(span.children.as_ref()[0].as_text(), Some("inline"));
4459
4460 println!("Test passed: XmlNode structure preserves text nodes correctly");
4461 }
4462}