From 2bc096bfd0257cd5d27f9f77826c88e348f5931f Mon Sep 17 00:00:00 2001 From: alexdevteam <alexpro820@gmail.com> Date: Wed, 29 Dec 2021 23:15:48 +0100 Subject: [PATCH] Refactor duplicate code --- src/map.rs | 9 +- src/tileset.rs | 217 ++++++++++++++++++++++++++----------------------- 2 files changed, 117 insertions(+), 109 deletions(-) diff --git a/src/map.rs b/src/map.rs index f717d8b..e22ff65 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,11 +1,4 @@ -use std::{ - collections::HashMap, - fmt, - fs::File, - io::Read, - path::{Path, PathBuf}, - str::FromStr, -}; +use std::{collections::HashMap, fmt, fs::File, io::Read, path::Path, str::FromStr}; use xml::{attribute::OwnedAttribute, reader::XmlEvent, EventReader}; diff --git a/src/tileset.rs b/src/tileset.rs index 1c49c3a..2b120ec 100644 --- a/src/tileset.rs +++ b/src/tileset.rs @@ -42,6 +42,20 @@ pub struct Tileset { pub source: Option<PathBuf>, } +/// Internal structure for holding mid-parse information. +struct TilesetProperties { + spacing: Option<u32>, + margin: Option<u32>, + tilecount: Option<u32>, + columns: Option<u32>, + first_gid: u32, + name: String, + tile_width: u32, + tile_height: u32, + path_relative_to: Option<PathBuf>, + source: Option<PathBuf>, +} + impl Tileset { /// Parse a buffer hopefully containing the contents of a Tiled tileset. /// @@ -79,12 +93,45 @@ impl Tileset { }) } + pub(crate) fn new_external<R: Read>( + file: R, + first_gid: u32, + path: Option<&Path>, + ) -> Result<Self, TiledError> { + let mut tileset_parser = EventReader::new(file); + loop { + match tileset_parser + .next() + .map_err(TiledError::XmlDecodingError)? + { + XmlEvent::StartElement { + name, attributes, .. + } => { + if name.local_name == "tileset" { + return Self::parse_external_tileset( + first_gid, + &mut tileset_parser, + &attributes, + path, + ); + } + } + XmlEvent::EndDocument => { + return Err(TiledError::PrematureEnd( + "Tileset Document ended before map was parsed".to_string(), + )) + } + _ => {} + } + } + } + fn parse_xml_embedded<R: Read>( parser: &mut EventReader<R>, attrs: &Vec<OwnedAttribute>, path_relative_to: Option<&Path>, ) -> Result<Tileset, TiledError> { - let ((spacing, margin, tilecount, columns), (first_gid, name, width, height)) = get_attrs!( + let ((spacing, margin, tilecount, columns), (first_gid, name, tile_width, tile_height)) = get_attrs!( attrs, optionals: [ ("spacing", spacing, |v:String| v.parse().ok()), @@ -101,50 +148,21 @@ impl Tileset { TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()) ); - let mut image = Option::None; - let mut tiles = Vec::new(); - let mut properties = HashMap::new(); - parse_tag!(parser, "tileset", { - "image" => |attrs| { - image = Some(Image::new(parser, attrs, path_relative_to.ok_or(TiledError::SourceRequired{object_to_parse: "Image".to_string()})?)?); - Ok(()) - }, - "properties" => |_| { - properties = parse_properties(parser)?; - Ok(()) - }, - "tile" => |attrs| { - tiles.push(Tile::new(parser, attrs, path_relative_to)?); - Ok(()) - }, - }); - - let columns = match columns { - Some(col) => col, - None => match &image { - None => { - return Err(TiledError::MalformedAttributes( - "No <image> nor columns attribute in <tileset>".to_string(), - )) - } - Some(image) => image.width as u32 / width, + Self::finish_parsing_xml( + parser, + TilesetProperties { + spacing, + margin, + name, + path_relative_to: path_relative_to.map(Path::to_owned), + columns, + tilecount, + tile_height, + tile_width, + first_gid, + source: None, }, - }; - - Ok(Tileset { - tile_width: width, - tile_height: height, - spacing: spacing.unwrap_or(0), - margin: margin.unwrap_or(0), - first_gid, - name, - tilecount, - columns, - image, - tiles, - properties, - source: None, - }) + ) } fn parse_xml_reference( @@ -175,46 +193,13 @@ impl Tileset { Tileset::new_external(file, first_gid, Some(&tileset_path)) } - pub(crate) fn new_external<R: Read>( - file: R, - first_gid: u32, - path: Option<&Path>, - ) -> Result<Self, TiledError> { - let mut tileset_parser = EventReader::new(file); - loop { - match tileset_parser - .next() - .map_err(TiledError::XmlDecodingError)? - { - XmlEvent::StartElement { - name, attributes, .. - } => { - if name.local_name == "tileset" { - return Self::parse_external_tileset( - first_gid, - &mut tileset_parser, - &attributes, - path, - ); - } - } - XmlEvent::EndDocument => { - return Err(TiledError::PrematureEnd( - "Tileset Document ended before map was parsed".to_string(), - )) - } - _ => {} - } - } - } - fn parse_external_tileset<R: Read>( first_gid: u32, parser: &mut EventReader<R>, attrs: &Vec<OwnedAttribute>, path: Option<&Path>, ) -> Result<Tileset, TiledError> { - let ((spacing, margin, tilecount, columns), (name, width, height)) = get_attrs!( + let ((spacing, margin, tilecount, columns), (name, tile_width, tile_height)) = get_attrs!( attrs, optionals: [ ("spacing", spacing, |v:String| v.parse().ok()), @@ -230,14 +215,35 @@ impl Tileset { TiledError::MalformedAttributes("tileset must have a firstgid, name tile width and height with correct types".to_string()) ); - let source_path = path.and_then(|p| p.parent()); + let source_path = path.and_then(|p| p.parent().map(Path::to_owned)); + Self::finish_parsing_xml( + parser, + TilesetProperties { + spacing, + margin, + name, + path_relative_to: source_path, + columns, + tilecount, + tile_height, + tile_width, + first_gid, + source: path.map(Path::to_owned), + }, + ) + } + + fn finish_parsing_xml<R: Read>( + parser: &mut EventReader<R>, + prop: TilesetProperties, + ) -> Result<Self, TiledError> { let mut image = Option::None; let mut tiles = Vec::new(); let mut properties = HashMap::new(); parse_tag!(parser, "tileset", { "image" => |attrs| { - image = Some(Image::new(parser, attrs, source_path.ok_or(TiledError::SourceRequired{object_to_parse: "Image".to_string()})?)?); + image = Some(Image::new(parser, attrs, prop.path_relative_to.as_ref().ok_or(TiledError::SourceRequired{object_to_parse: "Image".to_string()})?)?); Ok(()) }, "properties" => |_| { @@ -245,36 +251,45 @@ impl Tileset { Ok(()) }, "tile" => |attrs| { - tiles.push(Tile::new(parser, attrs, path)?); + tiles.push(Tile::new(parser, attrs, prop.path_relative_to.as_ref().and_then(|p| Some(p.as_path())))?); Ok(()) }, }); - let columns = match columns { - Some(col) => col, - None => match &image { - None => { - return Err(TiledError::MalformedAttributes( - "No <image> and no <columns> in <tileset>".to_string(), - )) - } - Some(image) => image.width as u32 / width, - }, - }; + let (margin, spacing) = (prop.margin.unwrap_or(0), prop.spacing.unwrap_or(0)); + + let columns = prop + .columns + .map(Ok) + .unwrap_or_else(|| Self::calculate_columns(&image, prop.tile_width, margin, spacing))?; Ok(Tileset { - first_gid: first_gid, - name: name, - tile_width: width, - tile_height: height, - spacing: spacing.unwrap_or(0), - margin: margin.unwrap_or(0), + first_gid: prop.first_gid, + name: prop.name, + tile_width: prop.tile_width, + tile_height: prop.tile_height, + spacing, + margin, columns, - tilecount, + tilecount: prop.tilecount, image, - tiles: tiles, + tiles, properties, - source: path.and_then(|x| Some(x.to_owned())), + source: prop.source, }) } + + fn calculate_columns( + image: &Option<Image>, + tile_width: u32, + margin: u32, + spacing: u32, + ) -> Result<u32, TiledError> { + image + .as_ref() + .ok_or(TiledError::MalformedAttributes( + "No <image> nor columns attribute in <tileset>".to_string(), + )) + .and_then(|image| Ok((image.width as u32 - margin + spacing) / (tile_width + spacing))) + } } -- GitLab