ooxl 0.0.1.2
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/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/ooxl.rb +18 -0
- data/lib/ooxl/ooxl.rb +90 -0
- data/lib/ooxl/util.rb +16 -0
- data/lib/ooxl/version.rb +3 -0
- data/lib/ooxl/xl_objects/cell.rb +106 -0
- data/lib/ooxl/xl_objects/column.rb +19 -0
- data/lib/ooxl/xl_objects/comments.rb +32 -0
- data/lib/ooxl/xl_objects/row.rb +29 -0
- data/lib/ooxl/xl_objects/sheet.rb +156 -0
- data/lib/ooxl/xl_objects/sheet/data_validation.rb +69 -0
- data/lib/ooxl/xl_objects/styles.rb +51 -0
- data/lib/ooxl/xl_objects/styles/cell_style_reference.rb +19 -0
- data/lib/ooxl/xl_objects/styles/fill.rb +31 -0
- data/lib/ooxl/xl_objects/styles/font.rb +24 -0
- data/lib/ooxl/xl_objects/styles/number_formatting.rb +13 -0
- data/lib/ooxl/xl_objects/workbook.rb +32 -0
- data/ooxml_excel.gemspec +34 -0
- metadata +158 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c939544ca8395b5cad397848b69e789073e4e036
|
4
|
+
data.tar.gz: 85910bb29f92f0bbfab570ca7125694efaa81a90
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 42f4c2e796683bbafddb15312be2a9e175205c2d1b8cf51a19cabdb944ba0d18a92e1462fc80588c565bb937384e2cb92666ae6e2db3162fc1d88614bed4cd02
|
7
|
+
data.tar.gz: 0db40f7d63f5647c901fc94d85a63835f246794a11ab26dfb2523b1a908046b6b74a26943246b36cdffb39087cab4d4b39b3b22e2f00769c627f6e37a3c435e9
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
# OOXML Excel
|
2
|
+
|
3
|
+
TODO: Description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'ooxml_excel'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install ooxml_excel
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Using `OOXML::Excel` to read spreadsheet:
|
24
|
+
```
|
25
|
+
ooxml_excel = OOXML::Excel.new('example.xlsx')
|
26
|
+
```
|
27
|
+
|
28
|
+
### Fetching all sheets:
|
29
|
+
```
|
30
|
+
ooxml_excel.sheets # ['Test Sheet 1', 'Test Sheet 2']
|
31
|
+
```
|
32
|
+
|
33
|
+
### Accessing Rows and Cells
|
34
|
+
```
|
35
|
+
sheet = ooxml_excel.sheet('Test Sheet 1')
|
36
|
+
|
37
|
+
# Rows
|
38
|
+
sheet[0] # Access the first row
|
39
|
+
sheet.rows[0] # Same as above
|
40
|
+
|
41
|
+
# Cells
|
42
|
+
sheet[0].cells # Fetch all cells
|
43
|
+
sheet.rows[0].cells # longer way to do it
|
44
|
+
|
45
|
+
sheet[0][0] # Access the first cell of the row
|
46
|
+
sheet.rows[0].cells[0] # longer way to do it
|
47
|
+
|
48
|
+
sheet[0][0].value # Access cell value
|
49
|
+
sheet.rows[0].cells[0].value # longer way to do it
|
50
|
+
|
51
|
+
ooxml_excel['Test Sheet 1'][0][0].value
|
52
|
+
```
|
53
|
+
|
54
|
+
### Iterating through each row:
|
55
|
+
```
|
56
|
+
# as an array of strings
|
57
|
+
ooxml_excel.sheet('Test Sheet 1').each_row do |row|
|
58
|
+
# Do something here...
|
59
|
+
p row # ['text', 'text']
|
60
|
+
end
|
61
|
+
|
62
|
+
# as an array of objects
|
63
|
+
ooxml_excel.sheet('Test Sheet 1').each_row_as_object do |row|
|
64
|
+
# Do something here...
|
65
|
+
p row.cells # [OOXML::Excel::Row::Cell, ...]
|
66
|
+
end
|
67
|
+
|
68
|
+
```
|
69
|
+
|
70
|
+
### Fetching Columns
|
71
|
+
```
|
72
|
+
# Fetch all columns
|
73
|
+
ooxml_excel.sheet('Test Sheet 1').columns
|
74
|
+
|
75
|
+
# Checking if the column is hidden
|
76
|
+
ooxml_excel.sheet('Test Sheet 1').column(1).hidden? # column index
|
77
|
+
ooxml_excel.sheet('Test Sheet 1').column('A').hidden? # column letter
|
78
|
+
```
|
79
|
+
|
80
|
+
### Fetching Styles
|
81
|
+
```
|
82
|
+
# Font
|
83
|
+
font_object = ooxml_excel.sheet('Test Sheet 1').font('A1')
|
84
|
+
font_object.bold? # false
|
85
|
+
font_object.name # Arial
|
86
|
+
font_object.rgb_color # FFE10000
|
87
|
+
font_object.size # 8
|
88
|
+
|
89
|
+
# Cell Fill
|
90
|
+
fill_object = ooxml_excel.sheet('Test Sheet 1').fill('A1')
|
91
|
+
fill_object.bg_color # FFE10000
|
92
|
+
fill_object.fg_color # FFE10000
|
93
|
+
```
|
94
|
+
### Fetching Data from named/cell range
|
95
|
+
```
|
96
|
+
# named range
|
97
|
+
ooxml_excel.named_range('my_named_range') # ['value' 'from', 'range']
|
98
|
+
|
99
|
+
# cell range
|
100
|
+
ooxml['Lists'!$A$1:$A$6] # ['1','2','3','4','5','6']
|
101
|
+
|
102
|
+
# or
|
103
|
+
ooxml['Lists'!A1:A6] # ['1','2','3','4','5','6']
|
104
|
+
|
105
|
+
# or loading a single value
|
106
|
+
ooxml['Lists'!A1] # ['1']
|
107
|
+
|
108
|
+
# or loading a box type values
|
109
|
+
ooxml['Lists!A1:B2'] # [['1', '2'], ['2','3']]
|
110
|
+
|
111
|
+
```
|
112
|
+
### Fetching Data Validation
|
113
|
+
```
|
114
|
+
# All Validations
|
115
|
+
data_validations = ooxml_excel.sheet('Test Sheet 1').data_validations
|
116
|
+
|
117
|
+
# Specific validation for cell
|
118
|
+
data_validation = ooxml.sheet('Input Sheet').data_validation('D4')
|
119
|
+
|
120
|
+
data_validation.prompt # "Sample Validation Message"
|
121
|
+
data_validation.formula # 20
|
122
|
+
data_validation.type #textLength
|
123
|
+
|
124
|
+
```
|
125
|
+
|
126
|
+
## Development
|
127
|
+
|
128
|
+
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.
|
129
|
+
|
130
|
+
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).
|
131
|
+
|
132
|
+
## Contributing
|
133
|
+
|
134
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/halcjames/ooxml_excel.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "ooxml_excel"
|
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/ooxl.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# dependencies
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'active_support/all'
|
4
|
+
require 'zip'
|
5
|
+
|
6
|
+
# gem
|
7
|
+
require "ooxl/version"
|
8
|
+
|
9
|
+
# library
|
10
|
+
require "ooxl/ooxl"
|
11
|
+
require "ooxl/util"
|
12
|
+
require "ooxl/xl_objects/workbook"
|
13
|
+
require "ooxl/xl_objects/comments"
|
14
|
+
require "ooxl/xl_objects/styles"
|
15
|
+
require "ooxl/xl_objects/sheet"
|
16
|
+
require "ooxl/xl_objects/row"
|
17
|
+
require "ooxl/xl_objects/column"
|
18
|
+
require "ooxl/xl_objects/cell"
|
data/lib/ooxl/ooxl.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
class OOXL
|
2
|
+
include Enumerable
|
3
|
+
def initialize(spreadsheet_filepath)
|
4
|
+
@workbook = nil
|
5
|
+
@sheets = {}
|
6
|
+
@styles = []
|
7
|
+
@comments = {}
|
8
|
+
parse_spreadsheet_contents(spreadsheet_filepath)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.open(spreadsheet_filepath)
|
12
|
+
new(spreadsheet_filepath)
|
13
|
+
end
|
14
|
+
|
15
|
+
def sheets
|
16
|
+
@workbook.sheets.map { |sheet| sheet[:name]}
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
sheets.each do |sheet_name|
|
21
|
+
yield sheet(sheet_name)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def sheet(sheet_name)
|
26
|
+
sheet_index = @workbook.sheets.index { |sheet| sheet[:name] == sheet_name}
|
27
|
+
raise "No #{sheet_name} in workbook." if sheet_index.nil?
|
28
|
+
sheet = @sheets.fetch((sheet_index+1).to_s)
|
29
|
+
|
30
|
+
# shared variables
|
31
|
+
sheet.name = sheet_name
|
32
|
+
sheet.comments = @comments[(sheet_index+1)]
|
33
|
+
sheet.styles = @styles
|
34
|
+
sheet.defined_names = @workbook.defined_names
|
35
|
+
sheet
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](text)
|
39
|
+
# immediately treat as cell range if an exclamation point is detected
|
40
|
+
# otherwise, normally load a sheet
|
41
|
+
|
42
|
+
text.include?('!') ? load_list_values(text) : sheet(text)
|
43
|
+
end
|
44
|
+
|
45
|
+
def named_range(name)
|
46
|
+
# "Hidden11390550_39"=>"Hidden!$B$734:$B$735"
|
47
|
+
# ooxml.named_range('Hidden11390550_107')
|
48
|
+
# a typical named range would be be
|
49
|
+
# yes_no => 'Lists'!$A$1:$A$6
|
50
|
+
defined_name = @workbook.defined_names[name]
|
51
|
+
load_list_values(defined_name) if defined_name.present?
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def load_list_values(range_text)
|
57
|
+
# get the sheet name => 'Lists'
|
58
|
+
sheet_name = range_text.gsub(/[\$\']/, '').scan(/^[^!]*/).first
|
59
|
+
# fetch the cell range => '$A$1:$A$6'
|
60
|
+
cell_range = range_text.gsub(/\$/, '').scan(/(?<=!).+/).first
|
61
|
+
# get the sheet object and fetch the cells in range
|
62
|
+
sheet(sheet_name).list_values_from_cell_range(cell_range)
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_spreadsheet_contents(spreadsheet)
|
66
|
+
shared_strings = []
|
67
|
+
Zip::File.open(spreadsheet) do |spreadsheet_zip|
|
68
|
+
spreadsheet_zip.each do |entry|
|
69
|
+
case entry.name
|
70
|
+
when /xl\/worksheets\/sheet(\d+)?\.xml/
|
71
|
+
sheet_id = entry.name.scan(/xl\/worksheets\/sheet(\d+)?\.xml/).flatten.first
|
72
|
+
@sheets[sheet_id] = OOXL::Sheet.new(entry.get_input_stream.read, shared_strings)
|
73
|
+
when /xl\/styles\.xml/
|
74
|
+
@styles = OOXL::Styles.load_from_stream(entry.get_input_stream.read)
|
75
|
+
when /xl\/comments(\d+)?\.xml/
|
76
|
+
comment_id = entry.name.scan(/xl\/comments(\d+)\.xml/).flatten.first
|
77
|
+
@comments[comment_id] = OOXL::Comments.load_from_stream(entry.get_input_stream.read)
|
78
|
+
when "xl/sharedStrings.xml"
|
79
|
+
Nokogiri.XML(entry.get_input_stream.read).remove_namespaces!.xpath('sst/si').each do |shared_string_node|
|
80
|
+
shared_strings << shared_string_node.at('t').text
|
81
|
+
end
|
82
|
+
when "xl/workbook.xml"
|
83
|
+
@workbook = OOXL::Workbook.load_from_stream(entry.get_input_stream.read)
|
84
|
+
else
|
85
|
+
# unsupported for now..
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/ooxl/util.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
class OOXL
|
2
|
+
module Util
|
3
|
+
COLUMN_LETTERS = ('A'..'ZZZZ').to_a
|
4
|
+
def letter_equivalent(index)
|
5
|
+
COLUMN_LETTERS.fetch(index)
|
6
|
+
end
|
7
|
+
|
8
|
+
def letter_index(letter)
|
9
|
+
COLUMN_LETTERS.index { |c_letter| c_letter == letter}
|
10
|
+
end
|
11
|
+
|
12
|
+
def uniform_reference(ref)
|
13
|
+
ref.to_s[/[A-Z]/] ? letter_index(ref) + 1 : ref
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/ooxl/version.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
class OOXL
|
2
|
+
class BlankCell
|
3
|
+
attr_reader :id
|
4
|
+
|
5
|
+
def initialize(id)
|
6
|
+
@id = id
|
7
|
+
end
|
8
|
+
def value
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Cell
|
14
|
+
attr_accessor :id, :type_id, :style_id, :value_id, :shared_strings, :styles
|
15
|
+
|
16
|
+
def initialize(**attrs)
|
17
|
+
attrs.each { |property, value| send("#{property}=", value)}
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_id(offset: 1, location: "bottom")
|
21
|
+
_, column_letter, column_index = id.partition(/[A-Z]/)
|
22
|
+
|
23
|
+
# ensure that all are numbers
|
24
|
+
column_index = column_index.to_i
|
25
|
+
offset = offset.to_i if offset.is_a?(String)
|
26
|
+
|
27
|
+
# increment based on specified location
|
28
|
+
case location
|
29
|
+
when "top"
|
30
|
+
if column_index == 1 || column_index < offset
|
31
|
+
column_index = 1
|
32
|
+
else
|
33
|
+
column_index -= offset
|
34
|
+
end
|
35
|
+
when "bottom"
|
36
|
+
column_index += offset
|
37
|
+
when "left"
|
38
|
+
return id if column_letter == 'A'
|
39
|
+
1.upto(offset) { |count| column_letter = (column_letter.ord-1).chr unless column_letter == 'A' }
|
40
|
+
when "right"
|
41
|
+
1.upto(offset) { |count| column_letter.next! }
|
42
|
+
else
|
43
|
+
id
|
44
|
+
end
|
45
|
+
|
46
|
+
"#{column_letter}#{column_index}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def type
|
50
|
+
@type ||= begin
|
51
|
+
case type_id
|
52
|
+
when 's' then :string
|
53
|
+
when 'n' then :number
|
54
|
+
when 'b' then :boolean
|
55
|
+
when 'd' then :date
|
56
|
+
when 'str' then :formula
|
57
|
+
when 'inlineStr' then :inline_str
|
58
|
+
else
|
59
|
+
:error
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def style
|
65
|
+
@style ||= begin
|
66
|
+
if style_id.present?
|
67
|
+
style = styles.by_id(style_id.to_i)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def number_format
|
73
|
+
if (style.present?)
|
74
|
+
nf = style[:number_format]
|
75
|
+
(nf.present?) ? nf.gsub("\\", "") : nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def font
|
80
|
+
(style.present?) ? style[:font] : nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def fill
|
84
|
+
(style.present?) ? style[:fill]: nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def value
|
88
|
+
case type
|
89
|
+
when :string
|
90
|
+
(value_id.present?) ? shared_strings[value_id.to_i] : nil
|
91
|
+
else
|
92
|
+
# TODO: to support other types soon
|
93
|
+
value_id
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.load_from_node(cell_node, shared_strings, styles)
|
98
|
+
new(id: cell_node.attributes["r"].try(:value),
|
99
|
+
type_id: cell_node.attributes["t"].try(:value),
|
100
|
+
style_id: cell_node.attributes["s"].try(:value),
|
101
|
+
value_id: cell_node.at('v').try(:text),
|
102
|
+
shared_strings: shared_strings,
|
103
|
+
styles: styles )
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Column
|
3
|
+
attr_accessor :id, :width, :custom_width, :id_range, :hidden
|
4
|
+
alias_method :hidden?, :hidden
|
5
|
+
|
6
|
+
def initialize(**attrs)
|
7
|
+
attrs.each { |property, value| send("#{property}=", value)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.load_from_node(column_node)
|
11
|
+
hidden_attr = column_node.attributes["hidden"]
|
12
|
+
new(id: column_node.attributes["min"].try(:value),
|
13
|
+
width: column_node.attributes["width"].try(:value),
|
14
|
+
custom_width: column_node.attributes["custom_width"].try(:value),
|
15
|
+
id_range: (column_node.attributes["min"].value.to_i..column_node.attributes["max"].value.to_i).to_a,
|
16
|
+
hidden: (hidden_attr.present?) ? hidden_attr.value == "1" : false)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Comments
|
3
|
+
attr_reader :comments
|
4
|
+
|
5
|
+
def initialize(comments)
|
6
|
+
@comments = comments
|
7
|
+
end
|
8
|
+
|
9
|
+
def [](id)
|
10
|
+
@comments[id]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.load_from_stream(comment_xml)
|
14
|
+
comment_xml =Nokogiri.XML(comment_xml).remove_namespaces!
|
15
|
+
|
16
|
+
comments = comment_xml.xpath("//comments/commentList/comment").map do |comment_node|
|
17
|
+
comment_text_node = comment_node.xpath('./text/r/t')
|
18
|
+
|
19
|
+
value = if comment_text_node.is_a?(Array)
|
20
|
+
comment_text_node.map { |comment_text_node| comment_text_node.text }.join('')
|
21
|
+
else
|
22
|
+
comment_text_node.text
|
23
|
+
end
|
24
|
+
|
25
|
+
# value = (comment_node.xpath('./text/r/t').last || comment_node.at_xpath('./text/r/t') || comment_node.at_xpath('./text/t')).text
|
26
|
+
id = comment_node.attributes["ref"].to_s
|
27
|
+
[id, value]
|
28
|
+
end.to_h
|
29
|
+
new(comments)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Row
|
3
|
+
include Enumerable
|
4
|
+
attr_accessor :id, :spans, :cells
|
5
|
+
|
6
|
+
def initialize(**attrs)
|
7
|
+
attrs.each { |property, value| send("#{property}=", value)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](id)
|
11
|
+
cell = if id.is_a?(String)
|
12
|
+
cells.find { |row| row.id == id}
|
13
|
+
else
|
14
|
+
cells[id]
|
15
|
+
end
|
16
|
+
(cell.present?) ? cell : BlankCell.new(id)
|
17
|
+
end
|
18
|
+
|
19
|
+
def each
|
20
|
+
cells.each { |cell| yield cell }
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.load_from_node(row_node, shared_strings, styles)
|
24
|
+
new(id: row_node.attributes["r"].try(:value),
|
25
|
+
spans: row_node.attributes["spans"].try(:value),
|
26
|
+
cells: row_node.xpath('c').map { |cell_node| OOXL::Cell.load_from_node(cell_node, shared_strings, styles) } )
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require_relative 'sheet/data_validation'
|
2
|
+
class OOXL
|
3
|
+
class Sheet
|
4
|
+
include OOXL::Util
|
5
|
+
include Enumerable
|
6
|
+
attr_reader :columns, :data_validations, :shared_strings
|
7
|
+
attr_accessor :comments, :styles, :defined_names, :name
|
8
|
+
|
9
|
+
def initialize(xml, shared_strings)
|
10
|
+
@xml = Nokogiri.XML(xml).remove_namespaces!
|
11
|
+
@shared_strings = shared_strings
|
12
|
+
@comments = {}
|
13
|
+
@defined_names = {}
|
14
|
+
@styles = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def code_name
|
18
|
+
@code_name ||= @xml.xpath('//sheetPr').attribute('codeName').try(:value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def comment(cell_ref)
|
22
|
+
@comments[cell_ref]
|
23
|
+
end
|
24
|
+
|
25
|
+
def data_validation(cell_ref)
|
26
|
+
data_validations.find { |data_validation| data_validation.in_sqref_range?(cell_ref)}
|
27
|
+
end
|
28
|
+
|
29
|
+
def column(id)
|
30
|
+
uniformed_reference = uniform_reference(id)
|
31
|
+
columns.find { |column| column.id_range.include?(uniformed_reference)}
|
32
|
+
end
|
33
|
+
|
34
|
+
def columns
|
35
|
+
@columns ||= begin
|
36
|
+
@xml.xpath('//cols/col').map do |column_node|
|
37
|
+
Column.load_from_node(column_node)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def [](id)
|
43
|
+
if id.is_a?(String)
|
44
|
+
rows.find { |row| row.id == id}
|
45
|
+
else
|
46
|
+
rows[id]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def row(index)
|
51
|
+
rows.find { |row| row.id == index.to_s}
|
52
|
+
end
|
53
|
+
|
54
|
+
def rows
|
55
|
+
@rows ||= begin
|
56
|
+
# TODO: get the value of merged cells
|
57
|
+
# merged_cells = @xml.xpath('//mergeCells/mergeCell').map { |merged_cell| merged_cell.attributes["ref"].try(:value) }
|
58
|
+
@xml.xpath('//sheetData/row').map do |row_node|
|
59
|
+
Row.load_from_node(row_node, @shared_strings, styles)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def each
|
65
|
+
rows.each { |row| yield row }
|
66
|
+
end
|
67
|
+
|
68
|
+
def font(cell_reference)
|
69
|
+
style_id = fetch_style_style_id(cell_reference)
|
70
|
+
if style_id.present?
|
71
|
+
style = @styles.by_id(style_id.to_i)
|
72
|
+
|
73
|
+
(style.present?) ? style[:font] : nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def fill(cell_reference)
|
78
|
+
style_id = fetch_style_style_id(cell_reference)
|
79
|
+
if style_id.present?
|
80
|
+
style = @styles.by_id(style_id.to_i)
|
81
|
+
(style.present?) ? style[:fill] : nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def data_validations
|
87
|
+
@data_validations ||= begin
|
88
|
+
@xml.xpath('//dataValidations/dataValidation').map do |data_validation_node|
|
89
|
+
Sheet::DataValidation.load_from_node(data_validation_node)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# a shortcut for:
|
95
|
+
# formula = data_validation('A1').formula
|
96
|
+
# ooxl.named_range(formula)
|
97
|
+
def cell_range(cell_ref)
|
98
|
+
data_validation = data_validations.find { |data_validation| data_validation.in_sqref_range?(cell_ref)}
|
99
|
+
if data_validation.respond_to?(:type) && data_validation.type == "list"
|
100
|
+
if data_validation.formula[/[\s\$\,\:]/]
|
101
|
+
(data_validation.formula[/\$/].present?) ? "#{name}!#{data_validation.formula}" : data_validation.formula
|
102
|
+
else
|
103
|
+
@defined_names.fetch(data_validation.formula)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def list_values_from_cell_range(cell_range)
|
109
|
+
return [] if cell_range.blank?
|
110
|
+
|
111
|
+
# cell_range values separated by comma
|
112
|
+
if cell_range.include?(":")
|
113
|
+
cell_letters = cell_range.gsub(/[\d]/, '').split(':')
|
114
|
+
start_index, end_index = cell_range.gsub(/[^\d:]/, '').split(':').map(&:to_i)
|
115
|
+
|
116
|
+
# This will allow values from this pattern
|
117
|
+
# 'SheetName!A1:C3'
|
118
|
+
# The number after the cell letter will be the index
|
119
|
+
# 1 => start_index
|
120
|
+
# 3 => end_index
|
121
|
+
# Expected output would be: [['value', 'value', 'value'], ['value', 'value', 'value'], ['value', 'value', 'value']]
|
122
|
+
if cell_letters.uniq.size > 1
|
123
|
+
start_index.upto(end_index).map do |row_index|
|
124
|
+
(cell_letters.first..cell_letters.last).map do |cell_letter|
|
125
|
+
row = rows[row_index-1]
|
126
|
+
next if row.blank?
|
127
|
+
row["#{cell_letter}#{row_index}"].value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
else
|
131
|
+
cell_letter = cell_letters.uniq.first
|
132
|
+
(start_index..end_index).to_a.map do |row_index|
|
133
|
+
row = rows[row_index-1]
|
134
|
+
next if row.blank?
|
135
|
+
row["#{cell_letter}#{row_index}"].value
|
136
|
+
end
|
137
|
+
end
|
138
|
+
else
|
139
|
+
# when only one value: B2
|
140
|
+
row_index = cell_range.gsub(/[^\d:]/, '').split(':').map(&:to_i).first
|
141
|
+
row = rows[row_index-1]
|
142
|
+
return if row.blank?
|
143
|
+
[row[cell_range].value]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def fetch_style_style_id(cell_reference)
|
149
|
+
raise 'Invalid Cell Reference!' if cell_reference[/[A-Z]{1,}\d+/].blank?
|
150
|
+
row_index = cell_reference.scan(/[A-Z{1,}](\d+)/).flatten.first.to_i - 1
|
151
|
+
return if rows[row_index].blank? || rows[row_index][cell_reference].blank?
|
152
|
+
rows[row_index][cell_reference].style_id
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Sheet
|
3
|
+
class DataValidation
|
4
|
+
attr_accessor :allow_blank, :prompt, :type, :sqref, :formula
|
5
|
+
|
6
|
+
def in_sqref_range?(cell_id)
|
7
|
+
return if cell_id.blank?
|
8
|
+
cell_letter = cell_id.gsub(/[\d]/, '')
|
9
|
+
index = cell_id.gsub(/[^\d]/, '').to_i
|
10
|
+
range = sqref_range.find { |single_cell_letter_or_range, row_range| single_cell_letter_or_range.is_a?(Range) ? single_cell_letter_or_range.cover?(cell_letter) : single_cell_letter_or_range == cell_letter}
|
11
|
+
range.last.include?(index) if range.present?
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.load_from_node(data_validation_node)
|
15
|
+
allow_blank = data_validation_node.attribute('allowBlank').try(:value)
|
16
|
+
prompt = data_validation_node.attribute('prompt').try(:value)
|
17
|
+
type = data_validation_node.attribute('type').try(:value)
|
18
|
+
sqref = data_validation_node.attribute('sqref').try(:value)
|
19
|
+
formula = data_validation_node.at('formula1').try(:content)
|
20
|
+
|
21
|
+
self.new(allow_blank: allow_blank,
|
22
|
+
prompt: prompt,
|
23
|
+
type: type,
|
24
|
+
sqref: sqref,
|
25
|
+
formula: formula)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def initialize(**attrs)
|
30
|
+
attrs.each { |property, value| send("#{property}=", value)}
|
31
|
+
end
|
32
|
+
|
33
|
+
def sqref_range
|
34
|
+
@sqref_range ||= begin
|
35
|
+
# "BH5:BH271 BI5:BI271"
|
36
|
+
if !sqref.include?(':')
|
37
|
+
cell_letter = sqref.gsub(/[\d]/, '')
|
38
|
+
index = sqref.gsub(/[^\d]/, '').to_i
|
39
|
+
{ cell_letter => (index..index)}
|
40
|
+
else
|
41
|
+
sqref.split( ' ').map do |splitted_by_space_sqref|
|
42
|
+
# ["BH5:BH271, "BI5:BI271"]
|
43
|
+
if splitted_by_space_sqref.is_a?(Array)
|
44
|
+
splitted_by_space_sqref.map do |sqref|
|
45
|
+
build_range(splitted_by_space_sqref)
|
46
|
+
end
|
47
|
+
else
|
48
|
+
# "BH5:BH271"
|
49
|
+
build_range(splitted_by_space_sqref)
|
50
|
+
end
|
51
|
+
end.to_h
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def build_range(sqref)
|
57
|
+
splitted_sqref = sqref.gsub(/[\d]/, '')
|
58
|
+
sqref_without_letters = sqref.gsub(/[^\d:]/, '')
|
59
|
+
if sqref.include?(':')
|
60
|
+
start_letter, end_letter = splitted_sqref.split(':')
|
61
|
+
start_index, end_index = sqref_without_letters.split(':').map(&:to_i)
|
62
|
+
[(start_letter..end_letter),(start_index..end_index)]
|
63
|
+
else
|
64
|
+
[(splitted_sqref..splitted_sqref),(sqref_without_letters..sqref_without_letters)]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require_relative 'styles/cell_style_reference'
|
2
|
+
require_relative 'styles/fill'
|
3
|
+
require_relative 'styles/font'
|
4
|
+
require_relative 'styles/number_formatting'
|
5
|
+
class OOXL
|
6
|
+
class Styles
|
7
|
+
attr_accessor :fonts, :fills, :number_formats, :cell_style_xfs
|
8
|
+
def initialize(**attrs)
|
9
|
+
attrs.each { |property, value| send("#{property}=", value)}
|
10
|
+
end
|
11
|
+
|
12
|
+
def by_id(id)
|
13
|
+
cell_style = cell_style_xfs.fetch(id)
|
14
|
+
{
|
15
|
+
font: fonts_by_index(cell_style.font_id),
|
16
|
+
fill: fills_by_index(cell_style.fill_id),
|
17
|
+
number_format: number_formats_by_index(cell_style.number_formatting_id),
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def fonts_by_index(font_index)
|
22
|
+
@fonts[font_index]
|
23
|
+
end
|
24
|
+
|
25
|
+
def fills_by_index(fill_index)
|
26
|
+
@fills[fill_index]
|
27
|
+
end
|
28
|
+
|
29
|
+
def number_formats_by_index(number_format_index)
|
30
|
+
@number_formats.find { |number_format| number_format.id == number_format_index.to_s}.try(:code)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.load_from_stream(xml_stream)
|
34
|
+
style_doc = Nokogiri.XML(xml_stream).remove_namespaces!
|
35
|
+
fonts = style_doc.xpath('//fonts/font')
|
36
|
+
fills = style_doc.xpath('//fills/fill')
|
37
|
+
number_formats = style_doc.xpath('//numFmts/numFmt')
|
38
|
+
# This element contains the master formatting records (xf) which
|
39
|
+
# define the formatting applied to cells in this workbook.
|
40
|
+
# link: https://msdn.microsoft.com/en-us/library/documentformat.openxml.spreadsheet.cellformats(v=office.14).aspx
|
41
|
+
cell_style_xfs = style_doc.xpath('//cellXfs/xf')
|
42
|
+
|
43
|
+
self.new(
|
44
|
+
fonts: fonts.map { |font_node| Styles::Font.load_from_node(font_node)},
|
45
|
+
fills: fills.map { |fill_node| Styles::Fill.load_from_node(fill_node) if fill_node.to_s.include?('patternFill')},
|
46
|
+
number_formats: number_formats.map { |num_fmt_node| Styles::NumberFormatting.load_from_node(num_fmt_node) },
|
47
|
+
cell_style_xfs: cell_style_xfs.map { |cell_style_xfs_node| Styles::CellStyleReference.load_from_node(cell_style_xfs_node)}
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Styles
|
3
|
+
class CellStyleReference
|
4
|
+
attr_accessor :id, :number_formatting_id, :fill_id, :font_id
|
5
|
+
def initialize(**attrs)
|
6
|
+
attrs.each { |property, value| send("#{property}=", value)}
|
7
|
+
end
|
8
|
+
def self.load_from_node(cell_style_xfs_node)
|
9
|
+
attributes = cell_style_xfs_node.attributes
|
10
|
+
self.new(
|
11
|
+
id: attributes["xfId"].value.to_i,
|
12
|
+
number_formatting_id: attributes["numFmtId"].value.to_i,
|
13
|
+
fill_id: attributes["fillId"].value.to_i,
|
14
|
+
font_id: attributes["fontId"].value.to_i
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Styles
|
3
|
+
class Fill
|
4
|
+
attr_accessor :pattern_type, :fg_color, :fg_color_theme, :fg_color_tint, :bg_color_index, :bg_color, :fg_color_index
|
5
|
+
|
6
|
+
def initialize(**attrs)
|
7
|
+
attrs.each { |property, value| send("#{property}=", value)}
|
8
|
+
end
|
9
|
+
def self.load_from_node(fill_node)
|
10
|
+
pattern_fill = fill_node.at('patternFill')
|
11
|
+
|
12
|
+
pattern_type = pattern_fill.attributes["patternType"].value
|
13
|
+
if pattern_type == "solid"
|
14
|
+
fg_color = pattern_fill.at('fgColor')
|
15
|
+
bg_color = pattern_fill.at('bgColor')
|
16
|
+
fg_color_index = fg_color.class == Nokogiri::XML::Element ? fg_color.attributes["indexed"].try(:value) : nil
|
17
|
+
|
18
|
+
self.new(pattern_type: pattern_type,
|
19
|
+
fg_color: (fg_color.present?) ? fg_color.attributes["rgb"].try(:value) : nil,
|
20
|
+
fg_color_theme: (fg_color.present?) ? fg_color.attributes["theme"].try(:value) : nil,
|
21
|
+
fg_color_tint: (fg_color.present?) ? fg_color.attributes["tint"].try(:value) : nil,
|
22
|
+
bg_color: (bg_color.present?) ? bg_color.attributes["rgb"].try(:value) : nil,
|
23
|
+
bg_color_index: (bg_color.present?) ? bg_color.attributes["index"].try(:value) : nil,
|
24
|
+
fg_color_index: fg_color_index)
|
25
|
+
else
|
26
|
+
self.new(pattern_type: pattern_type)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Styles
|
3
|
+
class Font
|
4
|
+
attr_accessor :size, :name, :rgb_color, :theme, :bold
|
5
|
+
alias_method :bold?, :bold
|
6
|
+
def initialize(**attrs)
|
7
|
+
attrs.each { |property, value| send("#{property}=", value)}
|
8
|
+
end
|
9
|
+
def self.load_from_node(font_node)
|
10
|
+
font_size_node = font_node.at('sz')
|
11
|
+
font_color_node = font_node.at('color')
|
12
|
+
font_name_node = font_node.at('name')
|
13
|
+
font_bold_node = font_node.at('b')
|
14
|
+
self.new(
|
15
|
+
size: font_size_node && font_size_node.attributes["val"].value,
|
16
|
+
name: font_name_node && font_name_node.attributes["val"].value,
|
17
|
+
rgb_color: font_color_node && font_color_node.attributes["rgb"].try(:value) ,
|
18
|
+
theme: font_color_node && font_color_node.attributes["theme"].try(:value),
|
19
|
+
bold: font_bold_node.present?
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Styles
|
3
|
+
class NumberFormatting
|
4
|
+
attr_accessor :id, :code
|
5
|
+
def self.load_from_node(num_fmt_node)
|
6
|
+
new_format = self.new.tap do |number_format|
|
7
|
+
number_format.id = num_fmt_node.attributes["numFmtId"].try(:value)
|
8
|
+
number_format.code = num_fmt_node.attributes["formatCode"].try(:value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class OOXL
|
2
|
+
class Workbook
|
3
|
+
def initialize(xml)
|
4
|
+
@xml = xml
|
5
|
+
end
|
6
|
+
|
7
|
+
def sheets
|
8
|
+
@sheets ||= begin
|
9
|
+
@xml.xpath('//sheets/sheet').map do |sheet_node|
|
10
|
+
name = sheet_node.attribute('name').value
|
11
|
+
rel_id = sheet_node.attribute('id').value.gsub(/[^\d+]/, '')
|
12
|
+
sheet_id = sheet_node.attribute('sheetId').value
|
13
|
+
{ name: name, sheet_id: sheet_id, relationship_id: rel_id}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def defined_names
|
19
|
+
@defined_names ||= begin
|
20
|
+
@xml.xpath('//definedNames/definedName').map do |defined_names_node|
|
21
|
+
name = defined_names_node.attribute('name').value
|
22
|
+
reference = defined_names_node.text
|
23
|
+
[name, reference]
|
24
|
+
end.to_h
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.load_from_stream(xml_stream)
|
29
|
+
self.new (Nokogiri.XML(xml_stream).remove_namespaces!)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/ooxml_excel.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'ooxl/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "ooxl"
|
8
|
+
spec.version = OOXL::VERSION
|
9
|
+
spec.authors = ["James Mones"]
|
10
|
+
spec.email = ["bajong009@gmail.com"]
|
11
|
+
spec.summary = %q{OOXL Excel - Parse Excel Spreadsheets (xlsx, xlsm).}
|
12
|
+
spec.description = %q{A Ruby spreadsheet parser for Excel (xlsx, xlsm).}
|
13
|
+
spec.homepage = "https://github.com/halcjames/ooxml_excel"
|
14
|
+
|
15
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
16
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
17
|
+
# if spec.respond_to?(:metadata)
|
18
|
+
# spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
19
|
+
# else
|
20
|
+
# raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
21
|
+
# end
|
22
|
+
|
23
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
spec.add_dependency 'activesupport'
|
28
|
+
spec.add_dependency 'nokogiri', '~> 1'
|
29
|
+
spec.add_dependency 'rubyzip', '~> 1.1', '< 2.0.0'
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.12"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
34
|
+
end
|
metadata
ADDED
@@ -0,0 +1,158 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ooxl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Mones
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubyzip
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.1'
|
48
|
+
- - "<"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: 2.0.0
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.1'
|
58
|
+
- - "<"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 2.0.0
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '1.12'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.12'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '10.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '10.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: rspec
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '3.0'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '3.0'
|
103
|
+
description: A Ruby spreadsheet parser for Excel (xlsx, xlsm).
|
104
|
+
email:
|
105
|
+
- bajong009@gmail.com
|
106
|
+
executables: []
|
107
|
+
extensions: []
|
108
|
+
extra_rdoc_files: []
|
109
|
+
files:
|
110
|
+
- ".gitignore"
|
111
|
+
- ".rspec"
|
112
|
+
- ".travis.yml"
|
113
|
+
- Gemfile
|
114
|
+
- README.md
|
115
|
+
- Rakefile
|
116
|
+
- bin/console
|
117
|
+
- bin/setup
|
118
|
+
- lib/ooxl.rb
|
119
|
+
- lib/ooxl/ooxl.rb
|
120
|
+
- lib/ooxl/util.rb
|
121
|
+
- lib/ooxl/version.rb
|
122
|
+
- lib/ooxl/xl_objects/cell.rb
|
123
|
+
- lib/ooxl/xl_objects/column.rb
|
124
|
+
- lib/ooxl/xl_objects/comments.rb
|
125
|
+
- lib/ooxl/xl_objects/row.rb
|
126
|
+
- lib/ooxl/xl_objects/sheet.rb
|
127
|
+
- lib/ooxl/xl_objects/sheet/data_validation.rb
|
128
|
+
- lib/ooxl/xl_objects/styles.rb
|
129
|
+
- lib/ooxl/xl_objects/styles/cell_style_reference.rb
|
130
|
+
- lib/ooxl/xl_objects/styles/fill.rb
|
131
|
+
- lib/ooxl/xl_objects/styles/font.rb
|
132
|
+
- lib/ooxl/xl_objects/styles/number_formatting.rb
|
133
|
+
- lib/ooxl/xl_objects/workbook.rb
|
134
|
+
- ooxml_excel.gemspec
|
135
|
+
homepage: https://github.com/halcjames/ooxml_excel
|
136
|
+
licenses: []
|
137
|
+
metadata: {}
|
138
|
+
post_install_message:
|
139
|
+
rdoc_options: []
|
140
|
+
require_paths:
|
141
|
+
- lib
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - ">="
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '0'
|
147
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '0'
|
152
|
+
requirements: []
|
153
|
+
rubyforge_project:
|
154
|
+
rubygems_version: 2.4.5.1
|
155
|
+
signing_key:
|
156
|
+
specification_version: 4
|
157
|
+
summary: OOXL Excel - Parse Excel Spreadsheets (xlsx, xlsm).
|
158
|
+
test_files: []
|