< Summary

Class:src/worksheet.jl
Assembly:Default
File(s):src/worksheet.jl
Covered lines:148
Uncovered lines:17
Coverable lines:165
Total lines:338
Line coverage:89.6% (148 of 165)
Covered branches:0
Total branches:0
Tag:43_456648716

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
 4520    xlsxpath::String
 21    pointer::Array{Pointer,1}
 22    data::Array{T,1} where T
 23    sheetname::String
 24end
 25function JSONWorksheet(xlsxpath, sheet, arr;
 26                        delim=";", squeeze=false)
 10027    arr = dropemptyrange(arr)
 5228    @assert !isempty(arr) "'$(xlsxpath)!$(sheet)' don't have valid column names, try change optional argument'start_line
 29
 9630    pointer = _column_to_pointer.(arr[1, :])
 4831    real_keys = map(el -> el.tokens, pointer)
 32    # TODO more robust key validity check
 4833    if !allunique(real_keys)
 334        throw(AssertionError("column names must be unique, check for duplication $(arr[1, :])"))
 35    end
 36
 4537    if squeeze
 138        data = squeezerow_to_jsonarray(arr, pointer, delim)
 39    else
 4440        data = eachrow_to_jsonarray(arr, pointer, delim)
 41    end
 4342    JSONWorksheet(normpath(xlsxpath), pointer, data, String(sheet))
 43end
 44function JSONWorksheet(xf::XLSX.XLSXFile, sheet;
 45                       start_line=1,
 46                       row_oriented=true,
 47                       delim=";", squeeze=false)
 8648    ws = isa(sheet, Symbol) ? xf[String(sheet)] : xf[sheet]
 4349    sheet = ws.name
 50    # orientation handling
 051    ws = begin
 8652        rg = XLSX.get_dimension(ws)
 4353        if row_oriented
 4254            rg = XLSX.CellRange(XLSX.CellRef(start_line, rg.start.column_number), rg.stop)
 4255            dt = ws[rg]
 56        else
 157            rg = XLSX.CellRange(XLSX.CellRef(rg.start.row_number, start_line), rg.stop)
 4458            dt = permutedims(ws[rg])
 59        end
 60    end
 61
 4362    JSONWorksheet(xf.filepath, sheet, ws; delim=delim, squeeze=squeeze)
 63end
 64function JSONWorksheet(xlsxpath, sheet; kwargs...)
 3265    xf = XLSX.readxlsx(xlsxpath)
 1666    x = JSONWorksheet(xf, sheet; kwargs...)
 967    close(xf)
 968    return x
 69end
 70
 71function eachrow_to_jsonarray(data::Array{T,2}, pointers, delim) where T
 4472    json = Array{OrderedDict,1}(undef, size(data, 1) - 1)
 8873    Threads.@threads for i in 1:length(json)
 13474        json[i] = row_to_jsonarray(data[i + 1, :], pointers, delim)
 75    end
 4276    return json
 77end
 78
 79function row_to_jsonarray(row, pointers, delim)
 13480    x = OrderedDict{String,Any}()
 26881    for (col, p) in enumerate(pointers)
 114082        x[p] = collect_cell(p, row[col], delim)
 83    end
 13284    return x
 85end
 86
 87function squeezerow_to_jsonarray(data::Array{T,2}, pointers, delim) where T
 188    arr_pointer = map(p -> begin
 489        U = Vector{eltype(p)}; Pointer{U}(p.tokens)
 90    end, pointers)
 91
 192    squzzed_json = OrderedDict{String, Any}()
 293    @inbounds for (col, p) in enumerate(pointers)
 20294        val = map(i -> collect_cell(p, data[i + 1, :][col], delim), 1:size(data, 1) - 1)
 395        squzzed_json[arr_pointer[col]] = val
 96    end
 197    return [squzzed_json]
 98end
 99
 100@inline function dropemptyrange(arr::Array{T,2}) where T
 50101    cols = falses(size(arr, 2))
 100102    @inbounds for c in 1:size(arr, 2)
 103        # There must be a column name, or it's a commet line
 464104        if !ismissing(arr[1, c])
 460105            for r in 1:size(arr, 1)
 457106                if !ismissing(arr[r, c])
 230107                    cols[c] = true
 417108                    break
 109                end
 110            end
 111        end
 112    end
 113
 50114    arr = arr[:, cols]
 50115    rows = falses(size(arr, 1))
 100116    @inbounds for r in 1:size(arr, 1)
 588117        for c in 1:size(arr, 2)
 587118            if !ismissing(arr[r, c])
 287119                rows[r] = true
 550120                break
 121            end
 122        end
 123    end
 50124    return arr[rows, :]
 125end
 126
 127function collect_cell(p::Pointer{T}, cell, delim) where T
 837128    if ismissing(cell)
 89129        val = JSONPointer._null_value(p)
 130    else
 748131        if T <: AbstractArray
 167132            if isa(cell, AbstractString)
 153133                val = split(cell, delim)
 153134                isempty(val[end]) && pop!(val)
 153135                if eltype(T) <: Real
 260136                    val = parse.(eltype(T), val)
 23137                elseif eltype(T) <: AbstractString
 159138                    val = string.(val)
 139                end
 140            else
 14141                val = cell
 14142                if eltype(T) <: Real
 6143                    if isa(cell, AbstractString)
 6144                        val = parse(eltype(T), cell)
 145                    end
 8146                elseif eltype(T) <: AbstractString
 3147                    if !isa(cell, AbstractString)
 3148                        val = string(cell)
 149                    end
 150                end
 175151                val = convert(T, [val])
 152            end
 153        else
 581154            val = cell
 155        end
 156    end
 837157    return val
 158end
 159
 34160data(jws::JSONWorksheet) = getfield(jws, :data)
 1161xlsxpath(jws::JSONWorksheet) = getfield(jws, :xlsxpath)
 42162sheetnames(jws::JSONWorksheet) = getfield(jws, :sheetname)
 47163Base.keys(jws::JSONWorksheet) = jws.pointer
 164function Base.haskey(jws::JSONWorksheet, key::Pointer)
 25165    t = key.tokens
 25166    for el in getfield.(keys(jws), :tokens)
 67167        if el == key.tokens
 12168            return true
 55169        elseif length(el) > length(t)
 21170            if el[1:length(t)] == t
 13171                return true
 172            end
 173        end
 174    end
 10175    return false
 176end
 177
 5178Base.iterate(jws::JSONWorksheet) = iterate(data(jws))
 9179Base.iterate(jws::JSONWorksheet, i) = iterate(data(jws), 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
 10186Base.length(jws::JSONWorksheet) = length(data(jws))
 187
 188##############################################################################
 189##
 190## getindex() definitions
 191##
 192##############################################################################
 111193Base.getindex(jws::JSONWorksheet, i) = getindex(jws.data, i)
 1194Base.getindex(jws::JSONWorksheet, ::Colon, ::Colon) = getindex(jws, eachindex(jws.data), eachindex(jws.pointer))
 0195Base.getindex(jws::JSONWorksheet, row_ind, ::Colon) = getindex(jws, row_ind, eachindex(jws.pointer))
 196
 1197Base.firstindex(jws::JSONWorksheet) = firstindex(jws.data)
 1198Base.lastindex(jws::JSONWorksheet) = lastindex(jws.data)
 199function Base.lastindex(jws::JSONWorksheet, i::Integer)
 6200    i == 1 ? lastindex(jws.data) :
 201    i == 2 ? lastindex(jws.pointer) :
 202    throw(DimensionMismatch("JSONWorksheet only has two dimensions"))
 203end
 204
 205function Base.getindex(jws::JSONWorksheet, row_ind::Integer, col_ind::Integer)
 8206    p = keys(jws)[col_ind]
 207
 7208    jws[row_ind, p]
 209end
 210function Base.getindex(jws::JSONWorksheet, row_ind::Integer, col_ind::AbstractArray)
 4211    pointers = keys(jws)[col_ind]
 212
 17213    permutedims(map(p -> jws[row_ind, p], pointers))
 214end
 215@inline function Base.getindex(jws::JSONWorksheet, row_inds::AbstractArray, col_ind::AbstractArray)
 7216    pointers = keys(jws)[col_ind]
 4217    rows = jws[row_inds]
 218
 219    # v = vcat(map(el -> jws[el, col_ind], row_inds)...)
 4220    v = Array{Any,2}(undef, length(rows), length(pointers))
 4221    @inbounds for (r, _row) in enumerate(rows)
 16222        for (c, _col) in enumerate(pointers)
 28223            v[r, c] = if haskey(_row, _col)
 28224                _row[_col]
 225            else
 48226                missing
 227            end
 228        end
 229    end
 230
 4231    return v
 232end
 233
 234function Base.getindex(jws::JSONWorksheet, row_ind::Integer, col_ind::Pointer)
 24235    row = jws[row_ind]
 236
 23237    return row[col_ind]
 238end
 239@inline function Base.getindex(jws::JSONWorksheet, row_inds, p::Pointer)
 24240    map(row -> row[p], jws[row_inds])
 241end
 242@inline function Base.getindex(jws::JSONWorksheet, row_inds, col_ind::Integer)
 2243    p = keys(jws)[col_ind]
 244
 2245    getindex(jws, row_inds, p)
 246end
 247
 248function Base.setindex!(jws::JSONWorksheet, value::Vector, p::Pointer)
 4249    if length(jws) != length(value)
 1250        throw(ArgumentError("New column must have the same length as old columns"))
 251    end
 6252    @inbounds for (i, row) in enumerate(jws)
 7253        row[p] = value[i]
 254    end
 2255    if !haskey(jws, p)
 1256        push!(jws.pointer, p)
 257    end
 2258    return jws
 259end
 260function Base.setindex!(jws::JSONWorksheet, value, i::Integer, p::Pointer)
 2261    jws[i][p] = value
 262end
 263
 264"""
 265    merge(a::JSONWorksheet, b::JSONWorksheet, bykey::AbstractString)
 266
 267Construct a merged JSONWorksheet from the given JSONWorksheets.
 268If the same Pointer is present in another collection, the value for that key will be the
 269value it has in the last collection listed.
 270"""
 271function Base.merge(a::JSONWorksheet, b::JSONWorksheet, key::AbstractString)
 3272    merge(a::JSONWorksheet, b::JSONWorksheet, Pointer(key))
 273end
 274function Base.merge(a::JSONWorksheet, b::JSONWorksheet, key::Pointer)
 4275    @assert haskey(a, key) "$key is not found in the JSONWorksheet(\"$(a.sheetname)\")"
 2276    @assert haskey(b, key) "$key is not found in the JSONWorksheet(\"$(b.sheetname)\")"
 277
 2278    pointers = unique([a.pointer; b.pointer])
 279
 10280    keyvalues_a = map(el -> el[key], a.data)
 10281    keyvalues_b = map(el -> el[key], b.data)
 2282    ind = indexin(keyvalues_b, keyvalues_a)
 283
 2284    data = deepcopy(a.data)
 2285    for (i, _b) in enumerate(b.data)
 8286        j = ind[i]
 14287        if isnothing(j)
 2288            _a = deepcopy(_b)
 2289            for p in a.pointer
 10290                _a[p] = JSONPointer._null_value(p)
 291            end
 2292            push!(data, _a)
 293        else
 6294            _a = data[j]
 295        end
 8296        for p in b.pointer
 32297            _a[p] = _b[p]
 298        end
 299    end
 2300    JSONWorksheet(a.xlsxpath, pointers, data, a.sheetname)
 301end
 302function Base.append!(a::JSONWorksheet, b::JSONWorksheet)
 6303    ak = map(el -> el.tokens, keys(a))
 6304    bk = map(el -> el.tokens, keys(b))
 305
 2306    if sort(ak) != sort(bk)
 1307        throw(AssertionError("""Column names must be same for append!
 308         $(setdiff(collect(ak), collect(bk)))"""))
 309    end
 310
 1311    append!(a.data, b.data)
 312end
 313
 0314function Base.sort!(jws::JSONWorksheet, key; kwargs...)
 0315    sort!(jws, Pointer(key); kwargs...)
 316end
 317function Base.sort!(jws::JSONWorksheet, pointer::Pointer; kwargs...)
 9318    sorted_idx = sortperm(map(el -> el[pointer], data(jws)); kwargs...)
 1319    jws.data = data(jws)[sorted_idx]
 1320    return jws
 321end
 322
 0323function Base.summary(io::IO, jws::JSONWorksheet)
 0324    @printf("%d×%d %s - %s!%s\n", size(jws)..., "JSONWorksheet",
 325        basename(xlsxpath(jws)), sheetnames(jws))
 326end
 0327function Base.show(io::IO, jws::JSONWorksheet)
 0328    summary(io, jws)
 329    # TODO truncate based on screen size
 0330    x = data(jws)
 0331    print(io, "row 1 => ")
 0332    print(io, JSON.json(x[1], 1))
 0333    if length(x) > 1
 0334        print("...")
 0335        print(io, "row $(length(x)) => ")
 0336        print(io, JSON.json(x[end]))
 337    end
 338end

Methods/Properties