xlsx_drone 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/ext/xlsx_drone_x64.dll +0 -0
- data/ext/xlsx_drone_x86.dll +0 -0
- data/lib/xlsx_drone.rb +17 -0
- data/lib/xlsx_drone/exceptions.rb +42 -0
- data/lib/xlsx_drone/native_binding.rb +76 -0
- data/lib/xlsx_drone/sheet.rb +108 -0
- data/lib/xlsx_drone/workbook.rb +76 -0
- data/lib/xlsx_drone/xlsx_drone.rb +27 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8555cd4364e05902cd79fcd328a43ff23f694193291bbb6cbcdb1515cddfd2d2
|
4
|
+
data.tar.gz: 8812b80c1c535f14ea3968a6f1e5b4e2854d3ef5531d7a9a7ebe18030e248368
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 850d6628ac744ab5d5ff85aea37fa8368a43ccd583abebb16bbc3004333d9925683b367a9f5bf1984f680d5053e476a43303ce9512e6b1cc3d90f984b521b645
|
7
|
+
data.tar.gz: 8ab46bb4c07eee9375eea43ea08db2eaa487990737d371f833259d35483cc8ca6992595e1d64e8d65e0227e09c80babbac296b4d1b602e2e92bc54974151aab1
|
Binary file
|
Binary file
|
data/lib/xlsx_drone.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# external libraries
|
2
|
+
require 'ffi'
|
3
|
+
|
4
|
+
# source code
|
5
|
+
require_relative 'xlsx_drone/exceptions'
|
6
|
+
require_relative 'xlsx_drone/native_binding'
|
7
|
+
require_relative 'xlsx_drone/xlsx_drone'
|
8
|
+
require_relative 'xlsx_drone/workbook'
|
9
|
+
require_relative 'xlsx_drone/sheet'
|
10
|
+
|
11
|
+
# turn off err printing from the native library
|
12
|
+
XLSXDrone::NativeBinding.xlsx_set_print_err_messages(0)
|
13
|
+
|
14
|
+
# ensure that all opened workbooks get closed (if can't OS will claim it anyways, just filling a duty here)
|
15
|
+
at_exit do
|
16
|
+
XLSXDrone::Workbook.close_workbooks()
|
17
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Exceptions defs.
|
2
|
+
module XLSXDrone
|
3
|
+
|
4
|
+
# Errors caused by the user because of doing something invalid or not allowed.
|
5
|
+
module UserError
|
6
|
+
|
7
|
+
# May happen on xlsx_load_sheet().
|
8
|
+
class IndexOutOfBounds < RuntimeError; end
|
9
|
+
|
10
|
+
# May happen on xlsx_load_sheet().
|
11
|
+
class NonExistent < RuntimeError; end
|
12
|
+
|
13
|
+
# May happen when try to interact with a workbook already closed.
|
14
|
+
class WorkbookClosed < RuntimeError; end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Errors caused by the system itself.
|
18
|
+
module LogicError
|
19
|
+
|
20
|
+
# Errors on the library itself.
|
21
|
+
module InternalError
|
22
|
+
|
23
|
+
# May happen on xlsx_open().
|
24
|
+
class CantDeployFile < RuntimeError; end
|
25
|
+
|
26
|
+
# May happen on xlsx_open(), xlsx_load_sheet().
|
27
|
+
class XMLParsingError < RuntimeError; end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Caused because of wrong use of the library.
|
31
|
+
module ClientError
|
32
|
+
|
33
|
+
# May happen on xlsx_load_sheet().
|
34
|
+
class MalformedParams < RuntimeError; end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Something is flooded or temporary offline, usually the user is suggested to try later.
|
39
|
+
module TransientFailure
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Namespace (protector) of the library.
|
2
|
+
module XLSXDrone
|
3
|
+
|
4
|
+
# All things related to the binding with the native C library.
|
5
|
+
module NativeBinding
|
6
|
+
|
7
|
+
PLATFORM_X64 = RUBY_PLATFORM.match(/64/) ? true : false
|
8
|
+
EXT_PATH = "#{File.dirname(File.dirname(File.dirname(__FILE__)))}/ext"
|
9
|
+
DLL_PATH = PLATFORM_X64 ? "#{EXT_PATH}/xlsx_drone_x64.dll" : "#{EXT_PATH}/xlsx_drone_x86.dll"
|
10
|
+
|
11
|
+
class XLSXWorkbookT < FFI::Struct
|
12
|
+
|
13
|
+
layout \
|
14
|
+
:deployment_path, :pointer,
|
15
|
+
:shared_strings_xml, :pointer,
|
16
|
+
:n_styles, :int,
|
17
|
+
:styles, :pointer,
|
18
|
+
:n_sheets, :int,
|
19
|
+
:sheets, :pointer
|
20
|
+
end
|
21
|
+
|
22
|
+
class XLSXStyleT < FFI::Struct
|
23
|
+
|
24
|
+
layout \
|
25
|
+
:style_id, :int,
|
26
|
+
:related_category, :int,
|
27
|
+
:format_code, :pointer
|
28
|
+
end
|
29
|
+
|
30
|
+
class XLSXReferenceToRowT < FFI::Struct
|
31
|
+
|
32
|
+
layout \
|
33
|
+
:row_n, :int,
|
34
|
+
:sheetdata_child_i, :int
|
35
|
+
end
|
36
|
+
|
37
|
+
class XLSXSheetT < FFI::Struct
|
38
|
+
|
39
|
+
layout \
|
40
|
+
:xlsx, :pointer,
|
41
|
+
:name, :pointer,
|
42
|
+
:sheet_xml, :pointer,
|
43
|
+
:sheetdata, :pointer,
|
44
|
+
:last_row, :int,
|
45
|
+
:last_row_looked, XLSXReferenceToRowT
|
46
|
+
end
|
47
|
+
|
48
|
+
class XLSXCellValue < FFI::Union
|
49
|
+
|
50
|
+
layout \
|
51
|
+
:pointer_to_char_value, :pointer,
|
52
|
+
:int_value, :int,
|
53
|
+
:long_long_value, :long_long,
|
54
|
+
:double_value, :double
|
55
|
+
end
|
56
|
+
|
57
|
+
class XLSXCellT < FFI::Struct
|
58
|
+
|
59
|
+
layout \
|
60
|
+
:style, :pointer,
|
61
|
+
:value_type, :int,
|
62
|
+
:value, XLSXCellValue
|
63
|
+
end
|
64
|
+
|
65
|
+
extend FFI::Library
|
66
|
+
ffi_lib DLL_PATH
|
67
|
+
|
68
|
+
# function attachings
|
69
|
+
attach_function :xlsx_get_xlsx_errno, [], :int
|
70
|
+
attach_function :xlsx_set_print_err_messages, [:int], :void
|
71
|
+
attach_function :xlsx_open, [:string, :pointer], :int
|
72
|
+
attach_function :xlsx_load_sheet, [:pointer, :int, :string], :pointer
|
73
|
+
attach_function :xlsx_read_cell, [:pointer, :uint, :string, :pointer], :void
|
74
|
+
attach_function :xlsx_close, [:pointer], :int
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Namespace (protector) of the library.
|
2
|
+
module XLSXDrone
|
3
|
+
|
4
|
+
# XLSX Sheet.
|
5
|
+
class Sheet
|
6
|
+
|
7
|
+
# @return [XLSXDrone::Sheet]
|
8
|
+
def initialize(xlsx_sheet_mpointer)
|
9
|
+
@native_sheet = XLSXDrone::NativeBinding::XLSXSheetT.new(xlsx_sheet_mpointer)
|
10
|
+
@native_cell = XLSXDrone::NativeBinding::XLSXCellT.new(FFI::MemoryPointer.new(1, XLSXDrone::NativeBinding::XLSXCellT.size, true))
|
11
|
+
@styles = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Integer] 0 if the sheet is empty
|
15
|
+
def last_row
|
16
|
+
@native_sheet[:last_row]
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String]
|
20
|
+
def name
|
21
|
+
@native_sheet[:name].get_string(0).force_encoding(Encoding::UTF_8)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param row [Integer]
|
25
|
+
# @param column [String]
|
26
|
+
# @return [Integer, Float, String, Time, NilClass]
|
27
|
+
def read_cell(row, column)
|
28
|
+
XLSXDrone::NativeBinding.xlsx_read_cell(@native_sheet, row, column, @native_cell)
|
29
|
+
# if it has no style, then it's either a string or a number
|
30
|
+
if(@native_cell[:style].null?)
|
31
|
+
case @native_cell[:value_type]
|
32
|
+
when 0
|
33
|
+
@native_cell[:value][:pointer_to_char_value].get_string(0).force_encoding(Encoding::UTF_8)
|
34
|
+
when 1
|
35
|
+
@native_cell[:value][:int_value]
|
36
|
+
when 2
|
37
|
+
@native_cell[:value][:long_long_value]
|
38
|
+
when 3
|
39
|
+
@native_cell[:value][:double_value]
|
40
|
+
else
|
41
|
+
nil
|
42
|
+
end
|
43
|
+
else
|
44
|
+
address = @native_cell[:style].address
|
45
|
+
# speeding purpose
|
46
|
+
if(!(@styles.has_key?(address)))
|
47
|
+
style_obj = XLSXDrone::NativeBinding::XLSXStyleT.new(@native_cell[:style])
|
48
|
+
@styles[address] = style_obj[:related_category]
|
49
|
+
end
|
50
|
+
case @styles[address]
|
51
|
+
when 2
|
52
|
+
# XLSX_DATE, it could be represented also as plain string
|
53
|
+
if(@native_cell[:value_type] == 0)
|
54
|
+
@native_cell[:value][:pointer_to_char_value].get_string(0).force_encoding(Encoding::UTF_8)
|
55
|
+
else
|
56
|
+
Time.new(1900) + ((@native_cell[:value][:int_value] - 2) * 86400)
|
57
|
+
end
|
58
|
+
when 4
|
59
|
+
# XLSX_DATE_TIME, there are specific cases in which it's a DATE_TIME, but the internal representation appears as an int, so basically
|
60
|
+
# the "time" part of the data comes fixed at mid-day or at the start of the day, that's what you actually see on Excel
|
61
|
+
case(@native_cell[:value_type])
|
62
|
+
when 0
|
63
|
+
@native_cell[:value][:pointer_to_char_value].get_string(0).force_encoding(Encoding::UTF_8)
|
64
|
+
when 1
|
65
|
+
Time.new(1900) + ((@native_cell[:value][:int_value] - 2) * 86400)
|
66
|
+
else
|
67
|
+
match = @native_cell[:value][:double_value].to_s.match(/(\d+)\.(\d+)/)
|
68
|
+
integral_part = match[1].to_i
|
69
|
+
floating_part = "0.#{match[2]}".to_f
|
70
|
+
Time.new(1900) + ((integral_part - 2) * 86400) + (floating_part * 86400)
|
71
|
+
end
|
72
|
+
when 0
|
73
|
+
# XLSX_NUMBER
|
74
|
+
case @native_cell[:value_type]
|
75
|
+
when 1
|
76
|
+
@native_cell[:value][:int_value]
|
77
|
+
when 2
|
78
|
+
@native_cell[:value][:long_long_value]
|
79
|
+
when 3
|
80
|
+
@native_cell[:value][:double_value]
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
when 1
|
85
|
+
# XLSX_TEXT
|
86
|
+
@native_cell[:value][:pointer_to_char_value].get_string(0).force_encoding(Encoding::UTF_8)
|
87
|
+
when 3
|
88
|
+
# XLSX_TIME
|
89
|
+
(Time.new(1900) + (@native_cell[:value][:double_value] * 86400)).strftime("%H:%M:%S")
|
90
|
+
else
|
91
|
+
# XLSX_UNKNOWN
|
92
|
+
case @native_cell[:value_type]
|
93
|
+
when 0
|
94
|
+
@native_cell[:value][:pointer_to_char_value].get_string(0).force_encoding(Encoding::UTF_8)
|
95
|
+
when 1
|
96
|
+
@native_cell[:value][:int_value]
|
97
|
+
when 2
|
98
|
+
@native_cell[:value][:long_long_value]
|
99
|
+
when 3
|
100
|
+
@native_cell[:value][:double_value]
|
101
|
+
else
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# Namespace (protector) of the library.
|
2
|
+
module XLSXDrone
|
3
|
+
|
4
|
+
# XLSX Workbook.
|
5
|
+
class Workbook
|
6
|
+
|
7
|
+
@@opened_workbooks = []
|
8
|
+
|
9
|
+
# You could use this method to close all opened workbooks at the same time.
|
10
|
+
def self.close_workbooks
|
11
|
+
@@opened_workbooks.each do |wb|
|
12
|
+
wb.close
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param xlsx_workbook_mpointer [FFI::MemoryPointer]
|
17
|
+
# @return [Workbook]
|
18
|
+
def initialize(xlsx_workbook_mpointer)
|
19
|
+
@native_workbook = XLSXDrone::NativeBinding::XLSXWorkbookT.new(xlsx_workbook_mpointer)
|
20
|
+
@@opened_workbooks << self
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sheets aren't loaded by default. You have to load them one by one, once you need them. You can *reference* a sheet passing its name or its index (first one is 1). Raises an exception if it can't for some reason.
|
24
|
+
# @param reference [String, Integer]
|
25
|
+
# @return [XLSXDrone::Sheet]
|
26
|
+
def load_sheet(reference)
|
27
|
+
if(@native_workbook)
|
28
|
+
loaded_sheet = \
|
29
|
+
case reference
|
30
|
+
when String
|
31
|
+
XLSXDrone::NativeBinding.xlsx_load_sheet(@native_workbook, 0, reference)
|
32
|
+
when Integer
|
33
|
+
XLSXDrone::NativeBinding.xlsx_load_sheet(@native_workbook, reference, nil)
|
34
|
+
else
|
35
|
+
raise XLSXDrone::LogicError::ClientError::MalformedParams, "Pass a valid index as an #Integer (> 0 && <= #sheets_amount()), or a valid sheet name as a #String."
|
36
|
+
end
|
37
|
+
if(!loaded_sheet.null?)
|
38
|
+
XLSXDrone::Sheet.new(loaded_sheet)
|
39
|
+
else
|
40
|
+
# no sheet was loaded
|
41
|
+
case XLSXDrone::NativeBinding.xlsx_get_xlsx_errno()
|
42
|
+
when -11
|
43
|
+
raise XLSXDrone::LogicError::ClientError::MalformedParams, "Pass a valid index (> 0 && <= #sheets_amount()), or a valid sheet name."
|
44
|
+
when -12
|
45
|
+
raise NoMemoryError
|
46
|
+
when -13
|
47
|
+
raise XLSXDrone::UserError::IndexOutOfBounds, "If you pass an integer as parameter, note that can't surpass #sheets_amount()."
|
48
|
+
when -14
|
49
|
+
raise XLSXDrone::LogicError::InternalError::XMLParsingError, "The XLSX may be corrupted or it belongs to a version unsupported by this library."
|
50
|
+
when -15
|
51
|
+
raise XLSXDrone::UserError::NonExistent, "There's not such sheet with that name."
|
52
|
+
end
|
53
|
+
end
|
54
|
+
else
|
55
|
+
raise XLSXDrone::UserError::WorkbookClosed, "The workbook you're trying to access was already closed."
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Integer] the amount of sheets contained on this workbook
|
60
|
+
def sheets_amount
|
61
|
+
@native_workbook[:n_sheets]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Should-call method, once you finish working with the workbook.
|
65
|
+
# @return [TrueClass, FalseClass] depending on if the close was successful or not
|
66
|
+
def close
|
67
|
+
if(XLSXDrone::NativeBinding.xlsx_close(@native_workbook) == 1)
|
68
|
+
@@opened_workbooks.delete(self)
|
69
|
+
@native_workbook = nil
|
70
|
+
true
|
71
|
+
else
|
72
|
+
false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Namespace (protector) and #open() functionallity for the library.
|
2
|
+
module XLSXDrone
|
3
|
+
|
4
|
+
# @param path [String]
|
5
|
+
# @return [XLSXDrone::Workbook]
|
6
|
+
# Opens an XLSX file, should be closed after working with him. Can raise several exceptions.
|
7
|
+
def self.open(path)
|
8
|
+
# check that the *path* is always a #String
|
9
|
+
raise XLSXDrone::LogicError::ClientError::MalformedParams, "A #String is expected." if(!path.is_a?(String))
|
10
|
+
# reserve memory for an xlsx_workbook struct
|
11
|
+
xlsx_workbook_mpointer = FFI::MemoryPointer.new(1, XLSXDrone::NativeBinding::XLSXWorkbookT.size, false)
|
12
|
+
if(XLSXDrone::NativeBinding.xlsx_open(File.absolute_path(path), xlsx_workbook_mpointer) == 1)
|
13
|
+
# everything went ok
|
14
|
+
XLSXDrone::Workbook.new(xlsx_workbook_mpointer)
|
15
|
+
else
|
16
|
+
# something went wrong
|
17
|
+
case XLSXDrone::NativeBinding.xlsx_get_xlsx_errno()
|
18
|
+
when -2
|
19
|
+
raise NoMemoryError
|
20
|
+
when -3
|
21
|
+
raise XLSXDrone::LogicError::InternalError::CantDeployFile, "Can't deploy #{path}. Check that the file isn't already opened.unl"
|
22
|
+
when -4
|
23
|
+
raise XLSXDrone::LogicError::InternalError::XMLParsingError, "The XLSX may be corrupted or it belongs to a version unsupported by this library."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xlsx_drone
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Damián M. González
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
description:
|
28
|
+
email:
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- ext/xlsx_drone_x64.dll
|
34
|
+
- ext/xlsx_drone_x86.dll
|
35
|
+
- lib/xlsx_drone.rb
|
36
|
+
- lib/xlsx_drone/exceptions.rb
|
37
|
+
- lib/xlsx_drone/native_binding.rb
|
38
|
+
- lib/xlsx_drone/sheet.rb
|
39
|
+
- lib/xlsx_drone/workbook.rb
|
40
|
+
- lib/xlsx_drone/xlsx_drone.rb
|
41
|
+
homepage: https://github.com/damian-m-g/xlsx_drone_rb
|
42
|
+
licenses:
|
43
|
+
- MIT
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubygems_version: 3.1.4
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Fast Microsoft Excel's XLSX reader. Binding of C's xlsx_drone lib.
|
64
|
+
test_files: []
|