dae_parser/core/
meta.rs

1use crate::*;
2use chrono::{DateTime, FixedOffset, Local};
3
4/// Collada spec says that `created` and `modified` times should follow ISO 8601,
5/// but `chrono` asserts that this means that timezones are required and Blender
6/// doesn't seem to add them. To avoid crashing, we store the unparsed date as a string.
7#[derive(Clone, Debug)]
8pub enum MaybeDateTime {
9    /// A proper ISO 8601 date-time.
10    Ok(DateTime<FixedOffset>),
11    /// A date-time that failed to parse.
12    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/// Defines asset-management information regarding its parent element.
41#[derive(Clone, Debug)]
42pub struct Asset {
43    /// Defines data related to a contributor that worked on the parent element.
44    pub contributor: Vec<Contributor>,
45    /// Contains date and time that the parent element was created.
46    /// Represented in an ISO 8601 format as per the XML Schema
47    /// `dateTime` primitive type.
48    pub created: MaybeDateTime,
49    /// Contains a list of words used as search criteria for the parent element.
50    pub keywords: Vec<String>,
51    /// Contains date and time that the parent element was last
52    /// modified. Represented in an ISO 8601 format as per the
53    /// XML Schema `dateTime` primitive type.
54    pub modified: MaybeDateTime,
55    /// Contains revision information for the parent element.
56    pub revision: Option<String>,
57    /// Contains a description of the topical subject of the parent element.
58    pub subject: Option<String>,
59    /// Contains title information for the parent element.
60    pub title: Option<String>,
61    /// Defines unit of distance for COLLADA elements and objects.
62    pub unit: Unit,
63    /// Contains descriptive information about the coordinate system of the geometric data.
64    pub up_axis: UpAxis,
65}
66
67impl Asset {
68    /// Create a new `Asset` with the given creation and modification dates
69    /// and defaulting everything else.
70    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    /// Create a new `Asset` object representing an object created at the current date/time.
85    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/// Defines authoring information for asset management.
141#[derive(Clone, Default, Debug)]
142pub struct Contributor {
143    /// The author’s name
144    pub author: Option<String>,
145    /// The name of the authoring tool
146    pub authoring_tool: Option<String>,
147    /// Comments from this contributor
148    pub comments: Option<String>,
149    /// Copyright information
150    pub copyright: Option<String>,
151    /// A reference to the source data used for this asset
152    pub source_data: Option<Url>,
153}
154
155impl Contributor {
156    /// Does this element contain no data?
157    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/// Defines unit of distance for COLLADA elements and objects.
206/// This unit of distance applies to all spatial measurements
207/// within the scope of `Asset`’s parent element, unless
208/// overridden by a more local `Asset` / `Unit`.
209///
210/// The value of the unit is self-describing and does not have to be consistent
211/// with any real-world measurement.
212#[derive(Clone, Debug, PartialEq)]
213pub struct Unit {
214    /// The name of the distance unit. For example, "meter", "centimeter", "inches",
215    /// or "parsec". This can be the real name of a measurement, or an imaginary name.
216    pub name: Option<String>,
217    /// How many real-world meters in one
218    /// distance unit as a floating-point number. For
219    /// example, 1.0 for the name "meter"; 1000 for the
220    /// name "kilometer"; 0.3048 for the name "foot".
221    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/// Descriptive information about the coordinate system of the geometric data.
260/// All coordinates are right-handed by definition.
261#[derive(Clone, Copy, Debug, PartialEq, Eq)]
262pub enum UpAxis {
263    /// Right: `-y`, Up: `+x`, In: `+z`
264    XUp,
265    /// Right: `+x`, Up: `+y`, In: `+z`
266    YUp,
267    /// Right: `+x`, Up: `+z`, In: `-y`
268    ZUp,
269}
270
271impl Default for UpAxis {
272    fn default() -> Self {
273        Self::YUp
274    }
275}
276
277impl UpAxis {
278    /// The XML name of a value in this enumeration.
279    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}