1#[derive(hard_xml::XmlWrite, hard_xml::XmlRead, Default, PartialEq, Clone, Debug)]
14#[xml(tag = "Extensions")]
15pub struct Extensions {
16 #[xml(child = "Extension", default)]
18 pub extensions: Vec<Extension>,
19}
20
21#[derive(Default, PartialEq, Clone, Debug)]
36pub struct Extension {
37 pub mime_type: Option<String>,
39
40 pub xml: String,
42}
43
44impl Extension {
45 pub fn from_struct<T: hard_xml::XmlWrite>(
46 mime_type: &str,
47 value: &T,
48 ) -> hard_xml::XmlResult<Self> {
49 Ok(Self {
50 mime_type: Some(mime_type.to_owned()),
51 xml: hard_xml::XmlWrite::to_string(value)?,
52 })
53 }
54
55 pub fn as_struct<'a, 'b, T>(&'a self) -> hard_xml::XmlResult<T>
56 where
57 T: hard_xml::XmlRead<'b>,
58 'a: 'b,
59 {
60 <T as hard_xml::XmlRead<'b>>::from_str(&self.xml)
61 }
62}
63
64impl hard_xml::XmlWrite for Extension {
65 fn to_writer<W: std::io::Write>(
66 &self,
67 writer: &mut hard_xml::XmlWriter<W>,
68 ) -> hard_xml::XmlResult<()> {
69 writer.write_element_start("Extension")?;
70 if let Some(ref attr) = self.mime_type {
71 writer.write_attribute("type", attr)?;
72 }
73 writer.write_element_end_open()?;
74 write!(writer.inner, "{}", self.xml)?;
75 writer.write_element_end_close("Extension")?;
76 Ok(())
77 }
78}
79
80impl<'a> hard_xml::XmlRead<'a> for Extension {
81 fn from_reader(reader: &mut hard_xml::XmlReader<'a>) -> hard_xml::XmlResult<Self> {
82 use hard_xml::xmlparser::{ElementEnd, Token};
83
84 reader.read_till_element_start("Extension")?;
85
86 let mut mime_type = None;
87 while let Some((name, value)) = reader.find_attribute()? {
88 if name == "type" {
89 mime_type = Some(match value {
90 std::borrow::Cow::Borrowed(s) => s.to_owned(),
91 std::borrow::Cow::Owned(s) => s,
92 });
93 }
94 }
95
96 if let Some(t) = reader.next() {
97 let t = t?;
98 if !matches!(
99 t,
100 Token::ElementEnd {
101 end: ElementEnd::Open,
102 ..
103 }
104 ) {
105 return Err(hard_xml::XmlError::UnexpectedToken {
106 token: format!("{t:?}"),
107 });
108 }
109 }
110
111 let mut depth = 0;
112 let mut xml = String::new();
113
114 while let Some(t) = reader.next() {
115 match t? {
116 Token::Declaration { span, .. }
117 | Token::ProcessingInstruction { span, .. }
118 | Token::Comment { span, .. }
119 | Token::DtdStart { span, .. }
120 | Token::EmptyDtd { span, .. }
121 | Token::EntityDeclaration { span, .. }
122 | Token::DtdEnd { span }
123 | Token::ElementStart { span, .. }
124 | Token::Cdata { span, .. } => xml.push_str(span.as_str()),
125 Token::Attribute { span, .. } => {
126 xml.push(' ');
127 xml.push_str(span.as_str())
128 }
129 Token::Text { text } => xml.push_str(text.as_str()),
130 Token::ElementEnd { end, span } => {
131 match end {
132 ElementEnd::Open => {
133 depth += 1;
134 }
135 ElementEnd::Close(_, name) => {
136 if depth == 0 && name.as_str() == "Extension" {
137 break;
138 } else {
139 depth -= 1;
140 }
141 }
142 ElementEnd::Empty => {}
143 };
144 xml.push_str(span.as_str())
145 }
146 }
147 }
148
149 Ok(Self { mime_type, xml })
150 }
151}
152
153#[cfg(test)]
154#[test]
155fn test_extension_attribute_flexible() {
156 let xml = r#"<Extension type="application/xml" anyAttribute1="value1">
157 <CreativeExtension type="text/javascript">
158 <![CDATA[
159 <Ext>
160 <Some id="hoge">fuga</Some>
161 </Ext>
162 ]]>
163 </CreativeExtension>
164 <CreativeExtension type="text/javascript">
165 <Ext>
166 <Some id="hoge">fuga</Some>
167 </Ext>
168 <Extension>hoge</Extension>
169 </CreativeExtension>
170 </Extension>"#;
171 assert!(crate::from_str::<Extension>(xml).is_ok());
172}
173
174crate::declare_test!(
175 test_extension,
176 Extension,
177 r#"<Extension>
178 <CreativeExtension type="text/javascript">
179 <![CDATA[
180 <Ext>
181 <Some id="hoge">fuga</Some>
182 </Ext>
183 ]]>
184 </CreativeExtension>
185 <CreativeExtension type="text/javascript">
186 <Ext>
187 <Some id="hoge">fuga</Some>
188 </Ext>
189 <Extension>hoge</Extension>
190 </CreativeExtension>
191 </Extension>"#,
192 Extension {
193 mime_type: None,
194 xml: r#"
195 <CreativeExtension type="text/javascript">
196 <![CDATA[
197 <Ext>
198 <Some id="hoge">fuga</Some>
199 </Ext>
200 ]]>
201 </CreativeExtension>
202 <CreativeExtension type="text/javascript">
203 <Ext>
204 <Some id="hoge">fuga</Some>
205 </Ext>
206 <Extension>hoge</Extension>
207 </CreativeExtension>
208 "#
209 .into()
210 }
211);