dae_parser/
lib.rs

1//! # Collada parser
2//!
3//! This is a parser for the Collada (`.dae`) format, used for interchange between 3D renderers
4//! and games. Compared to the [`collada`](https://crates.io/crates/collada) crate,
5//! this crate attempts to more directly represent the Collada data model, and it is also
6//! significantly more complete. It supports both reading and writing.
7//!
8//! ## Usage
9//!
10//! The main entry point is the [`Document`] type, which has a [`FromStr`] implementation to convert
11//! literal strings / slices, or [`Document::from_file`] to read from a `.dae` file on disk.
12//!
13//! Collada documents are parsed eagerly, validating everything according to the
14//! [COLLADA schema](https://www.khronos.org/files/collada_spec_1_4.pdf).
15//! Once parsed, the data structures (structs and enums) can be navigated directly,
16//! as all the data structures are public, and reflect the XML schema closely.
17//!
18//! This library implements only version 1.4.1 of the Collada spec, although it may be expanded
19//! in the future. (Please open an issue or PR if you find anything missing from the spec,
20//! or if you have a use case for a later version.)
21//!
22//! ```
23//! use std::str::FromStr;
24//! use dae_parser::*;
25//!
26//! let dae_file = r##"<?xml version="1.0" encoding="utf-8"?>
27//! <COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
28//!   <asset>
29//!     <created>1970-01-01T00:00:00Z</created>
30//!     <modified>1970-01-01T00:00:00Z</modified>
31//!   </asset>
32//!   <library_geometries>
33//!     <geometry id="Cube-mesh" name="Cube">
34//!       <mesh>
35//!         <source id="Cube-mesh-positions">
36//!           <float_array id="Cube-mesh-positions-array" count="18">
37//!             1 1 1 1 -1 1 1 -1 -1 -1 1 1 -1 -1 1 -1 -1 -1
38//!           </float_array>
39//!           <technique_common>
40//!             <accessor source="#Cube-mesh-positions-array" count="6" stride="3">
41//!               <param name="X" type="float"/>
42//!               <param name="Y" type="float"/>
43//!               <param name="Z" type="float"/>
44//!             </accessor>
45//!           </technique_common>
46//!         </source>
47//!         <vertices id="Cube-mesh-vertices">
48//!           <input semantic="POSITION" source="#Cube-mesh-positions"/>
49//!         </vertices>
50//!         <triangles material="Material-material" count="4">
51//!           <input semantic="VERTEX" source="#Cube-mesh-vertices" offset="0"/>
52//!           <p>3 1 0 1 5 2 3 4 1 1 4 5</p>
53//!         </triangles>
54//!       </mesh>
55//!     </geometry>
56//!   </library_geometries>
57//! </COLLADA>"##;
58//!
59//! let document = Document::from_str(dae_file).unwrap();
60//! let cube = document.local_map::<Geometry>().unwrap().get_str("Cube-mesh").unwrap();
61//! let sources_map = document.local_map::<Source>().unwrap();
62//! let vertices_map = document.local_map::<Vertices>().unwrap();
63//! // sources.get("Cube-mesh-positions").unwrap();
64//! assert_eq!(cube.id.as_ref().unwrap(), "Cube-mesh");
65//! let mesh = cube.element.as_mesh().unwrap();
66//! let tris = mesh.elements[0].as_triangles().unwrap();
67//! assert_eq!(
68//!     tris.data.as_deref().unwrap(),
69//!     &[3, 1, 0, 1, 5, 2, 3, 4, 1, 1, 4, 5]
70//! );
71//! assert_eq!(tris.inputs[0].semantic, Semantic::Vertex);
72//! let vertices = vertices_map.get_raw(&tris.inputs[0].source).unwrap();
73//! assert_eq!(vertices.id, "Cube-mesh-vertices");
74//! let source = sources_map
75//!     .get_raw(&vertices.position_input().source)
76//!     .unwrap();
77//! assert_eq!(source.id.as_deref(), Some("Cube-mesh-positions"));
78//! ```
79//! ## License
80//!
81//! Licensed under either of
82//!
83//!  * Apache License, Version 2.0
84//!    ([LICENSE-APACHE](LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
85//!  * MIT license
86//!    ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
87//!
88//! at your option.
89//!
90//! ## Contribution
91//!
92//! Unless you explicitly state otherwise, any contribution intentionally submitted
93//! for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
94//! dual licensed as above, without any additional terms or conditions.
95
96#![deny(unsafe_code)]
97#![warn(
98    missing_docs,
99    missing_debug_implementations,
100    missing_copy_implementations,
101    trivial_casts,
102    trivial_numeric_casts,
103    unused_import_braces,
104    unused_qualifications
105)]
106
107#[macro_use]
108mod macros;
109mod api;
110mod core;
111mod fx;
112mod physics;
113mod url;
114
115use std::convert::{TryFrom, TryInto};
116use std::fmt::{Debug, Display};
117use std::io::{BufRead, BufReader, Write};
118use std::ops::Deref;
119use std::path::Path;
120use std::str::FromStr;
121
122pub use crate::{api::*, core::*, fx::*, physics::*};
123use minidom::quick_xml::events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event};
124pub use minidom::Element;
125pub use url::Url;
126
127type XReader<R> = minidom::quick_xml::Reader<R>;
128type XWriter<R> = minidom::quick_xml::Writer<R>;
129
130/// The main error type used by this library.
131#[derive(Debug)]
132pub enum Error {
133    /// An error during XML parsing.
134    Minidom(minidom::Error),
135    /// A generic error given by a string.
136    Other(&'static str),
137    /// A generic error given by a string.
138    Str(String),
139}
140
141impl From<std::io::Error> for Error {
142    fn from(v: std::io::Error) -> Self {
143        Self::Minidom(v.into())
144    }
145}
146
147impl From<minidom::Error> for Error {
148    fn from(v: minidom::Error) -> Self {
149        Self::Minidom(v)
150    }
151}
152
153impl From<minidom::quick_xml::Error> for Error {
154    fn from(v: minidom::quick_xml::Error) -> Self {
155        Self::Minidom(v.into())
156    }
157}
158
159impl From<&'static str> for Error {
160    fn from(v: &'static str) -> Self {
161        Self::Other(v)
162    }
163}
164
165impl From<String> for Error {
166    fn from(v: String) -> Self {
167        Self::Str(v)
168    }
169}
170
171type Result<T, E = Error> = std::result::Result<T, E>;
172
173type ElementIter<'a> = std::iter::Peekable<minidom::Children<'a>>;
174
175fn get_text(element: &Element) -> Option<&str> {
176    let mut it = element.nodes();
177    let text = match it.next() {
178        None => "",
179        Some(s) => s.as_text()?,
180    };
181    if it.next().is_some() {
182        return None;
183    }
184    Some(text)
185}
186
187fn parse_text(element: &Element) -> Result<String> {
188    Ok(get_text(element).ok_or("expecting a text node")?.to_owned())
189}
190
191fn parse_array<T: FromStr>(e: &Element) -> Result<Box<[T]>> {
192    get_text(e)
193        .ok_or("expected text node")?
194        .split_ascii_whitespace()
195        .map(|s| s.parse())
196        .collect::<Result<_, _>>()
197        .map_err(|_| "parse error".into())
198}
199
200fn parse_array_n<T: FromStr, const N: usize>(element: &Element) -> Result<Box<[T; N]>> {
201    Ok(parse_array(element)?
202        .try_into()
203        .map_err(|_| "unexpected number of elements")?)
204}
205
206fn parse_elem<T: FromStr>(e: &Element) -> Result<T> {
207    get_text(e)
208        .ok_or("expected text node")?
209        .parse()
210        .map_err(|_| "parse error".into())
211}
212
213fn parse_attr<T: FromStr>(attr: Option<&str>) -> Result<Option<T>> {
214    Ok(match attr {
215        None => None,
216        Some(s) => Some(s.parse().map_err(|_| "parse failure")?),
217    })
218}
219
220fn parse_one<'a, T>(
221    name: &str,
222    it: &mut impl Iterator<Item = &'a Element>,
223    f: impl FnOnce(&'a Element) -> Result<T>,
224) -> Result<T> {
225    let e = it.next().ok_or_else(|| format!("expected <{}>", name))?;
226    if e.name() != name {
227        return Err(format!("expected <{}>", name).into());
228    }
229    f(e)
230}
231
232fn parse_one_many<'a, T>(
233    it: &mut impl Iterator<Item = &'a Element>,
234    f: impl FnOnce(&'a Element) -> Result<Option<T>>,
235) -> Result<T> {
236    let e = it.next().ok_or("expected element")?;
237    Ok(f(e)?.ok_or("expected element")?)
238}
239
240fn parse_opt<'a, T>(
241    name: &str,
242    it: &mut ElementIter<'a>,
243    f: impl FnOnce(&'a Element) -> Result<T>,
244) -> Result<Option<T>> {
245    let mut res = None;
246    if let Some(&e) = it.peek() {
247        if e.name() == name {
248            res = Some(f(e)?);
249            it.next();
250        }
251    }
252    Ok(res)
253}
254
255fn parse_opt_many<'a, T>(
256    it: &mut ElementIter<'a>,
257    f: impl FnOnce(&'a Element) -> Result<Option<T>>,
258) -> Result<Option<T>> {
259    let res = match it.peek() {
260        None => None,
261        Some(&e) => f(e)?,
262    };
263    if res.is_some() {
264        it.next();
265    }
266    Ok(res)
267}
268
269fn parse_list<'a, T>(
270    name: &str,
271    it: &mut ElementIter<'a>,
272    mut f: impl FnMut(&'a Element) -> Result<T>,
273) -> Result<Vec<T>> {
274    parse_list_many(it, |e| {
275        Ok(if e.name() == name { Some(f(e)?) } else { None })
276    })
277}
278
279fn finish<'a, T>(t: T, mut it: impl Iterator<Item = &'a Element>) -> Result<T> {
280    if let Some(e) = it.next() {
281        return Err(format!("unexpected node <{}>", e.name()).into());
282    }
283    Ok(t)
284}
285
286fn parse_list_many<'a, T>(
287    it: &mut ElementIter<'a>,
288    mut f: impl FnMut(&'a Element) -> Result<Option<T>>,
289) -> Result<Vec<T>> {
290    let mut res = vec![];
291    while let Some(&e) = it.peek() {
292        match f(e)? {
293            Some(t) => res.push(t),
294            None => break,
295        }
296        it.next();
297    }
298    Ok(res)
299}
300
301fn print_str(s: &str, w: &mut XWriter<impl Write>) -> Result<()> {
302    Ok(w.write_event(Event::Text(BytesText::new(s)))?)
303}
304
305fn print_elem<T: Display>(elem: &T, w: &mut XWriter<impl Write>) -> Result<()> {
306    print_str(&format!("{}", elem), w)
307}
308
309#[inline]
310fn opt<'a, T, E>(elem: &'a Option<T>, f: impl FnOnce(&'a T) -> Result<(), E>) -> Result<(), E> {
311    if let Some(elem) = elem {
312        f(elem)?
313    }
314    Ok(())
315}
316
317#[inline]
318fn many<'a, T, E>(elem: &'a [T], f: impl FnMut(&'a T) -> Result<(), E>) -> Result<(), E> {
319    elem.iter().try_for_each(f)
320}
321
322fn arr_to_string<T: Display>(elem: &[T]) -> Option<String> {
323    let (e1, rest) = elem.split_first()?;
324    let mut s = format!("{}", e1);
325    for e in rest {
326        use std::fmt::Write;
327        write!(s, " {}", e).expect("can't fail")
328    }
329    Some(s)
330}
331fn print_arr<T: Display>(elem: &[T], w: &mut XWriter<impl Write>) -> Result<()> {
332    opt(&arr_to_string(elem), |s| print_str(s, w))
333}
334
335#[doc(hidden)]
336#[derive(Debug)]
337pub struct ElemBuilder<'a>(BytesStart<'a>);
338
339struct ElemEnd<'a>(BytesEnd<'a>);
340
341impl<'a> ElemBuilder<'a> {
342    #[inline]
343    fn new(name: &'a str) -> Self {
344        Self(BytesStart::new(name))
345    }
346
347    fn print_str(name: &'a str, elem: &str, w: &mut XWriter<impl Write>) -> Result<()> {
348        let e = Self::new(name).start(w)?;
349        print_str(elem, w)?;
350        e.end(w)
351    }
352
353    fn print<T: Display>(name: &'a str, elem: &T, w: &mut XWriter<impl Write>) -> Result<()> {
354        let e = Self::new(name).start(w)?;
355        print_elem(elem, w)?;
356        e.end(w)
357    }
358
359    fn opt_print<T: Display>(
360        name: &'a str,
361        elem: &Option<T>,
362        w: &mut XWriter<impl Write>,
363    ) -> Result<()> {
364        opt(elem, |e| Self::print(name, e, w))
365    }
366
367    fn def_print<T: Display + PartialEq>(
368        name: &'a str,
369        value: T,
370        def: T,
371        w: &mut XWriter<impl Write>,
372    ) -> Result<()> {
373        if value != def {
374            Self::print(name, &value, w)?
375        }
376        Ok(())
377    }
378
379    fn print_arr<T: Display>(name: &'a str, elem: &[T], w: &mut XWriter<impl Write>) -> Result<()> {
380        let e = Self::new(name).start(w)?;
381        print_arr(elem, w)?;
382        e.end(w)
383    }
384
385    #[inline]
386    fn raw_attr(&mut self, key: &str, value: &[u8]) {
387        self.0.push_attribute((key.as_bytes(), value));
388    }
389
390    #[inline]
391    fn attr(&mut self, key: &str, value: &str) {
392        self.0.push_attribute((key, value));
393    }
394
395    fn opt_attr(&mut self, key: &str, value: &Option<String>) {
396        if let Some(value) = value {
397            self.attr(key, value)
398        }
399    }
400
401    #[inline]
402    fn print_attr(&mut self, key: &str, value: impl Display) {
403        self.0.push_attribute((key, &*format!("{}", value)));
404    }
405
406    fn opt_print_attr(&mut self, key: &str, value: &Option<impl Display>) {
407        if let Some(value) = value {
408            self.0.push_attribute((key, &*format!("{}", value)));
409        }
410    }
411
412    fn def_print_attr<T: Display + PartialEq>(&mut self, key: &str, value: T, def: T) {
413        if value != def {
414            self.print_attr(key, value)
415        }
416    }
417
418    fn start(self, w: &mut XWriter<impl Write>) -> Result<ElemEnd<'static>> {
419        let end = self.0.to_end().into_owned();
420        w.write_event(Event::Start(self.0))?;
421        Ok(ElemEnd(end))
422    }
423
424    fn end(self, w: &mut XWriter<impl Write>) -> Result<()> {
425        Ok(w.write_event(Event::Empty(self.0))?)
426    }
427}
428
429impl<'a> ElemEnd<'a> {
430    fn end(self, w: &mut XWriter<impl Write>) -> Result<()> {
431        Ok(w.write_event(Event::End(self.0))?)
432    }
433}
434
435use private::{XNode, XNodeWrite};
436pub(crate) mod private {
437    use super::*;
438
439    /// A common trait for all data structures that represent an XML element.
440    pub trait XNode: XNodeWrite + Sized {
441        /// The name of the XML element.
442        const NAME: &'static str;
443
444        /// Parse an XML element into this type. In most cases, the parser will require with a
445        /// `debug_assert` that the element to parse has name [`Self::NAME`].
446        fn parse(element: &Element) -> Result<Self>;
447
448        /// Parse an XML element and return the data structure in a `Box`.
449        /// This can be faster in some cases when the data structure is large.
450        fn parse_box(element: &Element) -> Result<Box<Self>> {
451            Self::parse(element).map(Box::new)
452        }
453
454        /// Parse a single required element from the given element iterator.
455        fn parse_one<'a>(it: &mut impl Iterator<Item = &'a Element>) -> Result<Self> {
456            parse_one(Self::NAME, it, Self::parse)
457        }
458
459        /// Parse an optional element from the given element iterator, using [`Self::NAME`] to
460        /// determine if it is the correct type.
461        fn parse_opt(it: &mut ElementIter<'_>) -> Result<Option<Self>> {
462            parse_opt(Self::NAME, it, Self::parse)
463        }
464
465        /// Parse an optional boxed element from the given element iterator, using [`Self::NAME`] to
466        /// determine if it is the correct type.
467        fn parse_opt_box(it: &mut ElementIter<'_>) -> Result<Option<Box<Self>>> {
468            parse_opt(Self::NAME, it, Self::parse_box)
469        }
470
471        /// Parse a list of elements from the given element iterator,
472        /// as long as it continues yielding elements of name [`Self::NAME`].
473        fn parse_list(it: &mut ElementIter<'_>) -> Result<Vec<Self>> {
474            parse_list(Self::NAME, it, Self::parse)
475        }
476
477        /// Parse a list of elements from the given element iterator,
478        /// as long as it continues yielding elements of name [`Self::NAME`],
479        /// and assert that the resulting list has length at least `N`.
480        fn parse_list_n<const N: usize>(it: &mut ElementIter<'_>) -> Result<Vec<Self>> {
481            let arr = parse_list(Self::NAME, it, Self::parse)?;
482            if arr.len() < N {
483                return Err(format!("parse error: expected {} {} elements", N, Self::NAME).into());
484            }
485            Ok(arr)
486        }
487
488        /// Create a new element builder.
489        #[doc(hidden)]
490        fn elem<'a>() -> ElemBuilder<'a> {
491            ElemBuilder::new(Self::NAME)
492        }
493    }
494
495    pub trait XNodeWrite {
496        /// Writes the node to the given [`quick_xml::Writer`](minidom::quick_xml::Writer).
497        fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()>;
498    }
499}
500
501impl<T: XNodeWrite> XNodeWrite for Box<T> {
502    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
503        (**self).write_to(w)
504    }
505}
506
507impl<T: XNodeWrite> XNodeWrite for Option<T> {
508    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
509        opt(self, |e| e.write_to(w))
510    }
511}
512
513impl<T: XNodeWrite> XNodeWrite for Vec<T> {
514    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
515        many(self, |e| e.write_to(w))
516    }
517}
518
519impl XNodeWrite for Element {
520    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
521        use std::{cell::RefCell, collections::BTreeMap};
522        thread_local! {
523            static COLLADA_PREFIX: RefCell<BTreeMap<Option<String>, String>> =
524                const { RefCell::new(BTreeMap::new()) };
525        }
526        COLLADA_PREFIX.with(|pfxs| {
527            let mut pfxs = pfxs.borrow_mut();
528            if pfxs.is_empty() {
529                pfxs.insert(None, "http://www.collada.org/2005/11/COLLADASchema".into());
530            }
531            Ok(self.write_to_inner(w, &mut pfxs)?)
532        })
533    }
534}
535
536impl XNodeWrite for () {
537    fn write_to<W: Write>(&self, _: &mut XWriter<W>) -> Result<()> {
538        Ok(())
539    }
540}
541
542/// A Collada document. Represents the `<COLLADA>` root node.
543///
544/// This is the main entry point for parsing.
545#[derive(Clone, Debug)]
546pub struct Document {
547    /// Metadata about the origin of the document
548    pub asset: Asset,
549    /// The main content, organized into a list of "libraries".
550    pub library: Vec<LibraryElement>,
551    /// The actual scene being described, which references / instantiates
552    /// objects in the libraries.
553    pub scene: Option<Scene>,
554    /// Provides arbitrary additional information about this element.
555    pub extra: Vec<Extra>,
556}
557
558impl FromStr for Document {
559    type Err = Error;
560    fn from_str(s: &str) -> Result<Self> {
561        Self::try_from(s.as_bytes())
562    }
563}
564
565impl TryFrom<&str> for Document {
566    type Error = Error;
567    fn try_from(s: &str) -> Result<Self> {
568        Self::from_str(s)
569    }
570}
571
572impl TryFrom<&[u8]> for Document {
573    type Error = Error;
574    fn try_from(s: &[u8]) -> Result<Self> {
575        Self::from_reader(std::io::Cursor::new(s))
576    }
577}
578
579impl Document {
580    /// Constructs a new empty [`Document`].
581    pub fn new(asset: Asset) -> Self {
582        Self {
583            asset,
584            library: vec![],
585            scene: None,
586            extra: vec![],
587        }
588    }
589
590    /// Constructs a new empty [`Document`] with creation date set to the current date/time.
591    pub fn create_now() -> Self {
592        Self::new(Asset::create_now())
593    }
594
595    /// Add a new library element with the given items.
596    pub fn push_library<T: ParseLibrary>(&mut self, items: Vec<T>) {
597        self.library.push(T::mk_element(Library::new(items)))
598    }
599
600    /// Constructs a [`Document`] from a file.
601    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
602        Self::from_reader(BufReader::new(std::fs::File::open(path)?))
603    }
604
605    /// Constructs a [`Document`] from any [`BufRead`] reader.
606    /// Use [`BufReader`] to construct a [`BufRead`] if you only have a
607    /// [`Read`](std::io::Read) instance.
608    pub fn from_reader<R: BufRead>(reader: R) -> Result<Self> {
609        Self::from_xml_reader(&mut XReader::from_reader(reader))
610    }
611
612    /// Constructs a [`Document`] from a
613    /// [`quick_xml::Reader`](minidom::quick_xml::Reader).
614    pub fn from_xml_reader<R: BufRead>(reader: &mut XReader<R>) -> Result<Self> {
615        let root = Element::from_reader(reader)?;
616        Self::parse(&root)
617    }
618
619    /// Write the document to a writer.
620    pub fn write_to<W: Write>(&self, w: W) -> Result<()> {
621        XNodeWrite::write_to(self, &mut XWriter::new_with_indent(w, b' ', 2))
622    }
623}
624
625impl XNode for Document {
626    const NAME: &'static str = "COLLADA";
627    /// Parse a [`Document`] from a `<COLLADA>` [`Element`].
628    fn parse(element: &Element) -> Result<Self> {
629        if element.name() != Self::NAME {
630            return Err("Expected COLLADA root node".into());
631        }
632        if element.attr("version") != Some("1.4.1") {
633            return Err("Unsupported COLLADA version".into());
634        }
635        let mut it = element.children().peekable();
636        Ok(Document {
637            asset: Asset::parse_one(&mut it)?,
638            library: parse_list_many(&mut it, LibraryElement::parse)?,
639            scene: Scene::parse_opt(&mut it)?,
640            extra: Extra::parse_many(it)?,
641        })
642    }
643}
644
645impl XNodeWrite for Document {
646    fn write_to<W: Write>(&self, w: &mut XWriter<W>) -> Result<()> {
647        w.write_event(Event::Decl(BytesDecl::new("1.0", Some("utf-8"), None)))?;
648        let mut e = Self::elem();
649        e.raw_attr("xmlns", b"http://www.collada.org/2005/11/COLLADASchema");
650        e.raw_attr("version", b"1.4.1");
651        e.raw_attr("xmlns:xsi", b"http://www.w3.org/2001/XMLSchema-instance");
652        let e = e.start(w)?;
653        self.asset.write_to(w)?;
654        self.library.write_to(w)?;
655        self.scene.write_to(w)?;
656        self.extra.write_to(w)?;
657        e.end(w)
658    }
659}
660
661impl CollectLocalMaps for Document {
662    fn collect_local_maps<'a>(&'a self, maps: &mut LocalMaps<'a>) {
663        self.library.collect_local_maps(maps);
664    }
665}