diff --git a/Cargo.toml b/Cargo.toml index 82d19fd..b1e9923 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,8 @@ lz4 = { package = "lz4_flex", version = "0.10", optional = true } flate2 = { version = "1.0.19", optional = true } xz2 = { version = "0.1.6", optional = true } # LZMA #quick-xml = { version = "0.25", features = ["serialize"], optional = true } -quick-xml = { path = "../quick-xml", features = ["serialize"], optional = true } +# quick-xml = { path = "../quick-xml", features = ["serialize"], optional = true } +quick-xml = { git = "https://github.com/elrnv/quick-xml", features = ["serialize"], optional = true, branch = "binary-support" } serde = { version = "1.0", features = ["derive"], optional = true } tokio = { version = "1.3", features = ["fs", "io-util"], optional = true } rayon = { version = "1.0", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 4c1f5a4..5695407 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! let mut vtk_file = Vtk::import(&file_path) //! .expect(&format!("Failed to load file: {:?}", file_path)); //! -//! vtk_file.version = Version::new((4,2)); // arbitrary change +//! vtk_file.version = Version::new_legacy(4,2); // arbitrary change //! //! vtk_file.export_ascii(&file_path) //! .expect(&format!("Failed to save file: {:?}", file_path)); @@ -36,7 +36,7 @@ //! //! let mut vtk_file = Vtk::parse_legacy_be(data).expect(&format!("Failed to parse file")); //! -//! vtk_file.version = Version::new((4,2)); // arbitrary change +//! vtk_file.version = Version::new_legacy(4,2); // arbitrary change //! //! let mut output = String::new(); //! Vtk::write_legacy_ascii(vtk_file, &mut output).expect(&format!("Failed to write file")); @@ -251,7 +251,7 @@ impl Vtk { /// let vtk = Vtk::parse_legacy_be(vtk_ascii).expect("Failed to parse vtk file"); /// /// assert_eq!(vtk, Vtk { - /// version: Version::new((2,0)), + /// version: Version::new_legacy(2,0), /// byte_order: ByteOrder::BigEndian, /// title: String::from("Triangle example"), /// file_path: None, @@ -299,7 +299,7 @@ impl Vtk { /// let vtk = Vtk::parse_legacy_le(vtk_ascii).expect("Failed to parse vtk file"); /// /// assert_eq!(vtk, Vtk { - /// version: Version::new((2,0)), + /// version: Version::new_legacy(2,0), /// byte_order: ByteOrder::LittleEndian, /// title: String::from("Triangle example"), /// file_path: None, @@ -368,7 +368,7 @@ impl Vtk { /// let vtk = Vtk::parse_xml(input).expect("Failed to parse XML VTK file"); /// /// assert_eq!(vtk, Vtk { - /// version: Version::new((2,0)), + /// version: Version::new_xml(2,0), /// byte_order: ByteOrder::BigEndian, // This is default /// title: String::new(), /// file_path: None, @@ -603,7 +603,7 @@ impl Vtk { /// use vtkio::model::*; /// use std::path::PathBuf; /// let vtk = Vtk { - /// version: Version::new((4,1)), + /// version: Version::new_legacy(4,1), /// byte_order: ByteOrder::BigEndian, /// title: String::from("Tetrahedron"), /// file_path: Some(PathBuf::from("./test.vtk")), @@ -671,7 +671,7 @@ impl Vtk { /// let mut vtk_bytes = Vec::::new(); /// /// Vtk { - /// version: Version::new((2,0)), + /// version: Version::new_legacy(2,0), /// byte_order: ByteOrder::BigEndian, /// title: String::from("Triangle example"), /// file_path: None, @@ -705,7 +705,7 @@ impl Vtk { /// let mut vtk_string = String::new(); /// /// Vtk { - /// version: Version::new((2,0)), + /// version: Version::new_legacy(2,0), /// byte_order: ByteOrder::BigEndian, // Ignored /// title: String::from("Triangle example"), /// file_path: None, @@ -756,7 +756,7 @@ impl Vtk { /// let mut vtk_bytes = Vec::::new(); /// /// Vtk { - /// version: Version::new((2,0)), + /// version: Version::new_xml(2,0), /// byte_order: ByteOrder::BigEndian, /// title: String::from("Triangle example"), /// file_path: None, @@ -829,7 +829,7 @@ impl Vtk { /// use vtkio::model::*; /// use std::path::PathBuf; /// let vtk = Vtk { - /// version: Version::new((4,1)), + /// version: Version::new_legacy(4,1), /// title: String::from("Tetrahedron"), /// byte_order: ByteOrder::BigEndian, /// file_path: Some(PathBuf::from("./test.vtk")), diff --git a/src/model.rs b/src/model.rs index ab9375c..bb8c51e 100644 --- a/src/model.rs +++ b/src/model.rs @@ -231,31 +231,54 @@ impl Vtk { } } -/// Version number (e.g. `4.1 => Version { major: 4, minor: 1 }`) +/// Version number enum +/// +/// Legacy and XML versions are distinct, and this enum splits the two into distinct variants. +/// New files can use the `Auto` variant, which will default to the minimum version supporting the latest features in `vtkio`. #[derive(Copy, Clone, PartialEq, Debug)] -pub struct Version { - pub major: u8, - pub minor: u8, +pub enum Version { + /// Automatically handle versioning on write for both Legacy and XML formats. + Auto, + /// Loaded Legacy format with this version. Writing in XML format is handled as with the `Auto` variant. + Legacy { major: u32, minor: u32 }, + /// Loaded XML format with this version. Writing in Legacy is handled as with the `Auto` variant. + XML { major: u32, minor: u32 }, } impl Version { - pub fn new(pair: (u8, u8)) -> Self { - Version { - major: pair.0, - minor: pair.1, + pub fn new() -> Self { + Version::Auto + } + pub fn new_legacy(major: u32, minor: u32) -> Self { + Version::Legacy { major, minor } + } + pub fn new_xml(major: u32, minor: u32) -> Self { + Version::XML { major, minor } + } + pub fn to_xml(self) -> (u32, u32) { + match self { + Version::XML { major, minor } => (major, minor), + Version::Auto | Version::Legacy { .. } => { + // This is a conservative estimate. + // It is not established what the actual minimum version we write. + // Presumably this would only be a problem when working with very old versions of VTK. + (1, 0) + } } } -} - -impl From<(u8, u8)> for Version { - fn from(pair: (u8, u8)) -> Self { - Version::new(pair) + pub fn to_legacy(self) -> (u32, u32) { + match self { + Version::Legacy { major, minor } => (major, minor), + Version::Auto | Version::XML { .. } => (2, 0), + } } -} - -impl fmt::Display for Version { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}.{}", self.major, self.minor) + pub fn fmt_as_xml(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (major, minor) = self.to_xml(); + write!(f, "{}.{}", major, minor) + } + pub fn fmt_as_legacy(&self, f: &mut fmt::Formatter) -> fmt::Result { + let (major, minor) = self.to_legacy(); + write!(f, "{}.{}", major, minor) } } diff --git a/src/parser.rs b/src/parser.rs index ba9c44c..91f84ea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -26,8 +26,8 @@ fn version(input: &[u8]) -> IResult<&[u8], Version> { sp(tag_no_case("Version")), ))(input)?; sp(map( - separated_pair(parse_u8, tag("."), parse_u8), - Version::new, + separated_pair(parse_u32, tag("."), parse_u32), + |(major, minor)| Version::new_legacy(major, minor), ))(input) } @@ -744,7 +744,7 @@ mod tests { #[test] fn version_test() { let f = version("# vtk DataFile Version 2.0 \ntitle\n".as_bytes()); - assert_eq!(f, Ok(("\ntitle\n".as_bytes(), Version::new((2, 0))))); + assert_eq!(f, Ok(("\ntitle\n".as_bytes(), Version::new_legacy(2, 0)))); } #[test] fn title_test() { @@ -759,7 +759,7 @@ mod tests { Ok(( "".as_bytes(), ( - Version::new((2, 0)), + Version::new_legacy(2, 0), "This is a title".to_string(), FileType::Binary ) diff --git a/src/writer.rs b/src/writer.rs index e3598de..0a52b02 100644 --- a/src/writer.rs +++ b/src/writer.rs @@ -435,8 +435,9 @@ mod write_vtk_impl { &mut self, vtk: Vtk, ) -> std::result::Result<&mut Self, Error> { + let version = vtk.version.to_legacy(); let source_path = vtk.file_path.as_ref().map(|p| p.as_ref()); - writeln!(self, "# vtk DataFile Version {}", vtk.version) + writeln!(self, "# vtk DataFile Version {}.{}", version.0, version.1) .map_err(|_| Error::Header(Header::Version))?; writeln!(self, "{}", vtk.title).map_err(|_| Error::Header(Header::Version))?; self.write_file_type()?; @@ -578,7 +579,7 @@ mod write_vtk_impl { let num_cells = cells.cell_verts.num_cells(); // Write CELLS structure. - if vtk.version.major >= 5 { + if version.0 >= 5 { // From version 5 and on the cells are written as an offsets and connectivity pair. let (connectivity, offsets) = cells.cell_verts.into_xml(); @@ -666,7 +667,7 @@ mod write_vtk_impl { Error::DataSet(DataSetError::StructuredPoints(DataSetPart::Origin)) })?; - if vtk.version.major < 2 { + if version.0 < 2 { write!(self, "ASPECT_RATIO") } else { write!(self, "SPACING") diff --git a/src/xml.rs b/src/xml.rs index fe1125f..28008cc 100644 --- a/src/xml.rs +++ b/src/xml.rs @@ -304,7 +304,7 @@ mod version { elem.parse() .map_err(|e| de::Error::custom(format!("failed to parse version: {}", e))) }; - Ok(Version::new((advance(&mut iter)?, advance(&mut iter)?))) + Ok(Version::new_xml(advance(&mut iter)?, advance(&mut iter)?)) } } @@ -313,7 +313,7 @@ mod version { where S: Serializer, { - let Version { major, minor } = self; + let (major, minor) = self.to_xml(); s.collect_str(&format_args!("{}.{}", major, minor)) } } @@ -965,7 +965,7 @@ mod vtkfile { { let mut vtk = VTKFile { data_set_type: DataSetType::UnstructuredGrid, - version: model::Version::new((1, 0)), + version: model::Version::new_xml(0, 1), byte_order: model::ByteOrder::BigEndian, header_type: None, compressor: Compressor::None, @@ -1077,7 +1077,7 @@ impl Default for VTKFile { fn default() -> VTKFile { VTKFile { data_set_type: DataSetType::ImageData, - version: model::Version::new((0, 1)), + version: model::Version::new_xml(0, 1), byte_order: model::ByteOrder::BigEndian, header_type: None, compressor: Compressor::None, @@ -4059,7 +4059,7 @@ mod tests { assert_eq!( vtk, Vtk { - version: Version::new((1, 0)), + version: Version::new_xml(1, 0), byte_order: ByteOrder::LittleEndian, title: String::new(), data: DataSet::inline(RectilinearGridPiece { diff --git a/tests/legacy.rs b/tests/legacy.rs index 01b5b23..0788f42 100644 --- a/tests/legacy.rs +++ b/tests/legacy.rs @@ -69,7 +69,7 @@ fn para_tet_test() -> Result { let in1 = include_bytes!("../assets/para_tet.vtk"); let in2 = include_str!("../assets/para_tet_ascii.vtk").as_bytes(); let out1 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, title: String::from("vtk output"), file_path: None, @@ -107,7 +107,7 @@ fn para_tets_test() -> Result { let in1 = include_bytes!("../assets/para_test.vtk"); let in2 = include_str!("../assets/para_test_ascii.vtk").as_bytes(); let out1 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, title: String::from("vtk output"), file_path: None, @@ -180,7 +180,7 @@ fn tet_test() -> Result { let in2 = include_bytes!("../assets/tet_test.vtk"); let in3 = include_bytes!("../assets/tet_test_binary.vtk"); let out1 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, title: String::from("Tetrahedron example"), file_path: None, @@ -213,7 +213,7 @@ fn tet_test() -> Result { fn tri_test() -> Result { let in1 = include_str!("../assets/tri.vtk"); let out1 = Vtk { - version: Version::new((2, 0)), + version: Version::new_legacy(2, 0), byte_order: ByteOrder::BigEndian, title: String::from("Triangle example"), file_path: None, @@ -239,7 +239,7 @@ fn tri_test() -> Result { fn tri_attrib_ascii_test() -> Result { let in1 = include_str!("../assets/tri_attrib.vtk"); let out1 = Vtk { - version: Version::new((2, 0)), + version: Version::new_legacy(2, 0), byte_order: ByteOrder::BigEndian, title: String::from("Triangle example"), file_path: None, @@ -283,7 +283,7 @@ fn tri_attrib_ascii_test() -> Result { fn tri_attrib_binary_test() -> Result { let in1 = include_bytes!("../assets/tri_attrib_binary.vtk"); let out1 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, title: String::from("Triangle example"), file_path: None, @@ -327,7 +327,7 @@ fn tri_attrib_binary_test() -> Result { fn square_test() -> Result { let in1 = include_str!("../assets/square.vtk"); let out1 = Vtk { - version: Version::new((2, 0)), + version: Version::new_legacy(2, 0), byte_order: ByteOrder::BigEndian, title: String::from("Square example"), file_path: None, @@ -356,7 +356,7 @@ fn square_test() -> Result { fn cube_test() -> Result { let in1 = include_str!("../assets/cube.vtk"); let out1 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, title: String::from("Cube example"), file_path: None, @@ -389,7 +389,7 @@ fn cube_test() -> Result { fn structured_grid_test() -> Result { let in1 = include_str!("../assets/structured_grid.vtk"); let out1 = Vtk { - version: Version::new((3, 0)), + version: Version::new_legacy(3, 0), byte_order: ByteOrder::BigEndian, title: String::from("vtk output"), file_path: None, @@ -447,7 +447,7 @@ fn structured_grid_test() -> Result { fn rectilinear_grid_test() -> Result { let in1 = include_bytes!("../assets/rectilinear_grid.vtk"); let out1 = Vtk { - version: Version::new((3, 0)), + version: Version::new_legacy(3, 0), byte_order: ByteOrder::BigEndian, title: String::from("vtk output"), file_path: None, @@ -480,7 +480,7 @@ fn rectilinear_grid_test() -> Result { // Same thing should work in binary let in1 = include_bytes!("../assets/rectilinear_grid_binary.vtk"); let out2 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, ..out1 }; @@ -492,7 +492,7 @@ fn rectilinear_grid_test() -> Result { fn field_test() -> Result { let in1 = include_bytes!("../assets/field.vtk"); let out1 = Vtk { - version: Version::new((2, 0)), + version: Version::new_legacy(2, 0), byte_order: ByteOrder::BigEndian, title: String::from("field example"), file_path: None, @@ -593,7 +593,7 @@ fn cube_complex_test() -> Result { let in1 = include_str!("../assets/cube_complex.vtk"); let out1 = Vtk { - version: Version::new((2, 0)), + version: Version::new_legacy(2, 0), byte_order: ByteOrder::BigEndian, title: String::from("Cube example"), file_path: None, @@ -677,7 +677,7 @@ fn cube_complex_test() -> Result { fn unstructured_grid_complex_test() -> Result { let in1 = include_str!("../assets/unstructured_grid_complex.vtk"); let out1 = Vtk { - version: Version::new((2, 0)), + version: Version::new_legacy(2, 0), byte_order: ByteOrder::BigEndian, title: String::from("Unstructured Grid Example"), file_path: None, @@ -759,7 +759,7 @@ fn unstructured_grid_complex_test() -> Result { fn volume_complex_test() -> Result { let in1 = include_str!("../assets/volume_complex.vtk"); let out1 = Vtk { - version: Version::new((2, 0)), + version: Version::new_legacy(2, 0), byte_order: ByteOrder::BigEndian, title: String::from("Volume example"), file_path: None, @@ -796,7 +796,7 @@ fn volume_complex_test() -> Result { fn dodecagon_test() -> Result { let in1 = include_bytes!("../assets/dodecagon_ascii_simple.vtk"); let out1 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, title: String::from("Dodecagon example"), file_path: None, @@ -863,7 +863,7 @@ fn dodecagon_test() -> Result { fn dodecagon_with_meta_test() { let in1 = include_bytes!("../assets/dodecagon_ascii.vtk"); let out1 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, title: String::from("Dodecagon example"), file_path: None, @@ -994,7 +994,7 @@ fn dodecagon_with_meta_line_endings_test() { fn binary_dodecagon_test() { let in1 = include_bytes!("../assets/dodecagon_simple.vtk"); let out1 = Vtk { - version: Version::new((4, 2)), + version: Version::new_legacy(4, 2), byte_order: ByteOrder::BigEndian, title: String::from("Dodecagon example"), file_path: None, diff --git a/tests/pygmsh.rs b/tests/pygmsh.rs index fcceb90..58e7344 100644 --- a/tests/pygmsh.rs +++ b/tests/pygmsh.rs @@ -52,7 +52,7 @@ fn le(vtk: &Vtk) -> Vtk { fn make_test_file(leading_zero_offset: bool) -> Vtk { Vtk { - version: Version::new((5, 1)), + version: Version::new_legacy(5, 1), byte_order: ByteOrder::BigEndian, title: String::from("written by meshio v5.3.0"), file_path: None, @@ -230,8 +230,8 @@ fn compare_points_in_float_and_overwrite(vtu: &mut Vtk, expected: &Vtk) { #[cfg(feature = "xml")] fn assert_and_fix_xml_vtu(vtu: &mut Vtk) { vtu.file_path = None; // Reset file path to satisfy comparison - assert_eq!(vtu.version, Version::new((0, 1))); // XML file version is ignored. - vtu.version = (5, 1).into(); // Explicitly set version to satisfy comparison. + assert_eq!(vtu.version, Version::new_xml(0, 1)); // XML file version is ignored. + vtu.version = Version::new_legacy(5, 1); // Explicitly set version to satisfy comparison. assert_eq!(vtu.title, String::new()); // Default empty title vtu.title = "written by meshio v5.3.0".into(); // Match test file vtu.byte_order = ByteOrder::BigEndian; // Match test file diff --git a/tests/xml.rs b/tests/xml.rs index 4a211ee..c239a5c 100644 --- a/tests/xml.rs +++ b/tests/xml.rs @@ -11,7 +11,7 @@ fn init() { fn make_box_vtu() -> Vtk { Vtk { - version: Version { major: 4, minor: 2 }, + version: Version::new_xml(4, 2), title: String::new(), byte_order: ByteOrder::BigEndian, file_path: None, @@ -106,7 +106,7 @@ fn box_import() -> Result { fn make_box_para_vtu() -> Vtk { Vtk { - version: Version { major: 1, minor: 0 }, + version: Version::new_xml(1, 0), title: String::new(), byte_order: ByteOrder::LittleEndian, file_path: None, @@ -185,7 +185,7 @@ fn box_para_parse_xml() -> Result { fn make_hexahedron_vtu() -> Vtk { Vtk { - version: Version { major: 1, minor: 0 }, + version: Version::new_xml(1, 0), title: String::new(), byte_order: ByteOrder::LittleEndian, file_path: None, @@ -341,7 +341,7 @@ fn hexahedron_binary() -> Result { fn make_tet_vtu() -> Vtk { Vtk { - version: Version { major: 1, minor: 0 }, + version: Version::new_xml(1, 0), title: String::new(), byte_order: ByteOrder::LittleEndian, file_path: None,