1use crate::*;
2use chrono::{DateTime, FixedOffset, Local};
3
4#[derive(Clone, Debug)]
8pub enum MaybeDateTime {
9 Ok(DateTime<FixedOffset>),
11 Error(String),
13}
14
15impl From<DateTime<FixedOffset>> for MaybeDateTime {
16 fn from(v: DateTime<FixedOffset>) -> Self {
17 Self::Ok(v)
18 }
19}
20
21impl FromStr for MaybeDateTime {
22 type Err = std::convert::Infallible;
23
24 fn from_str(s: &str) -> Result<Self, Self::Err> {
25 Ok(match s.parse() {
26 Ok(dt) => Self::Ok(dt),
27 Err(_) => Self::Error(s.to_owned()),
28 })
29 }
30}
31
32impl Display for MaybeDateTime {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 MaybeDateTime::Ok(dt) => write!(f, "{}", dt.to_rfc3339()),
36 MaybeDateTime::Error(s) => write!(f, "{}", s),
37 }
38 }
39}
40#[derive(Clone, Debug)]
42pub struct Asset {
43 pub contributor: Vec<Contributor>,
45 pub created: MaybeDateTime,
49 pub keywords: Vec<String>,
51 pub modified: MaybeDateTime,
55 pub revision: Option<String>,
57 pub subject: Option<String>,
59 pub title: Option<String>,
61 pub unit: Unit,
63 pub up_axis: UpAxis,
65}
66
67impl Asset {
68 pub fn new(created: DateTime<FixedOffset>, modified: DateTime<FixedOffset>) -> Self {
71 Self {
72 contributor: Default::default(),
73 created: created.into(),
74 keywords: Default::default(),
75 modified: modified.into(),
76 revision: Default::default(),
77 subject: Default::default(),
78 title: Default::default(),
79 unit: Default::default(),
80 up_axis: Default::default(),
81 }
82 }
83
84 pub fn create_now() -> Self {
86 let now = Local::now().into();
87 Self::new(now, now)
88 }
89}
90
91impl XNode for Asset {
92 const NAME: &'static str = "asset";
93
94 fn parse_box(element: &Element) -> Result<Box<Self>> {
95 debug_assert_eq!(element.name(), Self::NAME);
96 let mut it = element.children().peekable();
97 let res = Box::new(Asset {
98 contributor: Contributor::parse_list(&mut it)?,
99 created: parse_one("created", &mut it, parse_elem)?,
100 keywords: parse_opt("keywords", &mut it, parse_text)?.map_or_else(Vec::new, |s| {
101 s.split_ascii_whitespace().map(|s| s.to_owned()).collect()
102 }),
103 modified: parse_one("modified", &mut it, parse_elem)?,
104 revision: parse_opt("revision", &mut it, parse_text)?,
105 subject: parse_opt("subject", &mut it, parse_text)?,
106 title: parse_opt("title", &mut it, parse_text)?,
107 unit: Unit::parse_opt(&mut it)?.unwrap_or_default(),
108 up_axis: UpAxis::parse_opt(&mut it)?.unwrap_or_default(),
109 });
110 finish(res, it)
111 }
112
113 fn parse(element: &Element) -> Result<Self> {
114 Ok(*Self::parse_box(element)?)
115 }
116}
117
118impl XNodeWrite for Asset {
119 fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
120 let e = Self::elem().start(w)?;
121 self.contributor.write_to(w)?;
122 ElemBuilder::print("created", &self.created, w)?;
123 if !self.keywords.is_empty() {
124 ElemBuilder::print_arr("keywords", &self.keywords, w)?;
125 }
126 ElemBuilder::print("modified", &self.modified, w)?;
127 opt(&self.revision, |e| ElemBuilder::print_str("revision", e, w))?;
128 opt(&self.subject, |e| ElemBuilder::print_str("subject", e, w))?;
129 opt(&self.title, |e| ElemBuilder::print_str("title", e, w))?;
130 if self.unit != Unit::default() {
131 self.unit.write_to(w)?;
132 }
133 if self.up_axis != UpAxis::default() {
134 self.up_axis.write_to(w)?;
135 }
136 e.end(w)
137 }
138}
139
140#[derive(Clone, Default, Debug)]
142pub struct Contributor {
143 pub author: Option<String>,
145 pub authoring_tool: Option<String>,
147 pub comments: Option<String>,
149 pub copyright: Option<String>,
151 pub source_data: Option<Url>,
153}
154
155impl Contributor {
156 pub fn is_empty(&self) -> bool {
158 self.author.is_none()
159 && self.authoring_tool.is_none()
160 && self.comments.is_none()
161 && self.copyright.is_none()
162 && self.source_data.is_none()
163 }
164}
165
166impl XNode for Contributor {
167 const NAME: &'static str = "contributor";
168 fn parse(element: &Element) -> Result<Self> {
169 debug_assert_eq!(element.name(), Self::NAME);
170 let mut it = element.children().peekable();
171 let res = Contributor {
172 author: parse_opt("author", &mut it, parse_text)?,
173 authoring_tool: parse_opt("authoring_tool", &mut it, parse_text)?,
174 comments: parse_opt("comments", &mut it, parse_text)?,
175 copyright: parse_opt("copyright", &mut it, parse_text)?,
176 source_data: parse_opt("source_data", &mut it, parse_elem)?,
177 };
178 finish(res, it)
179 }
180}
181
182impl XNodeWrite for Contributor {
183 fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
184 let e = Self::elem();
185 if self.is_empty() {
186 e.end(w)
187 } else {
188 let e = e.start(w)?;
189 opt(&self.author, |e| ElemBuilder::print_str("author", e, w))?;
190 opt(&self.authoring_tool, |e| {
191 ElemBuilder::print_str("authoring_tool", e, w)
192 })?;
193 opt(&self.comments, |e| ElemBuilder::print_str("comments", e, w))?;
194 opt(&self.copyright, |e| {
195 ElemBuilder::print_str("copyright", e, w)
196 })?;
197 opt(&self.source_data, |e| {
198 ElemBuilder::print("source_data", e, w)
199 })?;
200 e.end(w)
201 }
202 }
203}
204
205#[derive(Clone, Debug, PartialEq)]
213pub struct Unit {
214 pub name: Option<String>,
217 pub meter: f32,
222}
223
224impl Default for Unit {
225 fn default() -> Self {
226 Unit {
227 name: None,
228 meter: 1.,
229 }
230 }
231}
232
233impl XNode for Unit {
234 const NAME: &'static str = "unit";
235 fn parse(element: &Element) -> Result<Self> {
236 debug_assert_eq!(element.name(), Self::NAME);
237 Ok(Unit {
238 name: match element.attr("name") {
239 None | Some("meter") => None,
240 Some(name) => Some(name.into()),
241 },
242 meter: match element.attr("meter") {
243 None => 1.,
244 Some(s) => s.parse().map_err(|_| "parse error")?,
245 },
246 })
247 }
248}
249
250impl XNodeWrite for Unit {
251 fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
252 let mut e = Self::elem();
253 e.opt_attr("name", &self.name);
254 e.def_print_attr("meter", self.meter, 1.);
255 e.end(w)
256 }
257}
258
259#[derive(Clone, Copy, Debug, PartialEq, Eq)]
262pub enum UpAxis {
263 XUp,
265 YUp,
267 ZUp,
269}
270
271impl Default for UpAxis {
272 fn default() -> Self {
273 Self::YUp
274 }
275}
276
277impl UpAxis {
278 pub fn to_str(self) -> &'static str {
280 match self {
281 Self::XUp => "X_UP",
282 Self::YUp => "Y_UP",
283 Self::ZUp => "Z_UP",
284 }
285 }
286}
287
288impl Display for UpAxis {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 Display::fmt(self.to_str(), f)
291 }
292}
293
294impl XNode for UpAxis {
295 const NAME: &'static str = "up_axis";
296 fn parse(element: &Element) -> Result<Self> {
297 debug_assert_eq!(element.name(), Self::NAME);
298 Ok(match get_text(element) {
299 Some("X_UP") => UpAxis::XUp,
300 Some("Y_UP") => UpAxis::YUp,
301 Some("Z_UP") => UpAxis::ZUp,
302 _ => return Err("invalid <up_axis> value".into()),
303 })
304 }
305}
306
307impl XNodeWrite for UpAxis {
308 fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
309 ElemBuilder::print_str(Self::NAME, self.to_str(), w)
310 }
311}