1#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2pub struct Component {
3 pub root: String,
4 pub full_name: String,
5 pub arguments: ftd::Map<ftd::p2::Kind>,
6 pub locals: ftd::Map<ftd::p2::Kind>,
7 pub properties: ftd::Map<Property>,
8 pub instructions: Vec<Instruction>,
9 pub events: Vec<ftd::p2::Event>,
10 pub condition: Option<ftd::p2::Boolean>,
11 pub kernel: bool,
12 pub invocations: Vec<ftd::Map<ftd::Value>>,
13 pub line_number: usize,
14}
15
16#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
17#[serde(tag = "type")]
18pub enum Instruction {
19 ChildComponent {
20 child: ChildComponent,
21 },
22 Component {
23 parent: ChildComponent,
24 children: Vec<ChildComponent>,
25 },
26 ChangeContainer {
27 name: String,
28 },
29 RecursiveChildComponent {
30 child: ChildComponent,
31 },
32}
33
34impl Instruction {
35 pub fn without_line_number(&mut self) {
36 match self {
37 Instruction::ChildComponent { child } => {
38 child.line_number = 0;
39 }
40 Instruction::Component { parent, children } => {
41 parent.line_number = 0;
42 for mut child in children {
43 child.line_number = 0;
44 }
45 }
46 Instruction::RecursiveChildComponent { child } => {
47 child.line_number = 0;
48 }
49 _ => {}
50 };
51 }
52
53 pub fn resolve_id(&self) -> Option<&str> {
54 let id = match self {
55 Instruction::ChildComponent { child } => child.properties.get("id"),
56 Instruction::Component { parent, .. } => parent.properties.get("id"),
57 _ => None,
58 };
59 if let Some(property) = id {
60 if let Some(ftd::PropertyValue::Value {
61 value: ftd::variable::Value::String { text, .. },
62 }) = &property.default
63 {
64 return Some(text.as_str());
65 }
66 }
67 None
68 }
69}
70
71#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
72pub struct ChildComponent {
73 pub root: String,
74 pub condition: Option<ftd::p2::Boolean>,
75 pub properties: ftd::Map<Property>,
76 pub arguments: ftd::Map<ftd::p2::Kind>,
77 pub events: Vec<ftd::p2::Event>,
78 pub is_recursive: bool,
79 pub line_number: usize,
80 pub reference: Option<(String, ftd::p2::Kind)>,
81}
82
83#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
84pub struct Property {
85 pub default: Option<ftd::PropertyValue>,
86 pub conditions: Vec<(ftd::p2::Boolean, ftd::PropertyValue)>,
87 pub nested_properties: ftd::Map<ftd::component::Property>,
88}
89
90#[derive(Debug, Clone)]
91pub struct ElementWithContainer {
92 pub element: ftd::Element,
93 pub children: Vec<ftd::Element>,
94 pub child_container: Option<ftd::Map<Vec<Vec<usize>>>>,
95}
96
97impl Property {
98 fn eval(
99 &self,
100 line_number: usize,
101 name: &str,
102 doc: &ftd::p2::TDoc,
103 ) -> ftd::p1::Result<&ftd::PropertyValue> {
104 let mut property_value = ftd::p2::utils::e2(
105 format!("condition is not complete, name: {}", name),
106 doc.name,
107 line_number,
108 );
109 if let Some(property) = &self.default {
110 property_value = Ok(property);
111 }
112 for (boolean, property) in &self.conditions {
113 if boolean.eval(line_number, doc)? {
114 property_value = Ok(property);
115 }
116 }
117 property_value
118 }
119
120 pub(crate) fn add_default_properties(
121 reference: &ftd::Map<Property>,
122 properties: &mut ftd::Map<Property>,
123 ) {
124 for (key, arg) in reference {
125 if universal_arguments().contains_key(key) {
126 properties
127 .entry(key.to_string())
128 .or_insert_with(|| arg.to_owned());
129 }
130 }
131 }
132
133 fn resolve_default_value_string(
137 &self,
138 doc: &ftd::p2::TDoc,
139 line_number: usize,
140 ) -> ftd::p1::Result<String> {
141 if let Some(property_value) = &self.default {
142 if let Some(val) = property_value.resolve(line_number, doc)?.to_string() {
143 return Ok(val);
144 }
145 }
146 Ok("".to_string())
147 }
148}
149
150impl ChildComponent {
151 pub fn super_call(
152 &self,
153 children: &[Self],
154 doc: &mut ftd::p2::TDoc,
155 invocations: &mut ftd::Map<Vec<ftd::Map<ftd::Value>>>,
156 local_container: &[usize],
157 external_children_count: &Option<usize>,
158 ) -> ftd::p1::Result<ElementWithContainer> {
159 let id = ftd::p2::utils::string_optional(
160 "id",
161 &resolve_properties(self.line_number, &self.properties, doc)?,
162 doc.name,
163 self.line_number,
164 )?;
165
166 let ElementWithContainer {
167 mut element,
168 child_container,
169 ..
170 } = self.call(
171 doc,
172 invocations,
173 false,
174 local_container,
175 id.clone(),
176 external_children_count,
177 )?;
178 element.set_container_id(id.clone());
179 element.set_element_id(id);
180
181 let mut container_children = vec![];
182 match (&mut element, children.is_empty()) {
183 (ftd::Element::Column(_), _)
184 | (ftd::Element::Row(_), _)
185 | (ftd::Element::Scene(_), _)
186 | (ftd::Element::Grid(_), _) => {
187 let instructions = children
188 .iter()
189 .map(|child| {
190 if child.is_recursive {
191 ftd::Instruction::RecursiveChildComponent {
192 child: child.to_owned(),
193 }
194 } else {
195 ftd::Instruction::ChildComponent {
196 child: child.to_owned(),
197 }
198 }
199 })
200 .collect::<Vec<ftd::Instruction>>();
201 let elements = ftd::execute_doc::ExecuteDoc {
202 name: doc.name,
203 aliases: doc.aliases,
204 bag: doc.bag,
205 local_variables: doc.local_variables,
206 instructions: &instructions,
207 invocations,
208 }
209 .execute(local_container, None, doc.referenced_local_variables)?
210 .children;
211 container_children.extend(elements);
212 }
213 (ftd::Element::Null, false) => {
214 let root_name = ftd::p2::utils::get_root_component_name(
215 doc,
216 self.root.as_str(),
217 self.line_number,
218 )?;
219 match root_name.as_str() {
220 "ftd#row" | "ftd#column" | "ftd#scene" | "ftd#grid" | "ftd#text" => {}
221 t => {
222 return ftd::p2::utils::e2(
223 format!("{} cant have children", t),
224 doc.name,
225 self.line_number,
226 )
227 }
228 }
229 }
230 (ftd::Element::Markup(_), _) => {}
231 (t, false) => {
232 return ftd::p2::utils::e2(
233 format!("cant have children: {:?}", t),
234 doc.name,
235 self.line_number,
236 );
237 }
238 (_, true) => {}
239 }
240
241 if let ftd::Element::Markup(ref mut markups) = element {
246 if !children.is_empty() {
247 let named_container = markup_get_named_container(
248 children,
249 self.root.as_str(),
250 self.line_number,
251 doc,
252 invocations,
253 local_container,
254 )?;
255 reevalute_markups(markups, named_container, doc)?;
256 }
257 }
258
259 Ok(ElementWithContainer {
260 element,
261 children: container_children,
262 child_container,
263 })
264 }
265
266 pub fn recursive_call(
267 &self,
268 doc: &mut ftd::p2::TDoc,
269 invocations: &mut ftd::Map<Vec<ftd::Map<ftd::Value>>>,
270 is_child: bool,
271 local_container: &[usize],
272 ) -> ftd::p1::Result<Vec<ElementWithContainer>> {
273 let root = {
274 doc.get_component(self.line_number, self.root.as_str())
277 .unwrap()
278 };
279 let loop_property = resolve_recursive_property(self.line_number, &self.properties, doc)?;
280 let mut elements = vec![];
281
282 let reference_name = {
283 let mut reference_name = None;
284 if let Some(value) = self.properties.get("$loop$") {
285 if let Ok(ftd::PropertyValue::Reference { name, .. }) = value.eval(0, "$loop$", doc)
286 {
287 reference_name = Some(name);
288 }
289 }
290 reference_name
291 };
292
293 if let ftd::Value::List { data, kind } = loop_property {
294 for (i, d) in data.iter().enumerate() {
295 let mut element = construct_element(
296 self,
297 d,
298 i,
299 &root,
300 doc,
301 invocations,
302 is_child,
303 local_container,
304 )?;
305 if let Some(name) = reference_name {
306 if let Some(common) = element.element.get_mut_common() {
307 common.reference = Some(name.to_string());
308 }
309 }
310 elements.push(element);
311 }
312 if let Some(tmp_data) = construct_tmp_data(&kind) {
313 if let Some(name) = reference_name {
314 let mut element = construct_element(
315 self,
316 &tmp_data,
317 data.len(),
318 &root,
319 doc,
320 invocations,
321 is_child,
322 local_container,
323 )?;
324 if let Some(common) = element.element.get_mut_common() {
325 common.reference = Some(name.to_string());
326 common.is_dummy = true;
327 elements.push(element);
328 }
329 }
330 }
331 }
332 return Ok(elements);
333
334 fn construct_tmp_data(kind: &ftd::p2::Kind) -> Option<ftd::PropertyValue> {
335 match kind {
337 ftd::p2::Kind::String { .. } => Some(ftd::PropertyValue::Value {
338 value: ftd::Value::String {
339 text: "$loop$".to_string(),
340 source: ftd::TextSource::Header,
341 },
342 }),
343 ftd::p2::Kind::Integer { .. } => Some(ftd::PropertyValue::Value {
344 value: ftd::Value::Integer { value: 0 },
345 }),
346 ftd::p2::Kind::Decimal { .. } => Some(ftd::PropertyValue::Value {
347 value: ftd::Value::Decimal { value: 0.0 },
348 }),
349 ftd::p2::Kind::Boolean { .. } => Some(ftd::PropertyValue::Value {
350 value: ftd::Value::Boolean { value: false },
351 }),
352 ftd::p2::Kind::Optional { kind, .. } => {
353 construct_tmp_data(kind).map(|v| v.into_optional())
354 }
355 _ => None,
356 }
357 }
358
359 #[allow(clippy::too_many_arguments)]
360 fn construct_element(
361 child_component: &ChildComponent,
362 d: &ftd::PropertyValue,
363 index: usize,
364 root: &ftd::Component,
365 doc: &mut ftd::p2::TDoc,
366 invocations: &mut ftd::Map<Vec<ftd::Map<ftd::Value>>>,
367 is_child: bool,
368 local_container: &[usize],
369 ) -> ftd::p1::Result<ElementWithContainer> {
370 let mut root = root.to_owned();
371 let local_container = {
372 let mut container = local_container[..local_container.len() - 1].to_vec();
373 match local_container.last() {
374 Some(val) => container.push(val + index),
375 None => container.push(index),
376 }
377 container
378 };
379 let string_container = ftd::p2::utils::get_string_container(local_container.as_slice());
380 let loop_name = doc.resolve_name(0, format!("$loop$@{}", string_container).as_str())?;
381 doc.local_variables.insert(
382 loop_name,
383 ftd::p2::Thing::Variable(ftd::Variable {
384 name: "$loop$".to_string(),
385 value: d.to_owned(),
386 conditions: vec![],
387 flags: Default::default(),
388 }),
389 );
390 doc.insert_local_from_component(
391 &mut root,
392 &child_component.properties,
393 local_container.as_slice(),
394 &None,
395 )?;
396 let child_component = {
397 let mut child_component = child_component.clone();
398 doc.update_component_data(
399 string_container.as_str(),
400 string_container.as_str(),
401 &mut child_component.properties,
402 &mut child_component.reference,
403 &mut child_component.condition,
404 &mut child_component.events,
405 false,
406 false,
407 false,
408 )?;
409 child_component
410 };
411
412 let is_visible = {
413 let mut visible = true;
414 if let Some(ref b) = child_component.condition {
415 if b.is_constant() && !b.eval(child_component.line_number, doc)? {
416 visible = false;
417 if let Ok(true) = b.set_null(child_component.line_number, doc.name) {
418 return Ok(ElementWithContainer {
419 element: ftd::Element::Null,
420 children: vec![],
421 child_container: None,
422 });
423 }
424 }
425 }
426 visible
427 };
428 let conditional_attribute = get_conditional_attributes(
429 child_component.line_number,
430 &child_component.properties,
431 doc,
432 )?;
433
434 let mut element = root.call(
435 &child_component.properties,
436 doc,
437 invocations,
438 &None,
439 is_child,
440 &child_component.events,
441 local_container.as_slice(),
442 None,
443 &None,
444 )?;
445
446 if let Some(condition) = &child_component.condition {
447 element
448 .element
449 .set_non_visibility(!condition.eval(child_component.line_number, doc)?);
450 element.element.set_condition(
451 condition
452 .to_condition(child_component.line_number, doc)
453 .ok(),
454 );
455 }
456 if !is_visible {
457 element.element.set_non_visibility(!is_visible);
458 }
459 if let Some(common) = element.element.get_mut_common() {
460 common.conditional_attribute.extend(conditional_attribute);
461 }
462 Ok(element)
464 }
465 }
466
467 pub fn call(
468 &self,
469 doc: &mut ftd::p2::TDoc,
470 invocations: &mut ftd::Map<Vec<ftd::Map<ftd::Value>>>,
471 is_child: bool,
472 local_container: &[usize],
473 id: Option<String>,
474 external_children_count: &Option<usize>,
475 ) -> ftd::p1::Result<ElementWithContainer> {
476 if let Some(ref b) = self.condition {
477 if b.is_constant() && !b.eval(self.line_number, doc)? {
478 if let Ok(true) = b.set_null(self.line_number, doc.name) {
479 return Ok(ElementWithContainer {
480 element: ftd::Element::Null,
481 children: vec![],
482 child_container: None,
483 });
484 }
485 }
486 }
487
488 let mut root = {
489 doc.get_component(self.line_number, self.root.as_str())
492 .unwrap()
493 };
494
495 doc.insert_local_from_component(
496 &mut root,
497 &self.properties,
498 local_container,
499 external_children_count,
500 )?;
501
502 let conditional_attribute =
503 get_conditional_attributes(self.line_number, &self.properties, doc)?;
504
505 let mut element = root.call(
506 &self.properties,
507 doc,
508 invocations,
509 &self.condition,
510 is_child,
511 &self.events,
512 local_container,
513 id,
514 external_children_count,
515 )?;
516
517 if let Some(common) = element.element.get_mut_common() {
518 common.conditional_attribute.extend(conditional_attribute);
519 }
520
521 if let ftd::Element::Markup(ref mut markups) = element.element {
522 let named_container = match markup_get_named_container(
523 &[],
524 self.root.as_str(),
525 self.line_number,
526 doc,
527 invocations,
528 local_container,
529 ) {
530 Ok(n) => n,
531 _ => return Ok(element),
532 };
533 reevalute_markups(markups, named_container, doc).ok();
534 }
535
536 Ok(element)
537 }
538
539 pub fn from_p1(
540 line_number: usize,
541 name: &str,
542 p1: &ftd::p1::Header,
543 caption: &Option<String>,
544 body: &Option<(usize, String)>,
545 doc: &ftd::p2::TDoc,
546 arguments: &ftd::Map<ftd::p2::Kind>,
547 ) -> ftd::p1::Result<Self> {
548 let mut reference = None;
549 let root =
550 if let Some(ftd::p2::Kind::UI { default }) = arguments.get(name).map(|v| v.inner()) {
551 reference = Some((
552 name.to_string(),
553 ftd::p2::Kind::UI {
554 default: (*default).clone(),
555 },
556 ));
557 ftd::Component {
558 root: "ftd.kernel".to_string(),
559 full_name: "ftd#ui".to_string(),
560 line_number,
561 ..Default::default()
562 }
563 } else {
564 doc.get_component(line_number, name)?
565 };
566
567 assert_no_extra_properties(
568 line_number,
569 p1,
570 root.full_name.as_str(),
571 &root.arguments,
572 name,
573 doc,
574 )?;
575 let (local_arguments, inherits) =
576 read_arguments(p1, name, &root.arguments, arguments, doc)?;
577
578 let mut all_arguments = local_arguments.clone();
579 all_arguments.extend(arguments.clone());
580
581 let root_property =
582 get_root_property(line_number, name, caption, doc, &all_arguments, inherits)?;
583
584 assert_caption_body_checks(&root.full_name, p1, doc, caption, body, line_number)?;
585
586 return Ok(Self {
587 line_number,
588 properties: read_properties(
589 line_number,
590 p1,
591 caption,
592 body,
593 name,
594 root.full_name.as_str(),
595 &root.arguments,
596 &all_arguments,
597 doc,
598 &root_property,
599 reference.is_some(),
600 )?,
601 condition: match p1.str_optional(doc.name, line_number, "if")? {
602 Some(expr) => Some(ftd::p2::Boolean::from_expression(
603 expr,
604 doc,
605 &all_arguments,
606 (None, None),
607 line_number,
608 )?),
609 None => None,
610 },
611 root: doc.resolve_name(line_number, root.full_name.as_str())?,
612 events: p1.get_events(line_number, doc, &all_arguments)?,
613 is_recursive: false,
614 arguments: local_arguments,
615 reference,
616 });
617
618 fn get_root_property(
619 line_number: usize,
620 name: &str,
621 caption: &Option<String>,
622 doc: &ftd::p2::TDoc,
623 arguments: &ftd::Map<ftd::p2::Kind>,
624 inherits: Vec<String>,
625 ) -> ftd::p1::Result<ftd::Map<Property>> {
626 let mut properties: ftd::Map<Property> =
627 root_properties_from_inherits(line_number, arguments, inherits, doc)?;
628 if let Some(caption) = caption {
629 if let Ok(name) = doc.resolve_name(line_number, name) {
630 let kind = match name.as_str() {
631 "ftd#integer" => ftd::p2::Kind::integer(),
632 "ftd#boolean" => ftd::p2::Kind::boolean(),
633 "ftd#decimal" => ftd::p2::Kind::decimal(),
634 _ => return Ok(properties),
635 };
636 if let Ok(property_value) = ftd::PropertyValue::resolve_value(
637 line_number,
638 caption,
639 Some(kind),
640 doc,
641 arguments,
642 None,
643 ) {
644 properties.insert(
645 "value".to_string(),
646 ftd::component::Property {
647 default: Some(property_value),
648 conditions: vec![],
649 ..Default::default()
650 },
651 );
652 }
653 }
654 }
655 Ok(properties)
656 }
657 }
658}
659
660fn markup_get_named_container(
661 children: &[ChildComponent],
662 root: &str,
663 line_number: usize,
664 doc: &mut ftd::p2::TDoc,
665 invocations: &mut ftd::Map<Vec<ftd::Map<ftd::Value>>>,
666 local_container: &[usize],
667) -> ftd::p1::Result<ftd::Map<ftd::Element>> {
668 let children = {
669 let mut children = children.to_vec();
670 let root_name = ftd::p2::utils::get_root_component_name(doc, root, line_number)?;
671 if root_name.eq("ftd#text") {
672 let mut name = root.to_string();
673 while name != "ftd.kernel" {
674 let component = doc.get_component(line_number, name.as_str())?;
675 for instruction in component.instructions {
676 if let ftd::Instruction::ChildComponent { child } = instruction {
677 children.push(child);
678 }
679 }
680 name = component.root;
681 }
682 }
683 children
684 };
685 let mut elements_name = vec![];
686
687 let instructions = children
688 .iter()
689 .map(|child| {
690 let child = get_modified_child(child, &mut elements_name);
692 if child.is_recursive {
693 ftd::Instruction::RecursiveChildComponent { child }
694 } else {
695 ftd::Instruction::ChildComponent { child }
696 }
697 })
698 .collect::<Vec<ftd::Instruction>>();
699
700 let container_children = ftd::execute_doc::ExecuteDoc {
701 name: doc.name,
702 aliases: doc.aliases,
703 bag: doc.bag,
704 local_variables: doc.local_variables,
705 instructions: &instructions,
706 invocations,
707 }
708 .execute(local_container, None, doc.referenced_local_variables)?
709 .children;
710
711 return convert_to_named_container(&container_children, &elements_name, doc);
712
713 fn get_modified_child(
719 child: &ftd::ChildComponent,
720 elements_name: &mut Vec<String>,
721 ) -> ftd::ChildComponent {
722 let mut child = child.clone();
723 if let Some((ref c, ref element_name)) = child.root.split_once(' ') {
724 elements_name.push(element_name.to_string());
725 child.root = c.to_string();
726 }
727 child
728 }
729
730 fn convert_to_named_container(
734 container_children: &[ftd::Element],
735 elements_name: &[String],
736 doc: &ftd::p2::TDoc,
737 ) -> ftd::p1::Result<ftd::Map<ftd::Element>> {
738 let mut named_container = ftd::Map::new();
739 for (idx, container) in container_children.iter().enumerate() {
740 match elements_name.get(idx) {
741 Some(name) => {
742 named_container.insert(name.to_string(), container.to_owned());
743 }
744 None => {
745 return ftd::p2::utils::e2(
746 format!("cannot find name for container {:?}", container),
747 doc.name,
748 0,
749 )
750 }
751 }
752 }
753 Ok(named_container)
754 }
755}
756
757fn reevalute_markups(
761 markups: &mut ftd::Markups,
762 named_container: ftd::Map<ftd::Element>,
763 doc: &mut ftd::p2::TDoc,
764) -> ftd::p1::Result<()> {
765 if !markups.children.is_empty() {
766 return Ok(());
769 }
770 let mut all_children = markups.children.to_owned();
771 if markups.text.original.contains("\n\n") {
772 for v in markups.text.original.split("\n\n") {
773 let itext = ftd::IText::Markup(ftd::Markups {
774 text: if !markups.line {
775 ftd::rendered::markup(v)
776 } else {
777 ftd::rendered::markup_line(v)
778 },
779 ..Default::default()
780 });
781 all_children.push(ftd::Markup {
782 itext,
783 children: vec![],
784 });
785 }
786 }
787 if all_children.is_empty() {
788 let mut markup = ftd::Markup {
789 itext: ftd::IText::Markup(markups.clone()),
790 children: vec![],
791 };
792 reevalute_markup(&mut markup, &named_container, doc)?;
793 if let ftd::IText::Markup(m) = markup.itext {
794 *markups = m;
795 }
796 markups.line = true;
797 markups.children = markup.children;
798 return Ok(());
799 }
800 for markup in all_children.iter_mut() {
801 reevalute_markup(markup, &named_container, doc)?;
802 }
803 markups.children = all_children;
804
805 Ok(())
806}
807
808fn reevalute_markup(
809 markup: &mut ftd::Markup,
810 named_container: &ftd::Map<ftd::Element>,
811 doc: &mut ftd::p2::TDoc,
812) -> ftd::p1::Result<()> {
813 let text = match &markup.itext {
814 ftd::IText::Text(ftd::Text { text, .. })
815 | ftd::IText::TextBlock(ftd::TextBlock { text, .. })
816 | ftd::IText::Integer(ftd::Text { text, .. })
817 | ftd::IText::Boolean(ftd::Text { text, .. })
818 | ftd::IText::Decimal(ftd::Text { text, .. })
819 | ftd::IText::Markup(ftd::Markups { text, .. }) => {
820 text.original.chars().collect::<Vec<_>>()
821 }
822 };
823 let mut children = vec![];
824 let mut idx = 0;
825 let mut traverse_string = "".to_string();
826 while idx < text.len() {
827 if text[idx].eq(&'{') {
828 children.push(ftd::Markup {
829 itext: ftd::IText::Text(ftd::Text {
830 text: ftd::rendered::markup_line(traverse_string.as_str()),
831 ..Default::default()
832 }),
833 children: vec![],
834 });
835 traverse_string = get_inner_text(&text, &mut idx, doc.name)?;
836 let (style, text) = traverse_string
837 .split_once(':')
838 .map(|(v, n)| (v.trim(), Some(n)))
839 .unwrap_or((traverse_string.trim(), None));
840
841 let container = match named_container.get(style) {
842 Some(style) => style.to_owned(),
843 None => get_element_doc(doc, style)?,
844 };
845
846 let itext = element_to_itext(&container, doc, text, style, named_container)?;
847
848 children.push(ftd::Markup {
849 itext,
850 children: vec![],
851 });
852
853 traverse_string = "".to_string();
854 } else {
855 traverse_string.push(text[idx]);
856 }
857 idx += 1;
858 }
859
860 if !traverse_string.is_empty() && !children.is_empty() {
861 children.push(ftd::Markup {
862 itext: ftd::IText::Text(ftd::Text {
863 text: ftd::rendered::markup_line(traverse_string.as_str()),
864 ..Default::default()
865 }),
866 children: vec![],
867 });
868 }
869 for child in children.iter_mut() {
870 if let ftd::IText::Markup(_) = child.itext {
871 continue;
872 }
873 reevalute_markup(child, named_container, doc)?;
874 }
875 markup.children = children;
876
877 return Ok(());
878
879 fn get_inner_text(text: &[char], idx: &mut usize, doc_id: &str) -> ftd::p1::Result<String> {
881 let mut stack = vec!['{'];
882 let mut traverse_string = "".to_string();
883 while !stack.is_empty() {
884 *idx += 1;
885 if *idx >= text.len() {
886 return ftd::p2::utils::e2(
887 format!(
888 "cannot find closing-parenthesis before the string ends: {}",
889 traverse_string
890 ),
891 doc_id,
892 0,
893 );
894 }
895 if text[*idx].eq(&'{') {
896 stack.push('{');
897 } else if text[*idx].eq(&'}') {
898 stack.pop();
899 }
900 if !stack.is_empty() {
901 traverse_string.push(text[*idx]);
902 }
903 }
904 Ok(traverse_string)
905 }
906
907 fn element_to_itext(
908 element: &ftd::Element,
909 doc: &mut ftd::p2::TDoc,
910 text: Option<&str>,
911 root: &str,
912 named_container: &ftd::Map<ftd::Element>,
913 ) -> ftd::p1::Result<ftd::IText> {
914 Ok(match element {
915 ftd::Element::Integer(t) => {
916 let t = {
917 let mut t = t.clone();
918 if let Some(text) = text {
919 t.text = ftd::rendered::markup_line(text);
920 t.common.reference = None;
921 }
922 t
923 };
924 ftd::IText::Integer(t)
925 }
926 ftd::Element::Boolean(t) => {
927 let t = {
928 let mut t = t.clone();
929 if let Some(text) = text {
930 t.text = ftd::rendered::markup_line(text);
931 t.common.reference = None;
932 }
933 t
934 };
935 ftd::IText::Boolean(t)
936 }
937 ftd::Element::Decimal(t) => {
938 let t = {
939 let mut t = t.clone();
940 if let Some(text) = text {
941 t.text = ftd::rendered::markup_line(text);
942 t.common.reference = None;
943 }
944 t
945 };
946 ftd::IText::Decimal(t)
947 }
948 ftd::Element::TextBlock(t) => {
949 let t = {
950 let mut t = t.clone();
951 if let Some(text) = text {
952 t.text = ftd::rendered::markup_line(text);
953 t.common.reference = None;
954 }
955 t
956 };
957 ftd::IText::TextBlock(t)
958 }
959 ftd::Element::Markup(t) => {
960 let mut t = {
961 let mut t = t.clone();
962 if let Some(text) = text {
963 t.text = ftd::rendered::markup_line(text);
964 t.common.reference = None;
965 }
966 t
967 };
968 let named_container = if let Ok(mut get) =
969 markup_get_named_container(&[], root, 0, doc, &mut Default::default(), &[])
970 {
971 get.extend(named_container.clone());
972 get
973 } else {
974 named_container.clone()
983 };
984 reevalute_markups(&mut t, named_container, doc)?;
985 ftd::IText::Markup(t)
986 }
987 t => {
988 return ftd::p2::utils::e2(
989 format!(
990 "expected type istext, integer, boolean, decimal. found: {:?}",
991 t
992 ),
993 doc.name,
994 0,
995 )
996 }
997 })
998 }
999
1000 fn get_element_doc(doc: &mut ftd::p2::TDoc, name: &str) -> ftd::p1::Result<ftd::Element> {
1001 let mut root = doc
1002 .get_component(0, name)
1003 .map_err(|_| ftd::p1::Error::ParseError {
1004 message: format!("This component not found in ftd.text {}", name),
1005 doc_id: doc.name.to_string(),
1006 line_number: 0,
1007 })?;
1008
1009 let property_value = if let Some(p) = root.properties.get("text") {
1010 p
1011 } else if let Some(p) = root.properties.get("value") {
1012 p
1013 } else {
1014 return ftd::p2::utils::e2(
1015 format!(
1016 "expected type for ftd.text are text, integer, decimal and boolean, {:?}",
1017 root
1018 ),
1019 doc.name,
1020 0,
1021 );
1022 };
1023
1024 if let ftd::component::Property {
1025 default: Some(ftd::PropertyValue::Variable { kind, .. }),
1026 ..
1027 } = property_value
1028 {
1029 if !kind.has_default_value() {
1030 let property = ftd::component::Property {
1031 default: Some(ftd::PropertyValue::Value {
1032 value: ftd::Value::String {
1033 text: name.to_string(),
1034 source: ftd::TextSource::Header,
1035 },
1036 }),
1037 ..Default::default()
1038 };
1039 root.properties.insert("text".to_string(), property.clone());
1040 root.properties.insert("value".to_string(), property);
1041 }
1042 }
1043 root.arguments = Default::default();
1044 Ok(root.call_without_values(doc)?.element)
1045 }
1046}
1047
1048fn resolve_recursive_property(
1049 line_number: usize,
1050 self_properties: &ftd::Map<Property>,
1051 doc: &ftd::p2::TDoc,
1052) -> ftd::p1::Result<ftd::Value> {
1053 if let Some(value) = self_properties.get("$loop$") {
1054 if let Ok(property_value) = value.eval(line_number, "$loop$", doc) {
1055 return property_value.resolve(line_number, doc);
1056 }
1057 }
1058 ftd::p2::utils::e2(
1059 format!("$loop$ not found in properties {:?}", self_properties),
1060 doc.name,
1061 line_number,
1062 )
1063}
1064
1065pub fn resolve_properties(
1066 line_number: usize,
1067 self_properties: &ftd::Map<Property>,
1068 doc: &ftd::p2::TDoc,
1069) -> ftd::p1::Result<ftd::Map<ftd::Value>> {
1070 resolve_properties_by_id(line_number, self_properties, doc, None)
1071}
1072
1073pub fn resolve_properties_by_id(
1074 line_number: usize,
1075 self_properties: &ftd::Map<Property>,
1076 doc: &ftd::p2::TDoc,
1077 id: Option<String>,
1078) -> ftd::p1::Result<ftd::Map<ftd::Value>> {
1079 let mut properties: ftd::Map<ftd::Value> = Default::default();
1080 for (name, value) in self_properties.iter() {
1081 if name == "$loop$" {
1082 continue;
1083 }
1084 if let Some(ref id) = id {
1085 if !id.eq(name) {
1086 continue;
1087 }
1088 }
1089 if let Ok(property_value) = value.eval(line_number, name, doc) {
1090 properties.insert(name.to_string(), property_value.resolve(line_number, doc)?);
1091 }
1092 }
1093 Ok(properties)
1094}
1095
1096fn get_conditional_attributes(
1097 line_number: usize,
1098 properties: &ftd::Map<Property>,
1099 doc: &ftd::p2::TDoc,
1100) -> ftd::p1::Result<ftd::Map<ftd::ConditionalAttribute>> {
1101 let mut conditional_attribute: ftd::Map<ftd::ConditionalAttribute> = Default::default();
1102
1103 let mut dictionary: ftd::Map<Vec<String>> = Default::default();
1104 dictionary.insert(
1105 "padding-vertical".to_string(),
1106 vec!["padding-top".to_string(), "padding-bottom".to_string()],
1107 );
1108 dictionary.insert(
1109 "padding-horizontal".to_string(),
1110 vec!["padding-left".to_string(), "padding-right".to_string()],
1111 );
1112 dictionary.insert(
1113 "border-left".to_string(),
1114 vec!["border-left-width".to_string()],
1115 );
1116 dictionary.insert(
1117 "border-right".to_string(),
1118 vec!["border-right-width".to_string()],
1119 );
1120 dictionary.insert(
1121 "border-top".to_string(),
1122 vec!["border-top-width".to_string()],
1123 );
1124 dictionary.insert(
1125 "background-parallax".to_string(),
1126 vec!["background-attachment".to_string()],
1127 );
1128 dictionary.insert("size".to_string(), vec!["font-size".to_string()]);
1129 dictionary.insert(
1130 "border-bottom".to_string(),
1131 vec!["border-bottom-width".to_string()],
1132 );
1133 dictionary.insert(
1134 "border-top-radius".to_string(),
1135 vec![
1136 "border-top-left-radius".to_string(),
1137 "border-top-right-radius".to_string(),
1138 ],
1139 );
1140 dictionary.insert(
1141 "border-left-radius".to_string(),
1142 vec![
1143 "border-top-left-radius".to_string(),
1144 "border-bottom-left-radius".to_string(),
1145 ],
1146 );
1147 dictionary.insert(
1148 "border-right-radius".to_string(),
1149 vec![
1150 "border-bottom-right-radius".to_string(),
1151 "border-top-right-radius".to_string(),
1152 ],
1153 );
1154 dictionary.insert(
1155 "border-bottom-radius".to_string(),
1156 vec![
1157 "border-bottom-left-radius".to_string(),
1158 "border-bottom-right-radius".to_string(),
1159 ],
1160 );
1161
1162 dictionary.insert("slots".to_string(), vec!["grid-template-areas".to_string()]);
1163 dictionary.insert(
1164 "slot-widths".to_string(),
1165 vec!["grid-template-columns".to_string()],
1166 );
1167 dictionary.insert(
1168 "slot-heights".to_string(),
1169 vec!["grid-template-rows".to_string()],
1170 );
1171 if properties.contains_key("slots") {
1172 dictionary.insert("spacing".to_string(), vec!["grid-gap".to_string()]);
1173 }
1174 dictionary.insert("slot".to_string(), vec!["grid-area".to_string()]);
1175
1176 for (name, value) in properties {
1177 if !value.conditions.is_empty() {
1178 let styles = if let Some(styles) = dictionary.get(name) {
1179 styles.to_owned()
1180 } else {
1181 vec![name.to_string()]
1182 };
1183
1184 for name in styles {
1185 let mut conditions_with_value = vec![];
1186 for (condition, pv) in &value.conditions {
1187 if !condition.is_arg_constant() {
1188 let cond = condition.to_condition(line_number, doc)?;
1189 let value = pv.resolve(line_number, doc)?;
1190 if check_for_none(condition, pv, &value) {
1191 continue;
1193 }
1194 let string =
1195 get_string_value(&name, value, doc, line_number, pv.get_reference())?;
1196 conditions_with_value.push((cond, string));
1197 }
1198 }
1199 let default = {
1200 let mut default = None;
1201 if let Some(pv) = &value.default {
1202 let value = pv.resolve(line_number, doc)?;
1203 let string =
1204 get_string_value(&name, value, doc, line_number, pv.get_reference())?;
1205 default = Some(string);
1206 }
1207 default
1208 };
1209
1210 conditional_attribute.insert(
1211 get_style_name(name),
1212 ftd::ConditionalAttribute {
1213 attribute_type: ftd::AttributeType::Style,
1214 conditions_with_value,
1215 default,
1216 },
1217 );
1218 }
1219 }
1220 }
1221 return Ok(conditional_attribute);
1222
1223 fn check_for_none(
1224 condition: &ftd::p2::Boolean,
1225 pv: &ftd::PropertyValue,
1226 value: &ftd::Value,
1227 ) -> bool {
1228 let bool_name = if let ftd::p2::Boolean::IsNotNull { value } = condition {
1229 match value {
1230 ftd::PropertyValue::Reference { name, .. }
1231 | ftd::PropertyValue::Variable { name, .. } => name,
1232 _ => return false,
1233 }
1234 } else {
1235 return false;
1236 };
1237
1238 let pv_name = match pv {
1239 ftd::PropertyValue::Reference { name, .. }
1240 | ftd::PropertyValue::Variable { name, .. } => name,
1241 _ => return false,
1242 };
1243
1244 if !bool_name.eq(pv_name) {
1245 return false;
1246 }
1247
1248 match value {
1249 ftd::Value::None { .. } => true,
1250 ftd::Value::Optional { data, .. } if data.as_ref().eq(&None) => true,
1251 _ => false,
1252 }
1253 }
1254
1255 fn get_style_name(name: String) -> String {
1256 match name.as_str() {
1257 "sticky" => "position",
1258 t => t,
1259 }
1260 .to_string()
1261 }
1262
1263 fn get_string_value(
1264 name: &str,
1265 value: ftd::Value,
1266 doc: &ftd::p2::TDoc,
1267 line_number: usize,
1268 reference: Option<String>,
1269 ) -> ftd::p1::Result<ftd::ConditionalValue> {
1270 let style_integer = vec![
1271 "padding",
1272 "padding-left",
1273 "padding-right",
1274 "padding-top",
1275 "padding-bottom",
1276 "margin-left",
1277 "margin-right",
1278 "margin-top",
1279 "margin-bottom",
1280 "top",
1281 "bottom",
1282 "left",
1283 "right",
1284 "shadow-offset-x",
1285 "shadow-offset-y",
1286 "shadow-size",
1287 "shadow-blur",
1288 "font-size",
1289 "border-width",
1290 "grid-gap",
1291 "line-height",
1292 ];
1293
1294 let style_length = vec![
1295 "width",
1296 "min-width",
1297 "max-width",
1298 "height",
1299 "min-height",
1300 "max-height",
1301 ];
1302
1303 let style_color = vec!["background-color", "color", "border-color", "shadow-color"];
1304
1305 let style_integer_important = vec![
1306 "border-left-width",
1307 "border-right-width",
1308 "border-top-width",
1309 "border-bottom-width",
1310 "border-top-left-radius",
1311 "border-top-right-radius",
1312 "border-bottom-left-radius",
1313 "border-bottom-right-radius",
1314 ];
1315
1316 let style_string = vec![
1317 "cursor",
1318 "position",
1319 "align",
1320 "background-image",
1321 "grid-template-columns",
1322 "grid-template-rows",
1323 "grid-area",
1324 ];
1325
1326 let style_overflow = vec!["overflow-x", "overflow-y"];
1327
1328 let style_boolean = vec!["background-repeat"];
1329
1330 Ok(if style_integer.contains(&name) {
1331 match value {
1332 ftd::Value::Integer { value: v } => ftd::ConditionalValue {
1333 value: serde_json::Value::String(format!("{}px", v)),
1334 important: false,
1335 reference,
1336 },
1337 v => {
1338 return ftd::p2::utils::e2(
1339 format!("expected int, found3: {:?}", v),
1340 doc.name,
1341 line_number,
1342 )
1343 }
1344 }
1345 } else if style_integer_important.contains(&name) {
1346 match value {
1347 ftd::Value::Integer { value: v } => ftd::ConditionalValue {
1348 value: serde_json::Value::String(format!("{}px", v)),
1349 important: true,
1350 reference,
1351 },
1352 v => {
1353 return ftd::p2::utils::e2(
1354 format!("expected int, found4: {:?}", v),
1355 doc.name,
1356 line_number,
1357 )
1358 }
1359 }
1360 } else if style_length.contains(&name) {
1361 match value {
1362 ftd::Value::String { text: v, .. } => ftd::ConditionalValue {
1363 value: serde_json::Value::String(
1364 ftd::length(&ftd::Length::from(Some(v), doc.name)?.unwrap(), name).1,
1365 ),
1366 important: false,
1367 reference,
1368 },
1369 v => {
1370 return ftd::p2::utils::e2(
1371 format!("expected string, found 8: {:?}", v),
1372 doc.name,
1373 line_number,
1374 )
1375 }
1376 }
1377 } else if style_color.contains(&name) {
1378 match value {
1379 ftd::Value::Record { fields, .. } => {
1380 let properties = fields
1381 .iter()
1382 .map(|(k, v)| v.resolve(line_number, doc).map(|v| (k.to_string(), v)))
1383 .collect::<ftd::p1::Result<ftd::Map<ftd::Value>>>()?;
1384 let light = if let Some(light) = ftd::p2::element::color_from(
1385 ftd::p2::utils::string_optional("light", &properties, doc.name, 0)?,
1386 doc.name,
1387 )? {
1388 ftd::html::color(&light)
1389 } else {
1390 "auto".to_string()
1391 };
1392 let dark = if let Some(dark) = ftd::p2::element::color_from(
1393 ftd::p2::utils::string_optional("dark", &properties, doc.name, 0)?,
1394 doc.name,
1395 )? {
1396 ftd::html::color(&dark)
1397 } else {
1398 "auto".to_string()
1399 };
1400
1401 ftd::ConditionalValue {
1402 value: serde_json::json!({ "light": light, "dark": dark, "$kind$": "light" }),
1403 important: false,
1404 reference,
1405 }
1406 }
1407 v => {
1408 return ftd::p2::utils::e2(
1409 format!("expected string, found 9: {:?}", v),
1410 doc.name,
1411 line_number,
1412 )
1413 }
1414 }
1415 } else if style_overflow.contains(&name) {
1416 match value {
1417 ftd::Value::String { text: v, .. } => ftd::ConditionalValue {
1418 value: serde_json::Value::String(
1419 ftd::overflow(&ftd::Overflow::from(Some(v), doc.name)?.unwrap(), name).1,
1420 ),
1421 important: false,
1422 reference,
1423 },
1424 v => {
1425 return ftd::p2::utils::e2(
1426 format!("expected string, found 10: {:?}", v),
1427 doc.name,
1428 line_number,
1429 )
1430 }
1431 }
1432 } else if style_string.contains(&name) {
1433 match value {
1434 ftd::Value::String { text: v, .. } => ftd::ConditionalValue {
1435 value: serde_json::Value::String(v),
1436 important: false,
1437 reference,
1438 },
1439 v => {
1440 return ftd::p2::utils::e2(
1441 format!("expected string, found 11: {:?}", v),
1442 doc.name,
1443 line_number,
1444 )
1445 }
1446 }
1447 } else if style_boolean.contains(&name) {
1448 match value {
1449 ftd::Value::Boolean { value: v } => ftd::ConditionalValue {
1450 value: serde_json::Value::Bool(v),
1451 important: false,
1452 reference,
1453 },
1454 v => {
1455 return ftd::p2::utils::e2(
1456 format!("expected string, found 12: {:?}", v),
1457 doc.name,
1458 line_number,
1459 )
1460 }
1461 }
1462 } else if name.eq("sticky") {
1463 match value {
1464 ftd::Value::Boolean { value: v } => ftd::ConditionalValue {
1465 value: serde_json::Value::String({
1466 if v { "sticky" } else { "inherit" }.to_string()
1467 }),
1468 important: false,
1469 reference,
1470 },
1471 v => {
1472 return ftd::p2::utils::e2(
1473 format!("expected boolean, found: {:?}", v),
1474 doc.name,
1475 line_number,
1476 )
1477 }
1478 }
1479 } else if name.eq("background-attachment") {
1480 match value {
1481 ftd::Value::Boolean { value: v } => ftd::ConditionalValue {
1482 value: serde_json::Value::String({
1483 if v { "fixed" } else { "inherit" }.to_string()
1484 }),
1485 important: false,
1486 reference,
1487 },
1488 v => {
1489 return ftd::p2::utils::e2(
1490 format!("expected boolean, found: {:?}", v),
1491 doc.name,
1492 line_number,
1493 )
1494 }
1495 }
1496 } else if name.eq("line-clamp") {
1497 match value {
1498 ftd::Value::Integer { value: v } => ftd::ConditionalValue {
1499 value: serde_json::json!(v),
1500 important: false,
1501 reference,
1502 },
1503 v => {
1504 return ftd::p2::utils::e2(
1505 format!("expected int, found5: {:?}", v),
1506 doc.name,
1507 line_number,
1508 )
1509 }
1510 }
1511 } else if name.eq("grid-template-areas") {
1512 match value {
1513 ftd::Value::String { text: v, .. } => {
1514 let areas = v.split('|').map(|v| v.trim()).collect::<Vec<&str>>();
1515 let mut css_areas = "".to_string();
1516 for area in areas {
1517 css_areas = format!("{}'{}'", css_areas, area);
1518 }
1519 ftd::ConditionalValue {
1520 value: serde_json::Value::String(css_areas),
1521 important: false,
1522 reference,
1523 }
1524 }
1525 v => {
1526 return ftd::p2::utils::e2(
1527 format!("expected string, found 13: {:?}", v),
1528 doc.name,
1529 line_number,
1530 )
1531 }
1532 }
1533 } else {
1534 return ftd::p2::utils::e2(
1535 format!("unknown style name: `{}` value:`{:?}`", name, value),
1536 doc.name,
1537 line_number,
1538 );
1539 })
1540 }
1541}
1542
1543pub(crate) fn resolve_properties_with_ref(
1544 line_number: usize,
1545 self_properties: &ftd::Map<Property>,
1546 doc: &ftd::p2::TDoc,
1547) -> ftd::p1::Result<ftd::Map<(ftd::Value, Option<String>)>> {
1548 let mut properties: ftd::Map<(ftd::Value, Option<String>)> = Default::default();
1549 for (name, value) in self_properties.iter() {
1550 if name == "$loop$" {
1551 continue;
1552 }
1553 if let Ok(property_value) = value.eval(line_number, name, doc) {
1554 let reference = match property_value {
1555 ftd::PropertyValue::Reference { name, .. } => Some(name.to_string()),
1556 ftd::PropertyValue::Variable { name, .. } => Some(name.to_string()),
1557 _ => None,
1558 };
1559 let resolved_value = {
1560 let mut resolved_value = property_value.resolve(line_number, doc)?;
1561 if let ftd::Value::UI { data, .. } = &mut resolved_value {
1562 data.extend(value.nested_properties.clone())
1563 }
1564 resolved_value
1565 };
1566
1567 properties.insert(name.to_string(), (resolved_value, reference));
1568 }
1569 }
1570 Ok(properties)
1571}
1572
1573impl Component {
1574 fn call_sub_functions(
1575 &self,
1576 doc: &mut ftd::p2::TDoc,
1577 invocations: &mut ftd::Map<Vec<ftd::Map<ftd::Value>>>,
1578 call_container: &[usize],
1579 id: Option<String>,
1580 ) -> ftd::p1::Result<ElementWithContainer> {
1581 let new_instruction = {
1582 let mut instructions: Vec<Instruction> = self.instructions.clone();
1583 for instruction in instructions.iter_mut() {
1584 match instruction {
1585 Instruction::ChildComponent { child } => {
1586 reference_to_child_component(child, self.line_number, doc)?
1587 }
1588 Instruction::Component { parent, children } => {
1589 reference_to_child_component(parent, self.line_number, doc)?;
1590 for child in children.iter_mut() {
1591 reference_to_child_component(child, self.line_number, doc)?;
1592 }
1593 }
1594 Instruction::ChangeContainer { .. } => {}
1595 Instruction::RecursiveChildComponent { child } => {
1596 reference_to_child_component(child, self.line_number, doc)?
1597 }
1598 }
1599 }
1600 instructions
1601 };
1602
1603 return ftd::execute_doc::ExecuteDoc {
1604 name: doc.name,
1605 aliases: doc.aliases,
1606 bag: doc.bag,
1607 local_variables: doc.local_variables,
1608 instructions: &new_instruction,
1609 invocations,
1610 }
1611 .execute(call_container, id, doc.referenced_local_variables);
1612
1613 fn reference_to_child_component(
1614 child: &mut ChildComponent,
1615 line_number: usize,
1616 doc: &ftd::p2::TDoc,
1617 ) -> ftd::p1::Result<()> {
1618 if let Some(ref c) = child.reference {
1619 match doc.get_component(line_number, &c.0) {
1620 Ok(_) => {
1621 *child = ChildComponent {
1622 root: c.0.to_string(),
1623 condition: None,
1624 properties: Default::default(),
1625 arguments: Default::default(),
1626 events: vec![],
1627 is_recursive: false,
1628 line_number,
1629 reference: None,
1630 };
1631 }
1632 Err(e) => {
1633 match doc.get_value(line_number, &c.0) {
1634 Ok(ftd::Value::Optional { kind, .. })
1635 | Ok(ftd::Value::None { kind })
1636 if matches!(kind, ftd::p2::Kind::UI { .. }) =>
1637 {
1638 if let Some(ftd::p2::Boolean::IsNotNull { ref value }) =
1639 child.condition
1640 {
1641 match value {
1642 ftd::PropertyValue::Reference { name, .. }
1643 | ftd::PropertyValue::Variable { name, .. } => {
1644 if name.eq({
1645 if let Some(reference) = c.0.strip_prefix('@') {
1646 reference
1647 } else {
1648 c.0.as_str()
1649 }
1650 }) {
1651 *child = ChildComponent {
1652 root: "ftd#null".to_string(),
1653 condition: None,
1654 properties: Default::default(),
1655 arguments: Default::default(),
1656 events: vec![],
1657 is_recursive: false,
1658 line_number,
1659 reference: None,
1660 };
1661 return Ok(());
1662 }
1663 }
1664 _ => {}
1665 }
1666 }
1667 }
1668 _ => {}
1669 }
1670 return ftd::p2::utils::e2(format!("{:?}", e), doc.name, line_number);
1671 }
1672 }
1673 }
1674 Ok(())
1675 }
1676 }
1677
1678 pub fn get_caption(&self) -> Option<String> {
1679 let mut new_caption_title = None;
1680 for (arg, arg_kind) in self.arguments.clone() {
1681 if let ftd::p2::Kind::String { caption, .. } = arg_kind {
1682 if caption {
1683 new_caption_title = Some(arg);
1684 }
1685 }
1686 }
1687 new_caption_title
1688 }
1689
1690 pub fn from_p1(p1: &ftd::p1::Section, doc: &ftd::p2::TDoc) -> ftd::p1::Result<Self> {
1691 let var_data = ftd::variable::VariableData::get_name_kind(
1692 &p1.name,
1693 doc,
1694 p1.line_number,
1695 vec![].as_slice(),
1696 )?;
1697 if var_data.is_variable() {
1698 return ftd::p2::utils::e2(
1699 format!("expected component, found: {}", p1.name),
1700 doc.name,
1701 p1.line_number,
1702 );
1703 }
1704 let name = var_data.name;
1705 let root = doc.resolve_name(p1.line_number, var_data.kind.as_str())?;
1706 let root_component = doc.get_component(p1.line_number, root.as_str())?;
1707 let (mut arguments, inherits) = read_arguments(
1708 &p1.header,
1709 root.as_str(),
1710 &root_component.arguments,
1711 &Default::default(),
1712 doc,
1713 )?;
1714
1715 arguments.extend(universal_arguments());
1717
1718 assert_no_extra_properties(
1719 p1.line_number,
1720 &p1.header,
1721 root.as_str(),
1722 &root_component.arguments,
1723 &p1.name,
1724 doc,
1725 )?;
1726 let mut instructions: Vec<Instruction> = Default::default();
1727
1728 for sub in p1.sub_sections.0.iter() {
1729 if sub.is_commented {
1730 continue;
1731 }
1732 if let Ok(loop_data) = sub.header.str(doc.name, p1.line_number, "$loop$") {
1733 instructions.push(Instruction::RecursiveChildComponent {
1734 child: recursive_child_component(
1735 loop_data,
1736 sub,
1737 doc,
1738 &arguments,
1739 Some((name.to_string(), root_component.to_owned())),
1740 )?,
1741 });
1742 continue;
1743 }
1744
1745 instructions.push(if sub.name == "container" {
1746 Instruction::ChangeContainer {
1747 name: doc.resolve_name_without_full_path(
1748 sub.line_number,
1749 sub.caption(doc.name)?.as_str(),
1750 )?,
1751 }
1752 } else {
1753 let child = if ftd::p2::utils::get_root_component_name(
1754 doc,
1755 root_component.full_name.as_str(),
1756 sub.line_number,
1757 )?
1758 .eq("ftd#text")
1759 {
1760 ftd::p2::utils::get_markup_child(sub, doc, &arguments)?
1761 } else {
1762 ftd::ChildComponent::from_p1(
1763 sub.line_number,
1764 sub.name.as_str(),
1765 &sub.header,
1766 &sub.caption,
1767 &sub.body,
1768 doc,
1769 &arguments,
1770 )?
1771 };
1772 Instruction::ChildComponent { child }
1773 });
1774 }
1775
1776 let condition = match p1.header.str_optional(doc.name, p1.line_number, "if")? {
1777 Some(expr) => Some(ftd::p2::Boolean::from_expression(
1778 expr,
1779 doc,
1780 &arguments,
1781 (None, None),
1782 p1.line_number,
1783 )?),
1784 None => None,
1785 };
1786
1787 let events = p1.header.get_events(p1.line_number, doc, &arguments)?;
1788
1789 assert_caption_body_checks(
1790 &root,
1791 &p1.header,
1792 doc,
1793 &p1.caption,
1794 &p1.body,
1795 p1.line_number,
1796 )?;
1797
1798 Ok(Component {
1799 full_name: doc.resolve_name(p1.line_number, &name)?,
1800 properties: read_properties(
1801 p1.line_number,
1802 &p1.header,
1803 &p1.caption,
1804 &p1.body,
1805 name.as_str(),
1806 root.as_str(),
1807 &root_component.arguments,
1808 &arguments,
1809 doc,
1810 &root_properties_from_inherits(p1.line_number, &arguments, inherits, doc)?,
1811 false,
1812 )?,
1813 arguments,
1814 locals: Default::default(),
1815 root,
1816 instructions,
1817 kernel: false,
1818 invocations: Default::default(),
1819 condition,
1820 events,
1821 line_number: p1.line_number,
1822 })
1823 }
1824
1825 fn call_without_values(
1826 &self,
1827 doc: &mut ftd::p2::TDoc,
1828 ) -> ftd::p1::Result<ElementWithContainer> {
1829 self.call(
1830 &Default::default(),
1831 doc,
1832 &mut Default::default(),
1833 &Default::default(),
1834 false,
1835 &[],
1836 &[],
1837 Default::default(),
1838 &None,
1839 )
1840 }
1841
1842 #[allow(clippy::too_many_arguments)]
1843 fn call(
1844 &self,
1845 arguments: &ftd::Map<Property>,
1846 doc: &mut ftd::p2::TDoc,
1847 invocations: &mut ftd::Map<Vec<ftd::Map<ftd::Value>>>,
1848 condition: &Option<ftd::p2::Boolean>,
1849 is_child: bool,
1850 events: &[ftd::p2::Event],
1851 local_container: &[usize],
1852 id: Option<String>,
1853 external_children_count: &Option<usize>,
1854 ) -> ftd::p1::Result<ElementWithContainer> {
1855 invocations
1856 .entry(self.full_name.clone())
1857 .or_default()
1858 .push(resolve_properties(0, arguments, doc)?);
1859 if self.root == "ftd.kernel" {
1860 let element = match self.full_name.as_str() {
1861 "ftd#text-block" => {
1862 ftd::Element::TextBlock(ftd::p2::element::text_block_from_properties(
1863 arguments, doc, condition, is_child, events,
1864 )?)
1865 }
1866 "ftd#code" => ftd::Element::Code(ftd::p2::element::code_from_properties(
1867 arguments, doc, condition, is_child, events,
1868 )?),
1869 "ftd#image" => ftd::Element::Image(ftd::p2::element::image_from_properties(
1870 arguments, doc, condition, is_child, events,
1871 )?),
1872 "ftd#row" => ftd::Element::Row(ftd::p2::element::row_from_properties(
1873 arguments, doc, condition, is_child, events,
1874 )?),
1875 "ftd#column" => ftd::Element::Column(ftd::p2::element::column_from_properties(
1876 arguments, doc, condition, is_child, events,
1877 )?),
1878 "ftd#iframe" => ftd::Element::IFrame(ftd::p2::element::iframe_from_properties(
1879 arguments, doc, condition, is_child, events,
1880 )?),
1881 "ftd#integer" => ftd::Element::Integer(ftd::p2::element::integer_from_properties(
1882 arguments, doc, condition, is_child, events,
1883 )?),
1884 "ftd#decimal" => ftd::Element::Decimal(ftd::p2::element::decimal_from_properties(
1885 arguments, doc, condition, is_child, events,
1886 )?),
1887 "ftd#boolean" => ftd::Element::Boolean(ftd::p2::element::boolean_from_properties(
1888 arguments, doc, condition, is_child, events,
1889 )?),
1890 "ftd#input" => ftd::Element::Input(ftd::p2::element::input_from_properties(
1891 arguments, doc, condition, is_child, events,
1892 )?),
1893 "ftd#scene" => ftd::Element::Scene(ftd::p2::element::scene_from_properties(
1894 arguments, doc, condition, is_child, events,
1895 )?),
1896 "ftd#grid" => ftd::Element::Grid(ftd::p2::element::grid_from_properties(
1897 arguments, doc, condition, is_child, events,
1898 )?),
1899 "ftd#text" => ftd::Element::Markup(ftd::p2::element::markup_from_properties(
1900 arguments, doc, condition, is_child, events,
1901 )?),
1902 "ftd#null" => ftd::Element::Null,
1903 _ => unreachable!(),
1904 };
1905 Ok(ElementWithContainer {
1906 element,
1907 children: vec![],
1908 child_container: None,
1909 })
1910 } else {
1911 let mut root = {
1912 doc.get_component(self.line_number, self.root.as_str())
1915 .unwrap()
1916 };
1917 doc.insert_local_from_component(
1918 &mut root,
1919 &self.properties,
1920 local_container,
1921 external_children_count,
1922 )?;
1923
1924 let (get_condition, is_visible, is_null_element) = match condition {
1925 Some(c) => {
1926 let is_visible = c.eval(self.line_number, doc)?;
1927 if !c.is_arg_constant() {
1928 (
1929 Some(c.to_condition(self.line_number, doc)?),
1930 is_visible,
1931 false,
1932 )
1933 } else {
1934 (
1935 None,
1936 is_visible,
1937 !is_visible
1938 && c.set_null(self.line_number, doc.name).is_ok()
1939 && c.set_null(self.line_number, doc.name)?,
1940 )
1941 }
1942 }
1943 _ => (None, true, false),
1944 };
1945
1946 let events = ftd::p2::Event::get_events(self.line_number, events, doc)?;
1947
1948 let mut element = if !is_null_element {
1949 root.call(
1950 &self.properties,
1951 doc,
1952 invocations,
1953 &self.condition,
1954 is_child,
1955 &self.events,
1956 local_container,
1957 None,
1958 external_children_count,
1959 )?
1960 } else {
1961 ElementWithContainer {
1962 element: ftd::Element::Null,
1963 children: vec![],
1964 child_container: None,
1965 }
1966 }
1967 .element;
1968
1969 if get_condition.is_some() {
1970 let mut is_visible = is_visible;
1971 if let Some(common) = element.get_common() {
1972 is_visible &= !common.is_not_visible;
1973 }
1974 element.set_condition(get_condition);
1975 element.set_non_visibility(!is_visible);
1976 }
1977
1978 let conditional_attribute =
1979 get_conditional_attributes(self.line_number, &self.properties, doc)?;
1980
1981 let mut containers: Option<ftd::Map<Vec<Vec<usize>>>> = None;
1982 match &mut element {
1983 ftd::Element::TextBlock(_)
1984 | ftd::Element::Code(_)
1985 | ftd::Element::Image(_)
1986 | ftd::Element::IFrame(_)
1987 | ftd::Element::Input(_)
1988 | ftd::Element::Integer(_)
1989 | ftd::Element::Decimal(_)
1990 | ftd::Element::Boolean(_)
1991 | ftd::Element::Markup(_)
1992 | ftd::Element::Null => {}
1993 ftd::Element::Column(ftd::Column {
1994 ref mut container, ..
1995 })
1996 | ftd::Element::Row(ftd::Row {
1997 ref mut container, ..
1998 })
1999 | ftd::Element::Scene(ftd::Scene {
2000 ref mut container, ..
2001 })
2002 | ftd::Element::Grid(ftd::Grid {
2003 ref mut container, ..
2004 }) => {
2005 let ElementWithContainer {
2006 children,
2007 child_container,
2008 ..
2009 } = self.call_sub_functions(doc, invocations, local_container, id)?;
2010
2011 if let Some(ref append_at) = container.append_at {
2012 if let Some(ref child_container) = child_container {
2013 let id = if append_at.contains('.') {
2014 ftd::p2::utils::split(append_at.to_string(), ".")?.1
2015 } else {
2016 append_at.to_string()
2017 };
2018 if let Some(c) =
2019 child_container.get(append_at.replace('.', "#").as_str())
2020 {
2021 container.external_children = Some((id, c.to_owned(), vec![]));
2022 }
2023 }
2024 }
2025 if let Some(child_container) = child_container {
2026 match containers {
2027 Some(ref mut containers) => {
2028 containers.extend(child_container);
2029 }
2030 None => {
2031 containers = Some(child_container);
2032 }
2033 }
2034 }
2035 container.children.extend(children);
2036 }
2037 }
2038
2039 if let Some(common) = element.get_mut_common() {
2040 common.conditional_attribute.extend(conditional_attribute);
2041 common.events.extend(events);
2042 }
2043
2044 Ok(ElementWithContainer {
2045 element,
2046 children: vec![],
2047 child_container: containers,
2048 })
2049 }
2050 }
2051
2052 pub fn to_value(&self, kind: &ftd::p2::Kind) -> ftd::p1::Result<ftd::Value> {
2053 Ok(ftd::Value::UI {
2054 name: self.full_name.to_string(),
2055 kind: kind.to_owned(),
2056 data: Default::default(),
2057 })
2058 }
2059}
2060
2061pub fn recursive_child_component(
2062 loop_data: &str,
2063 sub: &ftd::p1::SubSection,
2064 doc: &ftd::p2::TDoc,
2065 arguments: &ftd::Map<ftd::p2::Kind>,
2066 name_with_component: Option<(String, ftd::Component)>,
2067) -> ftd::p1::Result<ftd::ChildComponent> {
2068 let mut loop_ref = "object".to_string();
2069 let mut loop_on_component = loop_data.to_string();
2070
2071 if loop_data.contains("as") {
2072 let parts = ftd::p2::utils::split(loop_data.to_string(), " as ")?;
2073 loop_on_component = parts.0;
2074 loop_ref = if let Some(loop_ref) = parts.1.strip_prefix('$') {
2075 loop_ref.to_string()
2076 } else {
2077 return ftd::p2::utils::e2(
2078 format!("loop variable should start with $, found: {}", parts.1),
2079 doc.name,
2080 sub.line_number,
2081 );
2082 };
2083 }
2084
2085 let recursive_property_value = ftd::PropertyValue::resolve_value(
2086 sub.line_number,
2087 &loop_on_component,
2088 None,
2089 doc,
2090 arguments,
2091 None,
2092 )?;
2093
2094 let recursive_kind = if let ftd::p2::Kind::List { kind, .. } = recursive_property_value.kind() {
2095 kind.as_ref().to_owned()
2096 } else {
2097 return ftd::p2::utils::e2(
2098 format!(
2099 "expected list for loop, found: {:?}",
2100 recursive_property_value.kind(),
2101 ),
2102 doc.name,
2103 sub.line_number,
2104 );
2105 };
2106
2107 let mut properties: ftd::Map<Property> = Default::default();
2108
2109 properties.insert(
2110 "$loop$".to_string(),
2111 ftd::component::Property {
2112 default: Some(recursive_property_value),
2113 conditions: vec![],
2114 ..Default::default()
2115 },
2116 );
2117
2118 let mut new_header = ftd::p1::Header(vec![]);
2119 let (mut left_boolean, mut right_boolean) = (None, None);
2120 for (i, k, v) in &sub.header.0 {
2121 if k == "$loop$" {
2122 continue;
2123 }
2124
2125 if k == "if" && contains_loop_ref(&loop_ref, v) {
2126 let v = v.replace(&format!("${}", loop_ref), "$loop$");
2127 let (_, left, right) =
2128 ftd::p2::Boolean::boolean_left_right(i.to_owned(), &v, doc.name)?;
2129 if left.contains("$loop$") {
2130 left_boolean = resolve_loop_reference(i, &recursive_kind, doc, left)?.default;
2131 }
2132 if let Some(r) = right {
2133 if r.contains("$loop$") {
2134 right_boolean = resolve_loop_reference(i, &recursive_kind, doc, r)?.default;
2135 }
2136 }
2137 }
2138
2139 if contains_loop_ref(&loop_ref, v) && v.starts_with(&format!("${}", loop_ref)) {
2140 let reference = v.to_string().replace(&format!("${}", loop_ref), "$loop$");
2141 let value = resolve_loop_reference(i, &recursive_kind, doc, reference)?;
2142 properties.insert(k.to_string(), value);
2143 } else {
2144 new_header.add(i, k, v);
2145 }
2146 }
2147
2148 let mut reference = None;
2149
2150 let (root_arguments, full_name, caption) = match name_with_component {
2151 Some((name, root_component)) if sub.name == name => (
2152 root_component.arguments.clone(),
2153 root_component.full_name.to_string(),
2154 root_component.get_caption(),
2155 ),
2156 _ => {
2157 let root = if let Some(ftd::p2::Kind::UI { default }) =
2158 arguments.get(&sub.name).map(|v| v.inner())
2159 {
2160 reference = Some((
2161 sub.name.to_string(),
2162 ftd::p2::Kind::UI {
2163 default: (*default).clone(),
2164 },
2165 ));
2166 ftd::Component {
2167 root: "ftd.kernel".to_string(),
2168 full_name: "ftd#ui".to_string(),
2169 arguments: Default::default(),
2170 locals: Default::default(),
2171 properties: Default::default(),
2172 instructions: vec![],
2173 events: vec![],
2174 condition: None,
2175 kernel: false,
2176 invocations: vec![],
2177 line_number: sub.line_number,
2178 }
2179 } else {
2180 doc.get_component(sub.line_number, sub.name.as_str())?
2181 };
2182 let root_arguments = root.arguments.clone();
2183 assert_no_extra_properties(
2184 sub.line_number,
2185 &new_header,
2186 root.full_name.as_str(),
2187 &root_arguments,
2188 sub.name.as_str(),
2189 doc,
2190 )?;
2191 (
2192 root_arguments,
2193 root.full_name.to_string(),
2194 root.get_caption(),
2195 )
2196 }
2197 };
2198
2199 let mut new_caption = sub.caption.clone();
2200 if let (Some(caption), Some(caption_arg)) = (sub.caption.clone(), caption) {
2201 if contains_loop_ref(&loop_ref, &caption) {
2202 let reference = caption.replace(&format!("${}", loop_ref), "$loop$");
2203 let value = resolve_loop_reference(&sub.line_number, &recursive_kind, doc, reference)?;
2204 properties.insert(caption_arg, value);
2205 new_caption = None;
2206 }
2207 }
2208
2209 assert_caption_body_checks(
2210 full_name.as_str(),
2211 &sub.header,
2212 doc,
2213 &sub.caption,
2214 &sub.body,
2215 sub.line_number,
2216 )?;
2217
2218 properties.extend(read_properties(
2219 sub.line_number,
2220 &new_header,
2221 &new_caption,
2222 &sub.body,
2223 &sub.name,
2224 full_name.as_str(),
2225 &root_arguments,
2226 arguments,
2227 doc,
2228 &properties,
2229 reference.is_some(),
2230 )?);
2231
2232 return Ok(ftd::ChildComponent {
2233 root: doc.resolve_name(sub.line_number, &sub.name.to_string())?,
2234 condition: match sub.header.str_optional(doc.name, sub.line_number, "if")? {
2235 Some(expr) => Some(ftd::p2::Boolean::from_expression(
2236 expr,
2237 doc,
2238 arguments,
2239 (left_boolean, right_boolean),
2240 sub.line_number,
2241 )?),
2242 None => None,
2243 },
2244 properties,
2245 arguments: Default::default(),
2246 events: vec![],
2247 is_recursive: true,
2248 line_number: sub.line_number,
2249 reference,
2250 });
2251
2252 fn resolve_loop_reference(
2253 line_number: &usize,
2254 recursive_kind: &ftd::p2::Kind,
2255 doc: &ftd::p2::TDoc,
2256 reference: String,
2257 ) -> ftd::p1::Result<Property> {
2258 let mut arguments: ftd::Map<ftd::p2::Kind> = Default::default();
2259 arguments.insert("$loop$".to_string(), recursive_kind.to_owned());
2260 let property = ftd::PropertyValue::resolve_value(
2261 *line_number,
2262 &format!("${}", reference),
2263 None,
2264 doc,
2265 &arguments,
2266 None,
2267 )?;
2268 Ok(ftd::component::Property {
2269 default: Some(property),
2270 conditions: vec![],
2271 ..Default::default()
2272 })
2273 }
2274
2275 fn contains_loop_ref(loop_ref: &str, pattern: &str) -> bool {
2276 let ref1 = format!("${}.", loop_ref);
2277 let pattern_vec: Vec<&str> = pattern.split(' ').collect();
2278 let partern_bool = pattern_vec
2279 .iter()
2280 .map(|v| v.contains(&ref1) || v == &format!("${}", loop_ref))
2281 .collect::<Vec<bool>>();
2282 for p in partern_bool {
2283 if p {
2284 return p;
2285 }
2286 }
2287 false
2288 }
2289}
2290
2291fn is_component(name: &str) -> bool {
2292 !(name.starts_with("component ")
2293 || name.starts_with("var ")
2294 || name.starts_with("record ")
2295 || name.starts_with("or-type")
2296 || name.starts_with("list ")
2297 || name.starts_with("map ")
2298 || (name == "container")
2299 || (name == "ftd.text")
2300 || (name == "ftd.text-block")
2301 || (name == "ftd.code")
2302 || (name == "ftd.image")
2303 || (name == "ftd.row")
2304 || (name == "ftd.column")
2305 || (name == "ftd.iframe")
2306 || (name == "ftd.integer")
2307 || (name == "ftd.decimal")
2308 || (name == "ftd.boolean")
2309 || (name == "ftd.input")
2310 || (name == "ftd.scene")
2311 || (name == "ftd.grid")
2312 || (name == "ftd.markup"))
2313}
2314
2315fn assert_no_extra_properties(
2316 line_number: usize,
2317 p1: &ftd::p1::Header,
2318 root: &str,
2319 root_arguments: &ftd::Map<ftd::p2::Kind>,
2320 name: &str,
2321 doc: &ftd::p2::TDoc,
2322) -> ftd::p1::Result<()> {
2323 for (i, k, _) in p1.0.iter() {
2324 if k == "component"
2325 || k.starts_with('$')
2326 || k == "if"
2327 || ftd::variable::VariableData::get_name_kind(k, doc, line_number, vec![].as_slice())
2328 .is_ok()
2329 {
2330 continue;
2331 }
2332 let key = if k.contains(" if ") {
2333 let mut parts = k.splitn(2, " if ");
2334 parts.next().unwrap().trim()
2335 } else {
2336 k
2337 };
2338
2339 if !(root_arguments.contains_key(key)
2340 || (is_component(name) && universal_arguments().contains_key(key)))
2341 {
2342 return ftd::p2::utils::e2(
2343 format!(
2344 "unknown key found: {}, {} has: {}",
2345 k,
2346 root,
2347 root_arguments
2348 .keys()
2349 .map(ToString::to_string)
2350 .collect::<Vec<_>>()
2351 .join(", ")
2352 ),
2353 doc.name,
2354 i.to_owned(),
2355 );
2356 }
2357 }
2358
2359 Ok(())
2360}
2361
2362fn check_input_conflicting_values(
2383 properties: &ftd::Map<Property>,
2384 doc: &ftd::p2::TDoc,
2385 line_number: usize,
2386) -> ftd::p1::Result<()> {
2387 fn get_property_default_value(
2388 property_name: &str,
2389 properties: &ftd::Map<Property>,
2390 doc: &ftd::p2::TDoc,
2391 line_number: usize,
2392 ) -> ftd::p1::Result<String> {
2393 if let Some(property) = properties.get(property_name) {
2394 return property.resolve_default_value_string(doc, line_number);
2395 }
2396 Err(ftd::p1::Error::NotFound {
2397 doc_id: doc.name.to_string(),
2398 line_number,
2399 key: property_name.to_string(),
2400 })
2401 }
2402
2403 let contains_value = properties.contains_key("value");
2404 let contains_default_value = properties.contains_key("default-value");
2405
2406 match (contains_value, contains_default_value) {
2407 (true, true) => {
2408 let value = get_property_default_value("value", properties, doc, line_number)?;
2409 let default_value =
2410 get_property_default_value("default-value", properties, doc, line_number)?;
2411
2412 Err(ftd::p1::Error::ForbiddenUsage {
2413 message: format!(
2414 "value: \'{}\', default-value: \'{}\' both are used in ftd.input",
2415 value, default_value
2416 ),
2417 doc_id: doc.name.to_string(),
2418 line_number,
2419 })
2420 }
2421 (_, _) => Ok(()),
2422 }
2423}
2424
2425#[allow(clippy::too_many_arguments)]
2426pub fn read_properties(
2427 line_number: usize,
2428 p1: &ftd::p1::Header,
2429 caption: &Option<String>,
2430 body: &Option<(usize, String)>,
2431 fn_name: &str,
2432 root: &str,
2433 root_arguments: &ftd::Map<ftd::p2::Kind>,
2434 arguments: &ftd::Map<ftd::p2::Kind>,
2435 doc: &ftd::p2::TDoc,
2436 root_properties: &ftd::Map<Property>,
2437 is_reference: bool,
2438) -> ftd::p1::Result<ftd::Map<Property>> {
2439 let mut properties: ftd::Map<Property> = Default::default();
2440
2441 for (name, kind) in root_arguments.iter() {
2442 if let Some(prop) = root_properties.get(name) {
2443 properties.insert(name.to_string(), prop.clone());
2444 continue;
2445 }
2446 let (conditional_vector, source) = match (
2447 p1.conditional_str(doc, line_number, name, arguments),
2448 kind.inner(),
2449 ) {
2450 (Ok(v), _) => (
2451 v.iter()
2452 .map(|(a, b, c, d)| (Some(a.to_owned()), b.to_owned(), c.to_owned(), *d))
2453 .collect::<Vec<(Option<usize>, String, Option<String>, bool)>>(),
2454 ftd::TextSource::Header,
2455 ),
2456 (
2457 Err(ftd::p1::Error::NotFound { .. }),
2458 ftd::p2::Kind::String {
2459 caption: c,
2460 body: b,
2461 default: d,
2462 is_reference: r,
2463 },
2464 ) => {
2465 if *c && caption.is_some() {
2466 (
2467 vec![(None, caption.as_ref().unwrap().to_string(), None, *r)],
2468 ftd::TextSource::Caption,
2469 )
2470 } else if *b && body.is_some() {
2471 (
2472 vec![(None, body.as_ref().unwrap().1.to_string(), None, *r)],
2473 ftd::TextSource::Body,
2474 )
2475 } else if matches!(kind, ftd::p2::Kind::Optional { .. }) {
2476 continue;
2477 } else if let Some(d) = d {
2478 (
2479 vec![(None, d.to_string(), None, *r)],
2480 ftd::TextSource::Default,
2481 )
2482 } else if is_reference {
2483 continue;
2484 } else {
2485 return ftd::p2::utils::e2(
2486 format!(
2487 "{} is calling {}, without a required argument 1 `{}`",
2488 fn_name, root, name
2489 ),
2490 doc.name,
2491 line_number,
2492 );
2493 }
2494 }
2495 (Err(ftd::p1::Error::NotFound { .. }), k) => {
2496 if matches!(kind, ftd::p2::Kind::Optional { .. }) {
2497 continue;
2498 }
2499
2500 if let Some(d) = k.get_default_value_str() {
2501 (
2502 vec![(None, d.to_string(), None, k.is_reference())],
2503 ftd::TextSource::Default,
2504 )
2505 } else if is_reference {
2506 continue;
2507 } else {
2508 return ftd::p2::utils::e2(
2509 format!(
2510 "{} is calling {}, without a required argument `{}`",
2511 fn_name, root, name
2512 ),
2513 doc.name,
2514 line_number,
2515 );
2516 }
2517 }
2518 (Err(e), _) => {
2519 return Err(e);
2520 }
2521 };
2522 for (idx, value, conditional_attribute, is_referenced) in conditional_vector {
2523 if kind.is_reference() && !is_referenced {
2524 return ftd::p2::utils::e2(
2525 format!(
2526 "{} is calling {}, without a referenced argument `{}`",
2527 fn_name, root, value
2528 ),
2529 doc.name,
2530 line_number,
2531 );
2532 }
2533 let mut property_value = match ftd::PropertyValue::resolve_value(
2534 line_number,
2535 value.as_str(),
2536 Some(kind.to_owned()),
2537 doc,
2538 arguments,
2539 Some(source.clone()),
2540 ) {
2541 Ok(p) => p,
2542 _ if source.eq(&ftd::TextSource::Default) => ftd::PropertyValue::resolve_value(
2543 line_number,
2544 value.as_str(),
2545 Some(kind.to_owned()),
2546 doc,
2547 root_arguments,
2548 Some(source.clone()),
2549 )?,
2550 Err(e) => return Err(e),
2551 };
2552
2553 if is_referenced {
2554 property_value.set_reference();
2555 }
2556
2557 let nested_properties = match property_value {
2558 ftd::PropertyValue::Reference { ref kind, .. }
2559 if matches!(kind.inner(), ftd::p2::Kind::UI { .. }) =>
2560 {
2561 let headers = if source.eq(&ftd::TextSource::Default) {
2562 let mut headers = Default::default();
2563 if let ftd::p2::Kind::UI {
2564 default: Some((_, h)),
2565 } = kind.inner()
2566 {
2567 headers = h.clone();
2568 }
2569 headers
2570 } else {
2571 let mut headers = vec![];
2572 if let Some(idx) = idx {
2573 let p1 = &p1.0;
2574 for i in idx + 1..p1.len() {
2575 let p1 = p1.get(i).unwrap();
2576 if let Some(k) = p1.1.strip_prefix('>') {
2577 headers.push((p1.0, k.trim().to_string(), p1.2.to_string()));
2578 } else {
2579 break;
2580 }
2581 }
2582 }
2583 ftd::p1::Header(headers)
2584 };
2585 ftd::p2::utils::structure_header_to_properties(
2586 &value,
2587 arguments,
2588 doc,
2589 line_number,
2590 &headers,
2591 )?
2592 }
2593 _ => Default::default(),
2594 };
2595
2596 let (condition_value, default_value) =
2597 if let Some(ref attribute) = conditional_attribute {
2598 let condition = ftd::p2::Boolean::from_expression(
2599 attribute,
2600 doc,
2601 arguments,
2602 (None, None),
2603 line_number,
2604 )?;
2605 (vec![(condition, property_value)], None)
2606 } else {
2607 (vec![], Some(property_value))
2608 };
2609 if let Some(property) = properties.get_mut(name) {
2610 if default_value.is_some() {
2611 property.default = default_value;
2612 } else {
2613 property.conditions.append(&mut condition_value.clone());
2614 }
2615 property.nested_properties = nested_properties;
2616 } else {
2617 let value = Property {
2618 default: default_value,
2619 conditions: condition_value,
2620 nested_properties,
2621 };
2622 properties.insert(name.to_string(), value);
2623 }
2624 }
2625 }
2626
2627 if root.eq("ftd#input") {
2629 check_input_conflicting_values(&properties, doc, line_number)?;
2630 }
2631
2632 Ok(properties)
2633}
2634
2635fn assert_caption_body_checks(
2650 root: &str,
2651 p1: &ftd::p1::Header,
2652 doc: &ftd::p2::TDoc,
2653 caption: &Option<String>,
2654 body: &Option<(usize, String)>,
2655 line_number: usize,
2656) -> ftd::p1::Result<()> {
2657 if is_it_ui(root) {
2659 return Ok(());
2660 }
2661
2662 let mut has_caption = caption.is_some();
2663 let mut has_body = body.is_some();
2664
2665 let mut properties = None;
2666 let mut header_list: Option<&ftd::p1::Header> = Some(p1);
2667
2668 let mut thing = doc.get_thing(line_number, root)?;
2669 loop {
2670 if let ftd::p2::Thing::Component(c) = thing {
2672 let local_arguments = &c.arguments;
2673
2674 check_caption_body_conflicts(
2675 &c.full_name,
2676 local_arguments,
2677 properties,
2678 header_list,
2679 doc,
2680 has_caption,
2681 has_body,
2682 line_number,
2683 )?;
2684
2685 if c.kernel || is_it_ui(&c.root) {
2687 break;
2688 }
2689
2690 thing = doc.get_thing(line_number, &c.root)?;
2692 properties = Some(c.properties.clone());
2693
2694 has_caption = false;
2696 has_body = false;
2697 header_list = None;
2698 }
2699 }
2700
2701 return Ok(());
2702
2703 fn is_it_ui(root: &str) -> bool {
2705 root.eq("ftd#ui")
2706 }
2707
2708 #[allow(clippy::too_many_arguments)]
2711 fn check_caption_body_conflicts(
2712 full_name: &str,
2713 arguments: &std::collections::BTreeMap<String, ftd::p2::Kind>,
2714 properties: Option<std::collections::BTreeMap<String, Property>>,
2715 p1: Option<&ftd::p1::Header>,
2716 doc: &ftd::p2::TDoc,
2717 has_caption: bool,
2718 has_body: bool,
2719 line_number: usize,
2720 ) -> ftd::p1::Result<()> {
2721 fn get_header_set_with_values(
2723 p1: Option<&ftd::p1::Header>,
2724 ) -> std::collections::HashSet<String> {
2725 let mut header_set = std::collections::HashSet::new();
2726
2727 if let Some(header) = p1 {
2729 for (_ln, k, v) in header.0.iter() {
2730 if !v.is_empty() {
2731 header_set.insert(k.to_string());
2732 }
2733 }
2734 }
2735
2736 header_set
2737 }
2738
2739 fn has_header_value(
2741 argument: &str,
2742 header_set: Option<&std::collections::HashSet<String>>,
2743 ) -> bool {
2744 if let Some(s) = header_set {
2745 s.contains(argument)
2746 } else {
2747 false
2748 }
2749 }
2750
2751 fn has_property_value(
2753 argument: &str,
2754 properties: &Option<std::collections::BTreeMap<String, Property>>,
2755 ) -> bool {
2756 if let Some(p) = properties {
2757 p.contains_key(argument)
2758 } else {
2759 false
2760 }
2761 }
2762
2763 let mut caption_pass = false;
2764 let mut body_pass = false;
2765 let header_set = get_header_set_with_values(p1);
2766
2767 for (arg, kind) in arguments.iter() {
2768 let inner_kind = kind.inner();
2770
2771 let has_value = has_header_value(arg, Some(&header_set));
2772 let has_property = has_property_value(arg, &properties);
2773
2774 match inner_kind {
2775 ftd::p2::Kind::String {
2776 caption,
2777 body,
2778 default,
2779 ..
2780 } => {
2781 let has_default = default.is_some();
2782 match (caption, body) {
2783 (true, true) => {
2784 if ((has_property || has_body || has_caption && has_value)
2787 && (has_caption || has_value))
2788 || (has_property && has_body)
2789 {
2790 return Err(ftd::p1::Error::ForbiddenUsage {
2791 message: format!(
2792 "pass either body or caption or header_value, ambiguity in \'{}\'",
2793 arg
2794 ),
2795 doc_id: doc.name.to_string(),
2796 line_number,
2797 });
2798 }
2799
2800 if !(has_caption
2804 || has_body
2805 || has_value
2806 || has_property
2807 || has_default
2808 || kind.is_optional())
2809 {
2810 return Err(ftd::p1::Error::MissingData {
2811 message: format!(
2812 "body or caption or header_value, none of them are passed for \'{}\'",
2813 arg
2814 ),
2815 doc_id: doc.name.to_string(),
2816 line_number,
2817 });
2818 }
2819
2820 if has_caption {
2822 caption_pass = true;
2823 }
2824
2825 if has_body {
2827 body_pass = true;
2828 }
2829 }
2830 (true, false) => {
2831 if ((has_property || has_value) && has_caption)
2834 || (has_value && has_property)
2835 {
2836 return Err(ftd::p1::Error::ForbiddenUsage {
2837 message: format!(
2838 "pass either caption or header_value for header \'{}\'",
2839 arg
2840 ),
2841 doc_id: doc.name.to_string(),
2842 line_number,
2843 });
2844 }
2845
2846 if !(has_caption
2850 || has_value
2851 || has_property
2852 || has_default
2853 || kind.is_optional())
2854 {
2855 return Err(ftd::p1::Error::MissingData {
2856 message: format!(
2857 "caption or header_value, none of them are passed for \'{}\'",
2858 arg
2859 ),
2860 doc_id: doc.name.to_string(),
2861 line_number,
2862 });
2863 }
2864
2865 if has_caption {
2867 caption_pass = true;
2868 }
2869 }
2870 (false, true) => {
2871 if ((has_property || has_value) && has_body)
2874 || (has_property && has_value)
2875 {
2876 return Err(ftd::p1::Error::ForbiddenUsage {
2877 message: format!(
2878 "pass either body or header_value for header \'{}\'",
2879 arg
2880 ),
2881 doc_id: doc.name.to_string(),
2882 line_number,
2883 });
2884 }
2885
2886 if !(has_body
2890 || has_value
2891 || has_property
2892 || has_default
2893 || kind.is_optional())
2894 {
2895 return Err(ftd::p1::Error::MissingData {
2896 message: format!(
2897 "body or header_value, none of them are passed for \'{}\'",
2898 arg
2899 ),
2900 doc_id: doc.name.to_string(),
2901 line_number,
2902 });
2903 }
2904
2905 if has_body {
2907 body_pass = true;
2908 }
2909 }
2910 (false, false) => continue,
2911 }
2912 }
2913 ftd::p2::Kind::Integer { default, .. }
2914 | ftd::p2::Kind::Decimal { default, .. }
2915 | ftd::p2::Kind::Boolean { default, .. }
2916 if arg.eq("value")
2917 && matches!(full_name, "ftd#integer" | "ftd#boolean" | "ftd#decimal") =>
2918 {
2919 let has_default = default.is_some();
2924
2925 if ((has_property || has_value) && has_caption) || (has_value && has_property) {
2927 return Err(ftd::p1::Error::ForbiddenUsage {
2928 message: format!(
2929 "pass either caption or header_value for header \'{}\'",
2930 arg
2931 ),
2932 doc_id: doc.name.to_string(),
2933 line_number,
2934 });
2935 }
2936
2937 if !(has_caption
2939 || has_value
2940 || has_property
2941 || has_default
2942 || kind.is_optional())
2943 {
2944 return Err(ftd::p1::Error::MissingData {
2945 message: format!(
2946 "caption or header_value, none of them are passed for \'{}\'",
2947 arg
2948 ),
2949 doc_id: doc.name.to_string(),
2950 line_number,
2951 });
2952 }
2953
2954 if has_caption {
2956 caption_pass = true;
2957 }
2958 }
2959 _ => continue,
2960 }
2961 }
2962
2963 if !(caption_pass && body_pass) {
2965 if !caption_pass && has_caption {
2967 return Err(ftd::p1::Error::UnknownData {
2968 message: "caption passed with no header accepting it !!".to_string(),
2969 doc_id: doc.name.to_string(),
2970 line_number,
2971 });
2972 }
2973
2974 if !body_pass && has_body {
2976 return Err(ftd::p1::Error::UnknownData {
2977 message: "body passed with no header accepting it !!".to_string(),
2978 doc_id: doc.name.to_string(),
2979 line_number,
2980 });
2981 }
2982 }
2983
2984 Ok(())
2985 }
2986}
2987
2988pub(crate) fn universal_arguments() -> ftd::Map<ftd::p2::Kind> {
2989 let mut universal_arguments: ftd::Map<ftd::p2::Kind> = Default::default();
2990 universal_arguments.insert("id".to_string(), ftd::p2::Kind::string().into_optional());
2991 universal_arguments.insert("top".to_string(), ftd::p2::Kind::integer().into_optional());
2992 universal_arguments.insert(
2993 "bottom".to_string(),
2994 ftd::p2::Kind::integer().into_optional(),
2995 );
2996 universal_arguments.insert("left".to_string(), ftd::p2::Kind::integer().into_optional());
2997 universal_arguments.insert(
2998 "move-up".to_string(),
2999 ftd::p2::Kind::integer().into_optional(),
3000 );
3001 universal_arguments.insert(
3002 "move-down".to_string(),
3003 ftd::p2::Kind::integer().into_optional(),
3004 );
3005 universal_arguments.insert(
3006 "move-left".to_string(),
3007 ftd::p2::Kind::integer().into_optional(),
3008 );
3009 universal_arguments.insert(
3010 "move-right".to_string(),
3011 ftd::p2::Kind::integer().into_optional(),
3012 );
3013 universal_arguments.insert(
3014 "right".to_string(),
3015 ftd::p2::Kind::integer().into_optional(),
3016 );
3017
3018 universal_arguments.insert("align".to_string(), ftd::p2::Kind::string().into_optional());
3019 universal_arguments.insert(
3020 "scale".to_string(),
3021 ftd::p2::Kind::decimal().into_optional(),
3022 );
3023 universal_arguments.insert(
3024 "rotate".to_string(),
3025 ftd::p2::Kind::integer().into_optional(),
3026 );
3027 universal_arguments.insert(
3028 "scale-x".to_string(),
3029 ftd::p2::Kind::decimal().into_optional(),
3030 );
3031 universal_arguments.insert(
3032 "scale-y".to_string(),
3033 ftd::p2::Kind::decimal().into_optional(),
3034 );
3035 universal_arguments.insert("slot".to_string(), ftd::p2::Kind::string().into_optional());
3036
3037 universal_arguments
3038}
3039
3040fn root_properties_from_inherits(
3041 line_number: usize,
3042 arguments: &ftd::Map<ftd::p2::Kind>,
3043 inherits: Vec<String>,
3044 doc: &ftd::p2::TDoc,
3045) -> ftd::p1::Result<ftd::Map<Property>> {
3046 let mut root_properties: ftd::Map<Property> = Default::default();
3047 for inherit in inherits {
3048 let pv = ftd::PropertyValue::resolve_value(
3049 line_number,
3050 &format!("${}", inherit),
3051 None,
3052 doc,
3053 arguments,
3054 None,
3055 )?;
3056 root_properties.insert(
3057 inherit,
3058 ftd::component::Property {
3059 default: Some(pv),
3060 conditions: vec![],
3061 ..Default::default()
3062 },
3063 );
3064 }
3065 Ok(root_properties)
3066}
3067
3068fn read_arguments(
3069 p1: &ftd::p1::Header,
3070 root: &str,
3071 root_arguments: &ftd::Map<ftd::p2::Kind>,
3072 arguments: &ftd::Map<ftd::p2::Kind>,
3073 doc: &ftd::p2::TDoc,
3074) -> ftd::p1::Result<(ftd::Map<ftd::p2::Kind>, Vec<String>)> {
3075 let mut args: ftd::Map<ftd::p2::Kind> = Default::default();
3076 let mut inherits: Vec<String> = Default::default();
3077
3078 let mut all_args = arguments.clone();
3080
3081 let universal_arguments_set: std::collections::HashSet<String> =
3083 universal_arguments().keys().cloned().collect();
3084
3085 let mut root_args_set: std::collections::HashSet<String> = std::collections::HashSet::new();
3087 for (idx, (i, k, v)) in p1.0.iter().enumerate() {
3088 if (k.starts_with('$') && k.ends_with('$')) || k.starts_with('>') {
3089 continue;
3091 }
3092
3093 let var_data = match ftd::variable::VariableData::get_name_kind(
3094 k,
3095 doc,
3096 i.to_owned(),
3097 vec![].as_slice(),
3098 ) {
3099 Ok(v) => v,
3100 _ => {
3101 if root_args_set.contains(k) {
3103 if let Some(kind) = root_arguments.get(k) {
3104 if kind.inner().is_list() {
3105 continue;
3106 }
3107 return Err(ftd::p1::Error::ForbiddenUsage {
3108 message: format!("repeated usage of \'{}\' not allowed !!", k),
3109 doc_id: doc.name.to_string(),
3110 line_number: *i,
3111 });
3112 }
3113 } else {
3114 root_args_set.insert(k.to_string());
3115 }
3116
3117 continue;
3118 }
3119 };
3120
3121 let option_v = if v.is_empty() {
3122 None
3123 } else {
3124 Some(v.to_string())
3125 };
3126
3127 let mut kind = if var_data.kind.eq("inherit") {
3128 match root_arguments.get(&var_data.name) {
3129 Some(kind) => {
3130 inherits.push(var_data.name.to_string());
3131 let default = {
3132 let mut default = option_v;
3134 if let Some(ref v) = default {
3135 default =
3136 Some(doc.resolve_reference_name(i.to_owned(), v, &all_args)?);
3137 }
3138 default
3139 };
3140 kind.clone().set_default(default)
3141 }
3142 None => {
3143 return ftd::p2::utils::e2(
3144 format!("'{}' is not an argument of {}", var_data.name, root),
3145 doc.name,
3146 i.to_owned(),
3147 )
3148 }
3149 }
3150 } else {
3151 ftd::p2::Kind::for_variable(i.to_owned(), k, option_v, doc, None, &all_args)?
3152 };
3153 if let ftd::p2::Kind::UI {
3154 default: Some((ui_id, h)),
3155 } = &mut kind.mut_inner()
3156 {
3157 let headers = {
3158 let mut headers = vec![];
3159 let p1 = &p1.0;
3160 for i in idx + 1..p1.len() {
3161 let p1 = p1.get(i).unwrap();
3162 if let Some(k) = p1.1.strip_prefix('>') {
3163 headers.push((p1.0, k.trim().to_string(), p1.2.to_string()));
3164 } else {
3165 break;
3166 }
3167 }
3168 ftd::p1::Header(headers)
3169 };
3170 *h = headers;
3171 *ui_id = doc.resolve_name(*i, ui_id.as_str())?;
3172 }
3173
3174 if args.contains_key(var_data.name.as_str()) {
3176 return Err(ftd::p1::Error::ForbiddenUsage {
3177 message: format!(
3178 "\'{}\' is already used as header name/identifier !!",
3179 &var_data.name
3180 ),
3181 doc_id: doc.name.to_string(),
3182 line_number: *i,
3183 });
3184 }
3185
3186 if universal_arguments_set.contains(&var_data.name) {
3188 return Err(ftd::p1::Error::ForbiddenUsage {
3189 message: format!(
3190 "redundant declaration of universal argument \'{}\' !!",
3191 &var_data.name
3192 ),
3193 doc_id: doc.name.to_string(),
3194 line_number: *i,
3195 });
3196 }
3197
3198 args.insert(var_data.name.to_string(), kind.clone());
3199 all_args.insert(var_data.name.to_string(), kind);
3200 }
3201
3202 Ok((args, inherits))
3203}