Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: skip fields with undocumented classes instead of hard error #119

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
*~
.DS_Store
13 changes: 13 additions & 0 deletions src/MAT_HDF5.jl
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,11 @@ function m_read(dset::HDF5Dataset)

# Regular arrays of values
# Convert to Julia type
if !haskey(str2type_matlab, mattype)
@warn "Class not convertable - skipping field - set value to missing"
return missing
end

T = str2type_matlab[mattype]

# Check for a COMPOUND data set, and if so handle complex numbers specially
Expand Down Expand Up @@ -207,6 +212,11 @@ function m_read(g::HDF5Group)
# This matrix is not empty.
ir = add!(convert(Vector{Int}, read(g, "ir")), 1)
dset = g["data"]
if !haskey(str2type_matlab, mattype)
@warn "Class not convertable - skipping field - set value to missing"
return missing
end

T = str2type_matlab[mattype]
try
dtype = datatype(dset)
Expand Down Expand Up @@ -258,6 +268,9 @@ function read(f::MatlabHDF5File, name::String)
obj = f.plain[name]
try
val = m_read(obj)
catch
@warn "Field not readable - skipping field - set value to missing"
val = missing
finally
close(obj)
end
Expand Down
41 changes: 27 additions & 14 deletions src/MAT_v5.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,13 @@ function read_bswap(f::IO, swap_bytes::Bool, d::AbstractArray{T}) where T
d
end

skip_padding(f::IO, nbytes::Int, hbytes::Int) = if nbytes % hbytes != 0
skip(f, hbytes-(nbytes % hbytes))
function skip_padding(f::IO, nbytes::Int, hbytes::Int)
if nbytes % hbytes != 0
skip(f, hbytes-(nbytes % hbytes))
return hbytes-(nbytes % hbytes)
else
return 0
end
end

# Read data type and number of bytes at the start of a data element
Expand All @@ -121,8 +126,9 @@ end
function read_element(f::IO, swap_bytes::Bool, ::Type{T}) where T
(dtype, nbytes, hbytes) = read_header(f, swap_bytes)
data = read_bswap(f, swap_bytes, T, Int(div(nbytes, sizeof(T))))
skip_padding(f, nbytes, hbytes)
data
skipped_bytes = skip_padding(f, nbytes, hbytes)
bytes_read = nbytes + hbytes + skipped_bytes
return data, bytes_read
end

# Read data element as encoded type
Expand Down Expand Up @@ -168,10 +174,11 @@ end

function read_struct(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, is_object::Bool)
if is_object
class = String(read_element(f, swap_bytes, UInt8))
class = String(read_element(f, swap_bytes, UInt8)[1])
end
field_length = read_element(f, swap_bytes, Int32)[1]
field_names = read_element(f, swap_bytes, UInt8)
field_lengths, _ = read_element(f, swap_bytes, Int32)
field_length = field_lengths[1]
field_names, _ = read_element(f, swap_bytes, UInt8)
n_fields = div(length(field_names), field_length)

# Get field names as strings
Expand Down Expand Up @@ -232,11 +239,11 @@ function read_sparse(f::IO, swap_bytes::Bool, dimensions::Vector{Int32}, flags::

m = isempty(dimensions) ? 0 : dimensions[1]
n = length(dimensions) <= 1 ? 0 : dimensions[2]
ir = plusone!(convert(Vector{Int}, read_element(f, swap_bytes, Int32)))
jc = plusone!(convert(Vector{Int}, read_element(f, swap_bytes, Int32)))
ir = plusone!(convert(Vector{Int}, read_element(f, swap_bytes, Int32)[1]))
jc = plusone!(convert(Vector{Int}, read_element(f, swap_bytes, Int32)[1]))
if (flags[1] & (1 << 9)) != 0 # logical
# WTF. For some reason logical sparse matrices are tagged as doubles.
pr = read_element(f, swap_bytes, Bool)
pr = read_element(f, swap_bytes, Bool)[1]
else
pr = read_data(f, swap_bytes)
if (flags[1] & (1 << 11)) != 0 # complex
Expand Down Expand Up @@ -316,9 +323,10 @@ function read_matrix(f::IO, swap_bytes::Bool)
return ("", Matrix{Union{}}(undef, 0, 0))
end

flags = read_element(f, swap_bytes, UInt32)
dimensions = read_element(f, swap_bytes, Int32)
name = String(read_element(f, swap_bytes, UInt8))
flags, f_bytes = read_element(f, swap_bytes, UInt32)
dimensions, d_bytes = read_element(f, swap_bytes, Int32)
raw_name, n_bytes = read_element(f, swap_bytes, UInt8)
name = String(raw_name)

class = flags[1] & 0xFF
local data
Expand All @@ -330,6 +338,11 @@ function read_matrix(f::IO, swap_bytes::Bool)
data = read_sparse(f, swap_bytes, dimensions, flags)
elseif class == mxCHAR_CLASS && length(dimensions) <= 2
data = read_string(f, swap_bytes, dimensions)
elseif class > length(CONVERT_TYPES) # undocumented classes
@warn "Class not convertable - skipping field - set value to missing"
remaining_bytes = nbytes - f_bytes - d_bytes - n_bytes
skip(f, remaining_bytes)
data = missing
else
if (flags[1] & (1 << 9)) != 0 # logical
data = read_data(f, swap_bytes, Bool, dimensions)
Expand Down Expand Up @@ -378,7 +391,7 @@ function getvarnames(matfile::Matlabv5File)

read_element(f, matfile.swap_bytes, UInt32)
read_element(f, matfile.swap_bytes, Int32)
varnames[String(read_element(f, matfile.swap_bytes, UInt8))] = offset
varnames[String(read_element(f, matfile.swap_bytes, UInt8)[1])] = offset

seek(matfile.ios, offset+nbytes+hbytes)
end
Expand Down
43 changes: 30 additions & 13 deletions test/read.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using MAT, Test

function check(filename, result)
function check(filename, result; skip_keys=String[])
matfile = matopen(filename)
for (k, v) in result
k in skip_keys && continue
@test exists(matfile, k)
got = read(matfile, k)
if !isequal(got, v) || (typeof(got) != typeof(v) && (!isa(got, String) || !(isa(v, String))))
Expand All @@ -20,24 +21,28 @@ function check(filename, result)
""")
end
end
@test union!(Set(), names(matfile)) == union!(Set(), keys(result))
if isempty(skip_keys)
@test union!(Set(), names(matfile)) == union!(Set(), keys(result))
end
close(matfile)

mat = matread(filename)
if !isequal(mat, result)
error("""
Data mismatch reading $filename ($format)
if isempty(skip_keys)
mat = matread(filename)
if !isequal(mat, result)
error("""
Data mismatch reading $filename ($format)

Got:
Got:

$(repr(mat))
$(repr(mat))

Expected:
Expected:

$(repr(result))
""")
close(matfile)
return false
$(repr(result))
""")
close(matfile)
return false
end
end

return true
Expand Down Expand Up @@ -136,6 +141,18 @@ for _format in ["v6", "v7", "v7.3"]
)
check("sparse.mat", result)

result = Dict(
"t" => Dict(
"int" => 66.0,
"struct" => Dict{String,Any}(
"b"=>[5.0 6.0 7.0],
"a"=>[1.0 2.0 3.0]),
"skip_field" => missing
)
)

check("skip_field.mat", result, skip_keys=["", "#subsystem#"])

matfile = matopen("partial.mat")
var1 = read(matfile, "var1")
@assert var1[28, 33] == 5
Expand Down
Binary file added test/v6/skip_field.mat
Binary file not shown.
Binary file added test/v7.3/skip_field.mat
Binary file not shown.
Binary file added test/v7/skip_field.mat
Binary file not shown.
12 changes: 11 additions & 1 deletion test/write.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,17 @@ function test_write(data)
end

if !isequal(result, data)
error("Data mismatch")
error("""
Data mismatch

Got:

$(repr(result))

Expected:

$(repr(data))
""")
end
end

Expand Down