rbx_xml/
lib.rs

1//! Configurable Roblox XML place/model format (rbxmx and rbxlx) serializer and
2//! deserializer.
3//!
4//! rbx_xml uses the [rbx_dom_weak][rbx_dom_weak] crate as its DOM.
5//!
6//! This crate implements most of the format and is driven by an up-to-date
7//! reflection database.
8//!
9//! ## Deserialization
10//! To decode a place or model, use a method like
11//! [`from_reader_default`][from_reader_default] if you're reading from a file,
12//! or [`from_str_default`][from_str_default] if you already have a string.
13//! These methods also have variants like [`from_str`][from_str] that let you
14//! pass in custom options.
15//!
16//! ```
17//! use rbx_dom_weak::{ustr, types::Variant};
18//!
19//! let model_file = r#"
20//! <roblox version="4">
21//!     <Item class="NumberValue" referent="RBX3B3D9D3DB43D4E6793B190B081E0A886">
22//!         <Properties>
23//!             <string name="Name">My NumberValue</string>
24//!             <double name="Value">12345</double>
25//!         </Properties>
26//!     </Item>
27//! </roblox>
28//! "#;
29//!
30//! let model = rbx_xml::from_str_default(model_file)?;
31//!
32//! let data_model = model.root();
33//! let number_value_ref = data_model.children()[0];
34//! let number_value = model.get_by_ref(number_value_ref).unwrap();
35//!
36//! assert_eq!(
37//!     number_value.properties.get(&ustr("Value")),
38//!     Some(&Variant::Float64(12345.0)),
39//! );
40//! # Ok::<(), Box<dyn std::error::Error>>(())
41//! ```
42//!
43//! If you're decoding from a file, you'll want to do your own I/O buffering,
44//! like with [`BufReader`][BufReader]:
45//!
46//! ```no_run
47//! use std::{
48//!     io::BufReader,
49//!     fs::File,
50//! };
51//!
52//! let file = BufReader::new(File::open("place.rbxlx")?);
53//! let place = rbx_xml::from_reader_default(file)?;
54//! # Ok::<(), Box<dyn std::error::Error>>(())
55//! ```
56//!
57//! Note that the `WeakDom` instance returned by the rbx_xml decode methods will
58//! have a root instance with the class name `DataModel`. This is great for
59//! deserializing a place, but kind of strange for deserializing a model.
60//!
61//! Because models can have multiple instances at the top level, rbx_xml can't
62//! just return an `WeakDom` with your single instance at the top. Instead, the
63//! crate instead always creates a top-level `DataModel` instance which is
64//! pretty close to free.
65//!
66//! ## Serialization
67//! To serialize an existing `WeakDom` instance, use methods like
68//! [`to_writer_default`][to_writer_default] or [`to_writer`][to_writer].
69//!
70//! For example, to re-save the place file we loaded above:
71//!
72//! ```no_run
73//! use std::{
74//!     io::BufWriter,
75//!     fs::File,
76//! };
77//! use rbx_dom_weak::{WeakDom, InstanceBuilder};
78//!
79//! let place = WeakDom::new(InstanceBuilder::new("DataModel"));
80//!
81//! // A Roblox place file contains all of its top-level instances.
82//! let top_level_refs = place.root().children();
83//!
84//! // Just like when reading a place file, we should buffer our I/O.
85//! let file = BufWriter::new(File::create("place-2.rbxlx")?);
86//!
87//! rbx_xml::to_writer_default(file, &place, top_level_refs)?;
88//! # Ok::<(), Box<dyn std::error::Error>>(())
89//! ```
90//!
91//! ## Configuration
92//! rbx_xml exposes a few configuration options at the moment in the form of
93//! [`DecodeOptions`][DecodeOptions] and [`EncodeOptions`][EncodeOptions].
94//! For information on the configuration, see the documentation for those
95//! structs.
96//!
97//! The non-default reader and writer functions accept these as their `options`
98//! argument.
99//!
100//! [DecodeOptions]: struct.DecodeOptions.html
101//! [EncodeOptions]: struct.EncodeOptions.html
102//! [from_str]: fn.from_str.html
103//! [from_reader_default]: fn.from_reader_default.html
104//! [from_str_default]: fn.from_str_default.html
105//! [to_writer]: fn.to_writer.html
106//! [to_writer_default]: fn.to_writer_default.html
107//! [rbx_dom_weak]: https://crates.io/crates/rbx_dom_weak
108//! [BufReader]: https://doc.rust-lang.org/std/io/struct.BufReader.html
109
110#![deny(missing_docs)]
111
112mod conversion;
113mod core;
114mod deserializer;
115mod deserializer_core;
116mod error;
117mod serializer;
118mod serializer_core;
119mod types;
120
121#[cfg(test)]
122mod test_util;
123#[cfg(test)]
124mod tests;
125
126use std::io::{Read, Write};
127
128use rbx_dom_weak::{types::Ref, WeakDom};
129
130use crate::{deserializer::decode_internal, serializer::encode_internal};
131
132pub use crate::{
133    deserializer::{DecodeOptions, DecodePropertyBehavior},
134    error::{DecodeError, EncodeError},
135    serializer::{EncodeOptions, EncodePropertyBehavior},
136};
137
138/// Decodes an XML-format model or place from something that implements the
139/// `std::io::Read` trait.
140pub fn from_reader<R: Read>(reader: R, options: DecodeOptions) -> Result<WeakDom, DecodeError> {
141    decode_internal(reader, options)
142}
143
144/// Decodes an XML-format model or place from something that implements the
145/// `std::io::Read` trait using the default decoder options.
146pub fn from_reader_default<R: Read>(reader: R) -> Result<WeakDom, DecodeError> {
147    decode_internal(reader, DecodeOptions::default())
148}
149
150/// Decodes an XML-format model or place from a string.
151pub fn from_str<S: AsRef<str>>(reader: S, options: DecodeOptions) -> Result<WeakDom, DecodeError> {
152    decode_internal(reader.as_ref().as_bytes(), options)
153}
154
155/// Decodes an XML-format model or place from a string using the default decoder
156/// options.
157pub fn from_str_default<S: AsRef<str>>(reader: S) -> Result<WeakDom, DecodeError> {
158    decode_internal(reader.as_ref().as_bytes(), DecodeOptions::default())
159}
160
161/// Serializes a subset of the given tree to an XML format model or place,
162/// writing to something that implements the `std::io::Write` trait.
163pub fn to_writer<W: Write>(
164    writer: W,
165    tree: &WeakDom,
166    ids: &[Ref],
167    options: EncodeOptions,
168) -> Result<(), EncodeError> {
169    encode_internal(writer, tree, ids, options)
170}
171
172/// Serializes a subset of the given tree to an XML format model or place,
173/// writing to something that implements the `std::io::Write` trait using the
174/// default encoder options.
175pub fn to_writer_default<W: Write>(
176    writer: W,
177    tree: &WeakDom,
178    ids: &[Ref],
179) -> Result<(), EncodeError> {
180    encode_internal(writer, tree, ids, EncodeOptions::default())
181}