Add DateTime parser.

This change introduces a new variant to Error and breaks the API
compatibility.
This commit is contained in:
KAMADA Ken'ichi 2017-03-24 22:57:38 +09:00
parent 7f66863d84
commit 5600fb205f
4 changed files with 77 additions and 1 deletions

View File

@ -38,6 +38,10 @@ pub enum Error {
Io(io::Error),
/// Exif attribute information was not found.
NotFound(&'static str),
/// The value of the field is blank. Some fields have blank values
/// whose meanings are defined as "unknown". Such a blank value
/// should be treated the same as the absence of the field.
BlankValue(&'static str),
}
impl From<io::Error> for Error {
@ -52,6 +56,7 @@ impl fmt::Display for Error {
Error::InvalidFormat(msg) => f.write_str(msg),
Error::Io(ref err) => err.fmt(f),
Error::NotFound(msg) => f.write_str(msg),
Error::BlankValue(msg) => f.write_str(msg),
}
}
}
@ -62,6 +67,7 @@ impl error::Error for Error {
Error::InvalidFormat(msg) => msg,
Error::Io(ref err) => err.description(),
Error::NotFound(msg) => msg,
Error::BlankValue(msg) => msg,
}
}
@ -70,6 +76,7 @@ impl error::Error for Error {
Error::InvalidFormat(_) => None,
Error::Io(ref err) => Some(err),
Error::NotFound(_) => None,
Error::BlankValue(_) => None,
}
}
}

View File

@ -50,7 +50,7 @@ pub use jpeg::get_exif_attr as get_exif_attr_from_jpeg;
pub use reader::Reader;
pub use tag_priv::{Context, Tag};
pub use tag_priv::constants as tag;
pub use tiff::Field;
pub use tiff::{DateTime, Field};
pub use tiff::parse_exif;
pub use value::Value;
pub use value::{Rational, SRational};

View File

@ -30,6 +30,7 @@ use tag;
use tag_priv::{Context, Tag};
use value::Value;
use value::get_type_info;
use util::atou16;
// TIFF header magic numbers [EXIF23 4.5.2].
const TIFF_BE: u16 = 0x4d4d;
@ -155,6 +156,40 @@ pub fn is_tiff(buf: &[u8]) -> bool {
buf.starts_with(&TIFF_BE_SIG) || buf.starts_with(&TIFF_LE_SIG)
}
/// A struct used to parse a DateTime field.
#[derive(Debug)]
pub struct DateTime {
pub year: u16,
pub month: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
}
impl DateTime {
/// Parse an ASCII data of a DateTime field. The range of a number
/// is not validated, so, for example, 13 may be returned as the month.
pub fn from_ascii(data: &[u8]) -> Result<DateTime, Error> {
if data == b" : : : : " || data == b" " {
return Err(Error::BlankValue("DateTime is blank"));
} else if data.len() < 19 {
return Err(Error::InvalidFormat("DateTime too short"));
} else if !(data[4] == b':' && data[7] == b':' && data[10] == b' ' &&
data[13] == b':' && data[16] == b':') {
return Err(Error::InvalidFormat("Invalid DateTime delimiter"));
}
Ok(DateTime {
year: try!(atou16(&data[0..4])),
month: try!(atou16(&data[5..7])) as u8,
day: try!(atou16(&data[8..10])) as u8,
hour: try!(atou16(&data[11..13])) as u8,
minute: try!(atou16(&data[14..16])) as u8,
second: try!(atou16(&data[17..19])) as u8,
})
}
}
#[cfg(test)]
mod tests {
use error::Error;

View File

@ -26,6 +26,8 @@
use std::io;
use error::Error;
pub fn read8<R>(reader: &mut R) -> Result<u8, io::Error> where R: io::Read {
let mut buf: [u8; 1] = unsafe { ::std::mem::uninitialized() };
reader.read_exact(&mut buf).and(Ok(buf[0]))
@ -37,6 +39,27 @@ pub fn read16<R>(reader: &mut R) -> Result<u16, io::Error> where R: io::Read {
Ok(((buf[0] as u16) << 8) + buf[1] as u16)
}
// This function must not be called with more than 4 bytes.
pub fn atou16(bytes: &[u8]) -> Result<u16, Error> {
const ASCII_0: u8 = 0x30;
const ASCII_9: u8 = 0x39;
if cfg!(debug_assertions) && bytes.len() >= 5 {
panic!("atou16 accepts up to 4 bytes");
}
if bytes.len() == 0 {
return Err(Error::InvalidFormat("Not a number"));
}
let mut n = 0;
for &c in bytes {
if c < ASCII_0 || ASCII_9 < c {
return Err(Error::InvalidFormat("Not a number"));
}
n = n * 10 + (c - ASCII_0) as u16;
}
Ok(n)
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
@ -71,4 +94,15 @@ mod tests {
assert_ok!(reader.read_to_end(&mut buf), 1);
assert_eq!(buf, [0x03]);
}
#[test]
fn atou16_misc() {
assert_ok!(atou16(b"0"), 0);
assert_ok!(atou16(b"0010"), 10);
assert_ok!(atou16(b"9999"), 9999);
assert_err_pat!(atou16(b""), Error::InvalidFormat(_));
assert_err_pat!(atou16(b"/"), Error::InvalidFormat(_));
assert_err_pat!(atou16(b":"), Error::InvalidFormat(_));
assert_err_pat!(atou16(b"-1"), Error::InvalidFormat(_));
}
}