< Summary

Information
Class: src/worksheet.jl
Assembly: Default
File(s): src/worksheet.jl
Tag: 40_3060321334
Line coverage
93%
Covered lines: 172
Uncovered lines: 12
Coverable lines: 184
Total lines: 341
Line coverage: 93.4%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Method coverage 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
 4520    xlsxpath::String
 21    pointer::Array{Pointer,1}
 22    data::Array{T,1} where T
 23    sheetname::String
 24end
 10025function 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, :])
 28031    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
 8644function 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
 3264function JSONWorksheet(xlsxpath, sheet; kwargs...)
 3265    xf = XLSX.readxlsx(xlsxpath)
 1666    x = JSONWorksheet(xf, sheet; kwargs...)
 967    close(xf)
 968    return x
 69end
 70
 4471function eachrow_to_jsonarray(data::Array{T,2}, pointers, delim) where T
 4472    json = Array{PointerDict,1}(undef, size(data, 1) - 1)
 8873    Threads.@threads for i in 1:length(json)
 76674        json[i] = row_to_jsonarray(data[i + 1, :], pointers, delim)
 75    end
 4276    return json
 77end
 78
 13079function row_to_jsonarray(row, pointers, delim)
 13080    x = PointerDict()
 26081    for (col, p) in enumerate(pointers)
 114082        x[p] = collect_cell(p, row[col], delim)
 83    end
 12884    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()
 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
 50100@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
 239104        if !ismissing(arr[1, c])
 464105            for r in 1:size(arr, 1)
 232106                if !ismissing(arr[r, c])
 232107                    cols[c] = true
 421108                    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)
 580117        for c in 1:size(arr, 2)
 299118            if !ismissing(arr[r, c])
 283119                rows[r] = true
 542120                break
 121            end
 122        end
 123    end
 50124    return arr[rows, :]
 125end
 126
 835127function collect_cell(p::Pointer{T}, cell, delim) where T
 835128    if ismissing(cell)
 89129        val = JSONPointer._null_value(p)
 130    else
 746131        if T <: AbstractArray
 191132            if isa(cell, AbstractString)
 173133                val = split(cell, delim)
 173134                isempty(val[end]) && pop!(val)
 173135                if eltype(T) <: Real
 280136                    val = parse.(eltype(T), val)
 33137                elseif eltype(T) <: AbstractString
 184138                    val = string.(val)
 139                end
 140            else
 18141                val = cell
 18142                if eltype(T) <: Real
 8143                    if isa(cell, AbstractString)
 8144                        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
 191151                val = convert(T, [val])
 152            end
 153        else
 555154            val = cell
 155        end
 156    end
 835157    return val
 158end
 159
 160# data(jws::JSONWorksheet) = getfield(jws, :data)
 1161xlsxpath(jws::JSONWorksheet) = getfield(jws, :xlsxpath)
 42162sheetnames(jws::JSONWorksheet) = getfield(jws, :sheetname)
 74163Base.keys(jws::JSONWorksheet) = jws.pointer
 25164function 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(Tables.rows(jws))
 9179Base.iterate(jws::JSONWorksheet, i) = iterate(Tables.rows(jws), i)
 180
 1181Base.size(jws::JSONWorksheet) = (length(jws.data), length(jws.pointer))
 1182function Base.size(jws::JSONWorksheet, d)
 1183    d == 1 ? length(jws.data) :
 184    d == 2 ? length(jws.pointer) : throw(DimensionMismatch("only 2 dimensions of `JSONWorksheets` object are measurable"
 185end
 14186Base.length(jws::JSONWorksheet) = length(Tables.rows(jws))
 187
 188
 189########################################################################
 190##
 191## getindex() definitions
 192##
 193########################################################################
 160194Base.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
 20198function Base.getindex(jws::JSONWorksheet, row_ind::Integer, col_ind::Integer)
 20199    p = keys(jws)[col_ind]
 200
 19201    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)
 34215        for (c, _col) in enumerate(pointers)
 64216            v[r, c] = if haskey(_row, _col)
 64217                _row[_col]
 218            else
 111219                missing
 220            end
 221        end
 222    end
 223
 7224    return v
 225end
 226
 44227function Base.getindex(jws::JSONWorksheet, row_ind::Integer, col_ind::Pointer)
 44228    row = jws[row_ind]
 43229    return row[col_ind]
 230end
 24231@inline function Base.getindex(jws::JSONWorksheet, row_inds, p::Pointer)
 88232    map(row -> row[p], jws[row_inds])
 233end
 12234@inline function Base.getindex(jws::JSONWorksheet, row_inds, col_ind::Integer)
 12235    p = keys(jws)[col_ind]
 236
 12237    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)
 7245        row[p] = value[i]
 246    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
 254end
 255
 1256Base.firstindex(jws::JSONWorksheet) = firstindex(jws.data)
 2257Base.lastindex(jws::JSONWorksheet) = lastindex(jws.data)
 6258function Base.lastindex(jws::JSONWorksheet, i::Integer)
 6259    i == 1 ? lastindex(jws.data) :
 260    i == 2 ? lastindex(jws.pointer) :
 261    throw(DimensionMismatch("JSONWorksheet only has two dimensions"))
 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"""
 3271function Base.merge(a::JSONWorksheet, b::JSONWorksheet, key::AbstractString)
 3272    merge(a::JSONWorksheet, b::JSONWorksheet, Pointer(key))
 273end
 3274function 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]
 8287        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
 2302function 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
 2317function Base.sort!(jws::JSONWorksheet, pointer::Pointer; kwargs...)
 9318    sorted_idx = sortperm(map(el -> el[pointer], Tables.rows(jws)); kwargs...)
 1319    jws.data = Tables.rows(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)
 328    # By default, we align the columns to the left unless they are numbers, checks first row to determine datatype
 0329    alignment = fill(:l, size(jws, 2))
 330
 0331    for i in 1:size(jws, 2)
 0332        if isa(jws[1, i], Number)
 0333            alignment[i] = :r
 334        end
 335    end
 336
 0337    summary(io, jws)
 0338    pretty_table(io, Tables.matrix(jws);
 339        header = pointer_to_colname.(Tables.columnnames(jws)),
 340        alignment = alignment)
 341end

Methods/Properties