ftd/
component.rs

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    /// returns the value as string from property.default
134    ///
135    /// returns empty string in case if it's None
136    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        // In case markup the behaviour of container_children is not the same.
242        // They act as the component variables which are, then, referred to in markup text
243        // container_children copy there properties to the reference in markup text
244
245        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            // NOTE: doing unwrap to force bug report if we following fails, this function
275            // must have validated everything, and must not fail at run time
276            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            // todo: fix it for all kind (Arpita)
336            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            // doc.local_variables.remove(loop_name.as_str());
463            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            // NOTE: doing unwrap to force bug report if we following fails, this function
490            // must have validated everything, and must not fail at run time
491            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            // ftd#markup modifies the child
691            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    /// ftd#markup modifies the child because root name contains both the component and also the variable name
714    /// Like this: --- <component-name> <variable-name>:
715    /// Example: --- ftd.text name:
716    /// We need to remove variable name before passing it to instruction and later append it to resolve variables
717    /// in ftd#markup.
718    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    /// first convert the container children into the named container.
731    /// which basically make a map of component's variable name with the container_child.
732    /// which was initially  seperated by `get_modified_child()`
733    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
757/// In case markup the behaviour of container_children is not the same.
758/// They act as the component variables which are, then, referred to in markup text
759/// container_children copy there properties to the reference in markup text
760fn 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        // no need to re-evalute
767        // already evaluted
768        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    /// Get text between `{` and `}`
880    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                    // In case of component variable of markup defined internally,
975                    // it won't be present inside doc.bag
976                    // Example:
977                    // -- ftd.text foo: {bar: Hello}
978                    // --- ftd.text bar:
979                    // color: red
980                    //
981                    // `bar` here won't be present inside doc.bag
982                    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                            // todo: send default value
1192                            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        // Extend the local arguments with universal arguments
1716        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                // NOTE: doing unwrap to force bug report if we following fails, this function
1913                // must have validated everything, and must not fail at run time
1914                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
2362/// Throws error if the user specifies both value and default-value for ftd.input
2363/// otherwise returns Ok(())
2364///
2365/// # No error in these cases
2366///
2367/// ```markup
2368/// -- ftd.input:
2369/// value: v1
2370///
2371/// -- ftd.input:
2372/// default-value: d1
2373/// ```
2374///
2375/// # Error in this case
2376///
2377/// ```markup
2378/// -- ftd.input:
2379/// value: v2
2380/// default-value: d2
2381/// ```
2382fn 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    // Checking if the user has entered conflicting values for ftd.input
2628    if root.eq("ftd#input") {
2629        check_input_conflicting_values(&properties, doc, line_number)?;
2630    }
2631
2632    Ok(properties)
2633}
2634
2635/// asserts caption-body checks on components.
2636///
2637/// It includes
2638///
2639/// # Caption/Body/Header_value conflicts
2640/// - This happens if any argument accepts data from more than one way
2641/// and the user doesn't pass this data in exactly one way
2642///
2643/// # Missing data checks
2644/// - This happens if any (required) argument doesn't get the data from any way it takes
2645///
2646/// # Unknown data checks
2647/// - This happens when there is no argument to accept the data passed from caption/body
2648///
2649fn 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    // No checks on ftd#ui
2658    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        // Either the component is kernel or variable/derived component
2671        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            // stop checking once you hit the top-most kernel component or ftd#ui component
2686            if c.kernel || is_it_ui(&c.root) {
2687                break;
2688            }
2689
2690            // get the parent component and do the same checks
2691            thing = doc.get_thing(line_number, &c.root)?;
2692            properties = Some(c.properties.clone());
2693
2694            // These things are only available to the lowest level component
2695            has_caption = false;
2696            has_body = false;
2697            header_list = None;
2698        }
2699    }
2700
2701    return Ok(());
2702
2703    /// checks if the root == ftd#ui
2704    fn is_it_ui(root: &str) -> bool {
2705        root.eq("ftd#ui")
2706    }
2707
2708    /// checks for body and caption conflicts using the given header list,
2709    /// arguments and properties map of the component
2710    #[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        /// returns a hashset`<key>` of header keys which have non-empty values
2722        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            // For Some(header = p1) we need to make a set of headers with values
2728            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        /// checks if the hashset of headers has this particular argument or not
2740        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        /// checks if the argument has been passed down as property
2752        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            // in case the kind is optional
2769            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                            // accepts data from either body or caption or header_value
2785                            // if passed by 2 or more ways then throw error
2786                            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                            // check if data is available in exactly one way
2801                            // also avoid throwing error when argument is optional kind or has default value
2802                            // and no data is passed in any way
2803                            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                            // check if caption is utilized if passed
2821                            if has_caption {
2822                                caption_pass = true;
2823                            }
2824
2825                            // check if body is utilized if passed
2826                            if has_body {
2827                                body_pass = true;
2828                            }
2829                        }
2830                        (true, false) => {
2831                            // check if the component has caption or header_value (not both)
2832                            // if data conflicts from any 2 ways
2833                            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                            // check if data is available from either caption/header_value
2847                            // also avoid throwing error when argument is optional kind or has default value
2848                            // and no data is passed in any way
2849                            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                            // check if caption is utilized if passed
2866                            if has_caption {
2867                                caption_pass = true;
2868                            }
2869                        }
2870                        (false, true) => {
2871                            // check if the component has body or not
2872                            // if body is not passed throw error
2873                            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                            // check if data is available from either body/header_value
2887                            // also avoid throwing error when argument is optional kind or has default value
2888                            // and no data is passed in any way
2889                            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                            // check if body is utilized if passed
2906                            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                    // checks on ftd.integer, ftd.decimal, ftd.boolean
2920                    // these components take data from either caption or
2921                    // header_value when invoked or when data is passed to it
2922                    // from any variable component
2923                    let has_default = default.is_some();
2924
2925                    // check if data conflicts from any 2 two ways
2926                    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                    // check if data is available in exactly one way
2938                    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                    // check if caption is utilized if passed
2955                    if has_caption {
2956                        caption_pass = true;
2957                    }
2958                }
2959                _ => continue,
2960            }
2961        }
2962
2963        // check if both caption and body is utilized
2964        if !(caption_pass && body_pass) {
2965            // if caption is passed and caption not utilized then throw error
2966            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 is passed and body not utilized then throw error
2975            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    // contains parent arguments and current arguments
3079    let mut all_args = arguments.clone();
3080
3081    // Set of all universal arguments available to all components
3082    let universal_arguments_set: std::collections::HashSet<String> =
3083        universal_arguments().keys().cloned().collect();
3084
3085    // Set of root arguments which are invoked once
3086    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            // event and loop matches
3090            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                // Duplicate header usage check
3102                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                        // resolve the default value
3133                        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        // Duplicate header definition check
3175        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        // checking if any universal argument is declared by the user (forbidden)
3187        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}