< Summary

Information
Class: src/worksheet.jl
Assembly: Default
File(s): src/worksheet.jl
Tag: 61_25545638018
Line coverage
90%
Covered lines: 183
Uncovered lines: 19
Coverable lines: 202
Total lines: 345
Line coverage: 90.5%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

File(s)

src/worksheet.jl

#LineLine coverage
 1"""
 2    JSONWorksheet
 3
 4construct 'Array{OrderedDict, 1}' for each row from Worksheet
 5
 6# Constructors
 7```julia
 8JSONWorksheet("Example.xlsx", "Sheet1")
 9JSONWorksheet("Example.xlsx", 1)
 10
 11```
 12# Arguments
 13- `row_oriented` : if 'true'(the default) it will look for colum names in '1:1', if `false` it will look for colum names
 14- `start_line` : starting index of position of columnname.
 15- `squeeze` : squeezes all rows of Worksheet to a singe row.
 16- `delim` : a String or Regrex that of deliminator for converting single cell to array.
 17
 18"""
 19mutable struct JSONWorksheet
 5520    source::String
 21    pointer::Array{Pointer,1}
 22    data::Array{T,1} where T
 23    sheetname::String
 24end
 12025function JSONWorksheet(source, sheet, arr;
 26                        delim=";", squeeze=false)
 6027    arr = dropemptyrange(arr)
 6228    @assert !isempty(arr) "'$(source)!$(sheet)' don't have valid column names, try change optional argument'start_line'"
 29
 32630    pointer = parse_column_header.(arr[1, :])
 32831    real_keys = map(el -> el.tokens, pointer)
 32    # TODO more robust key validity check
 5833    if !allunique(real_keys)
 334        throw(AssertionError("column names must be unique, check for duplication $(arr[1, :])"))
 35    end
 36
 5537    if squeeze
 138        data = squeezerow_to_jsonarray(arr, pointer, delim)
 39    else
 5440        data = eachrow_to_jsonarray(arr, pointer, delim)
 41    end
 5342    JSONWorksheet(normpath(source), pointer, data, String(sheet))
 43end
 9844function JSONWorksheet(xf::XLSX.XLSXFile, sheet;
 45                       start_line=1,
 46                       row_oriented=true,
 47                       delim=";", squeeze=false)
 4948    ws = isa(sheet, Symbol) ? xf[String(sheet)] : xf[sheet]
 4949    sheet = ws.name
 50    # orientation handling
 051    ws = begin
 4952        rg = XLSX.get_dimension(ws)
 4953        if row_oriented
 9654            rg = XLSX.CellRange(XLSX.CellRef(start_line, rg.start.column_number), rg.stop)
 4855            dt = ws[rg]
 56        else
 257            rg = XLSX.CellRange(XLSX.CellRef(rg.start.row_number, start_line), rg.stop)
 5058            dt = permutedims(ws[rg])
 59        end
 60    end
 61
 4962    JSONWorksheet(xf.source, sheet, ws; delim=delim, squeeze=squeeze)
 63end
 3264function JSONWorksheet(source, sheet; kwargs...)
 1665    xf = XLSX.readxlsx(source)
 1666    x = JSONWorksheet(xf, sheet; kwargs...)
 967    applicable(close, xf) && close(xf)
 968    return x
 69end
 70
 5471function eachrow_to_jsonarray(data::Array{T,2}, pointers, delim) where T
 5472    json = Array{PointerDict,1}(undef, size(data, 1) - 1)
 5473    Threads.@threads for i in 1:length(json)
 88574        json[i] = row_to_jsonarray(data[i + 1, :], pointers, delim)
 075    end
 5276    return json
 77end
 78
 15579function row_to_jsonarray(row, pointers, delim)
 15580    x = PointerDict()
 15581    for (col, p) in enumerate(pointers)
 117682        x[p] = collect_cell(p, row[col], delim)
 130183    end
 15384    return x
 85end
 86
 187function squeezerow_to_jsonarray(data::Array{T,2}, pointers, delim) where T
 388    arr_pointer = map(p -> begin
 489        U = Vector{eltype(p)}; Pointer{U}(p.tokens)
 90    end, pointers)
 91
 192    squzzed_json = PointerDict()
 193    @inbounds for (col, p) in enumerate(pointers)
 20294        val = map(i -> collect_cell(p, data[i + 1, :][col], delim), 1:size(data, 1) - 1)
 295        squzzed_json[arr_pointer[col]] = val
 396    end
 197    return [squzzed_json]
 98end
 99
 60100@inline function dropemptyrange(arr::Array{T,2}) where T
 60101    cols = falses(size(arr, 2))
 60102    @inbounds for c in 1:size(arr, 2)
 103        # There must be a column name, or it's a commet line
 278104        if !ismissing(arr[1, c])
 270105            for r in 1:size(arr, 1)
 270106                if !ismissing(arr[r, c])
 270107                    cols[c] = true
 270108                    break
 109                end
 0110            end
 111        end
 496112    end
 113
 60114    arr = arr[:, cols]
 60115    rows = falses(size(arr, 1))
 60116    @inbounds for r in 1:size(arr, 1)
 329117        for c in 1:size(arr, 2)
 338118            if !ismissing(arr[r, c])
 318119                rows[r] = true
 318120                break
 121            end
 20122        end
 598123    end
 60124    return arr[rows, :]
 125end
 126
 929127function collect_cell(p::Pointer{T}, cell, delim) where T
 929128    if ismissing(cell)
 103129        val = JSONPointer._null_value(p)
 130    else
 826131        if T <: AbstractArray
 197132            if isa(cell, AbstractString)
 179133                val = split(cell, delim)
 179134                isempty(val[end]) && pop!(val)
 179135                if eltype(T) <: Real
 144136                    val = parse.(eltype(T), val)
 35137                elseif eltype(T) <: AbstractString
 13138                    val = string.(val)
 139                end
 140            else
 18141                val = cell
 18142                if eltype(T) <: Real
 8143                    if isa(cell, AbstractString)
 0144                        val = parse(eltype(T), cell)
 145                    end
 10146                elseif eltype(T) <: AbstractString
 4147                    if !isa(cell, AbstractString)
 4148                        val = string(cell)
 149                    end
 150                end
 18151                val = convert(T, [val])
 152            end
 153        else
 629154            val = cell
 155        end
 156    end
 929157    return val
 158end
 159
 160# data(jws::JSONWorksheet) = getfield(jws, :data)
 1161xlsxpath(jws::JSONWorksheet) = getfield(jws, :source)
 48162sheetnames(jws::JSONWorksheet) = getfield(jws, :sheetname)
 82163Base.keys(jws::JSONWorksheet) = jws.pointer
 27164function Base.haskey(jws::JSONWorksheet, key::Pointer)
 27165    t = key.tokens
 27166    for el in getfield.(keys(jws), :tokens)
 70167        if el == key.tokens
 14168            return true
 56169        elseif length(el) > length(t)
 21170            if el[1:length(t)] == t
 3171                return true
 172            end
 173        end
 53174    end
 10175    return false
 176end
 177
 5178Base.iterate(jws::JSONWorksheet) = iterate(jws.data)
 9179Base.iterate(jws::JSONWorksheet, i) = iterate(jws.data, i)
 180
 1181Base.size(jws::JSONWorksheet) = (length(jws.data), length(jws.pointer))
 0182function Base.size(jws::JSONWorksheet, d)
 0183    d == 1 ? length(jws.data) :
 184    d == 2 ? length(jws.pointer) : throw(DimensionMismatch("only 2 dimensions of `JSONWorksheets` object are measurable"
 185end
 13186Base.length(jws::JSONWorksheet) = length(jws.data)
 187
 188
 189########################################################################
 190##
 191## getindex() definitions
 192##
 193########################################################################
 180194Base.getindex(jws::JSONWorksheet, i) = getindex(jws.data, i)
 4195Base.getindex(jws::JSONWorksheet, ::Colon, ::Colon) = getindex(jws, eachindex(jws.data), eachindex(jws.pointer))
 2196Base.getindex(jws::JSONWorksheet, row_ind, ::Colon) = getindex(jws, row_ind, eachindex(jws.pointer))
 197
 25198function Base.getindex(jws::JSONWorksheet, row_ind::Integer, col_ind::Integer)
 25199    p = keys(jws)[col_ind]
 200
 24201    jws[row_ind, p]
 202end
 6203function Base.getindex(jws::JSONWorksheet, row_ind::Integer, col_ind::AbstractArray)
 6204    pointers = keys(jws)[col_ind]
 205
 27206    permutedims(map(p -> jws[row_ind, p], pointers))
 207end
 7208@inline function Base.getindex(jws::JSONWorksheet, row_inds::AbstractArray, col_ind::AbstractArray)
 7209    pointers = keys(jws)[col_ind]
 7210    rows = jws[row_inds]
 211
 212    # v = vcat(map(el -> jws[el, col_ind], row_inds)...)
 7213    v = Array{Any,2}(undef, length(rows), length(pointers))
 7214    @inbounds for (r, _row) in enumerate(rows)
 17215        for (c, _col) in enumerate(pointers)
 64216            v[r, c] = if haskey(_row, _col)
 64217                _row[_col]
 218            else
 64219                missing
 220            end
 111221        end
 27222    end
 223
 7224    return v
 225end
 226
 52227function Base.getindex(jws::JSONWorksheet, row_ind::Integer, col_ind::Pointer)
 52228    row = jws[row_ind]
 51229    return row[col_ind]
 230end
 22231@inline function Base.getindex(jws::JSONWorksheet, row_inds, p::Pointer)
 80232    map(row -> row[p], jws[row_inds])
 233end
 13234@inline function Base.getindex(jws::JSONWorksheet, row_inds, col_ind::Integer)
 13235    p = keys(jws)[col_ind]
 236
 13237    getindex(jws, row_inds, p)
 238end
 239
 4240function Base.setindex!(jws::JSONWorksheet, value::Vector, p::Pointer)
 4241    if length(jws) != length(value)
 1242        throw(ArgumentError("New column must have the same length as old columns"))
 243    end
 6244    @inbounds for (i, row) in enumerate(jws)
 5245        row[p] = value[i]
 6246    end
 2247    if !haskey(jws, p)
 1248        push!(jws.pointer, p)
 249    end
 2250    return jws
 251end
 2252function Base.setindex!(jws::JSONWorksheet, value, i::Integer, p::Pointer)
 2253    jws[i][p] = value
 2254    if !haskey(jws, p)
 0255        push!(jws.pointer, p)
 256    end
 2257    return jws
 258end
 259
 1260Base.firstindex(jws::JSONWorksheet) = firstindex(jws.data)
 2261Base.lastindex(jws::JSONWorksheet) = lastindex(jws.data)
 6262function Base.lastindex(jws::JSONWorksheet, i::Integer)
 6263    i == 1 ? lastindex(jws.data) :
 264    i == 2 ? lastindex(jws.pointer) :
 265    throw(DimensionMismatch("JSONWorksheet only has two dimensions"))
 266end
 267
 268"""
 269    merge(a::JSONWorksheet, b::JSONWorksheet, bykey::AbstractString)
 270
 271Construct a merged JSONWorksheet from the given JSONWorksheets.
 272If the same Pointer is present in another collection, the value for that key will be the
 273value it has in the last collection listed.
 274"""
 3275function Base.merge(a::JSONWorksheet, b::JSONWorksheet, key::AbstractString)
 3276    merge(a::JSONWorksheet, b::JSONWorksheet, Pointer(key))
 277end
 3278function Base.merge(a::JSONWorksheet, b::JSONWorksheet, key::Pointer)
 4279    @assert haskey(a, key) "$key is not found in the JSONWorksheet(\"$(a.sheetname)\")"
 2280    @assert haskey(b, key) "$key is not found in the JSONWorksheet(\"$(b.sheetname)\")"
 281
 2282    pointers = unique([a.pointer; b.pointer])
 283
 10284    keyvalues_a = map(el -> el[key], a.data)
 10285    keyvalues_b = map(el -> el[key], b.data)
 2286    ind = indexin(keyvalues_b, keyvalues_a)
 287
 2288    data = deepcopy(a.data)
 2289    for (i, _b) in enumerate(b.data)
 8290        j = ind[i]
 14291        if isnothing(j)
 2292            _a = deepcopy(_b)
 2293            for p in a.pointer
 8294                _a[p] = JSONPointer._null_value(p)
 8295            end
 4296            push!(data, _a)
 297        else
 6298            _a = data[j]
 299        end
 8300        for p in b.pointer
 24301            _a[p] = _b[p]
 24302        end
 14303    end
 2304    JSONWorksheet(a.source, pointers, data, a.sheetname)
 305end
 2306function Base.append!(a::JSONWorksheet, b::JSONWorksheet)
 6307    ak = map(el -> el.tokens, keys(a))
 6308    bk = map(el -> el.tokens, keys(b))
 309
 2310    if sort(ak) != sort(bk)
 1311        throw(AssertionError("""Column names must be same for append!
 312         $(setdiff(collect(ak), collect(bk)))"""))
 313    end
 314
 1315    append!(a.data, b.data)
 316end
 317
 0318function Base.sort!(jws::JSONWorksheet, key; kwargs...)
 0319    sort!(jws, Pointer(key); kwargs...)
 320end
 2321function Base.sort!(jws::JSONWorksheet, pointer::Pointer; kwargs...)
 8322    sorted_idx = sortperm(map(el -> el[pointer], jws.data); kwargs...)
 1323    jws.data = jws.data[sorted_idx]
 1324    return jws
 325end
 326
 0327function Base.summary(io::IO, jws::JSONWorksheet)
 0328    @printf("%d×%d %s - %s!%s\n", size(jws)..., "JSONWorksheet",
 329        basename(xlsxpath(jws)), sheetnames(jws))
 330end
 0331function Base.show(io::IO, jws::JSONWorksheet)
 332    # By default, we align the columns to the left unless they are numbers, checks first row to determine datatype
 0333    alignment = fill(:l, size(jws, 2))
 334
 0335    for i in 1:size(jws, 2)
 0336        if isa(jws[1, i], Number)
 0337            alignment[i] = :r
 338        end
 0339    end
 340
 0341    summary(io, jws)
 0342    pretty_table(io, Tables.matrix(jws);
 343        column_labels = pointer_to_colname.(jws.pointer),
 344        alignment = alignment)
 345end

Methods/Properties