rxl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +179 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/cell.rb +130 -0
- data/lib/cells.rb +19 -0
- data/lib/rxl.rb +26 -0
- data/lib/rxl/version.rb +3 -0
- data/lib/workbook.rb +43 -0
- data/lib/worksheet.rb +96 -0
- data/rubocop.yml +2 -0
- data/rxl.gemspec +28 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5f566fd08f3ea5b2d4db76a07fc545b2752d7da3
|
4
|
+
data.tar.gz: 8f85cbc1c2a444b39c5a2fb9b12552b7a24f9ee6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bf0e88cdebaf333f4fc36fe9e06bd812c19560937e1b0417a433ca29c01ecd6b91471d2c2ac6f5c596b90b25a7b1f9e04b54a2ec238d0a4fecc9f21ea85fb3cd
|
7
|
+
data.tar.gz: 2a81b4685affb3c6725cf96b10834161fb8071ddeb3bbcfd68b6e39067006099967f33ef635a48c7a38cc336ff1a3763ecabe9473d4a30ae976f11f122408b61
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, and in the interest of
|
4
|
+
fostering an open and welcoming community, we pledge to respect all people who
|
5
|
+
contribute through reporting issues, posting feature requests, updating
|
6
|
+
documentation, submitting pull requests or patches, and other activities.
|
7
|
+
|
8
|
+
We are committed to making participation in this project a harassment-free
|
9
|
+
experience for everyone, regardless of level of experience, gender, gender
|
10
|
+
identity and expression, sexual orientation, disability, personal appearance,
|
11
|
+
body size, race, ethnicity, age, religion, or nationality.
|
12
|
+
|
13
|
+
Examples of unacceptable behavior by participants include:
|
14
|
+
|
15
|
+
* The use of sexualized language or imagery
|
16
|
+
* Personal attacks
|
17
|
+
* Trolling or insulting/derogatory comments
|
18
|
+
* Public or private harassment
|
19
|
+
* Publishing other's private information, such as physical or electronic
|
20
|
+
addresses, without explicit permission
|
21
|
+
* Other unethical or unprofessional conduct
|
22
|
+
|
23
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
24
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
25
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
26
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
27
|
+
threatening, offensive, or harmful.
|
28
|
+
|
29
|
+
By adopting this Code of Conduct, project maintainers commit themselves to
|
30
|
+
fairly and consistently applying these principles to every aspect of managing
|
31
|
+
this project. Project maintainers who do not follow or enforce the Code of
|
32
|
+
Conduct may be permanently removed from the project team.
|
33
|
+
|
34
|
+
This code of conduct applies both within project spaces and in public spaces
|
35
|
+
when an individual is representing the project or its community.
|
36
|
+
|
37
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
38
|
+
reported by contacting a project maintainer at ian.mcwilliams@f3mmedia.co.uk. All
|
39
|
+
complaints will be reviewed and investigated and will result in a response that
|
40
|
+
is deemed necessary and appropriate to the circumstances. Maintainers are
|
41
|
+
obligated to maintain confidentiality with regard to the reporter of an
|
42
|
+
incident.
|
43
|
+
|
44
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
45
|
+
version 1.3.0, available at
|
46
|
+
[http://contributor-covenant.org/version/1/3/0/][version]
|
47
|
+
|
48
|
+
[homepage]: http://contributor-covenant.org
|
49
|
+
[version]: http://contributor-covenant.org/version/1/3/0/
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Ian McWilliams
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
# Rxl
|
2
|
+
|
3
|
+
The purpose of the RXL gem is to provide a ruby/Excel interface that provides the following features:
|
4
|
+
|
5
|
+
1. Specification using Excel key indices (A1, B5 etc)
|
6
|
+
2. Avoiding multi-level class management by utilising the ruby hash
|
7
|
+
3. Simplified handling with the aim of doing less, better - eg no setting of properties for full rows/columns in Excel files
|
8
|
+
|
9
|
+
The mechanics of the conversion between xlsx and ruby hash have been implemented using the RubyXL gem:
|
10
|
+
|
11
|
+
https://github.com/weshatheleopard/rubyXL
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'rxl'
|
19
|
+
```
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install rxl
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
With some exceptions (due mainly to the vagaries of Excel) a file can be read in and the resulting hash passed to the write method to save a duplicate of the original.
|
32
|
+
|
33
|
+
### Read from file
|
34
|
+
|
35
|
+
To read a file to hash simply pass the filepath:
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
Rxl.read_file('path/to/file.xlxs')
|
39
|
+
```
|
40
|
+
|
41
|
+
The format of the excel read hash has the following skeleton:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
{
|
45
|
+
"Sheet1" => {
|
46
|
+
row_count: 1,
|
47
|
+
column_count: 1,
|
48
|
+
rows: {},
|
49
|
+
columns: {},
|
50
|
+
cells: {
|
51
|
+
'A1' => {
|
52
|
+
value: 'abc',
|
53
|
+
format: 'text'
|
54
|
+
}
|
55
|
+
}
|
56
|
+
}
|
57
|
+
}
|
58
|
+
```
|
59
|
+
|
60
|
+
Bear in mind the limitations of reading cell formats. Everything is read as a string other than:
|
61
|
+
* cells formatted as dates are converted to a DateTime object with the time portion set to midnight
|
62
|
+
* cells formatted as times are converted to a DateTime object with the date portion set to 31/12/1899 - unless the cell has a date prefix in which case this is carried in (this will be read as a date format as per below parsing rules)
|
63
|
+
* numbers (including floats and percentages) where the cell format is number or percentage are read in as integers - trailing zeroes are cropped from floats in this case and percentages are converted to numeric format (eg 100% = 1)
|
64
|
+
* formulas are not read from cells with date and time formats
|
65
|
+
|
66
|
+
Within these limitations the cell hash's :format holds the best analysis of the original cell format but as there's no way to extract all of the format information directly from the sheet some information may need to be refurbished as required after import via Rxl.
|
67
|
+
|
68
|
+
Further to the above, these rules are applied by Rxl when parsing cells:
|
69
|
+
* strings are given text format
|
70
|
+
* DateTime objects are given date format except where the date is 31/12/1899 in which case they are given time format
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
### Read tables from file
|
76
|
+
|
77
|
+
To read a file where the data is in table format - headers and values, no totals or otherwise extra content:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
Rxl.read_file_as_tables('path/to/file.xlsx')
|
81
|
+
```
|
82
|
+
|
83
|
+
The format of the excel table read hash has the following skeleton:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
{
|
87
|
+
"Sheet1" => [
|
88
|
+
{
|
89
|
+
header_a: value,
|
90
|
+
header_b: value
|
91
|
+
},
|
92
|
+
{
|
93
|
+
header_a: value,
|
94
|
+
header_b: value
|
95
|
+
},
|
96
|
+
]
|
97
|
+
}
|
98
|
+
```
|
99
|
+
|
100
|
+
### Write to file
|
101
|
+
|
102
|
+
To write a file pass the filename and hash:
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
Rxl.write_file('path/to/save.xlsx', hash_workbook)
|
106
|
+
```
|
107
|
+
|
108
|
+
The format of the excel hash_workbook must contain at least the following skeleton:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
{
|
112
|
+
"Sheet1" => {
|
113
|
+
cells: {
|
114
|
+
'A1' => {
|
115
|
+
value: 'abc',
|
116
|
+
format: 'text'
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
}
|
121
|
+
```
|
122
|
+
|
123
|
+
#### Cell specification
|
124
|
+
|
125
|
+
All cells are written with the format set to general except those with a number format specified
|
126
|
+
|
127
|
+
Specify the number format according to https://support.office.com/en-us/article/number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68?ui=en-US&rs=en-US&ad=US
|
128
|
+
|
129
|
+
Examples:
|
130
|
+
|
131
|
+
| value | number format | resulting cell format | resulting cell value |
|
132
|
+
|--------------|---------------|-----------------------|----------------------|
|
133
|
+
| 0 | 0 | number | 0 |
|
134
|
+
| 0.49 | 0 | number | 0 |
|
135
|
+
| 0.5 | 0 | number | 1 |
|
136
|
+
| 0 | 0.00 | number | 0.00 |
|
137
|
+
| 0 | 0% | percentage | 0% |
|
138
|
+
| 1 | 0% | percentage | 100% |
|
139
|
+
| '01/01/2000' | 'dd/mm/yyyy' | date | 01/01/2000 |
|
140
|
+
|
141
|
+
#### Write Validation
|
142
|
+
|
143
|
+
The following rules are validated for write_file:
|
144
|
+
|
145
|
+
* The hash_workbook must be a hash (NB if empty a blank file will be created with a single sheet called "Sheet1")
|
146
|
+
* The hash_workbook keys must be strings
|
147
|
+
* The hash_workbook values (hash_worksheet) must be hashes
|
148
|
+
|
149
|
+
|
150
|
+
* The hash_worksheet keys must be symbols
|
151
|
+
* The following keys are allowed for hash_worksheet: :cells, :rows, :columns
|
152
|
+
* The hash_worksheet values must be arrays - those arrays must contain only hashes
|
153
|
+
|
154
|
+
|
155
|
+
* The arrays' child hash keys must be strings of valid Excel cell id format (or stringified number for a row, capitalised alpha for a column)
|
156
|
+
* The arrays' child hash values must be hashes (hash_cell)
|
157
|
+
* The hash_cell keys must conform the the cell specification as below
|
158
|
+
|
159
|
+
* If a formula is provided the value must be nil or an empty string
|
160
|
+
* If a number format is provided the value must be consistent with it
|
161
|
+
|
162
|
+
|
163
|
+
TODO: Add further detail
|
164
|
+
|
165
|
+
## Development
|
166
|
+
|
167
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
168
|
+
|
169
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
170
|
+
|
171
|
+
## Contributing
|
172
|
+
|
173
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/rxl. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
174
|
+
|
175
|
+
|
176
|
+
## License
|
177
|
+
|
178
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
179
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rxl"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/cell.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'rubyXL'
|
2
|
+
|
3
|
+
module Cell
|
4
|
+
|
5
|
+
##############################################
|
6
|
+
### GET HASH CELL FROM RUBYXL CELL ###
|
7
|
+
##############################################
|
8
|
+
|
9
|
+
def self.rubyxl_cell_to_hash_cell(rubyxl_cell = nil)
|
10
|
+
rubyxl_cell_value = rubyxl_cell.nil? ? RubyXL::Cell.new.value : rubyxl_cell.value
|
11
|
+
{
|
12
|
+
value: rubyxl_cell_value,
|
13
|
+
format: hash_cell_format(rubyxl_cell_value),
|
14
|
+
formula: rubyxl_cell_formula(rubyxl_cell),
|
15
|
+
h_align: rubyxl_cell_horizontal_alignment(rubyxl_cell),
|
16
|
+
v_align: rubyxl_cell_vertical_alignment(rubyxl_cell)
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.rubyxl_cell_formula(rubyxl_cell)
|
21
|
+
return nil if rubyxl_cell.nil? || rubyxl_cell.formula.nil? || rubyxl_cell.formula.expression.empty?
|
22
|
+
rubyxl_cell.formula.expression
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.rubyxl_cell_horizontal_alignment(rubyxl_cell)
|
26
|
+
return nil if rubyxl_cell.nil? || rubyxl_cell.horizontal_alignment.nil?
|
27
|
+
rubyxl_cell.horizontal_alignment.to_sym
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.rubyxl_cell_vertical_alignment(rubyxl_cell)
|
31
|
+
return :bottom if rubyxl_cell.nil? || rubyxl_cell.vertical_alignment.nil?
|
32
|
+
rubyxl_cell.vertical_alignment.to_sym
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.hash_cell_format(rubyxl_cell_value)
|
36
|
+
format = {
|
37
|
+
nilclass: :general,
|
38
|
+
string: :text,
|
39
|
+
fixnum: :number,
|
40
|
+
float: :number,
|
41
|
+
datetime: :date,
|
42
|
+
}[rubyxl_cell_value.class.to_s.downcase.to_sym]
|
43
|
+
format == :date && rubyxl_cell_value.strftime('%Y%m%d') == '18991231' ? :time : format
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
##############################################
|
48
|
+
### GET RUBYXL CELL FROM HASH CELL ###
|
49
|
+
##############################################
|
50
|
+
|
51
|
+
def self.hash_cell_to_rubyxl_cell(combined_hash_cell, rubyxl_worksheet, row_index, column_index)
|
52
|
+
merge_row_index, merge_column_index = RubyXL::Reference.ref2ind(combined_hash_cell[:merge])
|
53
|
+
|
54
|
+
rubyxl_worksheet.merge_cells(row_index, column_index, merge_column_index, merge_row_index) if combined_hash_cell[:merge]
|
55
|
+
rubyxl_worksheet.change_column_width(column_index, combined_hash_cell[:width]) if combined_hash_cell[:width]
|
56
|
+
|
57
|
+
rubyxl_worksheet[row_index][column_index].change_font_name(combined_hash_cell[:font_style]) if combined_hash_cell[:font_style]
|
58
|
+
rubyxl_worksheet[row_index][column_index].change_font_size(combined_hash_cell[:font_size]) if combined_hash_cell[:font_size]
|
59
|
+
rubyxl_worksheet[row_index][column_index].change_fill(combined_hash_cell[:fill]) if combined_hash_cell[:fill]
|
60
|
+
rubyxl_worksheet[row_index][column_index].change_horizontal_alignment(combined_hash_cell[:align]) if combined_hash_cell[:align]
|
61
|
+
rubyxl_worksheet[row_index][column_index].change_font_bold(combined_hash_cell[:bold]) if combined_hash_cell[:bold]
|
62
|
+
|
63
|
+
if combined_hash_cell[:border_all]
|
64
|
+
rubyxl_worksheet[row_index][column_index].change_border('top' , combined_hash_cell[:border_all])
|
65
|
+
rubyxl_worksheet[row_index][column_index].change_border('bottom' , combined_hash_cell[:border_all])
|
66
|
+
rubyxl_worksheet[row_index][column_index].change_border('left' , combined_hash_cell[:border_all])
|
67
|
+
rubyxl_worksheet[row_index][column_index].change_border('right' , combined_hash_cell[:border_all])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.add_rubyxl_cells(combined_hash_cell, rubyxl_worksheet, row_index, column_index)
|
72
|
+
if combined_hash_cell[:formula]
|
73
|
+
rubyxl_worksheet.add_cell(row_index, column_index, '', combined_hash_cell[:formula]).set_number_format combined_hash_cell[:dp_2]
|
74
|
+
else
|
75
|
+
rubyxl_worksheet.add_cell(row_index, column_index, combined_hash_cell[:value])
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.get_combined_hash_cell(hash_worksheet, hash_cell_key, hash_cell)
|
80
|
+
# first get data from the matching column if it's specified
|
81
|
+
column_keys = hash_worksheet[:columns].keys.select { |key| hash_cell_key =~ /^#{key}\d+$/ }
|
82
|
+
column_keys.empty? ? hash_column = {} : hash_column = hash_worksheet[:columns][column_keys[0]]
|
83
|
+
combined_hash_cell = hash_column.merge(hash_cell)
|
84
|
+
# then get data from the matching row if it's specified
|
85
|
+
row_keys = hash_worksheet[:rows].keys.select { |key| hash_cell_key =~ /^\D+#{key}$/ }
|
86
|
+
row_keys.empty? ? hash_row = {} : hash_row = hash_worksheet[:rows][row_keys[0]]
|
87
|
+
combined_hash_cell = hash_row.merge(combined_hash_cell)
|
88
|
+
hash_worksheet[:worksheet].merge(combined_hash_cell)
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
##################################
|
93
|
+
### VALIDATE HASH CELL ###
|
94
|
+
##################################
|
95
|
+
|
96
|
+
def self.validate_hash_cell(hash_cell_key, hash_cell, trace)
|
97
|
+
unless validate_cell_key(hash_cell_key)
|
98
|
+
raise(%[invalid cell key at path #{trace}, must be String and in Excel format (eg "A1")])
|
99
|
+
end
|
100
|
+
unless hash_cell.is_a?(Hash)
|
101
|
+
raise("cell value at path #{trace + [hash_cell_key]} must be a Hash")
|
102
|
+
end
|
103
|
+
unless hash_cell.keys.reject { |key| key.is_a?(Symbol) }.empty?
|
104
|
+
raise("cell key at path #{trace + [hash_cell_key]} must be a Symbol")
|
105
|
+
end
|
106
|
+
unless hash_cell.keys.delete_if { |key| valid_cell_keys.include?(key) }.empty?
|
107
|
+
valid_cell_keys_string = ":#{valid_cell_keys.join(', :')}"
|
108
|
+
raise(%(invalid cell hash key at path #{trace + [hash_cell_key]}, valid keys are: [#{valid_cell_keys_string}]))
|
109
|
+
end
|
110
|
+
# TODO: add validation for hash_cell specification
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.validate_cell_key(cell_key)
|
114
|
+
return false unless cell_key.is_a?(String)
|
115
|
+
return false unless cell_key[/^[A-Z]{1,3}[0-9]{1,7}$/]
|
116
|
+
cell_index = RubyXL::Reference.ref2ind(cell_key)
|
117
|
+
return false unless cell_index[0].between?(0, 1_048_575)
|
118
|
+
return false unless cell_index[0].between?(0, 16383)
|
119
|
+
true
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.valid_cell_keys
|
123
|
+
%i[
|
124
|
+
value
|
125
|
+
number
|
126
|
+
formula
|
127
|
+
]
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
data/lib/cells.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Cells
|
2
|
+
|
3
|
+
def self.rubyxl_to_hash(rubyxl_rows)
|
4
|
+
hash_cells = {}
|
5
|
+
rubyxl_rows.each do |rubyxl_row_hash|
|
6
|
+
rubyxl_row = rubyxl_row_hash[:rubyxl_row]
|
7
|
+
rubyxl_row_index = rubyxl_row_hash[:rubyxl_row_index]
|
8
|
+
rubyxl_row_cells = rubyxl_row&.cells
|
9
|
+
unless rubyxl_row_cells.nil?
|
10
|
+
rubyxl_row_cells.each_with_index do |rubyxl_cell, rubyxl_column_index|
|
11
|
+
hash_cell_key = RubyXL::Reference.ind2ref(rubyxl_row_index, rubyxl_column_index)
|
12
|
+
hash_cells[hash_cell_key] = Cell.rubyxl_cell_to_hash_cell(rubyxl_cell)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
hash_cells
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
data/lib/rxl.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'rxl/version'
|
2
|
+
require 'rubyXL'
|
3
|
+
require_relative 'workbook'
|
4
|
+
|
5
|
+
module Rxl
|
6
|
+
|
7
|
+
def self.write_file(filepath, hash_workbook)
|
8
|
+
begin
|
9
|
+
rubyxl_workbook = Workbook.hash_workbook_to_rubyxl_workbook(hash_workbook)
|
10
|
+
rescue => e
|
11
|
+
return e
|
12
|
+
end
|
13
|
+
rubyxl_workbook.write(filepath)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.read_file(filepath)
|
17
|
+
rubyxl_workbook = RubyXL::Parser.parse(filepath)
|
18
|
+
Workbook.rubyxl_to_hash(rubyxl_workbook)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.read_file_as_tables(filepath)
|
22
|
+
hash_workbook = read_file(filepath)
|
23
|
+
Workbook.hash_workbook_to_hash_tables(hash_workbook)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/lib/rxl/version.rb
ADDED
data/lib/workbook.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'worksheet'
|
2
|
+
|
3
|
+
module Workbook
|
4
|
+
|
5
|
+
def self.rubyxl_to_hash(rubyxl_workbook)
|
6
|
+
hash_workbook = {}
|
7
|
+
rubyxl_workbook.each do |rubyxl_worksheet|
|
8
|
+
hash_workbook[rubyxl_worksheet.sheet_name] = Worksheet.rubyxl_to_hash(rubyxl_worksheet)
|
9
|
+
end
|
10
|
+
hash_workbook
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.hash_workbook_to_rubyxl_workbook(hash_workbook)
|
14
|
+
validate_hash_workbook(hash_workbook)
|
15
|
+
rubyxl_workbook = RubyXL::Workbook.new
|
16
|
+
first_worksheet = true
|
17
|
+
hash_workbook.each do |hash_key, hash_value|
|
18
|
+
if first_worksheet
|
19
|
+
rubyxl_workbook.worksheets[0].sheet_name = hash_key
|
20
|
+
first_worksheet = false
|
21
|
+
else
|
22
|
+
rubyxl_workbook.add_worksheet(hash_key)
|
23
|
+
end
|
24
|
+
Worksheet.set_hash_worksheet_defaults(hash_value)
|
25
|
+
Worksheet.hash_worksheet_to_rubyxl_worksheet(hash_value, rubyxl_workbook[hash_key])
|
26
|
+
end
|
27
|
+
rubyxl_workbook
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.hash_workbook_to_hash_tables(hash_workbook)
|
31
|
+
hash_workbook.keys.each_with_object({}) do |key, hash_tables|
|
32
|
+
hash_tables[key] = Worksheet.hash_worksheet_to_hash_table(hash_workbook[key])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.validate_hash_workbook(hash_workbook)
|
37
|
+
raise('workbook must be a Hash') unless hash_workbook.is_a?(Hash)
|
38
|
+
hash_workbook.each do |hash_worksheet_name, hash_worksheet|
|
39
|
+
Worksheet.validate_hash_worksheet(hash_worksheet_name, hash_worksheet)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/lib/worksheet.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'rubyXL'
|
2
|
+
require 'mitrush'
|
3
|
+
require_relative 'cell'
|
4
|
+
require_relative 'cells'
|
5
|
+
|
6
|
+
module Worksheet
|
7
|
+
|
8
|
+
########################################################
|
9
|
+
### GET HASH WORKSHEET FROM RUBYXL WORKSHEET ###
|
10
|
+
########################################################
|
11
|
+
|
12
|
+
def self.rubyxl_to_hash(rubyxl_worksheet)
|
13
|
+
rubyxl_rows = rubyxl_worksheet.each_with_index.map do |rubyxl_row, rubyxl_row_index|
|
14
|
+
{ rubyxl_row: rubyxl_row, rubyxl_row_index: rubyxl_row_index }
|
15
|
+
end
|
16
|
+
hash_worksheet = Cells.rubyxl_to_hash(rubyxl_rows)
|
17
|
+
process_sheet_to_populated_block(hash_worksheet)
|
18
|
+
Mitrush.delete_keys(hash_worksheet, %i[row_count column_count])
|
19
|
+
hash_worksheet
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
########################################################
|
24
|
+
### GET RUBYXL WORKSHEET FROM HASH WORKSHEET ###
|
25
|
+
########################################################
|
26
|
+
|
27
|
+
def self.hash_worksheet_to_rubyxl_worksheet(hash_worksheet, rubyxl_worksheet)
|
28
|
+
process_sheet_to_populated_block(hash_worksheet)
|
29
|
+
(hash_worksheet[:cells] || {}).sort.each do |hash_cell_key, hash_cell|
|
30
|
+
combined_hash_cell = Cell.get_combined_hash_cell(hash_worksheet, hash_cell_key, hash_cell)
|
31
|
+
row_index, column_index = RubyXL::Reference.ref2ind(hash_cell_key)
|
32
|
+
Cell.add_rubyxl_cells(combined_hash_cell, rubyxl_worksheet, row_index, column_index)
|
33
|
+
Cell.hash_cell_to_rubyxl_cell(combined_hash_cell, rubyxl_worksheet, row_index, column_index)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
##############################
|
39
|
+
### SHARED METHODS ###
|
40
|
+
##############################
|
41
|
+
|
42
|
+
def self.process_sheet_to_populated_block(hash_worksheet)
|
43
|
+
extent = hash_worksheet_extents(hash_worksheet)
|
44
|
+
extent[:row_count].times do |row_index|
|
45
|
+
extent[:column_count].times do |column_index|
|
46
|
+
cell_key = RubyXL::Reference.ind2ref(row_index, column_index)
|
47
|
+
hash_worksheet[cell_key] = Cell.rubyxl_cell_to_hash_cell unless hash_worksheet[cell_key]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.hash_worksheet_extents(hash_worksheet)
|
53
|
+
extents = { row_count: 0, column_count: 0 }
|
54
|
+
(hash_worksheet || {}).keys.each do |hash_cell_key|
|
55
|
+
row_index, column_index = RubyXL::Reference.ref2ind(hash_cell_key)
|
56
|
+
extents[:row_count] = row_index + 1 if row_index >= extents[:row_count]
|
57
|
+
extents[:column_count] = column_index + 1 if column_index >= extents[:column_count]
|
58
|
+
end
|
59
|
+
extents
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
####################################
|
64
|
+
### OTHER PUBLIC METHODS ###
|
65
|
+
####################################
|
66
|
+
|
67
|
+
def self.set_hash_worksheet_defaults(hash_worksheet)
|
68
|
+
%i[worksheet columns rows].each do |key|
|
69
|
+
hash_worksheet[key] = {} unless hash_worksheet.has_key?(key)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.hash_worksheet_to_hash_table(raw_hash)
|
74
|
+
cells = raw_hash[:cells]
|
75
|
+
columns = cells.keys.map { |key| key[/\D+/] }.uniq
|
76
|
+
cells.keys.map { |key| key[/\d+/] }.uniq[1..-1].map do |row_number|
|
77
|
+
columns.each_with_object({}) do |column_letter, this_hash|
|
78
|
+
this_hash[cells["#{column_letter}1"][:value]] = cells["#{column_letter}#{row_number}"][:value]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.validate_hash_worksheet(hash_worksheet_name, hash_worksheet)
|
84
|
+
unless hash_worksheet_name.is_a?(String)
|
85
|
+
raise('worksheet name must be a String')
|
86
|
+
end
|
87
|
+
raise('worksheet name must not be an empty String') if hash_worksheet_name.empty?
|
88
|
+
unless hash_worksheet.is_a?(Hash)
|
89
|
+
raise(%(worksheet value at path ["#{hash_worksheet_name}"] must be a Hash))
|
90
|
+
end
|
91
|
+
hash_worksheet.each do |hash_cell_key, hash_cell|
|
92
|
+
Cell.validate_hash_cell(hash_cell_key, hash_cell, [hash_worksheet_name])
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/rubocop.yml
ADDED
data/rxl.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rxl/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "rxl"
|
8
|
+
spec.version = Rxl::VERSION
|
9
|
+
spec.authors = ["Ian McWilliams"]
|
10
|
+
spec.email = ["ian.mcwilliams@f3mmedia.co.uk"]
|
11
|
+
|
12
|
+
spec.summary = "A ruby spreadsheet interface"
|
13
|
+
spec.description = <<~DESC
|
14
|
+
Implements functionality written with Excel users in mind for straight reading and writing of sheets.
|
15
|
+
Row and column scope values are written to only the cells used; cells are specified by their Excel (A1) ID.
|
16
|
+
DESC
|
17
|
+
spec.homepage = "https://github.com/f3mmedia/rxl"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
spec.bindir = "exe"
|
22
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
|
25
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
26
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
27
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rxl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ian McWilliams
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-02-02 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: |
|
56
|
+
Implements functionality written with Excel users in mind for straight reading and writing of sheets.
|
57
|
+
Row and column scope values are written to only the cells used; cells are specified by their Excel (A1) ID.
|
58
|
+
email:
|
59
|
+
- ian.mcwilliams@f3mmedia.co.uk
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- ".rspec"
|
66
|
+
- ".travis.yml"
|
67
|
+
- CODE_OF_CONDUCT.md
|
68
|
+
- Gemfile
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- bin/console
|
73
|
+
- bin/setup
|
74
|
+
- lib/cell.rb
|
75
|
+
- lib/cells.rb
|
76
|
+
- lib/rxl.rb
|
77
|
+
- lib/rxl/version.rb
|
78
|
+
- lib/workbook.rb
|
79
|
+
- lib/worksheet.rb
|
80
|
+
- rubocop.yml
|
81
|
+
- rxl.gemspec
|
82
|
+
homepage: https://github.com/f3mmedia/rxl
|
83
|
+
licenses:
|
84
|
+
- MIT
|
85
|
+
metadata: {}
|
86
|
+
post_install_message:
|
87
|
+
rdoc_options: []
|
88
|
+
require_paths:
|
89
|
+
- lib
|
90
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
requirements: []
|
101
|
+
rubyforge_project:
|
102
|
+
rubygems_version: 2.5.2.3
|
103
|
+
signing_key:
|
104
|
+
specification_version: 4
|
105
|
+
summary: A ruby spreadsheet interface
|
106
|
+
test_files: []
|