rspreadsheet 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +9 -4
- data/DEVEL_BLOG.md +1 -0
- data/GUIDE.md +4 -1
- data/lib/rspreadsheet/cell.rb +64 -178
- data/lib/rspreadsheet/cell_format.rb +203 -0
- data/lib/rspreadsheet/tools.rb +6 -2
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/workbook.rb +12 -10
- data/lib/rspreadsheet/xml_tied_array.rb +3 -1
- data/rspreadsheet.gemspec +4 -4
- data/spec/cell_spec.rb +38 -8
- data/spec/testfile1.ods +0 -0
- data/spec/tools_spec.rb +1 -1
- metadata +46 -5
- data/w/test.rb +0 -8
- data/w/time.ods +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3845679733a12aaed33574dc981b22d49d297fe
|
4
|
+
data.tar.gz: c7f151f9ed92317b006eec3e2fc323514b43a5f4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0307c5a79b0e9aebc2ce1cfc56b6f8445542e4cc8dab661a92398c8c7e4debeaa55ed0f6dce7a7a9a47cbda6d53bdf24cc19f95a441e94b4a3458e4c16d947eb
|
7
|
+
data.tar.gz: 25acbd10e229976cd2b2112dbc35dbf2d4e8610302928df753a7a1110c64f23a0f052362684188fb33dba9341ab402075a0ed5e2b7635e90cc574d46c3b13ba2
|
data/.travis.yml
CHANGED
@@ -7,12 +7,17 @@ rvm:
|
|
7
7
|
- 2.3.3
|
8
8
|
- 2.4.0
|
9
9
|
script: 'bundle exec rake'
|
10
|
-
|
11
|
-
|
12
|
-
-
|
10
|
+
matrix:
|
11
|
+
include:
|
12
|
+
- rvm: 2.4.0
|
13
|
+
before_install:
|
14
|
+
- gem uninstall libxml-ruby
|
15
|
+
- sudo apt-get install libxml2
|
16
|
+
- gem update bundler
|
17
|
+
|
13
18
|
notifications:
|
14
19
|
email:
|
15
20
|
recipients:
|
16
21
|
- jatesins@ncsu.edu
|
17
22
|
on_failure: change
|
18
|
-
on_success: never
|
23
|
+
on_success: never
|
data/DEVEL_BLOG.md
CHANGED
@@ -9,6 +9,7 @@ After you have cloned the source run `bundle` command in gem directory to instal
|
|
9
9
|
|
10
10
|
## Ideas/wishlist
|
11
11
|
|
12
|
+
* make it configurable whether Time or DateTime will be used.
|
12
13
|
* In future inntroduce syntax like `sheet.range('C3:E4')` for mass operations.
|
13
14
|
* Trying to make row Enumerable - perhaps skipping empty or undefined cells.
|
14
15
|
* Maybe insted two syntaxes for accessing cell, we make both of them do the same and return Proxy object which would act either as value or cell.
|
data/GUIDE.md
CHANGED
@@ -40,6 +40,9 @@ The file needs to be saved after doing changes.
|
|
40
40
|
@workbook.save('new_filename.ods') # changes filename and saves
|
41
41
|
@workbook.save(any_io_object) # file can be saved to any IO like object as well
|
42
42
|
````
|
43
|
+
### Date and Time
|
44
|
+
OpenDocument and ruby have different models of date, time and datetime. Ruby containg three different objects. Time and DateTime cover all cases, Date covers dates only. OpenDocument distinguishes two groups - time of a day (time) and everything else (date). To simplify things a little we return cell values containg time of day as Time object and cell values containg datetime of date as DateTime. I am aware that this is very arbitrary choice, but it is very practical. This way and to some extend the types of values from OpenDocument are preserved when read from files, beeing acted upon and written back to spreadshhet.
|
45
|
+
|
43
46
|
|
44
47
|
## Examples
|
45
48
|
|
@@ -47,7 +50,7 @@ The file needs to be saved after doing changes.
|
|
47
50
|
* [extended examples](https://gist.github.com/gorn/b432e6a69e82628349e6) of lots of alternative syntax
|
48
51
|
|
49
52
|
## Conventions
|
50
|
-
* **all indexes are 1-based**. This applies to rows, cells cordinates, and all array like structures like list od worksheets etc. Spreadsheet world is 1-based, ruby is 0-based
|
53
|
+
* **all indexes are 1-based**. This applies to rows, cells cordinates, and all array like structures like list od worksheets etc. Spreadsheet world is 1-based, ruby is 0-based so I had to make a decision. I intend to make an global option for this, but in early stage I need to keep things simple.
|
51
54
|
* with numeric coordinates row always comes before col as in (row,col)
|
52
55
|
* with alphanumerical col always comes before row as in F12
|
53
56
|
* Shorter syntax worksheet[x,y] returns value, longer syntax worksheet.cell(x,y) return cell objects. This allows to work conviniently with values using short syntax and access the cell object if needed (to access formatting for example).
|
data/lib/rspreadsheet/cell.rb
CHANGED
@@ -5,14 +5,17 @@
|
|
5
5
|
require 'andand'
|
6
6
|
require 'rspreadsheet/xml_tied_item'
|
7
7
|
require 'date'
|
8
|
-
require 'time'
|
8
|
+
require 'time' # extended functions for time like Time.strptime
|
9
9
|
require 'bigdecimal'
|
10
10
|
require 'bigdecimal/util' # for to_d method
|
11
11
|
require 'helpers/class_extensions'
|
12
|
+
require 'rspreadsheet/cell_format' # CellFormat and Border classes
|
12
13
|
|
13
14
|
module Rspreadsheet
|
14
15
|
using ClassExtensions if RUBY_VERSION > '2.1'
|
15
16
|
|
17
|
+
StartOfEpoch = Time.new(1899,12,30,0,0,0,0)
|
18
|
+
|
16
19
|
###
|
17
20
|
# Represents a cell in spreadsheet which has coordinates, contains value, formula and can be formated.
|
18
21
|
# You can get this object like this (suppose that @worksheet contains {Rspreadsheet::Worksheet} object)
|
@@ -20,15 +23,14 @@ using ClassExtensions if RUBY_VERSION > '2.1'
|
|
20
23
|
# @worksheet.cells(5,2)
|
21
24
|
#
|
22
25
|
# Note that when using syntax like `@worksheet[5,2]` or `@worksheet.B5` you won't get this object, but rather the value of the cell.
|
23
|
-
# More precisely it is equvalient to @worksheet.cells(5,2).value.
|
26
|
+
# More precisely it is equvalient to @worksheet.cells(5,2).value. Brief overview can be faound at [README]
|
24
27
|
#
|
25
28
|
|
26
29
|
class Cell < XMLTiedItem
|
30
|
+
# RSpreadsheet::Worksheet in which the cell is contained.
|
27
31
|
attr_accessor :worksheet
|
32
|
+
# Row index of a cell. If you want to access the row object, see #row.
|
28
33
|
attr_reader :rowi
|
29
|
-
InternalDateFormat = '%Y-%m-%d'
|
30
|
-
InternalTimeFormat = 'PT%HH%MM%SS'
|
31
|
-
# InternalTimeFormat = 'PT%HH%MM%SS,%LS'
|
32
34
|
|
33
35
|
# @!group XMLTiedItem related methods and extensions
|
34
36
|
def xml_options; {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'} end
|
@@ -64,8 +66,8 @@ class Cell < XMLTiedItem
|
|
64
66
|
when gt == nil then nil
|
65
67
|
when gt == Float then xmlnode.attributes['value'].to_f
|
66
68
|
when gt == String then xmlnode.elements.first.andand.content.to_s
|
67
|
-
when gt ==
|
68
|
-
when gt ==
|
69
|
+
when gt == :datetime then datetime_value
|
70
|
+
when gt == :time then time_value
|
69
71
|
when gt == :percentage then xmlnode.attributes['value'].to_f
|
70
72
|
when gt == :currency then xmlnode.attributes['value'].to_d
|
71
73
|
end
|
@@ -75,6 +77,47 @@ class Cell < XMLTiedItem
|
|
75
77
|
raise "Unknown cell mode #{self.mode}"
|
76
78
|
end
|
77
79
|
end
|
80
|
+
|
81
|
+
## according to http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#__RefHeading__1417674_253892949
|
82
|
+
## the value od time-value is in a "duration" format defined here https://www.w3.org/TR/xmlschema-2/#duration
|
83
|
+
## this method converts the time-value to Time object. Note that it does not check if the cell is in time-value
|
84
|
+
## or not, this is the responibility of caller. However beware that specification does not specify how the time
|
85
|
+
## should be interpreted. By observing LibreOffice behaviour, I have found these options
|
86
|
+
## 1. "Time only cell" has time is stored as PT16H22M35S (16:22:35) where the duration is duration from midnight.
|
87
|
+
## Because ruby does NOT have TimeOfDay type we need to represent that as DateTime. I have chosen 1899-12-30 00:00:00 as
|
88
|
+
## StartOfEpoch time, because it plays well with case 2.
|
89
|
+
## 2. "DateTime converted to Time only cell" has time stored as PT923451H33M00.000000168S (15:33:00 with date part 2005-05-05
|
90
|
+
## before conversion to time only). It is strange format which seems to have hours meaning number of hours after 1899-12-30 00:00:00
|
91
|
+
##
|
92
|
+
## Returns time-value of the cell. It does not check if cell has or should have this value, it is responibility of caller to do so.
|
93
|
+
def time_value
|
94
|
+
Cell.parse_time_value(xmlnode.attributes['time-value'].to_s)
|
95
|
+
end
|
96
|
+
def self.parse_time_value(svalue)
|
97
|
+
if (m = /^PT((?<hours>[0-9]+)H)?((?<minutes>[0-9]+)M)?((?<seconds>[0-9]+(\.[0-9]+)?)S)$/.match(svalue.delete(' ')))
|
98
|
+
# time was parsed manually
|
99
|
+
(StartOfEpoch + m[:hours].to_i*60*60 + m[:minutes].to_i*60 + m[:seconds].to_f.round(5))
|
100
|
+
#BASTL: Rounding is here because LibreOffice adds some fractions of seconds randomly
|
101
|
+
else
|
102
|
+
begin
|
103
|
+
Time.strptime(svalue, InternalTimeFormat)
|
104
|
+
rescue
|
105
|
+
Time.parse(svalue) # maybe add defaults for year-mont-day
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
def datetime_value
|
110
|
+
vs = xmlnode.attributes['date-value'].to_s
|
111
|
+
begin
|
112
|
+
DateTime.strptime(vs, InternalDateTimeFormat)
|
113
|
+
rescue
|
114
|
+
begin
|
115
|
+
DateTime.strptime(vs, InternalDateFormat)
|
116
|
+
rescue
|
117
|
+
DateTime.parse(vs)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
78
121
|
def value=(avalue)
|
79
122
|
detach_if_needed
|
80
123
|
if self.mode == :regular
|
@@ -90,13 +133,13 @@ class Cell < XMLTiedItem
|
|
90
133
|
remove_all_value_attributes_and_content(xmlnode)
|
91
134
|
set_type_attribute('string')
|
92
135
|
xmlnode << Tools.prepare_ns_node('text','p', avalue.to_s)
|
93
|
-
when gt ==
|
136
|
+
when gt == :datetime then
|
94
137
|
remove_all_value_attributes_and_content(xmlnode)
|
95
138
|
set_type_attribute('date')
|
96
|
-
avalue = avalue.strftime(
|
139
|
+
avalue = avalue.strftime(InternalDateTimeFormat)
|
97
140
|
Tools.set_ns_attribute(xmlnode,'office','date-value', avalue)
|
98
141
|
xmlnode << Tools.prepare_ns_node('text','p', avalue)
|
99
|
-
when gt ==
|
142
|
+
when gt == :time then
|
100
143
|
remove_all_value_attributes_and_content(xmlnode)
|
101
144
|
set_type_attribute('time')
|
102
145
|
Tools.set_ns_attribute(xmlnode,'office','time-value', avalue.strftime(InternalTimeFormat))
|
@@ -133,8 +176,8 @@ class Cell < XMLTiedItem
|
|
133
176
|
case
|
134
177
|
when gct == Float then :float
|
135
178
|
when gct == String then :string
|
136
|
-
when gct ==
|
137
|
-
when gct ==
|
179
|
+
when gct == :datetime then :datetime
|
180
|
+
when gct == :time then :time
|
138
181
|
when gct == :percentage then :percentage
|
139
182
|
when gct == :unassigned then :unassigned
|
140
183
|
when gct == :currency then :currency
|
@@ -147,8 +190,8 @@ class Cell < XMLTiedItem
|
|
147
190
|
# try guessing by value
|
148
191
|
valueguess = case avalue
|
149
192
|
when Numeric then Float
|
150
|
-
when Time then
|
151
|
-
when Date then
|
193
|
+
when Time then :time
|
194
|
+
when Date, DateTime then :datetime
|
152
195
|
when String,nil then nil
|
153
196
|
else nil
|
154
197
|
end
|
@@ -160,8 +203,8 @@ class Cell < XMLTiedItem
|
|
160
203
|
when nil then nil
|
161
204
|
when 'float' then Float
|
162
205
|
when 'string' then String
|
163
|
-
when 'time' then
|
164
|
-
when 'date' then
|
206
|
+
when 'time' then :time
|
207
|
+
when 'date' then :datetime
|
165
208
|
when 'percentage' then :percentage
|
166
209
|
when 'N/A' then :unassigned
|
167
210
|
when 'currency' then :currency
|
@@ -231,167 +274,11 @@ class Cell < XMLTiedItem
|
|
231
274
|
def border_bottom; format.border_bottom end
|
232
275
|
def border_left; format.border_left end
|
233
276
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
def initialize(cell)
|
240
|
-
@cell = cell
|
241
|
-
end
|
242
|
-
def cellnode; @cell.xmlnode end
|
243
|
-
|
244
|
-
# text style attribute readers
|
245
|
-
def bold; get_text_style_node_attribute('font-weight') == 'bold' end
|
246
|
-
alias :bold? :bold
|
247
|
-
def italic; get_text_style_node_attribute('font-style') == 'italic' end
|
248
|
-
def color; get_text_style_node_attribute('color') end
|
249
|
-
def font_size; get_text_style_node_attribute('font-size') end
|
250
|
-
def get_text_style_node_attribute(attribute_name)
|
251
|
-
text_style_node.nil? ? nil : Tools.get_ns_attribute_value(text_style_node,'fo',attribute_name)
|
252
|
-
end
|
253
|
-
def background_color; get_cell_style_node_attribute('background-color') end
|
254
|
-
def get_cell_style_node_attribute(attribute_name)
|
255
|
-
cell_style_node.nil? ? nil : Tools.get_ns_attribute_value(cell_style_node,'fo',attribute_name)
|
256
|
-
end
|
257
|
-
|
258
|
-
# text style attribute writers
|
259
|
-
def bold=(value); set_text_style_node_attribute('font-weight', value ? 'bold' : 'normal') end
|
260
|
-
def italic=(value); set_text_style_node_attribute('font-style', value ? 'italic' : 'normal') end
|
261
|
-
def color=(value); set_text_style_node_attribute('color', value) end
|
262
|
-
def font_size=(value);set_text_style_node_attribute('font-size', value) end
|
263
|
-
def set_text_style_node_attribute(attribute_name,value)
|
264
|
-
@cell.detach if @cell.mode != :regular
|
265
|
-
if text_style_node.nil?
|
266
|
-
self.create_text_style_node
|
267
|
-
raise 'Style node was not correctly initialized' if text_style_node.nil?
|
268
|
-
end
|
269
|
-
Tools.set_ns_attribute(text_style_node,'fo',attribute_name,value)
|
270
|
-
end
|
271
|
-
def background_color=(value); set_cell_style_node_attribute('background-color', value) end
|
272
|
-
def set_cell_style_node_attribute(attribute_name,value)
|
273
|
-
@cell.detach if @cell.mode != :regular
|
274
|
-
if cell_style_node.nil?
|
275
|
-
self.create_cell_style_node
|
276
|
-
raise 'Style node was not correctly initialized' if cell_style_node.nil?
|
277
|
-
end
|
278
|
-
Tools.set_ns_attribute(cell_style_node,'fo',attribute_name,value)
|
279
|
-
end
|
280
|
-
|
281
|
-
# @!group initialization of style related nodes, if they do not exist
|
282
|
-
def create_text_style_node
|
283
|
-
create_style_node if style_name.nil? or style_node.nil?
|
284
|
-
raise 'text_style_node already exists' unless text_style_node.nil?
|
285
|
-
style_node << Tools.prepare_ns_node('style','text-properties')
|
286
|
-
end
|
287
|
-
def create_cell_style_node
|
288
|
-
create_style_node if style_name.nil? or style_node.nil?
|
289
|
-
raise 'cell_style_node already exists' unless cell_style_node.nil?
|
290
|
-
style_node << Tools.prepare_ns_node('style','table-cell-properties')
|
291
|
-
end
|
292
|
-
def create_style_node
|
293
|
-
if style_name.nil?
|
294
|
-
proposed_style_name = unused_cell_style_name
|
295
|
-
Tools.set_ns_attribute(cellnode,'table','style-name',proposed_style_name)
|
296
|
-
raise 'Style name was not correctly initialized' if style_name!=proposed_style_name
|
297
|
-
end
|
298
|
-
anode = Tools.prepare_ns_node('style','style')
|
299
|
-
Tools.set_ns_attribute(anode, 'style', 'name', proposed_style_name)
|
300
|
-
Tools.set_ns_attribute(anode, 'style', 'family', 'table-cell')
|
301
|
-
Tools.set_ns_attribute(anode, 'style', 'parent-style-name', 'Default')
|
302
|
-
automatic_styles_node << anode
|
303
|
-
raise 'Style node was not correctly initialized' if style_node.nil?
|
304
|
-
end
|
305
|
-
|
306
|
-
def unused_cell_style_name
|
307
|
-
last = (cellnode.nil? ? [] : cellnode.doc.root.find('./office:automatic-styles/style:style')).
|
308
|
-
collect {|node| node['name']}.
|
309
|
-
collect{ |name| /^ce(\d*)$/.match(name); $1.andand.to_i}.
|
310
|
-
compact.max || 0
|
311
|
-
"ce#{last+1}"
|
312
|
-
end
|
313
|
-
def automatic_styles_node; style_node_with_partial_xpath('') end
|
314
|
-
def style_name; Tools.get_ns_attribute_value(cellnode,'table','style-name',nil) end
|
315
|
-
def style_node; style_node_with_partial_xpath("/style:style[@style:name=\"#{style_name}\"]") end
|
316
|
-
def text_style_node; style_node_with_partial_xpath("/style:style[@style:name=\"#{style_name}\"]/style:text-properties") end
|
317
|
-
def cell_style_node; style_node_with_partial_xpath("/style:style[@style:name=\"#{style_name}\"]/style:table-cell-properties") end
|
318
|
-
def style_node_with_partial_xpath(xpath)
|
319
|
-
return nil if cellnode.nil?
|
320
|
-
cellnode.doc.root.find("./office:automatic-styles#{xpath}").first
|
321
|
-
end
|
322
|
-
def currency
|
323
|
-
Tools.get_ns_attribute_value(cellnode,'office','currency',nil)
|
324
|
-
end
|
325
|
-
#returns object representing top border of the cell
|
326
|
-
def top; @top ||= Border.new(self,:top) end
|
327
|
-
def bottom; @bottom ||= Border.new(self,:bottom) end
|
328
|
-
def left; @left ||= Border.new(self,:left) end
|
329
|
-
def right; @right ||= Border.new(self,:right) end
|
330
|
-
alias :border_top :top
|
331
|
-
alias :border_right :right
|
332
|
-
alias :border_bottom :bottom
|
333
|
-
alias :border_left :left
|
334
|
-
|
335
|
-
def inspect
|
336
|
-
"#<Rspreadsheet::CellFormat bold:#{bold?.inspect}, borders:#{top.get_value_string.inspect} #{right.get_value_string.inspect} #{bottom.get_value_string.inspect} #{left.get_value_string.inspect}>"
|
337
|
-
end
|
338
|
-
|
339
|
-
end
|
340
|
-
|
341
|
-
# represents one of the borders of a cell
|
342
|
-
class Border
|
343
|
-
def initialize(cellformat,side)
|
344
|
-
@cellformat = cellformat
|
345
|
-
@side = side.to_s
|
346
|
-
raise "Wrong side of border object, can be top, bottom, left or right" unless ['left','right','top','bottom'].include? @side
|
347
|
-
end
|
348
|
-
def cellnode; @cell.xmlnode end
|
349
|
-
def attribute_name; "border-#{@side}" end
|
350
|
-
|
351
|
-
def width=(value); set_border_string_part(1, value) end
|
352
|
-
def style=(value); set_border_string_part(2, value.to_s) end
|
353
|
-
def color=(value); set_border_string_part(3, value) end
|
354
|
-
def width; get_border_string_part(1).to_f end
|
355
|
-
def style; get_border_string_part(2) end
|
356
|
-
def color; get_border_string_part(3) end
|
357
|
-
def delete
|
358
|
-
@cellformat.set_cell_style_node_attribute(attribute_name, 'none')
|
359
|
-
end
|
360
|
-
|
361
|
-
|
362
|
-
## internals
|
363
|
-
|
364
|
-
# set parth-th part of string which represents the border. String looks like "0.06pt solid #00ee00"
|
365
|
-
# part is 1 for width, 2 for style or 3 for color
|
366
|
-
def set_border_string_part(part,value)
|
367
|
-
current_value = @cellformat.get_cell_style_node_attribute(attribute_name)
|
368
|
-
|
369
|
-
if current_value.nil? or (current_value=='none')
|
370
|
-
value_array = ['0.75pt', 'solid', '#000000'] # set default values
|
371
|
-
else
|
372
|
-
value_array = current_value.split(' ')
|
373
|
-
end
|
374
|
-
raise 'Strange border attribute value. Does not have 3 parts' unless value_array.length == 3
|
375
|
-
value_array[part-1]=value
|
376
|
-
@cellformat.set_cell_style_node_attribute(attribute_name, value_array.join(' '))
|
377
|
-
end
|
378
|
-
|
379
|
-
def get_border_string_part(part)
|
380
|
-
current_value = @cellformat.get_cell_style_node_attribute(attribute_name) || @cellformat.get_cell_style_node_attribute('border')
|
381
|
-
if current_value.nil? or (current_value=='none')
|
382
|
-
return nil
|
383
|
-
else
|
384
|
-
value_array = current_value.split(' ')
|
385
|
-
raise 'Strange border attribute value. Does not have 3 parts' unless value_array.length == 3
|
386
|
-
return value_array[part-1]
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
|
-
def get_value_string
|
391
|
-
@cellformat.get_cell_style_node_attribute(attribute_name)
|
392
|
-
end
|
393
|
-
|
394
|
-
end
|
277
|
+
private
|
278
|
+
InternalDateFormat = '%Y-%m-%d'
|
279
|
+
InternalDateTimeFormat = '%FT%T'
|
280
|
+
InternalTimeFormat = 'PT%HH%MM%SS'
|
281
|
+
end ## Cell
|
395
282
|
|
396
283
|
end # module
|
397
284
|
|
@@ -410,4 +297,3 @@ end # module
|
|
410
297
|
|
411
298
|
|
412
299
|
|
413
|
-
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# @markup markdown
|
2
|
+
# @author Jakub Tesinsky
|
3
|
+
# @title rspreadsheet Cell
|
4
|
+
|
5
|
+
# require 'andand'
|
6
|
+
# require 'rspreadsheet/xml_tied_item'
|
7
|
+
# require 'date'
|
8
|
+
# require 'time' # extended functions for time like Time.strptime
|
9
|
+
# require 'bigdecimal'
|
10
|
+
# require 'bigdecimal/util' # for to_d method
|
11
|
+
# require 'helpers/class_extensions'
|
12
|
+
|
13
|
+
module Rspreadsheet
|
14
|
+
using ClassExtensions if RUBY_VERSION > '2.1'
|
15
|
+
|
16
|
+
###
|
17
|
+
# Represents a format of a cell. This object is returned by `@cell.format` method and allows syntax like
|
18
|
+
#
|
19
|
+
# @cell.format.bold = true
|
20
|
+
# @cell.format.italic = false
|
21
|
+
# @cell.format.color = '#45AC00'
|
22
|
+
#
|
23
|
+
# Also handles all logic for formats.
|
24
|
+
# @private
|
25
|
+
class CellFormat
|
26
|
+
def initialize(cell)
|
27
|
+
@cell = cell
|
28
|
+
end
|
29
|
+
def cellnode; @cell.xmlnode end
|
30
|
+
|
31
|
+
# text style attribute readers
|
32
|
+
def bold; get_text_style_node_attribute('font-weight') == 'bold' end
|
33
|
+
alias :bold? :bold
|
34
|
+
def italic; get_text_style_node_attribute('font-style') == 'italic' end
|
35
|
+
def color; get_text_style_node_attribute('color') end
|
36
|
+
def font_size; get_text_style_node_attribute('font-size') end
|
37
|
+
def get_text_style_node_attribute(attribute_name)
|
38
|
+
text_style_node.nil? ? nil : Tools.get_ns_attribute_value(text_style_node,'fo',attribute_name)
|
39
|
+
end
|
40
|
+
def background_color; get_cell_style_node_attribute('background-color') end
|
41
|
+
def get_cell_style_node_attribute(attribute_name)
|
42
|
+
cell_style_node.nil? ? nil : Tools.get_ns_attribute_value(cell_style_node,'fo',attribute_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
# text style attribute writers
|
46
|
+
def bold=(value); set_text_style_node_attribute('font-weight', value ? 'bold' : 'normal') end
|
47
|
+
def italic=(value); set_text_style_node_attribute('font-style', value ? 'italic' : 'normal') end
|
48
|
+
def color=(value); set_text_style_node_attribute('color', value) end
|
49
|
+
def font_size=(value);set_text_style_node_attribute('font-size', value) end
|
50
|
+
def set_text_style_node_attribute(attribute_name,value)
|
51
|
+
@cell.detach if @cell.mode != :regular
|
52
|
+
if text_style_node.nil?
|
53
|
+
self.create_text_style_node
|
54
|
+
raise 'Style node was not correctly initialized' if text_style_node.nil?
|
55
|
+
end
|
56
|
+
Tools.set_ns_attribute(text_style_node,'fo',attribute_name,value)
|
57
|
+
end
|
58
|
+
def background_color=(value); set_cell_style_node_attribute('background-color', value) end
|
59
|
+
def set_cell_style_node_attribute(attribute_name,value)
|
60
|
+
@cell.detach if @cell.mode != :regular
|
61
|
+
if cell_style_node.nil?
|
62
|
+
self.create_cell_style_node
|
63
|
+
raise 'Style node was not correctly initialized' if cell_style_node.nil?
|
64
|
+
end
|
65
|
+
Tools.set_ns_attribute(cell_style_node,'fo',attribute_name,value)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @!group initialization of style related nodes, if they do not exist
|
69
|
+
def create_text_style_node
|
70
|
+
create_style_node if style_name.nil? or style_node.nil?
|
71
|
+
raise 'text_style_node already exists' unless text_style_node.nil?
|
72
|
+
style_node << Tools.prepare_ns_node('style','text-properties')
|
73
|
+
end
|
74
|
+
def create_cell_style_node
|
75
|
+
create_style_node if style_name.nil? or style_node.nil?
|
76
|
+
raise 'cell_style_node already exists' unless cell_style_node.nil?
|
77
|
+
style_node << Tools.prepare_ns_node('style','table-cell-properties')
|
78
|
+
end
|
79
|
+
def create_style_node
|
80
|
+
if style_name.nil?
|
81
|
+
proposed_style_name = unused_cell_style_name
|
82
|
+
Tools.set_ns_attribute(cellnode,'table','style-name',proposed_style_name)
|
83
|
+
raise 'Style name was not correctly initialized' if style_name!=proposed_style_name
|
84
|
+
end
|
85
|
+
anode = Tools.prepare_ns_node('style','style')
|
86
|
+
Tools.set_ns_attribute(anode, 'style', 'name', proposed_style_name)
|
87
|
+
Tools.set_ns_attribute(anode, 'style', 'family', 'table-cell')
|
88
|
+
Tools.set_ns_attribute(anode, 'style', 'parent-style-name', 'Default')
|
89
|
+
automatic_styles_node << anode
|
90
|
+
raise 'Style node was not correctly initialized' if style_node.nil?
|
91
|
+
end
|
92
|
+
|
93
|
+
def unused_cell_style_name
|
94
|
+
last = (cellnode.nil? ? [] : cellnode.doc.root.find('./office:automatic-styles/style:style')).
|
95
|
+
collect {|node| node['name']}.
|
96
|
+
collect{ |name| /^ce(\d*)$/.match(name); $1.andand.to_i}.
|
97
|
+
compact.max || 0
|
98
|
+
"ce#{last+1}"
|
99
|
+
end
|
100
|
+
def automatic_styles_node; style_node_with_partial_xpath('') end
|
101
|
+
def style_name; Tools.get_ns_attribute_value(cellnode,'table','style-name',nil) end
|
102
|
+
def style_node; style_node_with_partial_xpath("/style:style[@style:name=\"#{style_name}\"]") end
|
103
|
+
def text_style_node; style_node_with_partial_xpath("/style:style[@style:name=\"#{style_name}\"]/style:text-properties") end
|
104
|
+
def cell_style_node; style_node_with_partial_xpath("/style:style[@style:name=\"#{style_name}\"]/style:table-cell-properties") end
|
105
|
+
def style_node_with_partial_xpath(xpath)
|
106
|
+
return nil if cellnode.nil?
|
107
|
+
cellnode.doc.root.find("./office:automatic-styles#{xpath}").first
|
108
|
+
end
|
109
|
+
def currency
|
110
|
+
Tools.get_ns_attribute_value(cellnode,'office','currency',nil)
|
111
|
+
end
|
112
|
+
#returns object representing top border of the cell
|
113
|
+
def top; @top ||= Border.new(self,:top) end
|
114
|
+
def bottom; @bottom ||= Border.new(self,:bottom) end
|
115
|
+
def left; @left ||= Border.new(self,:left) end
|
116
|
+
def right; @right ||= Border.new(self,:right) end
|
117
|
+
alias :border_top :top
|
118
|
+
alias :border_right :right
|
119
|
+
alias :border_bottom :bottom
|
120
|
+
alias :border_left :left
|
121
|
+
|
122
|
+
def inspect
|
123
|
+
"#<Rspreadsheet::CellFormat bold:#{bold?.inspect}, borders:#{top.get_value_string.inspect} #{right.get_value_string.inspect} #{bottom.get_value_string.inspect} #{left.get_value_string.inspect}>"
|
124
|
+
end
|
125
|
+
# experimental. Allows to assign a style to cell.
|
126
|
+
def style_name=(value)
|
127
|
+
Tools.set_ns_attribute_value(cellnode,'table','style-name',value)
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
# represents one of the borders of a cell
|
133
|
+
class Border
|
134
|
+
def initialize(cellformat,side)
|
135
|
+
@cellformat = cellformat
|
136
|
+
@side = side.to_s
|
137
|
+
raise "Wrong side of border object, can be top, bottom, left or right" unless ['left','right','top','bottom'].include? @side
|
138
|
+
end
|
139
|
+
def cellnode; @cell.xmlnode end
|
140
|
+
def attribute_name; "border-#{@side}" end
|
141
|
+
|
142
|
+
def width=(value); set_border_string_part(1, value) end
|
143
|
+
def style=(value); set_border_string_part(2, value.to_s) end
|
144
|
+
def color=(value); set_border_string_part(3, value) end
|
145
|
+
def width; get_border_string_part(1).to_f end
|
146
|
+
def style; get_border_string_part(2) end
|
147
|
+
def color; get_border_string_part(3) end
|
148
|
+
def delete
|
149
|
+
@cellformat.set_cell_style_node_attribute(attribute_name, 'none')
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
## internals
|
154
|
+
|
155
|
+
# set parth-th part of string which represents the border. String looks like "0.06pt solid #00ee00"
|
156
|
+
# part is 1 for width, 2 for style or 3 for color
|
157
|
+
def set_border_string_part(part,value)
|
158
|
+
current_value = @cellformat.get_cell_style_node_attribute(attribute_name)
|
159
|
+
|
160
|
+
if current_value.nil? or (current_value=='none')
|
161
|
+
value_array = ['0.75pt', 'solid', '#000000'] # set default values
|
162
|
+
else
|
163
|
+
value_array = current_value.split(' ')
|
164
|
+
end
|
165
|
+
raise 'Strange border attribute value. Does not have 3 parts' unless value_array.length == 3
|
166
|
+
value_array[part-1]=value
|
167
|
+
@cellformat.set_cell_style_node_attribute(attribute_name, value_array.join(' '))
|
168
|
+
end
|
169
|
+
|
170
|
+
def get_border_string_part(part)
|
171
|
+
current_value = @cellformat.get_cell_style_node_attribute(attribute_name) || @cellformat.get_cell_style_node_attribute('border')
|
172
|
+
if current_value.nil? or (current_value=='none')
|
173
|
+
return nil
|
174
|
+
else
|
175
|
+
value_array = current_value.split(' ')
|
176
|
+
raise 'Strange border attribute value. Does not have 3 parts' unless value_array.length == 3
|
177
|
+
return value_array[part-1]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def get_value_string
|
182
|
+
@cellformat.get_cell_style_node_attribute(attribute_name)
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
end # module
|
188
|
+
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
|
196
|
+
|
197
|
+
|
198
|
+
|
199
|
+
|
200
|
+
|
201
|
+
|
202
|
+
|
203
|
+
|
data/lib/rspreadsheet/tools.rb
CHANGED
@@ -185,9 +185,13 @@ module Tools
|
|
185
185
|
end
|
186
186
|
raise 'Could not get unused filename within sane times of iterations'
|
187
187
|
end
|
188
|
-
|
189
|
-
end
|
190
188
|
|
189
|
+
def self.new_time_value(h,m,s)
|
190
|
+
Time.new(StartOfEpoch.year,StartOfEpoch.month,StartOfEpoch.day,h,m,s)
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
191
195
|
end
|
192
196
|
|
193
197
|
# @private
|
data/lib/rspreadsheet/version.rb
CHANGED
@@ -73,7 +73,7 @@ class Workbook
|
|
73
73
|
when @filename.nil? && (io.kind_of?(String) || io.kind_of?(File) || io.kind_of?(IO) || io.kind_of?(StringIO))
|
74
74
|
Zip::File.open(TEMPLATE_FILE_NAME) do |empty_template_zip| # open empty_template file
|
75
75
|
write_zip_to(io) do |output_zip| # open output stream of file
|
76
|
-
|
76
|
+
copy_internally_without_content(empty_template_zip,output_zip) # copy empty_template internals
|
77
77
|
update_manifest_and_content_xml(empty_template_zip,output_zip) # update xmls + pictures
|
78
78
|
end
|
79
79
|
end
|
@@ -91,7 +91,7 @@ class Workbook
|
|
91
91
|
when @filename.kind_of?(String) && (io.kind_of?(IO) || io.kind_of?(StringIO))
|
92
92
|
Zip::File.open(@filename) do | old_zip | # open old file
|
93
93
|
write_zip_to(io) do |output_zip_stream| # open output stream
|
94
|
-
|
94
|
+
copy_internally_without_content(old_zip,output_zip_stream) # copy the old internals
|
95
95
|
update_manifest_and_content_xml(old_zip,output_zip_stream) # update xmls + pictures
|
96
96
|
end
|
97
97
|
end
|
@@ -118,14 +118,14 @@ class Workbook
|
|
118
118
|
def update_manifest_xml(input_zip,output_zip)
|
119
119
|
# read manifest
|
120
120
|
@manifest_xml = LibXML::XML::Document.io input_zip.get_input_stream(MANIFEST_FILE_NAME)
|
121
|
-
|
121
|
+
modified = false
|
122
122
|
# save all pictures - iterate through sheets and pictures and check if they are saved and if not, save them
|
123
123
|
@worksheets.each do |sheet|
|
124
124
|
sheet.images.each do |image|
|
125
125
|
# check if it is saved
|
126
126
|
@ifname = image.internal_filename
|
127
|
-
if @ifname.nil? or input_zip.find_entry(@ifname).nil?
|
128
|
-
# if it does not have name -> make up unused name
|
127
|
+
if @ifname.nil? or input_zip.find_entry(@ifname).nil?
|
128
|
+
# if it does not have name -> make up unused name
|
129
129
|
if @ifname.nil?
|
130
130
|
@ifname = image.internal_filename = Rspreadsheet::Tools.get_unused_filename(input_zip,'Pictures/',File.extname(image.original_filename))
|
131
131
|
end
|
@@ -141,19 +141,21 @@ class Workbook
|
|
141
141
|
Tools.set_ns_attribute(node,'manifest','full-path',@ifname)
|
142
142
|
Tools.set_ns_attribute(node,'manifest','media-type',image.mime)
|
143
143
|
@manifest_xml.find_first("//manifest:manifest") << node
|
144
|
+
modified = true
|
144
145
|
end
|
145
146
|
end
|
146
147
|
end
|
147
148
|
end
|
148
149
|
|
149
|
-
# write manifest
|
150
|
-
save_entry_to_zip(output_zip, MANIFEST_FILE_NAME,
|
150
|
+
# write manifest if it was modified
|
151
|
+
save_entry_to_zip(output_zip, MANIFEST_FILE_NAME,
|
152
|
+
@manifest_xml.to_s) if modified
|
151
153
|
end
|
152
154
|
|
153
|
-
def
|
155
|
+
def copy_internally_without_content(input_zip,output_zip)
|
154
156
|
input_zip.each do |entry|
|
155
157
|
next unless entry.file?
|
156
|
-
next if entry.name == CONTENT_FILE_NAME
|
158
|
+
next if entry.name == CONTENT_FILE_NAME
|
157
159
|
save_entry_to_zip(output_zip, entry.name, entry.get_input_stream.read)
|
158
160
|
end
|
159
161
|
end
|
@@ -161,7 +163,7 @@ class Workbook
|
|
161
163
|
def save_entry_to_zip(zip,internal_filename,contents)
|
162
164
|
if zip.kind_of? Zip::File
|
163
165
|
# raise [internal_filename,contents].inspect unless File.exists?(internal_filename)
|
164
|
-
zip.get_output_stream(internal_filename) do |f|
|
166
|
+
zip.get_output_stream(internal_filename) do |f|
|
165
167
|
f.write contents
|
166
168
|
end
|
167
169
|
else
|
@@ -167,9 +167,11 @@ module XMLTiedArray
|
|
167
167
|
# array containing subnodes of xmlnode which represent subitems
|
168
168
|
def xmlsubnodes
|
169
169
|
return [] if xmlnode.nil?
|
170
|
+
ele = xmlnode.elements
|
171
|
+
return [] if ele.empty?
|
170
172
|
so = subitem_xml_options[:xml_items_node_name]
|
171
173
|
|
172
|
-
|
174
|
+
ele.select do |node|
|
173
175
|
node.andand.name == so
|
174
176
|
end
|
175
177
|
end
|
data/rspreadsheet.gemspec
CHANGED
@@ -22,12 +22,12 @@ Gem::Specification.new do |spec|
|
|
22
22
|
# if the shell fails, the system command returns nil. In that case we assume that the package is NOT installed. It might be overkill, because I am supresing the stderr as well
|
23
23
|
(system("dpkg-query -l #{pkgname} 2>/dev/null | grep -q '^i'")==true) or # debian based
|
24
24
|
(system("rpm -qa 2>/dev/null | grep -q '#{pkgname}' ")==true) or # rpm based
|
25
|
-
(system("pkg_info -q -e #{pkgname} >/dev/null 2>&1")==true)
|
25
|
+
(system("pkg_info -q -e '#{pkgname}-*' >/dev/null 2>&1")==true) # OpenBSD and alike (although OpenBSD registers the gem as a gem, so this is perhaps unnecessary
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
# runtime dependencies
|
29
29
|
unless package_natively_installed?('ruby-libxml')
|
30
|
-
spec.add_runtime_dependency 'libxml-ruby', '
|
30
|
+
spec.add_runtime_dependency 'libxml-ruby', '2.8' # parsing XML files
|
31
31
|
end
|
32
32
|
spec.add_runtime_dependency 'rubyzip', '~>1.1' # opening zip files
|
33
33
|
spec.add_runtime_dependency 'andand', '~>1.3'
|
@@ -45,7 +45,7 @@ Gem::Specification.new do |spec|
|
|
45
45
|
spec.add_development_dependency "guard", '~>2.13'
|
46
46
|
spec.add_development_dependency "guard-rspec", '~>4.6'
|
47
47
|
end
|
48
|
-
|
48
|
+
|
49
49
|
# spec.add_development_dependency 'equivalent-xml' # implementing xml diff
|
50
50
|
|
51
51
|
end
|
data/spec/cell_spec.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
-
|
2
|
+
|
3
3
|
describe Rspreadsheet::Cell do
|
4
4
|
before do
|
5
5
|
book1 = Rspreadsheet.new
|
@@ -38,6 +38,8 @@ describe Rspreadsheet::Cell do
|
|
38
38
|
@cell.value.should == 'zapate'
|
39
39
|
@sheet1.rows[1][2] = 'zaseste' ## tohle je divoka moznost
|
40
40
|
@cell.value.should == 'zaseste'
|
41
|
+
|
42
|
+
@sheet1.A11 = 'dalsi test'
|
41
43
|
end
|
42
44
|
it 'can include links' do
|
43
45
|
@sheet2.A12.should == '[http://example.org/]'
|
@@ -94,7 +96,7 @@ describe Rspreadsheet::Cell do
|
|
94
96
|
end
|
95
97
|
it 'returns correct type for the cell' do
|
96
98
|
@sheet2.cell(1,2).type.should eq :string
|
97
|
-
@sheet2.cell(2,2).type.should eq :
|
99
|
+
@sheet2.cell(2,2).type.should eq :datetime
|
98
100
|
@sheet2.cell(3,1).type.should eq :float
|
99
101
|
@sheet2.cell(3,2).type.should eq :percentage
|
100
102
|
@sheet2.cell(4,2).type.should eq :string
|
@@ -104,7 +106,7 @@ describe Rspreadsheet::Cell do
|
|
104
106
|
end
|
105
107
|
it 'returns value of correct type' do
|
106
108
|
@sheet2[1,2].should be_kind_of(String)
|
107
|
-
@sheet2[2,2].should be_kind_of(
|
109
|
+
@sheet2[2,2].should be_kind_of(DateTime) # maybe date
|
108
110
|
@sheet2[3,1].should be_kind_of(Float)
|
109
111
|
@sheet2[3,2].should be_kind_of(Float)
|
110
112
|
@sheet2.cell(3,2).type.should eq :percentage
|
@@ -276,14 +278,38 @@ describe Rspreadsheet::Cell do
|
|
276
278
|
@cell.value.min.should eq 42
|
277
279
|
@cell.value.sec.should eq 0
|
278
280
|
end
|
279
|
-
it '
|
280
|
-
|
281
|
+
it 'parse_time_value converts correcty different time values' do
|
282
|
+
dyear = 1899; dmonth = 12; dday = 30
|
283
|
+
Rspreadsheet::Cell.parse_time_value('PT923451H33M00S').should == Time.new(2005,5,5,3,33,00,0)
|
284
|
+
Rspreadsheet::Cell.parse_time_value('PT1H33M00S').should == Time.new(dyear,dmonth,dday,1,33,00,0)
|
285
|
+
end
|
286
|
+
it 'handles time of day correctly on assignement' do
|
287
|
+
@sheet1.A11 = Rspreadsheet::Tools.new_time_value(2,13,27)
|
288
|
+
@sheet1.A12 = @sheet1.A11
|
289
|
+
@sheet1.A12.should == @sheet1.A11
|
290
|
+
@sheet1.cell('A12').type.should == :time
|
291
|
+
end
|
292
|
+
it 'can read various types of times' do
|
281
293
|
expect {@cell = @sheet2.cell('D22'); @cell.value }.not_to raise_error
|
294
|
+
@cell.value.hour.should == 2
|
295
|
+
@cell.value.min.should == 22
|
282
296
|
expect {@cell = @sheet2.cell('D23'); @cell.value }.not_to raise_error
|
283
|
-
|
284
|
-
|
297
|
+
@cell.value.should == Time.new(2005,5,5,3,33,0,0)
|
298
|
+
end
|
299
|
+
it 'handles dates and datetimes correctly on assignement' do
|
300
|
+
@sheet1.A11 = DateTime.new(2011,1,1,2,13,27,0)
|
301
|
+
@sheet1.A12 = @sheet1.A11
|
302
|
+
@sheet1.A12.should == @sheet1.A11
|
303
|
+
@sheet1.cell('A12').type.should == :datetime
|
304
|
+
@sheet1.A11 = Date.new(2012,2,2)
|
305
|
+
@sheet1.A12 = @sheet1.A11
|
306
|
+
@sheet1.A12.should == @sheet1.A11
|
307
|
+
@sheet1.cell('A12').type.should == :datetime
|
308
|
+
end
|
309
|
+
it 'can read various types of dates' do
|
310
|
+
expect {@cell = @sheet2.cell('F22'); @cell.value }.not_to raise_error
|
311
|
+
@cell.value.should == DateTime.new(2006,6,6,3,44,21,0)
|
285
312
|
end
|
286
|
-
|
287
313
|
it 'can be addressed by even more ways and all are identical' do
|
288
314
|
@cell = @sheet1.cell(2,2)
|
289
315
|
@sheet1.cell('B2').value = 'zaseste'
|
@@ -408,4 +434,8 @@ describe Rspreadsheet::Cell do
|
|
408
434
|
@cell.format.top.style = 'none'
|
409
435
|
@cell.border_top.should_not be_nil ## ?????
|
410
436
|
end
|
437
|
+
|
438
|
+
it 'automatically creates new style, if a style is automatic, some of its attributes changes and there are several cells pointing to it', :penting=>'' do
|
439
|
+
|
440
|
+
end
|
411
441
|
end
|
data/spec/testfile1.ods
CHANGED
Binary file
|
data/spec/tools_spec.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspreadsheet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jakub A.Těšínský
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: libxml-ruby
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.8'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: rubyzip
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +122,34 @@ dependencies:
|
|
108
122
|
- - "~>"
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0.7'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: guard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '2.13'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '2.13'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: guard-rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - "~>"
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '4.6'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - "~>"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '4.6'
|
111
153
|
description: Manipulating OpenDocument spreadsheets with Ruby. This gem can create
|
112
154
|
new, read existing files abd modify them. When modyfying files, it tries to change
|
113
155
|
as little as possible, making it as much forward compatifle as possible.
|
@@ -136,6 +178,7 @@ files:
|
|
136
178
|
- lib/helpers/configuration.rb
|
137
179
|
- lib/rspreadsheet.rb
|
138
180
|
- lib/rspreadsheet/cell.rb
|
181
|
+
- lib/rspreadsheet/cell_format.rb
|
139
182
|
- lib/rspreadsheet/column.rb
|
140
183
|
- lib/rspreadsheet/empty_file_template.ods
|
141
184
|
- lib/rspreadsheet/image.rb
|
@@ -165,8 +208,6 @@ files:
|
|
165
208
|
- spec/tools_spec.rb
|
166
209
|
- spec/workbook_spec.rb
|
167
210
|
- spec/worksheet_spec.rb
|
168
|
-
- w/test.rb
|
169
|
-
- w/time.ods
|
170
211
|
homepage: https://github.com/gorn/rspreadsheet
|
171
212
|
licenses:
|
172
213
|
- GPL
|
@@ -187,7 +228,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
228
|
version: '0'
|
188
229
|
requirements: []
|
189
230
|
rubyforge_project:
|
190
|
-
rubygems_version: 2.
|
231
|
+
rubygems_version: 2.5.2
|
191
232
|
signing_key:
|
192
233
|
specification_version: 4
|
193
234
|
summary: Manipulating spreadsheets with Ruby (read / create / modify OpenDocument
|
data/w/test.rb
DELETED
data/w/time.ods
DELETED
Binary file
|