Skip to main content

FileFormat

Enum FileFormat 

Source
#[non_exhaustive]
pub enum FileFormat { SokobanSLC, Xsb, Txt, Hsb, Tsb, SokobanYASC, Json, }
Expand description

All supported Sokoban file formats.

Depending on file format, following restrictions apply:

File extensionrestrictions
.sokAny Tessellation puzzles, allows mixing different Tessellation in the same file
.hsbOnly Hexoban puzzles
.tsbOnly Trioban puzzles
.xsbOnly Sokoban puzzles
.txtOnly Sokoban puzzles
.slcOnly Sokoban puzzles
.jsonAny Tessellation puzzles, allows mixing different Tessellation in the same file

If file format restricts Tessellation of Puzzle in it, sokoban_elements will:

If file format doesn’t restrict Tessellation of Puzzle in it, sokoban_elements will allow and implement it too.

§Todo

  • make writing of metadata optional
  • make writer for individual Puzzles
    • with optional metadata
    • with optional saved games

Variants (Non-exhaustive)§

This enum is marked as non-exhaustive
Non-exhaustive enums could have additional variants added in future. Therefore, when matching against variants of non-exhaustive enums, an extra wildcard arm must be added to account for any future variants.
§

SokobanSLC

XML format associated with Sokoban for Windows.

Files typically end with .slc extension.

This is lossy format:

  • it doesn’t support writing any tessellation except Sokoban
  • it doesn’t support writing Sokoban+, saved games, notes, etc…
  • Collection writer reports error if trying to write Puzzle that is not Sokoban, but it silently skips writing unsupported data for supported tessellation.
§

Xsb

XSokoban file format.

Files typically end with .xsb extension.

This is lossy format:

  • it doesn’t support writing any tessellation except Sokoban
  • it doesn’t support writing Sokoban+, saved games, notes, etc…
  • Collection writer reports error if trying to write Puzzle that is not Sokoban, but it silently skips writing unsupported data for supported tessellation!
§

Txt

Same as FileFormat::Xsb, different file extension.

Files typically end with .txt extension.

This is lossy format:

  • it doesn’t support writing any tessellation except Sokoban
  • it doesn’t support writing Sokoban+, saved games, notes, etc…
  • Collection writer reports error if trying to write Puzzle that is not Sokoban, but it silently skips writing unsupported data for supported tessellation!
§

Hsb

File format base on Xsb but for Hexoban puzzles.

Files typically end with .hsb

This is lossy format:

  • it doesn’t support writing any tessellation except Hexoban
  • it doesn’t support writing Sokoban+, saved games, notes, etc…
  • Collection writer reports error if trying to write Puzzle that is not Hexoban, but it silently skips writing unsupported data for supported tessellation!
§

Tsb

File format base on Xsb but for Trioban puzzles.

Files typically end with .tsb

This is lossy format:

  • it doesn’t support writing any tessellation except Trioban
  • it doesn’t support writing Sokoban+, saved games, notes, etc…
  • Collection writer reports error if trying to write Puzzle that is not Trioban, but it silently skips writing unsupported data for supported tessellation.
§

SokobanYASC

Plain text format defined by SokobanYASC.

Files typically end with .sok extension.

This is most versatile file format because it supports saving complete Collection, Puzzle and SavedGame info along with Sokoban+, differing Tessellations in the same file, author and similar metadata etc..

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::         Sokoban (c) by Falcon Co., Ltd., Japan         ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::                    File Format 0.17                    ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::                                                        ::
:: File Notes                                  Optional   ::
:: Puzzle 1                                    Required   ::
::    Title                                    Optional*  ::
::    Board                                    See legend ::
::    Puzzle Notes                             Optional   ::
::    Saved Game or Solution 1                 Optional   ::
::      Title                                  Optional*  ::
::      Moves                                  See legend ::
::      Notes                                  Optional   ::
::    Saved Game or Solution 2                 Optional   ::
::    ... (more saved games and solutions)                ::
:: Puzzle 2                                    Optional   ::
:: ... (more puzzles)                                     ::
::                                                        ::
:: Remarks:                                               ::
::                                                        ::
:: File Notes                                             ::
::   File notes consist of unstructured text and          ::
::   key/value properties, such as "Author: Name". Lines  ::
::   beginning with "::" are comments meant to be read    ::
::   only by a person examining the file in a text        ::
::   editor, and should not be displayed by the Sokoban   ::
::   program.                                             ::
::                                                        ::
::   The optional but recommended property                ::
::   "Collection: Name" assigns a name to the puzzle      ::
::   collection. When a collection is copied from the     ::
::   internet, for example, and pasted into a Sokoban     ::
::   program, this information allows the collection to   ::
::   be saved with the proper name.                       ::
::                                                        ::
:: Titles                                                 ::
::   A title line is the last non-blank text line before  ::
::   a board, a saved game, or a solution, provided the   ::
::   line is preceded  by a blank line or it is the only  ::
::   text line at this position in the file.              ::
::                                                        ::
::   Title lines are optional unless a single or a last   ::
::   text line from a preceding puzzle, saved game,       ::
::   solution, or file header can be mistaken for a title ::
::   line.                                                ::
::                                                        ::
:: Puzzle Notes                                           ::
::   Two special key/value pairs are supported in puzzle  ::
::   notes: "Title" and "Author", hence, titles can       ::
::   either come from a title line or from a key/value    ::
::   pair.                                                ::
::                                                        ::
::::::::::::::::::::::::::: Board ::::::::::::::::::::::::::
:: Legend.................:      :.................Legend ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Wall...................: #  # :...................Wall ::
:: Pusher.................: p  @ :.................Pusher ::
:: Pusher on goal square..: P  + :..Pusher on goal square ::
:: Box....................: b  $ :....................Box ::
:: Box on goal square.....: B  * :.....Box on goal square ::
:: Goal square............: .  . :............Goal square ::
:: Floor..................:      :..................Floor ::
:: Floor..................: -  _ :..................Floor ::
::                                                        ::
:: Remarks:                                               ::
::                                                        ::
::   The first and the last non-empty square in each row  ::
::   must be a wall or a box on a goal. An empty interior ::
::   row is written with at least one "-" or "_".         ::
::                                                        ::
::   Boards may be run-length encoded (RLE), e.g.,        ::
::   "###----p.#" may be encoded as "3#4-p.#", and        ::
::   "#-#-#-##-#-#-#" may be encoded as "2(3(#-)#)".      ::
::   A row cannot be split over multiple lines.           ::
::                                                        ::
::   Rows may be combined on a single line by using "|"   ::
::   as a row separator, e.g., "--3#|3#-#|#pb.#|5#".      ::
::   A "|" at the end of a line is optional and may be    ::
::   omitted.                                             ::
::                                                        ::
::::::::::::::::::::::::::: Moves ::::::::::::::::::::::::::
:: Legend.................:      :.................Legend ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:: Move pusher up.........: u  U :.......Push/pull box up ::
:: Move pusher down.......: d  D :.....Push/pull box down ::
:: Move pusher left.......: l  L :.....Push/pull box left ::
:: Move pusher right......: r  R :....Push/pull box right ::
:: Begin jump.............: [  ] :...............End jump ::
:: Begin pusher change....: {  } :......End pusher change ::
:: Current position.......: *  * :.......Current position ::
::                                                        ::
:: Remarks:                                               ::
::                                                        ::
::   Moves may be run-length encoded, e.g., "3r4U" means  ::
::   "rrrUUUU", and "2(3(dr)R)" means "drdrdrRdrdrdrR".   ::
::   Each line must, however, have at least one proper    ::
::   non-digit character. Spaces between moves are        ::
::   allowed.                                             ::
::                                                        ::
::   Jumps and pulls: Only in reverse mode saved games    ::
::   and solutions.                                       ::
::                                                        ::
::   Reverse mode saved games and solutions must begin    ::
::   with a jump, even if it is empty. An example:        ::
::   "[]U[rr]d".                                          ::
::                                                        ::
::   Pusher changes: Only in puzzles with multiple        ::
::   pushers, e.g., Multiban. Moves inside the braces     ::
::   depict the relative movement to get from the         ::
::   currently active pusher to the next active pusher.   ::
::   At game start, a "{...}" sequence activates the      ::
::   pusher relative to the top-left pusher. An example:  ::
::   "{rddd}Urr{uul}uLU". If the top-left pusher is the   ::
::   first active pusher, then the empty "{}" can be      ::
::   omitted.                                             ::
::                                                        ::
::   The current position is optional and defaults to the ::
::   position after the last move.                        ::
::                                                        ::
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
§

Json

JSON serialized Collection, available with the json cargo feature.

Files typically end with .json extension.

The document is exactly what serde_json produces for a Collection - there is no envelope and no version field - written pretty printed. Like SokobanYASC it is lossless and unrestricted: it stores complete Collection, Puzzle and SavedGame data along with Sokoban+, and it allows mixing Tessellations in one file. See the # Serde sections of those types for the shape of each.

Unlike the other formats, this one has no options:

Two reading quirks are worth knowing:

  • A board or saved game that fails to parse is reported as JsonSyntaxError, not as BoardSyntaxError, because serde flattens the underlying error into a message. To tell malformed JSON apart from JSON this crate’s types rejected, downcast Error::source to serde_json::Error and inspect its classify().
  • Every field of Collection is optional, so any JSON object at all reads as a valid - possibly empty - collection. That is not specific to this format: a .sok file with no board in it also reads as an empty collection.

Implementations§

Source§

impl FileFormat

Source

pub fn file_extension(&self) -> &str

File extension for given file format.

Source

pub fn restricted_to_tessellation(&self) -> Option<Tessellation>

Some if file format restricts allowed Tessellation, otherwise None.

Source

pub fn from_file_extension(path: impl AsRef<Path>) -> Option<Self>

Some if there exists a FileFormat for file extension from path, otherwise None.

Source

pub fn read<R>( src: &mut R, settings: ReaderSettings, ) -> Result<Collection, IoReadError>
where R: Read + BufRead + Seek,

Loads Collection.

⚠️ Expects source is UTF-8 encoded, but doesn’t validate that expectation.

§Examples

Reading from file:

use std::fs::File;
use std::io::BufReader;
use std::path::Path;

use sokoban_elements::*;

let path = Path::new("Original and Extra.sok");
let mut reader = BufReader::new(File::open(path)?);
let settings = ReaderSettings::default();
let collection = FileFormat::read(&mut reader, settings)?;

Reading from in-memory string:

use std::io::Cursor;

use sokoban_elements::*;

let mut settings = ReaderSettings::default();
settings.file_format = FileFormat::Xsb;

let data: String = "sokoban collection data read from elsewhere...".to_string();
let mut reader = Cursor::new(data.as_bytes());

let collection = FileFormat::read(&mut reader, settings)?;
let puzzle = &collection.puzzles[0];
Source

pub fn read_from_path( src_file_path: impl AsRef<Path>, settings: Option<ReaderSettings>, ) -> Result<Collection, IoReadError>

Loads Collection from file.

⚠️ ReaderSettings::file_format is ignored and is instead constructed from src_file_path.

Source

pub fn write<W>( dest: &mut W, collection: &Collection, settings: WriterSettings, ) -> Result<(), IoWriteError>
where W: Write,

Writes Collection.

Assumes dest is not buffered.

§Examples

Write collection to file:

use std::fs::File;
use std::path::Path;

use sokoban_elements::*;

let collection = Collection::new();

let settings = WriterSettings::default();
let file_with_extension =
    format!("Original and Extra.{}", settings.file_format.file_extension());
let path = Path::new(&file_with_extension);
let mut file = File::create(path)?;

FileFormat::write(&mut file, &collection, settings)?;

Write collection to in-memory string:

use std::io::Cursor;

use sokoban_elements::*;

let collection = Collection::new();

let settings = WriterSettings::default();
let mut bytes: Vec<u8> = Vec::new();
let mut writer = Cursor::new(&mut bytes);

FileFormat::write(&mut writer, &collection, settings)?;

let collection_text = String::from_utf8(bytes).unwrap();

Write collection to stdout

use sokoban_elements::*;

let collection = Collection::new();
let settings = WriterSettings::default();
let mut stdout = std::io::stdout();
FileFormat::write(&mut stdout, &collection, settings)?;
Source

pub fn save_to_file( &self, collection: &Collection, dest_file_path: impl AsRef<Path>, settings: Option<WriterSettings>, ) -> Result<(), IoWriteError>

Writes Collection to file.

⚠️ WriterSettings::file_format is ignored and is instead constructed from dest_file_path.

Trait Implementations§

Source§

impl Clone for FileFormat

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for FileFormat

Source§

impl Debug for FileFormat

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de> Deserialize<'de> for FileFormat

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Eq for FileFormat

Source§

impl Hash for FileFormat

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for FileFormat

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for FileFormat

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for FileFormat

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> Conv for T

Source§

fn conv<T>(self) -> T
where Self: Into<T>,

Converts self into T using Into<T>. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> FmtForward for T

Source§

fn fmt_binary(self) -> FmtBinary<Self>
where Self: Binary,

Causes self to use its Binary implementation when Debug-formatted.
Source§

fn fmt_display(self) -> FmtDisplay<Self>
where Self: Display,

Causes self to use its Display implementation when Debug-formatted.
Source§

fn fmt_lower_exp(self) -> FmtLowerExp<Self>
where Self: LowerExp,

Causes self to use its LowerExp implementation when Debug-formatted.
Source§

fn fmt_lower_hex(self) -> FmtLowerHex<Self>
where Self: LowerHex,

Causes self to use its LowerHex implementation when Debug-formatted.
Source§

fn fmt_octal(self) -> FmtOctal<Self>
where Self: Octal,

Causes self to use its Octal implementation when Debug-formatted.
Source§

fn fmt_pointer(self) -> FmtPointer<Self>
where Self: Pointer,

Causes self to use its Pointer implementation when Debug-formatted.
Source§

fn fmt_upper_exp(self) -> FmtUpperExp<Self>
where Self: UpperExp,

Causes self to use its UpperExp implementation when Debug-formatted.
Source§

fn fmt_upper_hex(self) -> FmtUpperHex<Self>
where Self: UpperHex,

Causes self to use its UpperHex implementation when Debug-formatted.
Source§

fn fmt_list(self) -> FmtList<Self>
where &'a Self: for<'a> IntoIterator,

Formats each item in a sequence. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Pipe for T
where T: ?Sized,

Source§

fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> R
where Self: Sized,

Pipes by value. This is generally the method you want to use. Read more
Source§

fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> R
where R: 'a,

Borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> R
where R: 'a,

Mutably borrows self and passes that borrow into the pipe function. Read more
Source§

fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
where Self: Borrow<B>, B: 'a + ?Sized, R: 'a,

Borrows self, then passes self.borrow() into the pipe function. Read more
Source§

fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
where Self: BorrowMut<B>, B: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.borrow_mut() into the pipe function. Read more
Source§

fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
where Self: AsRef<U>, U: 'a + ?Sized, R: 'a,

Borrows self, then passes self.as_ref() into the pipe function.
Source§

fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
where Self: AsMut<U>, U: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.as_mut() into the pipe function.
Source§

fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
where Self: Deref<Target = T>, T: 'a + ?Sized, R: 'a,

Borrows self, then passes self.deref() into the pipe function.
Source§

fn pipe_deref_mut<'a, T, R>( &'a mut self, func: impl FnOnce(&'a mut T) -> R, ) -> R
where Self: DerefMut<Target = T> + Deref, T: 'a + ?Sized, R: 'a,

Mutably borrows self, then passes self.deref_mut() into the pipe function.
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Tap for T

Source§

fn tap(self, func: impl FnOnce(&Self)) -> Self

Immutable access to a value. Read more
Source§

fn tap_mut(self, func: impl FnOnce(&mut Self)) -> Self

Mutable access to a value. Read more
Source§

fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Immutable access to the Borrow<B> of a value. Read more
Source§

fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Mutable access to the BorrowMut<B> of a value. Read more
Source§

fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Immutable access to the AsRef<R> view of a value. Read more
Source§

fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Mutable access to the AsMut<R> view of a value. Read more
Source§

fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Immutable access to the Deref::Target of a value. Read more
Source§

fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Mutable access to the Deref::Target of a value. Read more
Source§

fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self

Calls .tap() only in debug builds, and is erased in release builds.
Source§

fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self

Calls .tap_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
where Self: Borrow<B>, B: ?Sized,

Calls .tap_borrow() only in debug builds, and is erased in release builds.
Source§

fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
where Self: BorrowMut<B>, B: ?Sized,

Calls .tap_borrow_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
where Self: AsRef<R>, R: ?Sized,

Calls .tap_ref() only in debug builds, and is erased in release builds.
Source§

fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
where Self: AsMut<R>, R: ?Sized,

Calls .tap_ref_mut() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
where Self: Deref<Target = T>, T: ?Sized,

Calls .tap_deref() only in debug builds, and is erased in release builds.
Source§

fn tap_deref_mut_dbg<T>(self, func: impl FnOnce(&mut T)) -> Self
where Self: DerefMut<Target = T> + Deref, T: ?Sized,

Calls .tap_deref_mut() only in debug builds, and is erased in release builds.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> TryConv for T

Source§

fn try_conv<T>(self) -> Result<T, Self::Error>
where Self: TryInto<T>,

Attempts to convert self into T using TryInto<T>. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.