tiled 0.15.0

A rust crate for loading maps created by the Tiled editor
Documentation
use std::{convert::TryInto, io::Read};

use base64::Engine;
use xml::reader::XmlEvent;

use crate::{util::XmlEventResult, CsvDecodingError, Error, LayerTileData, MapTilesetGid, Result};

pub(crate) fn parse_data_line(
    encoding: Option<String>,
    compression: Option<String>,
    parser: &mut impl Iterator<Item = XmlEventResult>,
    tilesets: &[MapTilesetGid],
) -> Result<Vec<Option<LayerTileData>>> {
    match (encoding.as_deref(), compression.as_deref()) {
        (Some("csv"), None) => decode_csv(parser, tilesets),

        (Some("base64"), None) => parse_base64(parser).map(|v| convert_to_tiles(&v, tilesets)),
        (Some("base64"), Some("zlib")) => parse_base64(parser)
            .and_then(|data| process_decoder(Ok(flate2::bufread::ZlibDecoder::new(&data[..]))))
            .map(|v| convert_to_tiles(&v, tilesets)),
        (Some("base64"), Some("gzip")) => parse_base64(parser)
            .and_then(|data| process_decoder(Ok(flate2::bufread::GzDecoder::new(&data[..]))))
            .map(|v| convert_to_tiles(&v, tilesets)),
        #[cfg(feature = "zstd")]
        (Some("base64"), Some("zstd")) => parse_base64(parser)
            .and_then(|data| process_decoder(zstd::stream::read::Decoder::with_buffer(&data[..])))
            .map(|v| convert_to_tiles(&v, tilesets)),

        _ => Err(Error::InvalidEncodingFormat {
            encoding,
            compression,
        }),
    }
}

fn parse_base64(parser: &mut impl Iterator<Item = XmlEventResult>) -> Result<Vec<u8>> {
    for next in parser {
        match next.map_err(Error::XmlDecodingError)? {
            XmlEvent::Characters(s) => {
                return base64::engine::GeneralPurpose::new(
                    &base64::alphabet::STANDARD,
                    base64::engine::general_purpose::PAD,
                )
                .decode(s.trim().as_bytes())
                .map_err(Error::Base64DecodingError);
            }
            XmlEvent::EndElement { name, .. } if name.local_name == "data" => {
                return Ok(Vec::new());
            }
            _ => {}
        }
    }
    Err(Error::PrematureEnd("Ran out of XML data".to_owned()))
}

fn process_decoder(decoder: std::io::Result<impl Read>) -> Result<Vec<u8>> {
    decoder
        .and_then(|mut decoder| {
            let mut data = Vec::new();
            decoder.read_to_end(&mut data)?;
            Ok(data)
        })
        .map_err(Error::DecompressingError)
}

fn decode_csv(
    parser: &mut impl Iterator<Item = XmlEventResult>,
    tilesets: &[MapTilesetGid],
) -> Result<Vec<Option<LayerTileData>>> {
    for next in parser {
        match next.map_err(Error::XmlDecodingError)? {
            XmlEvent::Characters(s) => {
                let mut tiles = Vec::new();
                for v in s.split(',') {
                    match v.trim().parse() {
                        Ok(bits) => tiles.push(LayerTileData::from_bits(bits, tilesets)),
                        Err(e) => {
                            return Err(Error::CsvDecodingError(
                                CsvDecodingError::TileDataParseError(e),
                            ))
                        }
                    }
                }
                return Ok(tiles);
            }
            XmlEvent::EndElement { name, .. } if name.local_name == "data" => {
                return Ok(Vec::new());
            }
            _ => {}
        }
    }
    Err(Error::PrematureEnd("Ran out of XML data".to_owned()))
}

fn convert_to_tiles(data: &[u8], tilesets: &[MapTilesetGid]) -> Vec<Option<LayerTileData>> {
    data.chunks_exact(4)
        .map(|chunk| {
            let bits = u32::from_le_bytes(chunk.try_into().unwrap());
            LayerTileData::from_bits(bits, tilesets)
        })
        .collect()
}