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.
@@ -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
@@ -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: []