xmlity/
macros.rs

1/// Construct an `XmlValue` from an XML-like literal.
2///
3/// ```
4/// # use xmlity::xml;
5/// #
6/// let value/*: xmlity:::value::XmlElement*/ = xml!(
7///     <"root">[
8///         <"child" "attribute"="value">["Text"]</"child">
9///         <![CDATA["CData content"]]>
10///         <?"pi-target" "instruction"?>
11///         <!-- "Comment" -->
12///        "Text node"
13///        <"child2":"http://example.com" "attr1"="value1" "attr2"="value2">[]</"child2">
14///     ]</"root">
15/// );
16/// ```
17///
18/// The macro supports the following XML constructs:
19/// - Elements with attributes
20/// - Text nodes
21/// - CDATA sections
22/// - Processing instructions
23/// - Comments
24/// - Sequences of children
25///
26/// The macro will automatically choose between an `XmlSeq` and a value-type depending on if the top-level element is a sequence or not.
27/// For example, the value above is an `XmlElement` with a sequence of children, while the example below returns an `XmlSeq`:
28///
29/// ```
30/// # use xmlity::xml;
31/// #
32/// let value/*: xmlity::value::XmlSeq<_>*/ = xml!(
33///     <"child" "attribute"="value">["Text"]</"child">
34///     <"child" "attribute"="value">["Text"]</"child">
35/// );
36/// ```
37#[macro_export]
38macro_rules! xml {
39    // Hide distracting implementation details from the generated rustdoc.
40    ($($xml:tt)+) => {
41        $crate::xml_internal!($($xml)+)
42    };
43}
44
45// Changes are fine as long as `xml_internal!` does not call any new helper
46// macros and can still be invoked as `xml_internal!($($xml)+)`.
47#[macro_export]
48#[doc(hidden)]
49macro_rules! xml_internal {
50    (@childseq_wrapper [$first_seq_element:expr$(,$($seq_elements:expr),+)?] [$($wrapped_elements:expr,)*]) => {
51        $crate::xml_internal!(@childseq_wrapper [$($seq_elements,)*] [$($wrapped_elements,)* $crate::value::XmlChild::from($first_seq_element)])
52    };
53    (@childseq_wrapper [] [$($wrapped_elements:expr,)*]) => {
54        $crate::value::XmlSeq::from_vec(vec![$($wrapped_elements)*])
55    };
56    // @seq
57    // $unwrapped_if_single = true/false
58    // $element_type = @child or @element
59    // [$($seq_elements:expr),*] = [$seq_element, ...]
60    // $($rest:tt)* = rest of the input
61
62    // Basic types (comments, cdata, pi)
63    (@seq $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] <!--$comment:literal--> $($rest:tt)*) => {
64        $crate::xml_internal!(@seq $unwrapped_if_single $element_type [$($seq_elements,)* $crate::value::XmlComment::new($comment.as_bytes())] $($rest)*)
65    };
66    (@seq $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] <![CDATA[$cdata:literal]]> $($rest:tt)*) => {
67        $crate::xml_internal!(@seq $unwrapped_if_single $element_type [$($seq_elements,)* $crate::value::XmlCData::new($cdata.as_bytes())] $($rest)*)
68    };
69    (@seq $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] <?$target:literal $content:literal?> $($rest:tt)*) => {
70        $crate::xml_internal!(@seq $unwrapped_if_single $element_type [$($seq_elements,)* $crate::value::XmlProcessingInstruction::new($target.as_bytes(), $content.as_bytes())] $($rest)*)
71    };
72
73    // Elements
74    // Element adding attribute with namespace
75    (@seqelem $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] $element_name:literal {$element_namespace:expr} [$($attributes:expr),*] $attribute_name:literal:$attribute_namespace:literal = $attribute_value:literal $($rest:tt)*) => {
76        $crate::xml_internal!(@seqelem $unwrapped_if_single $element_type [$($seq_elements),*] $element_name {$element_namespace} [$($attributes,)* $crate::value::XmlAttribute::new(
77            $crate::ExpandedNameBuf::new(<$crate::LocalNameBuf as std::str::FromStr>::from_str($attribute_name).unwrap(), Some(<$crate::XmlNamespaceBuf as std::str::FromStr>::from_str($attribute_namespace).unwrap())),
78            $attribute_value
79        )] $($rest)*)
80    };
81    // Element adding attribute without namespace
82    (@seqelem $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] $element_name:literal {$element_namespace:expr} [$($attributes:expr),*] $attribute_name:literal = $attribute_value:literal $($rest:tt)*) => {
83        $crate::xml_internal!(@seqelem $unwrapped_if_single $element_type [$($seq_elements),*] $element_name {$element_namespace} [$($attributes,)* $crate::value::XmlAttribute::new(
84            $crate::ExpandedNameBuf::new(<$crate::LocalNameBuf as std::str::FromStr>::from_str($attribute_name).unwrap(), $element_namespace),
85            $attribute_value
86        )] $($rest)*)
87    };
88    // Element end without children
89    (@seqelem $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] $element_name:literal {$element_namespace:expr} [$($attributes:expr),*] /> $($rest:tt)*) => {
90        $crate::xml_internal!(@seq $unwrapped_if_single $element_type [$($seq_elements,)* $crate::value::XmlElement::new(
91            $crate::ExpandedNameBuf::new(<$crate::LocalNameBuf as std::str::FromStr>::from_str($element_name).unwrap(), $element_namespace),
92        ).with_attributes::<$crate::value::XmlAttribute, _>(vec![$($attributes),*])] $($rest)*)
93    };
94    // Element end with children
95    (@seqelem $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] $element_name:literal {$element_namespace:expr} [$($attributes:expr),*] >[ $($children:tt)* ]</$name2:literal> $($rest:tt)*) => {
96        $crate::xml_internal!(@seq $unwrapped_if_single $element_type [$($seq_elements,)* {
97            assert_eq!($element_name, $name2, "Starting and ending element names must match");
98            $crate::value::XmlElement::new(
99                $crate::ExpandedNameBuf::new(<$crate::LocalNameBuf as std::str::FromStr>::from_str($element_name).unwrap(), $element_namespace),
100            ).with_attributes::<$crate::value::XmlAttribute, _>(vec![$($attributes),*])
101            .with_children::<$crate::value::XmlChild, _>($crate::xml_internal!(@seq false "child" [] $($children)*))
102        }] $($rest)*)
103    };
104
105    // Element start
106    //With namespace
107    (@seq $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] <$element_name:literal : $element_namespace:literal $($rest:tt)*) => {
108        $crate::xml_internal!(@seqelem $unwrapped_if_single $element_type [$($seq_elements),*] $element_name {Some(<$crate::XmlNamespaceBuf as std::str::FromStr>::from_str($element_namespace).unwrap())} [] $($rest)*)
109    };
110    //Without namespace
111    (@seq $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] <$element_name:literal $($rest:tt)*) => {
112        $crate::xml_internal!(@seqelem $unwrapped_if_single $element_type [$($seq_elements),*] $element_name {None} [] $($rest)*)
113    };
114
115    // Text
116    (@seq $unwrapped_if_single:tt $element_type:tt [$($seq_elements:expr),*] $text:literal $($rest:tt)*) => {
117        $crate::xml_internal!(@seq $unwrapped_if_single $element_type [$($seq_elements,)* $crate::value::XmlText::new($text)] $($rest)*)
118    };
119
120    // Sequence ends
121    // Ends sequence if single element
122    (@seq true $element_type:tt [$seq_element:expr]) => {
123        $seq_element
124    };
125    (@seq $unwrapped_if_single:tt "child" [$($seq_elements:expr),*]) => {
126        <$crate::value::XmlSeq<$crate::value::XmlChild> as FromIterator<$crate::value::XmlChild>>::from_iter([$($crate::value::XmlChild::from($seq_elements)),*])
127    };
128    (@seq $unwrapped_if_single:tt "value" [$($seq_elements:expr),*]) => {
129        <$crate::value::XmlSeq<$crate::value::XmlValue> as FromIterator<$crate::value::XmlValue>>::from_iter([$($crate::value::XmlValue::from($seq_elements)),*])
130    };
131
132    // Main entry point for the xml! macro.
133    (<!--$comment:literal--> $($rest:tt)*) => {
134        $crate::xml_internal!(@seq true "value" [] <!--$comment--> $($rest)*)
135    };
136    (<?$target:literal $content:literal?> $($rest:tt)*) => {
137        $crate::xml_internal!(@seq true "value" [] <?$target $content?> $($rest)*)
138    };
139    (<![CDATA[$cdata:literal]]> $($rest:tt)*) => {
140        $crate::xml_internal!(@seq true "value" [] <![CDATA[$cdata]]> $($rest)*)
141    };
142    (<$element_name:literal : $namespace:literal $($rest:tt)*) => {
143        $crate::xml_internal!(@seq true "value" [] <$element_name : $namespace $($rest)*)
144    };
145    (<$element_name:literal $($rest:tt)*) => {
146        $crate::xml_internal!(@seq true "value" [] <$element_name $($rest)*)
147    };
148    ($text:literal $($rest:tt)*) => {
149        $crate::xml_internal!(@seq true "value" [] $text $($rest)*)
150    };
151}