rubyXL 1.2.10 → 2.1.1
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.
- data/Gemfile +14 -10
- data/Gemfile.lock +80 -21
- data/LICENSE.txt +1 -1
- data/README.rdoc +88 -82
- data/Rakefile +7 -2
- data/VERSION +1 -1
- data/lib/rubyXL.rb +13 -7
- data/lib/rubyXL/cell.rb +108 -268
- data/lib/rubyXL/generic_storage.rb +40 -0
- data/lib/rubyXL/objects/border.rb +66 -0
- data/lib/rubyXL/objects/calculation_chain.rb +28 -0
- data/lib/rubyXL/objects/cell_style.rb +75 -0
- data/lib/rubyXL/objects/color.rb +25 -0
- data/lib/rubyXL/objects/column_range.rb +74 -0
- data/lib/rubyXL/objects/container_nodes.rb +122 -0
- data/lib/rubyXL/objects/data_validation.rb +43 -0
- data/lib/rubyXL/objects/document_properties.rb +76 -0
- data/lib/rubyXL/objects/extensions.rb +36 -0
- data/lib/rubyXL/objects/fill.rb +57 -0
- data/lib/rubyXL/objects/font.rb +111 -0
- data/lib/rubyXL/objects/formula.rb +24 -0
- data/lib/rubyXL/objects/ooxml_object.rb +295 -0
- data/lib/rubyXL/objects/reference.rb +110 -0
- data/lib/rubyXL/objects/relationships.rb +59 -0
- data/lib/rubyXL/objects/shared_strings.rb +57 -0
- data/lib/rubyXL/objects/sheet_data.rb +149 -0
- data/lib/rubyXL/objects/sheet_view.rb +71 -0
- data/lib/rubyXL/objects/stylesheet.rb +200 -0
- data/lib/rubyXL/objects/text.rb +87 -0
- data/lib/rubyXL/objects/theme.rb +64 -0
- data/lib/rubyXL/objects/workbook.rb +233 -0
- data/lib/rubyXL/objects/worksheet.rb +485 -0
- data/lib/rubyXL/parser.rb +78 -442
- data/lib/rubyXL/workbook.rb +216 -385
- data/lib/rubyXL/worksheet.rb +509 -1062
- data/lib/rubyXL/writer/content_types_writer.rb +104 -68
- data/lib/rubyXL/writer/core_writer.rb +26 -43
- data/lib/rubyXL/writer/generic_writer.rb +43 -0
- data/lib/rubyXL/writer/root_rels_writer.rb +11 -19
- data/lib/rubyXL/writer/styles_writer.rb +6 -398
- data/lib/rubyXL/writer/theme_writer.rb +321 -327
- data/lib/rubyXL/writer/workbook_writer.rb +63 -67
- data/lib/rubyXL/writer/worksheet_writer.rb +29 -218
- data/rdoc/created.rid +39 -0
- data/rdoc/fonts.css +167 -0
- data/rdoc/fonts/Lato-Light.ttf +0 -0
- data/rdoc/fonts/Lato-LightItalic.ttf +0 -0
- data/rdoc/fonts/Lato-Regular.ttf +0 -0
- data/rdoc/fonts/Lato-RegularItalic.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Bold.ttf +0 -0
- data/rdoc/fonts/SourceCodePro-Regular.ttf +0 -0
- data/rdoc/images/add.png +0 -0
- data/rdoc/images/arrow_up.png +0 -0
- data/rdoc/images/brick.png +0 -0
- data/rdoc/images/brick_link.png +0 -0
- data/rdoc/images/bug.png +0 -0
- data/rdoc/images/bullet_black.png +0 -0
- data/rdoc/images/bullet_toggle_minus.png +0 -0
- data/rdoc/images/bullet_toggle_plus.png +0 -0
- data/rdoc/images/date.png +0 -0
- data/rdoc/images/delete.png +0 -0
- data/rdoc/images/find.png +0 -0
- data/rdoc/images/loadingAnimation.gif +0 -0
- data/rdoc/images/macFFBgHack.png +0 -0
- data/rdoc/images/package.png +0 -0
- data/rdoc/images/page_green.png +0 -0
- data/rdoc/images/page_white_text.png +0 -0
- data/rdoc/images/page_white_width.png +0 -0
- data/rdoc/images/plugin.png +0 -0
- data/rdoc/images/ruby.png +0 -0
- data/rdoc/images/tag_blue.png +0 -0
- data/rdoc/images/tag_green.png +0 -0
- data/rdoc/images/transparent.png +0 -0
- data/rdoc/images/wrench.png +0 -0
- data/rdoc/images/wrench_orange.png +0 -0
- data/rdoc/images/zoom.png +0 -0
- data/rdoc/js/darkfish.js +140 -0
- data/rdoc/js/jquery.js +18 -0
- data/rdoc/js/navigation.js +142 -0
- data/rdoc/js/search.js +109 -0
- data/rdoc/js/search_index.js +1 -0
- data/rdoc/js/searcher.js +228 -0
- data/rdoc/rdoc.css +580 -0
- data/rubyXL.gemspec +90 -34
- data/spec/lib/cell_spec.rb +29 -59
- data/spec/lib/parser_spec.rb +35 -19
- data/spec/lib/reference_spec.rb +29 -0
- data/spec/lib/stylesheet_spec.rb +29 -0
- data/spec/lib/workbook_spec.rb +22 -17
- data/spec/lib/worksheet_spec.rb +47 -202
- metadata +185 -148
- data/lib/.DS_Store +0 -0
- data/lib/rubyXL/Hash.rb +0 -60
- data/lib/rubyXL/color.rb +0 -14
- data/lib/rubyXL/private_class.rb +0 -265
- data/lib/rubyXL/writer/app_writer.rb +0 -62
- data/lib/rubyXL/writer/calc_chain_writer.rb +0 -33
- data/lib/rubyXL/writer/shared_strings_writer.rb +0 -30
- data/lib/rubyXL/writer/workbook_rels_writer.rb +0 -59
- data/lib/rubyXL/zip.rb +0 -20
- data/spec/lib/hash_spec.rb +0 -28
data/lib/rubyXL/parser.rb
CHANGED
|
@@ -1,467 +1,103 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
2
|
require 'nokogiri'
|
|
3
|
-
require '
|
|
4
|
-
require
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
require 'rubyXL/generic_storage'
|
|
5
5
|
|
|
6
6
|
module RubyXL
|
|
7
7
|
|
|
8
8
|
class Parser
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
# converts cell string (such as "AA1") to matrix indices
|
|
12
|
-
def Parser.convert_to_index(cell_string)
|
|
13
|
-
index = [-1,-1]
|
|
14
|
-
if(cell_string =~ /^([A-Z]+)(\d+)$/)
|
|
15
|
-
|
|
16
|
-
one = $1
|
|
17
|
-
row = $2.to_i - 1 #-1 for 0 indexing
|
|
18
|
-
col = 0
|
|
19
|
-
i = 0
|
|
20
|
-
if @@parsed_column_hash[one].nil?
|
|
21
|
-
two = one.reverse #because of 26^i calculation
|
|
22
|
-
two.each_byte do |c|
|
|
23
|
-
int_val = c - 64 #converts A to 1
|
|
24
|
-
col += int_val * 26**(i)
|
|
25
|
-
i=i+1
|
|
26
|
-
end
|
|
27
|
-
@@parsed_column_hash[one] = col
|
|
28
|
-
else
|
|
29
|
-
col = @@parsed_column_hash[one]
|
|
30
|
-
end
|
|
31
|
-
col -= 1 #zer0 index
|
|
32
|
-
index[0] = row
|
|
33
|
-
index[1] = col
|
|
34
|
-
end
|
|
35
|
-
return index
|
|
9
|
+
def self.parse(file_path, opts = {})
|
|
10
|
+
self.new(opts).parse(file_path)
|
|
36
11
|
end
|
|
37
12
|
|
|
38
|
-
|
|
39
|
-
# data_only allows only the sheet data to be parsed, so as to speed up parsing
|
|
13
|
+
# +:data_only+ allows only the sheet data to be parsed, so as to speed up parsing
|
|
40
14
|
# However, using this option will result in date-formatted cells being interpreted as numbers
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@data_only = opts.is_a?(TrueClass)||!!opts[:data_only]
|
|
45
|
-
skip_filename_check = !!opts[:skip_filename_check]
|
|
46
|
-
|
|
47
|
-
files = Parser.decompress(file_path, skip_filename_check)
|
|
48
|
-
wb = Parser.fill_workbook(file_path, files)
|
|
49
|
-
|
|
50
|
-
if(files['sharedString'] != nil)
|
|
51
|
-
wb.num_strings = Integer(files['sharedString'].css('sst').attribute('count').value())
|
|
52
|
-
wb.size = Integer(files['sharedString'].css('sst').attribute('uniqueCount').value())
|
|
53
|
-
|
|
54
|
-
files['sharedString'].css('si').each do |node|
|
|
55
|
-
unless node.css('r').empty?
|
|
56
|
-
text = node.css('r t').children.to_a.join
|
|
57
|
-
node.children.remove
|
|
58
|
-
node << "<t xml:space=\"preserve\">#{text}</t>"
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
string_nodes = files['sharedString'].css('si t')
|
|
63
|
-
wb.shared_strings = {}
|
|
64
|
-
string_nodes.each_with_index do |node,i|
|
|
65
|
-
string = node.children.to_s
|
|
66
|
-
wb.shared_strings[i] = string
|
|
67
|
-
wb.shared_strings[string] = i
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
#styles are needed for formatting reasons as that is how dates are determined
|
|
71
|
-
styles = files['styles'].css('cellXfs xf')
|
|
72
|
-
style_hash = Hash.xml_node_to_hash(files['styles'].root)
|
|
73
|
-
fill_styles(wb,style_hash)
|
|
74
|
-
|
|
75
|
-
#will be nil if these files do not exist
|
|
76
|
-
wb.external_links = files['externalLinks']
|
|
77
|
-
wb.drawings = files['drawings']
|
|
78
|
-
wb.printer_settings = files['printerSettings']
|
|
79
|
-
wb.worksheet_rels = files['worksheetRels']
|
|
80
|
-
wb.macros = files['vbaProject']
|
|
81
|
-
|
|
82
|
-
#for each worksheet:
|
|
83
|
-
#1. find the dimensions of the data matrix
|
|
84
|
-
#2. Fill in the matrix with data from worksheet/shared_string files
|
|
85
|
-
#3. Apply styles
|
|
86
|
-
wb.worksheets.each_index do |i|
|
|
87
|
-
Parser.fill_worksheet(wb,i,files,wb.shared_strings)
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
return wb
|
|
15
|
+
def initialize(opts = {})
|
|
16
|
+
@data_only = opts.is_a?(TrueClass) || opts[:data_only]
|
|
17
|
+
@skip_filename_check = opts[:skip_filename_check]
|
|
91
18
|
end
|
|
92
19
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def Parser.fill_styles(wb,style_hash)
|
|
97
|
-
###NUM FORMATS###
|
|
98
|
-
if style_hash[:numFmts].nil?
|
|
99
|
-
style_hash[:numFmts] = {:attributes => {:count => 0}, :numFmt => []}
|
|
100
|
-
elsif style_hash[:numFmts][:attributes][:count]==1
|
|
101
|
-
style_hash[:numFmts][:numFmt] = [style_hash[:numFmts][:numFmt]]
|
|
102
|
-
end
|
|
103
|
-
wb.num_fmts = style_hash[:numFmts]
|
|
104
|
-
|
|
105
|
-
###FONTS###
|
|
106
|
-
wb.fonts = {}
|
|
107
|
-
if style_hash[:fonts][:attributes][:count]==1
|
|
108
|
-
style_hash[:fonts][:font] = [style_hash[:fonts][:font]]
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
style_hash[:fonts][:font].each_with_index do |f,i|
|
|
112
|
-
wb.fonts[i.to_s] = {:font=>f,:count=>0}
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
###FILLS###
|
|
116
|
-
wb.fills = {}
|
|
117
|
-
if style_hash[:fills][:attributes][:count]==1
|
|
118
|
-
style_hash[:fills][:fill] = [style_hash[:fills][:fill]]
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
style_hash[:fills][:fill].each_with_index do |f,i|
|
|
122
|
-
wb.fills[i.to_s] = {:fill=>f,:count=>0}
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
###BORDERS###
|
|
126
|
-
wb.borders = {}
|
|
127
|
-
if style_hash[:borders][:attributes][:count] == 1
|
|
128
|
-
style_hash[:borders][:border] = [style_hash[:borders][:border]]
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
style_hash[:borders][:border].each_with_index do |b,i|
|
|
132
|
-
wb.borders[i.to_s] = {:border=>b, :count=>0}
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
wb.cell_style_xfs = style_hash[:cellStyleXfs]
|
|
136
|
-
wb.cell_xfs = style_hash[:cellXfs]
|
|
137
|
-
wb.cell_styles = style_hash[:cellStyles]
|
|
138
|
-
|
|
139
|
-
wb.colors = style_hash[:colors]
|
|
140
|
-
|
|
141
|
-
#fills out count information for each font, fill, and border
|
|
142
|
-
if wb.cell_xfs[:xf].is_a?(::Hash)
|
|
143
|
-
wb.cell_xfs[:xf] = [wb.cell_xfs[:xf]]
|
|
144
|
-
end
|
|
145
|
-
wb.cell_xfs[:xf].each do |style|
|
|
146
|
-
id = style[:attributes][:fontId].to_s
|
|
147
|
-
unless id.nil?
|
|
148
|
-
wb.fonts[id][:count] += 1
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
id = style[:attributes][:fillId].to_s
|
|
152
|
-
unless id.nil?
|
|
153
|
-
wb.fills[id][:count] += 1
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
id = style[:attributes][:borderId].to_s
|
|
157
|
-
unless id.nil?
|
|
158
|
-
wb.borders[id][:count] += 1
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# i is the sheet number
|
|
164
|
-
# files is the hash which includes information for each worksheet
|
|
165
|
-
# shared_strings has group of indexed strings which the cells reference
|
|
166
|
-
def Parser.fill_worksheet(wb,i,files,shared_strings)
|
|
167
|
-
wb.worksheets[i] = Parser.create_matrix(wb, i, files)
|
|
168
|
-
j = i+1
|
|
169
|
-
|
|
170
|
-
namespaces = files[j].root.namespaces()
|
|
171
|
-
unless @data_only
|
|
172
|
-
sheet_views_node= files[j].xpath('/xmlns:worksheet/xmlns:sheetViews[xmlns:sheetView]',namespaces).first
|
|
173
|
-
wb.worksheets[i].sheet_view = Hash.xml_node_to_hash(sheet_views_node)[:sheetView]
|
|
174
|
-
|
|
175
|
-
##col styles##
|
|
176
|
-
cols_node_set = files[j].xpath('/xmlns:worksheet/xmlns:cols',namespaces)
|
|
177
|
-
unless cols_node_set.empty?
|
|
178
|
-
wb.worksheets[i].cols= Hash.xml_node_to_hash(cols_node_set.first)[:col]
|
|
179
|
-
end
|
|
180
|
-
##end col styles##
|
|
181
|
-
|
|
182
|
-
##merge_cells##
|
|
183
|
-
merge_cells_node = files[j].xpath('/xmlns:worksheet/xmlns:mergeCells[xmlns:mergeCell]',namespaces)
|
|
184
|
-
unless merge_cells_node.empty?
|
|
185
|
-
wb.worksheets[i].merged_cells = Hash.xml_node_to_hash(merge_cells_node.first)[:mergeCell]
|
|
186
|
-
end
|
|
187
|
-
##end merge_cells##
|
|
188
|
-
|
|
189
|
-
##sheet_view pane##
|
|
190
|
-
pane_data = wb.worksheets[i].sheet_view[:pane]
|
|
191
|
-
wb.worksheets[i].pane = pane_data
|
|
192
|
-
##end sheet_view pane##
|
|
193
|
-
|
|
194
|
-
##data_validation##
|
|
195
|
-
data_validations_node = files[j].xpath('/xmlns:worksheet/xmlns:dataValidations[xmlns:dataValidation]',namespaces)
|
|
196
|
-
unless data_validations_node.empty?
|
|
197
|
-
wb.worksheets[i].validations = Hash.xml_node_to_hash(data_validations_node.first)[:dataValidation]
|
|
198
|
-
else
|
|
199
|
-
wb.worksheets[i].validations=nil
|
|
200
|
-
end
|
|
201
|
-
##end data_validation##
|
|
202
|
-
|
|
203
|
-
#extLst
|
|
204
|
-
ext_list_node=files[j].xpath('/xmlns:worksheet/xmlns:extLst',namespaces)
|
|
205
|
-
unless ext_list_node.empty?
|
|
206
|
-
wb.worksheets[i].extLst = Hash.xml_node_to_hash(ext_list_node.first)
|
|
207
|
-
else
|
|
208
|
-
wb.worksheets[i].extLst=nil
|
|
209
|
-
end
|
|
210
|
-
#extLst
|
|
211
|
-
|
|
212
|
-
##legacy drawing##
|
|
213
|
-
legacy_drawing_node = files[j].xpath('/xmlns:worksheet/xmlns:legacyDrawing',namespaces)
|
|
214
|
-
unless legacy_drawing_node.empty?
|
|
215
|
-
wb.worksheets[i].legacy_drawing = Hash.xml_node_to_hash(legacy_drawing_node.first)
|
|
216
|
-
else
|
|
217
|
-
wb.worksheets[i].legacy_drawing = nil
|
|
218
|
-
end
|
|
219
|
-
##end legacy drawing
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
row_data = files[j].xpath('/xmlns:worksheet/xmlns:sheetData/xmlns:row[xmlns:c[xmlns:v]]',namespaces)
|
|
224
|
-
row_data.each do |row|
|
|
225
|
-
unless @data_only
|
|
226
|
-
##row styles##
|
|
227
|
-
row_style = '0'
|
|
228
|
-
row_attributes = row.attributes
|
|
229
|
-
unless row_attributes['s'].nil?
|
|
230
|
-
row_style = row_attributes['s'].value
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
wb.worksheets[i].row_styles[row_attributes['r'].content] = { :style => row_style }
|
|
234
|
-
|
|
235
|
-
if !row_attributes['ht'].nil? && (!row_attributes['ht'].content.nil? || row_attributes['ht'].content.strip != "" )
|
|
236
|
-
wb.worksheets[i].change_row_height(Integer(row_attributes['r'].content)-1,
|
|
237
|
-
Float(row_attributes['ht'].content))
|
|
238
|
-
end
|
|
239
|
-
##end row styles##
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
unless @data_only
|
|
243
|
-
c_row = row.search('./xmlns:c')
|
|
244
|
-
else
|
|
245
|
-
c_row = row.search('./xmlns:c[xmlns:v[text()]]')
|
|
246
|
-
end
|
|
247
|
-
c_row.each do |value|
|
|
248
|
-
#attributes is from the excel cell(c) and is basically location information and style and type
|
|
249
|
-
value_attributes= value.attributes
|
|
250
|
-
# r attribute contains the location like A1
|
|
251
|
-
cell_index = Parser.convert_to_index(value_attributes['r'].content)
|
|
252
|
-
style_index = 0
|
|
253
|
-
|
|
254
|
-
# t is optional and contains the type of the cell
|
|
255
|
-
data_type = value_attributes['t'].content if value_attributes['t']
|
|
256
|
-
element_hash ={}
|
|
257
|
-
value.children.each do |node|
|
|
258
|
-
element_hash["#{node.name()}_element"]=node
|
|
259
|
-
end
|
|
260
|
-
# v is the value element that is part of the cell
|
|
261
|
-
if element_hash["v_element"]
|
|
262
|
-
v_element_content = element_hash["v_element"].content
|
|
263
|
-
else
|
|
264
|
-
v_element_content=""
|
|
265
|
-
end
|
|
266
|
-
if v_element_content =="" #no data
|
|
267
|
-
cell_data = nil
|
|
268
|
-
elsif data_type == 's' #shared string
|
|
269
|
-
str_index = Integer(v_element_content)
|
|
270
|
-
cell_data = shared_strings[str_index].to_s
|
|
271
|
-
elsif data_type=='str' #raw string
|
|
272
|
-
cell_data = v_element_content
|
|
273
|
-
elsif data_type=='e' #error
|
|
274
|
-
cell_data = v_element_content
|
|
275
|
-
else# (value.css('v').to_s != "") && (value.css('v').children.to_s != "") #is number
|
|
276
|
-
data_type = ''
|
|
277
|
-
if(v_element_content =~ /\./ or v_element_content =~ /\d+e\-?\d+/i) #is float
|
|
278
|
-
cell_data = Float(v_element_content)
|
|
279
|
-
else
|
|
280
|
-
cell_data = Integer(v_element_content)
|
|
281
|
-
end
|
|
282
|
-
end
|
|
283
|
-
# f is the formula element
|
|
284
|
-
cell_formula = nil
|
|
285
|
-
fmla_css = element_hash["f_element"]
|
|
286
|
-
if fmla_css && fmla_css.content
|
|
287
|
-
fmla_css_content= fmla_css.content
|
|
288
|
-
if(fmla_css_content != "")
|
|
289
|
-
cell_formula = fmla_css_content
|
|
290
|
-
cell_formula_attr = {}
|
|
291
|
-
fmla_css_attributes = fmla_css.attributes
|
|
292
|
-
cell_formula_attr['t'] = fmla_css_attributes['t'].content if fmla_css_attributes['t']
|
|
293
|
-
cell_formula_attr['ref'] = fmla_css_attributes['ref'].content if fmla_css_attributes['ref']
|
|
294
|
-
cell_formula_attr['si'] = fmla_css_attributes['si'].content if fmla_css_attributes['si']
|
|
295
|
-
end
|
|
296
|
-
end
|
|
297
|
-
|
|
298
|
-
style_index = value['s'].to_i #nil goes to 0 (default)
|
|
299
|
-
|
|
300
|
-
wb.worksheets[i].sheet_data[cell_index[0]][cell_index[1]] =
|
|
301
|
-
Cell.new(wb.worksheets[i],cell_index[0],cell_index[1],cell_data,cell_formula,
|
|
302
|
-
data_type,style_index,cell_formula_attr)
|
|
303
|
-
cell = wb.worksheets[i].sheet_data[cell_index[0]][cell_index[1]]
|
|
304
|
-
end
|
|
305
|
-
end
|
|
20
|
+
def data_only
|
|
21
|
+
@data_only = true
|
|
22
|
+
self
|
|
306
23
|
end
|
|
307
24
|
|
|
308
|
-
def
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
dir_path = $1.to_s
|
|
312
|
-
else
|
|
313
|
-
if skip_filename_check
|
|
314
|
-
dir_path = file_path
|
|
315
|
-
else
|
|
316
|
-
raise 'Not .xlsx or .xlsm excel file'
|
|
317
|
-
end
|
|
318
|
-
end
|
|
25
|
+
def parse(xl_file_path, opts = {})
|
|
26
|
+
raise 'Not .xlsx or .xlsm excel file' unless @skip_filename_check ||
|
|
27
|
+
%w{.xlsx .xlsm}.include?(File.extname(xl_file_path))
|
|
319
28
|
|
|
320
|
-
dir_path = File.join(File.dirname(
|
|
321
|
-
#copies excel file to zip file in same directory
|
|
322
|
-
zip_path = dir_path + '.zip'
|
|
29
|
+
dir_path = File.join(File.dirname(xl_file_path), Dir::Tmpname.make_tmpname(['rubyXL', '.tmp'], nil))
|
|
323
30
|
|
|
324
|
-
|
|
31
|
+
::Zip::File.open(xl_file_path) { |zip_file|
|
|
32
|
+
zip_file.each { |f|
|
|
33
|
+
fpath = File.join(dir_path, f.name)
|
|
34
|
+
FileUtils.mkdir_p(File.dirname(fpath))
|
|
35
|
+
zip_file.extract(f, fpath) unless File.exist?(fpath)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
325
38
|
|
|
326
|
-
|
|
327
|
-
File.delete(zip_path)
|
|
39
|
+
workbook_file = Nokogiri::XML.parse(File.open(File.join(dir_path, 'xl', 'workbook.xml'), 'r'))
|
|
328
40
|
|
|
329
|
-
|
|
41
|
+
wb = RubyXL::Workbook.parse(workbook_file)
|
|
42
|
+
wb.filepath = xl_file_path
|
|
330
43
|
|
|
331
|
-
|
|
332
|
-
files['core'] = Nokogiri::XML.parse(File.open(File.join(dir_path,'docProps','core.xml'),'r'))
|
|
333
|
-
|
|
334
|
-
files['workbook'] = Nokogiri::XML.parse(File.open(File.join(dir_path,'xl','workbook.xml'),'r'))
|
|
335
|
-
|
|
336
|
-
if(File.exist?(File.join(dir_path,'xl','sharedStrings.xml')))
|
|
337
|
-
files['sharedString'] = Nokogiri::XML.parse(File.open(File.join(dir_path,'xl','sharedStrings.xml'),'r'))
|
|
338
|
-
end
|
|
44
|
+
wb.relationship_container = RubyXL::WorkbookRelationships.parse_file(dir_path)
|
|
339
45
|
|
|
340
46
|
unless @data_only
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
dir = Dir.new(drawings_path).entries.reject {|f| [".", "..", ".DS_Store"].include? f}
|
|
366
|
-
dir.each_with_index do |draw,i|
|
|
367
|
-
files['drawings'][i+1] = File.read(File.join(drawings_path,draw))
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
if File.directory?(File.join(dir_path,'xl','printerSettings'))
|
|
372
|
-
files['printerSettings'] = {}
|
|
373
|
-
printer_path = File.join(dir_path,'xl','printerSettings')
|
|
374
|
-
FileUtils.mkdir_p(printer_path)
|
|
375
|
-
dir = Dir.new(printer_path).entries.reject {|f| [".","..",".DS_Store"].include? f}
|
|
376
|
-
|
|
377
|
-
dir.each_with_index do |print, i|
|
|
378
|
-
files['printerSettings'][i+1] = File.open(File.join(printer_path,print), 'rb').read
|
|
379
|
-
end
|
|
380
|
-
end
|
|
381
|
-
|
|
382
|
-
if File.directory?(File.join(dir_path,"xl",'worksheets','_rels'))
|
|
383
|
-
files['worksheetRels'] = {}
|
|
384
|
-
worksheet_rels_path = File.join(dir_path,'xl','worksheets','_rels')
|
|
385
|
-
FileUtils.mkdir_p(worksheet_rels_path)
|
|
386
|
-
dir = Dir.new(worksheet_rels_path).entries.reject {|f| [".","..",".DS_Store"].include? f}
|
|
387
|
-
dir.each_with_index do |rel, i|
|
|
388
|
-
files['worksheetRels'][i+1] = File.read(File.join(worksheet_rels_path,rel))
|
|
389
|
-
end
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
if File.exist?(File.join(dir_path,'xl','vbaProject.bin'))
|
|
393
|
-
files['vbaProject'] = File.open(File.join(dir_path,"xl","vbaProject.bin"),'rb').read
|
|
394
|
-
end
|
|
395
|
-
end
|
|
396
|
-
files['styles'] = Nokogiri::XML.parse(File.open(File.join(dir_path,'xl','styles.xml'),'r'))
|
|
397
|
-
@num_sheets = files['workbook'].css('sheets').children.size
|
|
398
|
-
@num_sheets = Integer(@num_sheets)
|
|
47
|
+
wb.media = RubyXL::GenericStorage.new(File.join('xl', 'media')).binary.load_dir(dir_path)
|
|
48
|
+
wb.external_links = RubyXL::GenericStorage.new(File.join('xl', 'externalLinks')).load_dir(dir_path)
|
|
49
|
+
wb.external_links_rels = RubyXL::GenericStorage.new(File.join('xl', 'externalLinks', '_rels')).load_dir(dir_path)
|
|
50
|
+
wb.drawings = RubyXL::GenericStorage.new(File.join('xl', 'drawings')).load_dir(dir_path)
|
|
51
|
+
wb.drawings_rels = RubyXL::GenericStorage.new(File.join('xl', 'drawings', '_rels')).load_dir(dir_path)
|
|
52
|
+
wb.charts = RubyXL::GenericStorage.new(File.join('xl', 'charts')).load_dir(dir_path)
|
|
53
|
+
wb.chart_rels = RubyXL::GenericStorage.new(File.join('xl', 'charts', '_rels')).load_dir(dir_path)
|
|
54
|
+
wb.printer_settings = RubyXL::GenericStorage.new(File.join('xl', 'printerSettings')).binary.load_dir(dir_path)
|
|
55
|
+
wb.worksheet_rels = RubyXL::GenericStorage.new(File.join('xl', 'worksheets', '_rels')).load_dir(dir_path)
|
|
56
|
+
wb.macros = RubyXL::GenericStorage.new('xl').binary.load_file(dir_path, 'vbaProject.bin')
|
|
57
|
+
wb.theme = RubyXL::GenericStorage.new(File.join('xl', 'theme')).load_file(dir_path, 'theme1.xml')
|
|
58
|
+
|
|
59
|
+
core_file = Nokogiri::XML.parse(File.open(File.join(dir_path, 'docProps', 'core.xml'), 'r'))
|
|
60
|
+
wb.creator = core_file.css('dc|creator').children.to_s
|
|
61
|
+
wb.modifier = core_file.css('cp|last_modified_by').children.to_s
|
|
62
|
+
wb.created_at = core_file.css('dcterms|created').children.to_s
|
|
63
|
+
wb.modified_at = core_file.css('dcterms|modified').children.to_s
|
|
64
|
+
|
|
65
|
+
wb.document_properties = RubyXL::DocumentProperties.parse_file(dir_path)
|
|
66
|
+
wb.calculation_chain = RubyXL::CalculationChain.parse_file(dir_path)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
wb.shared_strings_container = RubyXL::SharedStringsTable.parse_file(dir_path)
|
|
70
|
+
wb.stylesheet = RubyXL::Stylesheet.parse_file(dir_path)
|
|
399
71
|
|
|
400
|
-
#
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
wb.
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
wb.shared_strings_XML = files['sharedString'].to_s
|
|
428
|
-
wb.defined_names = files['workbook'].css('definedNames').to_s
|
|
429
|
-
wb.date1904 = files['workbook'].css('workbookPr').attribute('date1904').to_s == '1'
|
|
430
|
-
|
|
431
|
-
wb.worksheets = Array.new(@num_sheets) #array of Worksheet objs
|
|
432
|
-
wb
|
|
433
|
-
end
|
|
434
|
-
|
|
435
|
-
#sheet_names, dimensions
|
|
436
|
-
def Parser.create_matrix(wb,i, files)
|
|
437
|
-
sheet_names = files['app'].css('TitlesOfParts vt|vector vt|lpstr').children
|
|
438
|
-
sheet = Worksheet.new(wb,sheet_names[i].to_s,[])
|
|
439
|
-
|
|
440
|
-
dimensions = files[i+1].css('dimension').attribute('ref').to_s
|
|
441
|
-
if(dimensions =~ /^([A-Z]+\d+:)?([A-Z]+\d+)$/)
|
|
442
|
-
index = convert_to_index($2)
|
|
443
|
-
|
|
444
|
-
rows = index[0]+1
|
|
445
|
-
cols = index[1]+1
|
|
446
|
-
|
|
447
|
-
#creates matrix filled with nils
|
|
448
|
-
rows.times {sheet.sheet_data << Array.new(cols)}
|
|
449
|
-
else
|
|
450
|
-
raise 'invalid file'
|
|
451
|
-
end
|
|
452
|
-
sheet
|
|
453
|
-
end
|
|
454
|
-
|
|
455
|
-
def Parser.safe_filename(name, allow_mb_chars=false)
|
|
456
|
-
# "\w" represents [0-9A-Za-z_] plus any multi-byte char
|
|
457
|
-
regexp = allow_mb_chars ? /[^\w]/ : /[^0-9a-zA-Z\_]/
|
|
458
|
-
name.gsub(regexp, "_")
|
|
459
|
-
end
|
|
72
|
+
#fills out count information for each font, fill, and border
|
|
73
|
+
wb.cell_xfs.each { |style|
|
|
74
|
+
id = style.font_id
|
|
75
|
+
wb.fonts[id].count += 1 #unless id.nil?
|
|
76
|
+
|
|
77
|
+
id = style.fill_id
|
|
78
|
+
wb.fills[id].count += 1 #unless id.nil?
|
|
79
|
+
|
|
80
|
+
id = style.border_id
|
|
81
|
+
wb.borders[id].count += 1 #unless id.nil?
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
wb.worksheet_container.sheets.each_with_index { |sheet, i|
|
|
85
|
+
sheet_file_path = wb.relationship_container.find_by_rid(sheet.r_id).target
|
|
86
|
+
worksheet = RubyXL::Worksheet.parse(File.open(File.join(dir_path, 'xl', sheet_file_path)))
|
|
87
|
+
worksheet.sheet_data.rows.each { |r|
|
|
88
|
+
next if r.nil?
|
|
89
|
+
r.worksheet = worksheet
|
|
90
|
+
r.cells.each { |c| c.worksheet = worksheet unless c.nil? }
|
|
91
|
+
}
|
|
92
|
+
worksheet.workbook = wb
|
|
93
|
+
worksheet.sheet_name = sheet.name
|
|
94
|
+
worksheet.sheet_id = sheet.sheet_id
|
|
95
|
+
wb.worksheets[i] = worksheet
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
FileUtils.remove_entry_secure(dir_path)
|
|
460
99
|
|
|
461
|
-
|
|
462
|
-
def Parser.make_safe_name(name, allow_mb_chars=false)
|
|
463
|
-
ext = safe_filename(File.extname(name), allow_mb_chars).gsub(/^_/, '.')
|
|
464
|
-
"#{safe_filename(name.gsub(ext, ""), allow_mb_chars)}#{ext}".gsub(/\(/, '_').gsub(/\)/, '_').gsub(/__+/, '_').gsub(/^_/, '').gsub(/_$/, '')
|
|
100
|
+
return wb
|
|
465
101
|
end
|
|
466
102
|
|
|
467
103
|
end
|