diff --git a/Makefile b/Makefile index 95caf80..654d1ed 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,17 @@ .PHONY: all clean docs lib format publish checknostd testunit all: - $(error You must specify one of the following targets: clean docs lib format publish checknostd check testunit test_EXAMPLE) + $(error You must specify one of the following targets: clean setup docs lib format publish checknostd check testunit test_EXAMPLE) clean: cd ensure_no_std && cargo clean && rm -rf Cargo.lock cargo clean @rm -rf pkg target* +setup: + rustup target add wasm32-unknown-unknown x86_64-unknown-none + cargo install wasm-pack + docs: RUSTDOCFLAGS="--extend-css musicxml/assets/docs.css" cargo doc --workspace --no-deps --release --exclude musicxml_internal --exclude musicxml_macros cp -r musicxml/assets/* target/doc/musicxml/ diff --git a/README.md b/README.md index fa0f8a3..33a82f2 100644 --- a/README.md +++ b/README.md @@ -60,4 +60,6 @@ This library is licensed under the [MIT license](http://opensource.org/licenses/ ## TODO -Add documentation about "std" feature, add options to parse MusicXML files directly from string (both compressed and uncompressed), same for writing to string +- [ ] Add documentation about "std" feature, including parsing to/from a data buffer instead of a file +- [ ] Remove dependency on regex +- [ ] Create WASM build diff --git a/musicxml/src/lib.rs b/musicxml/src/lib.rs index d991b10..12ae6dd 100644 --- a/musicxml/src/lib.rs +++ b/musicxml/src/lib.rs @@ -148,6 +148,9 @@ use elements::{ScorePartwise, ScoreTimewise}; /// Reads a MusicXML file and returns a [ScorePartwise] object. /// /// The specified file can be either a `.musicxml` file or a compressed `.mxl` file. +/// +/// # Errors +/// TODO pub fn read_score_partwise(path: &str) -> Result { parser::parse_score_partwise_from_file(path) } @@ -155,15 +158,41 @@ pub fn read_score_partwise(path: &str) -> Result { /// Reads a MusicXML file and returns a [ScoreTimewise] object. /// /// The specified file can be either a `.musicxml` file or a compressed `.mxl` file. +/// +/// # Errors +/// TODO pub fn read_score_timewise(path: &str) -> Result { parser::parse_score_timewise_from_file(path) } +/// Reads MusicXML data and returns a [ScorePartwise] object. +/// +/// The specified data should have been read directly from either a `.musicxml` file or a compressed `.mxl` file. +/// +/// # Errors +/// TODO +pub fn read_score_data_partwise(data: Vec) -> Result { + parser::parse_score_partwise_from_data(data) +} + +/// Reads MusicXML data and returns a [ScoreTimewise] object. +/// +/// The specified data should have been read directly from either a `.musicxml` file or a compressed `.mxl` file. +/// +/// # Errors +/// TODO +pub fn read_score_data_timewise(data: Vec) -> Result { + parser::parse_score_timewise_from_data(data) +} + /// Writes a [ScorePartwise] object into a MusicXML file. /// /// If the `compressed` parameter is set to `true`, the MusicXML file will be written as a compressed `.mxl` file. /// If the `write_as_timewise` parameter is set to `true`, the MusicXML file will be converted into a timewise format and /// written as a `` element. +/// +/// # Errors +/// TODO pub fn write_partwise_score( path: &str, score: &ScorePartwise, @@ -178,6 +207,9 @@ pub fn write_partwise_score( /// If the `compressed` parameter is set to `true`, the MusicXML file will be written as a compressed `.mxl` file. /// If the `write_as_partwise` parameter is set to `true`, the MusicXML file will be converted into a partwise format and /// written as a `` element. +/// +/// # Errors +/// TODO pub fn write_timewise_score( path: &str, score: &ScoreTimewise, @@ -186,3 +218,35 @@ pub fn write_timewise_score( ) -> Result<(), String> { parser::parse_score_timewise_to_file(path, score, compressed, true, write_as_partwise) } + +/// Writes a [ScorePartwise] object into a MusicXML data buffer. +/// +/// If the `compressed` parameter is set to `true`, the MusicXML contents will be written as compressed `.mxl` data. +/// If the `write_as_timewise` parameter is set to `true`, the MusicXML contents will be converted into a timewise +/// format and written as a `` element. +/// +/// # Errors +/// TODO +pub fn write_partwise_score_data( + score: &ScorePartwise, + compressed: bool, + write_as_timewise: bool, +) -> Result, String> { + parser::parse_score_partwise_to_data(score, compressed, true, write_as_timewise) +} + +/// Writes a [ScoreTimewise] object into a MusicXML data buffer. +/// +/// If the `compressed` parameter is set to `true`, the MusicXML contents will be written as compressed `.mxl` data. +/// If the `write_as_partwise` parameter is set to `true`, the MusicXML contents will be converted into a partwise +/// format and written as a `` element. +/// +/// # Errors +/// TODO +pub fn write_timewise_score_data( + score: &ScoreTimewise, + compressed: bool, + write_as_partwise: bool, +) -> Result, String> { + parser::parse_score_timewise_to_data(score, compressed, true, write_as_partwise) +} diff --git a/musicxml/src/parser/mod.rs b/musicxml/src/parser/mod.rs index 29d6672..54200df 100644 --- a/musicxml/src/parser/mod.rs +++ b/musicxml/src/parser/mod.rs @@ -6,34 +6,26 @@ use musicxml_internal::{ElementDeserializer, ElementSerializer, XmlElement}; extern crate std; #[cfg(feature = "std")] -use { - alloc::string::ToString, - std::io::{Read, Write}, -}; +use {alloc::string::ToString, std::io::Write}; mod xml_parser; mod zip_parser; -#[cfg(feature = "std")] -fn is_mxl_file(path: &str) -> bool { - let mut buffer: [u8; 4] = [0; 4]; - if let Ok(mut file) = std::fs::OpenOptions::new().read(true).open(path) { - file.read_exact(&mut buffer).is_ok() && (buffer == [0x50, 0x4b, 0x03, 0x04]) +#[inline] +fn is_mxl_data(data: Option<&[u8]>) -> bool { + if let Some(data) = data { + *data == [0x50, 0x4b, 0x03, 0x04] } else { false } } -#[cfg(feature = "std")] -fn get_musicxml_contents_from_file(path: &str) -> Result { +fn get_musicxml_contents(data: Vec) -> Result { let mut contents = String::new(); - if is_mxl_file(path) { + if is_mxl_data(data.get(0..4)) { let mut xml_path: Option = None; let mut mxl_data = zip_parser::ZipData::new(); - std::fs::File::open(path) - .map_err(|e| e.to_string())? - .read_to_end(&mut mxl_data.content) - .unwrap_or(0); + mxl_data.content = data; let archive = zip_parser::ZipArchive::new(&mut mxl_data); for file_name in archive.iter() { if file_name == "META-INF/container.xml" { @@ -53,15 +45,16 @@ fn get_musicxml_contents_from_file(path: &str) -> Result { Err(String::from("Cannot find MusicXML file in compressed archive"))?; } } else { - let mut file = std::fs::OpenOptions::new() - .read(true) - .open(path) - .map_err(|e| e.to_string())?; - file.read_to_string(&mut contents).map_err(|e| e.to_string())?; + contents = String::from_utf8(data).map_err(|e| e.to_string())?; } Ok(contents) } +#[cfg(feature = "std")] +fn get_musicxml_contents_from_file(path: &str) -> Result { + get_musicxml_contents(std::fs::read(path).map_err(|e| e.to_string())?) +} + #[cfg(not(feature = "std"))] fn get_musicxml_contents_from_file(_path: &str) -> Result { Err(String::from( @@ -69,25 +62,21 @@ fn get_musicxml_contents_from_file(_path: &str) -> Result { )) } -#[cfg(feature = "std")] -fn write_musicxml_file(file: &mut T, xml: &XmlElement, pretty_print: bool) -> Result<(), String> { - file - .write_all(b"\n") - .map_err(|e| e.to_string())?; +fn put_musicxml_contents(xml: &XmlElement, pretty_print: bool) -> Vec { + let mut buffer = Vec::new(); + buffer.extend_from_slice(b"\n"); if xml.name == "score-partwise" { - file.write_all(b"\n").map_err(|e| e.to_string())?; + buffer.extend_from_slice(b"\n"); } else if xml.name == "score-timewise" { - file.write_all(b"\n").map_err(|e| e.to_string())?; + buffer.extend_from_slice(b"\n"); } - file - .write_all(xml_parser::parse_to_string(xml, if pretty_print { 0 } else { -1 }).as_ref()) - .map_err(|e| e.to_string()) + buffer.extend_from_slice(xml_parser::parse_to_string(xml, if pretty_print { 0 } else { -1 }).as_ref()); + buffer } -#[cfg(feature = "std")] -fn write_musicxml_to_file(path: &str, xml: &XmlElement, _compressed: bool, pretty_print: bool) -> Result<(), String> { - /*if compressed { - let options = zip::write::SimpleFileOptions::default() +fn write_musicxml_contents(xml: &XmlElement, compressed: bool, pretty_print: bool) -> Result, String> { + if compressed { + /*let options = zip::write::SimpleFileOptions::default() .compression_method(zip::CompressionMethod::Deflated) .compression_level(Some(9)); let mut archive = zip::ZipWriter::new(std::fs::File::create(path).map_err(|e| e.to_string())?); @@ -99,22 +88,33 @@ fn write_musicxml_to_file(path: &str, xml: &XmlElement, _compressed: bool, prett archive .start_file("score.musicxml", options) .map_err(|e| e.to_string())?; - write_musicxml_file(&mut archive, xml, pretty_print)?; - archive.finish().map_err(|e| e.to_string())?; - Ok(()) - } else {*/ + archive.write_data(put_musicxml_contents(xml, pretty_print))?; + archive.finish().map_err(|e| e.to_string())?;*/ + Err(String::from("Bad")) + } else { + Ok(put_musicxml_contents(xml, pretty_print)) + } +} + +#[cfg(feature = "std")] +fn write_musicxml_contents_to_file( + path: &str, + xml: &XmlElement, + compressed: bool, + pretty_print: bool, +) -> Result<(), String> { let mut file = std::fs::OpenOptions::new() .write(true) .create(true) .truncate(true) .open(path) .map_err(|e| e.to_string())?; - write_musicxml_file(&mut file, xml, pretty_print) - //} + write_musicxml_contents(xml, compressed, pretty_print) + .and_then(|result| file.write_all(result.as_slice()).map_err(|e| e.to_string())) } #[cfg(not(feature = "std"))] -fn write_musicxml_to_file( +fn write_musicxml_contents_to_file( _path: &str, _xml: &XmlElement, _compressed: bool, @@ -232,6 +232,9 @@ fn convert_xml_timewise_to_partwise(xml: XmlElement) -> Result` or ``. +/// +/// # Errors +/// TODO pub fn parse_from_xml_str(str: &str) -> Result { let xml = xml_parser::parse_from_string(str)?; T::deserialize(&xml) @@ -249,6 +252,9 @@ pub fn parse_to_xml_str(data: &T, pretty_print: bool) -> S /// Parses the contents of the specified MusicXML file into a [ScorePartwise] element. /// /// The specified file can be either a `.musicxml` file or a compressed `.mxl` file. +/// +/// # Errors +/// TODO pub fn parse_score_partwise_from_file(path: &str) -> Result { let contents = get_musicxml_contents_from_file(path)?; let xml = xml_parser::parse_from_string(&contents)?; @@ -258,12 +264,39 @@ pub fn parse_score_partwise_from_file(path: &str) -> Result Result { let contents = get_musicxml_contents_from_file(path)?; let xml = xml_parser::parse_from_string(&contents)?; convert_xml_partwise_to_timewise(xml).and_then(|xml| ScoreTimewise::deserialize(&xml)) } +/// Parses the contents of the specified MusicXML data into a [ScorePartwise] element. +/// +/// The specified data should have been read directly from either a `.musicxml` file or a compressed `.mxl` file. +/// +/// # Errors +/// TODO +pub fn parse_score_partwise_from_data(data: Vec) -> Result { + let contents = get_musicxml_contents(data)?; + let xml = xml_parser::parse_from_string(&contents)?; + convert_xml_timewise_to_partwise(xml).and_then(|xml| ScorePartwise::deserialize(&xml)) +} + +/// Parses the contents of the specified MusicXML data into a [ScoreTimewise] element. +/// +/// The specified data should have been read directly from either a `.musicxml` file or a compressed `.mxl` file. +/// +/// # Errors +/// TODO +pub fn parse_score_timewise_from_data(data: Vec) -> Result { + let contents = get_musicxml_contents(data)?; + let xml = xml_parser::parse_from_string(&contents)?; + convert_xml_partwise_to_timewise(xml).and_then(|xml| ScoreTimewise::deserialize(&xml)) +} + /// Writes the contents of the specified [ScorePartwise] element into a MusicXML file. /// /// If the `compressed` parameter is set to `true`, the MusicXML file will be written as a compressed `.mxl` file. @@ -271,6 +304,9 @@ pub fn parse_score_timewise_from_file(path: &str) -> Result` element. /// /// The `pretty_print` parameter specifies whether the MusicXML file should be written with indentation and newlines. +/// +/// # Errors +/// TODO pub fn parse_score_partwise_to_file( path: &str, score: &ScorePartwise, @@ -280,9 +316,10 @@ pub fn parse_score_partwise_to_file( ) -> Result<(), String> { let xml = ScorePartwise::serialize(score); if write_timewise { - convert_xml_partwise_to_timewise(xml).and_then(|xml| write_musicxml_to_file(path, &xml, compressed, pretty_print)) + convert_xml_partwise_to_timewise(xml) + .and_then(|xml| write_musicxml_contents_to_file(path, &xml, compressed, pretty_print)) } else { - write_musicxml_to_file(path, &xml, compressed, pretty_print) + write_musicxml_contents_to_file(path, &xml, compressed, pretty_print) } } @@ -293,6 +330,9 @@ pub fn parse_score_partwise_to_file( /// written as a `` element. /// /// The `pretty_print` parameter specifies whether the MusicXML file should be written with indentation and newlines. +/// +/// # Errors +/// TODO pub fn parse_score_timewise_to_file( path: &str, score: &ScoreTimewise, @@ -302,8 +342,59 @@ pub fn parse_score_timewise_to_file( ) -> Result<(), String> { let xml = ScoreTimewise::serialize(score); if write_partwise { - convert_xml_timewise_to_partwise(xml).and_then(|xml| write_musicxml_to_file(path, &xml, compressed, pretty_print)) + convert_xml_timewise_to_partwise(xml) + .and_then(|xml| write_musicxml_contents_to_file(path, &xml, compressed, pretty_print)) + } else { + write_musicxml_contents_to_file(path, &xml, compressed, pretty_print) + } +} + +/// Writes the contents of the specified [ScorePartwise] element into a MusicXML data buffer. +/// +/// If the `compressed` parameter is set to `true`, the MusicXML contents will be written as compressed `.mxl` data. +/// If the `write_timewise` parameter is set to `true`, the MusicXML contents will be converted into a timewise +/// format and written as a `` element. +/// +/// The `pretty_print` parameter specifies whether the MusicXML contents should be written with indentation +/// and newlines. +/// +/// # Errors +/// TODO +pub fn parse_score_partwise_to_data( + score: &ScorePartwise, + compressed: bool, + pretty_print: bool, + write_timewise: bool, +) -> Result, String> { + let xml = ScorePartwise::serialize(score); + if write_timewise { + convert_xml_partwise_to_timewise(xml).and_then(|xml| write_musicxml_contents(&xml, compressed, pretty_print)) + } else { + write_musicxml_contents(&xml, compressed, pretty_print) + } +} + +/// Writes the contents of the specified [ScoreTimewise] element into a MusicXML data buffer. +/// +/// If the `compressed` parameter is set to `true`, the MusicXML contents will be written as compressed `.mxl` data. +/// If the `write_partwise` parameter is set to `true`, the MusicXML contents will be converted into a partwise +/// format and written as a `` element. +/// +/// The `pretty_print` parameter specifies whether the MusicXML contents should be written with indentation +/// and newlines. +/// +/// # Errors +/// TODO +pub fn parse_score_timewise_to_data( + score: &ScoreTimewise, + compressed: bool, + pretty_print: bool, + write_partwise: bool, +) -> Result, String> { + let xml = ScoreTimewise::serialize(score); + if write_partwise { + convert_xml_timewise_to_partwise(xml).and_then(|xml| write_musicxml_contents(&xml, compressed, pretty_print)) } else { - write_musicxml_to_file(path, &xml, compressed, pretty_print) + write_musicxml_contents(&xml, compressed, pretty_print) } }