///|
fn normalize_image_extension(extension : StringView) -> String raise XlsxError {
let value = extension.to_owned()
let trimmed = match value.strip_prefix(".") {
Some(rest) => {
let s = rest.to_owned()
if s == "" {
raise InvalidSheetBackground(msg="image extension missing")
}
s
}
None => value
}
if trimmed == "" {
raise InvalidSheetBackground(msg="image extension missing")
}
trimmed.to_lower()
}
///|
fn image_content_type(extension : StringView) -> String? {
match extension.to_lower() {
"bmp" => Some("image/bmp")
"emf" => Some("image/emf")
"emz" => Some("image/emz")
"gif" => Some("image/gif")
"ico" => Some("image/x-icon")
"jpeg" => Some("image/jpeg")
"jpg" => Some("image/jpeg")
"png" => Some("image/png")
"svg" => Some("image/svg+xml")
"tif" => Some("image/tiff")
"tiff" => Some("image/tiff")
"wmf" => Some("image/wmf")
"wmz" => Some("image/wmz")
_ => None
}
}
///|
priv struct ImagePartIdentity {
extension : String
content_type : String
}
///|
fn canonical_image_extension_for_content_type(
content_type : StringView,
) -> String? {
match content_type.to_lower() {
"image/bmp" => Some("bmp")
"image/emf" => Some("emf")
"image/emz" => Some("emz")
"image/gif" => Some("gif")
"image/x-icon" => Some("ico")
"image/jpeg" => Some("jpeg")
"image/png" => Some("png")
"image/svg+xml" => Some("svg")
"image/tiff" => Some("tiff")
"image/wmf" => Some("wmf")
"image/wmz" => Some("wmz")
_ => None
}
}
///|
/// Resolves an embedded image's identity from `[Content_Types].xml`, never
/// from a filename guess. A compatible filename extension is preserved (for
/// example `jpg`/`tif`); an override on an unconventional name receives the
/// canonical extension for its declared supported media type.
fn image_part_identity(
content_types : @ooxml.PackageContentTypes,
part_name : StringView,
role : StringView,
cancelled? : () -> Bool = () => false,
) -> ImagePartIdentity raise XlsxError {
let declared = declared_part_content_type(
content_types,
logical_archive_part_path(part_name),
role,
cancelled~,
)
let content_type = declared.to_lower()
let canonical = match
canonical_image_extension_for_content_type(content_type) {
Some(value) => value
None => raise InvalidXml(msg="\{role.to_owned()} content type is invalid")
}
let extension = match extension_from_path(part_name) {
Some(candidate) =>
match image_content_type(candidate) {
Some(value) if value == content_type => candidate
_ => canonical
}
None => canonical
}
{ extension, content_type }
}
///|
fn load_image_part(
archive : @zip.Archive,
content_types : @ooxml.PackageContentTypes,
path : StringView,
role : StringView,
cancelled? : () -> Bool = () => false,
) -> (BytesView, ImagePartIdentity) raise XlsxError {
let bytes = match archive.get(path) {
Some(value) => value
None => raise MissingPart(path=path.to_owned())
}
let identity = image_part_identity(content_types, path, role, cancelled~)
(bytes, identity)
}
///|
/// Shared manifest for package-private image reader tests. Production readers
/// always receive the manifest parsed from the archive.
fn image_reader_test_content_types() -> @ooxml.PackageContentTypes {
try! @ooxml.parse_package_content_types(
(
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
#|
),
)
}
///|
test "image part identity comes from supported manifest declarations" {
let missing = try! @ooxml.parse_package_content_types(
"",
)
let conflicting = try! @ooxml.parse_package_content_types(
"",
)
for
entry in [
(missing, "drawing image content type is missing"),
(conflicting, "drawing image content type is invalid"),
] {
let (content_types, expected) = entry
try
image_part_identity(content_types, "xl/media/image1.png", "drawing image")
catch {
InvalidXml(msg~) => assert_eq(msg, expected)
_ => fail("unexpected image content-type error")
} noraise {
_ => fail("expected image content-type rejection")
}
}
let overridden_types = try! @ooxml.parse_package_content_types(
"",
)
let identity = image_part_identity(
overridden_types, "xl/media/opaque.bin", "drawing image",
)
assert_eq(identity.extension, "png")
assert_eq(identity.content_type, "image/png")
let supported_conflict = try! @ooxml.parse_package_content_types(
"",
)
let jpeg = image_part_identity(
supported_conflict, "xl/media/image1.png", "drawing image",
)
assert_eq(jpeg.extension, "jpeg")
assert_eq(jpeg.content_type, "image/jpeg")
}
///|
test "image types wb: normalize image extension rejects dot-only and empty" {
let dot_only : Result[String, Error] = Ok(normalize_image_extension(".")) catch {
e => Err(e)
}
inspect(dot_only is Err(XlsxError::InvalidSheetBackground(_)), content="true")
let empty : Result[String, Error] = Ok(normalize_image_extension("")) catch {
e => Err(e)
}
inspect(empty is Err(XlsxError::InvalidSheetBackground(_)), content="true")
}
///|
test "image types wb: content type recognizes tif alias" {
debug_inspect(image_content_type("tif"), content="Some(\"image/tiff\")")
}