rspreadsheet 0.2.15 → 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,118 @@
1
+ module Rspreadsheet
2
+
3
+ # Represents images embeded in a Worksheet
4
+ class WorksheetImages
5
+ include XMLTiedArray
6
+ def initialize(parent_worksheet)
7
+ initialize_xml_tied_array
8
+ @worksheet = parent_worksheet
9
+ end
10
+
11
+ def insert_image(filename,mime='image/png')
12
+ if xmlnode.nil? #TODO: this needs to be solved more generally maybe on XMLTiedArray level
13
+ @worksheet.xmlnode
14
+ end
15
+ push_new
16
+ last.initialize_from_file(filename,mime)
17
+ end
18
+
19
+ # @!group XMLTiedArray_WithRepeatableItems related methods
20
+ def subitem_xml_options; {:xml_items_node_name => 'frame', :xml_items_node_namespace => 'draw'} end
21
+ def prepare_subitem(index); Image.new(self,index) end
22
+ def xmlnode; @worksheet.xmlnode.find('./table:shapes').first end
23
+ def prepare_empty_xmlnode
24
+ Tools.insert_as_first_node_child(
25
+ @worksheet.xmlnode,
26
+ Tools.prepare_ns_node('table', 'shapes')
27
+ )
28
+ end
29
+ def prepare_empty_subnode
30
+ main_node = super # prepares <draw:frame/> node but it is entirely empty
31
+ [
32
+ ['draw', 'z-index', '1'],
33
+ ['draw', 'name', 'test'],
34
+ ['draw', 'style-name', 'gr1'],
35
+ ['draw', 'text-style-name', 'P1'],
36
+ ['svg', 'width', '11.63mm'],
37
+ ['svg', 'height', '10.83mm']
38
+ ].each do |line|
39
+ Tools.set_ns_attribute(main_node,line[0],line[1],line[2])
40
+ end
41
+
42
+ sub_node = Tools.prepare_ns_node('draw', 'image')
43
+ [
44
+ ['xlink', 'type', 'simple'],
45
+ ['xlink', 'show', 'embed'],
46
+ ['xlink', 'actuate', 'onLoad']
47
+ ].each do |line|
48
+ Tools.set_ns_attribute(sub_node,line[0],line[1],line[2])
49
+ end
50
+
51
+ sub_node << Tools.prepare_ns_node('text','p')
52
+ main_node << sub_node
53
+ main_node
54
+ end
55
+
56
+ end
57
+
58
+ # Represents an image included in the spreadsheet. The Image can NOT exist
59
+ # "detached" from an spreadsheet
60
+ class Image < XMLTiedItem
61
+ attr_reader :mime
62
+
63
+ def initialize(worksheet,index)
64
+ super(worksheet,index)
65
+ @original_filename = nil
66
+ end
67
+
68
+ def initialize_from_file(filename,mime)
69
+ # ověřit, zda soubor na disku existuje TODO: tady by to chtělo zobecnit na IO
70
+ raise 'File does not exist or it is not accessible' unless File.exists?(filename)
71
+ @original_filename = filename
72
+ @mime = mime
73
+ self
74
+ end
75
+ def xml_image_subnode
76
+ xmlnode.find('./draw:image').first
77
+ end
78
+
79
+ def move_to(ax,ay)
80
+ self.x = ax
81
+ self.y = ay
82
+ end
83
+
84
+ def original_filename; @original_filename end
85
+
86
+ def copy_to(ax,ay,worksheet)
87
+ img = worksheet.insert_image_to(ax,ay,@original_filename)
88
+ img.height = height
89
+ img.width = width
90
+ end
91
+
92
+ # TODO: put some sanity check for values into these
93
+ def x=(value); Tools.set_ns_attribute(xmlnode,'svg','x', value) end
94
+ def y=(value); Tools.set_ns_attribute(xmlnode,'svg','y', value) end
95
+ def width=(value); Tools.set_ns_attribute(xmlnode,'svg','width', value) end
96
+ def height=(value); Tools.set_ns_attribute(xmlnode,'svg','height',value) end
97
+ def name=(value); Tools.set_ns_attribute(xmlnode,'draw','name', value) end
98
+ def x; Tools.get_ns_attribute_value(xmlnode,'svg','x') end
99
+ def y; Tools.get_ns_attribute_value(xmlnode,'svg','y') end
100
+ def width; Tools.get_ns_attribute_value(xmlnode,'svg','width') end
101
+ def height; Tools.get_ns_attribute_value(xmlnode,'svg','height') end
102
+ def name; Tools.get_ns_attribute_value(xmlnode, 'draw', 'name', nil) end
103
+ def internal_filename; Tools.get_ns_attribute_value(xml_image_subnode,'xlink','href') end
104
+ def internal_filename=(value)
105
+ Tools.set_ns_attribute(xml_image_subnode,'xlink','href', value )
106
+ end
107
+
108
+ # @!group XMLTiedItem related methods
109
+ def xml_options; {:xml_items_node_name => 'frame'} end
110
+
111
+ #
112
+ # Note: when creating new empty image we might have included xlink:type attribute but specification
113
+ # says it has default value simple [1] so we omit it. The same goes for
114
+ # [1](http://docs.oasis-open.org/office/v1.2/os/OpenDocument-v1.2-os-part1.html#attribute-xlink_type)
115
+ #
116
+ end
117
+
118
+ end
@@ -1,6 +1,5 @@
1
1
  require 'rspreadsheet/cell'
2
- require 'rspreadsheet/xml_tied'
3
-
2
+ require 'rspreadsheet/xml_tied_repeatable'
4
3
 
5
4
  module Rspreadsheet
6
5
 
@@ -9,7 +8,7 @@ module Rspreadsheet
9
8
  #
10
9
  # @row = @worksheet.row(5)
11
10
  #
12
- # Mostly you will this object to access row cells values
11
+ # Mostly you will this object to access values of cells in the row
13
12
  #
14
13
  # @row[2] # identical to @worksheet[5,2] or @row.cells(2).value
15
14
  #
@@ -25,22 +24,19 @@ module Rspreadsheet
25
24
  # and shifts all other rows down/up appropriatelly.
26
25
 
27
26
  class Row < XMLTiedItem
28
- include XMLTiedArray
27
+ include XMLTiedArray_WithRepeatableItems
29
28
  ## @return [Worksheet] worksheet which contains the row
30
29
  # @!attribute [r] worksheet
31
- attr_reader :worksheet
30
+ def worksheet; parent end
32
31
  ## @return [Integer] row index of the row
33
32
  # @!attribute [r] rowi
34
- attr_reader :rowi
35
-
33
+ def rowi; index end
34
+
36
35
  def initialize(aworksheet,arowi)
37
- @worksheet = aworksheet
38
- @rowi = arowi
39
- @itemcache = Hash.new #TODO: move to module XMLTiedArray
36
+ initialize_xml_tied_array
37
+ initialize_xml_tied_item(aworksheet,arowi)
40
38
  end
41
39
 
42
- def xmlnode; parent.find_my_subnode_respect_repeated(index, xml_options) end
43
-
44
40
  # @!group Syntactic sugar
45
41
  def cells(*params); subitems(*params) end
46
42
  alias :cell :cells
@@ -91,7 +87,7 @@ class Row < XMLTiedItem
91
87
  if myxmlnode.nil?
92
88
  []
93
89
  else
94
- @worksheet.find_nonempty_subnode_indexes(myxmlnode, {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'})
90
+ worksheet.find_nonempty_subnode_indexes(myxmlnode, subitem_xml_options)
95
91
  end
96
92
  end
97
93
  alias :used_range :range
@@ -103,370 +99,19 @@ class Row < XMLTiedItem
103
99
  # @!group Private methods, which should not be called directly
104
100
  # @private
105
101
  # shifts internal represetation of row by diff. This should not be called directly
106
- # by user, it is only used by XMLTiedArray as hook when shifting around rows
102
+ # by user, it is only used by XMLTiedArray_WithRepeatableItems as hook when shifting around rows
107
103
  def _shift_by(diff)
108
104
  super
109
- @itemcache.each_value{ |cell| cell.set_rowi(@rowi) }
105
+ @itemcache.each_value{ |cell| cell.set_rowi(rowi) }
110
106
  end
111
107
 
112
108
  private
113
- # @!group XMLTiedArray related methods
109
+ # @!group XMLTiedArray_WithRepeatableItems related methods
114
110
  def subitem_xml_options; {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'} end
115
- def prepare_subitem(coli); Cell.new(@worksheet,@rowi,coli) end
111
+ def prepare_subitem(coli); Cell.new(worksheet,rowi,coli) end
116
112
  # @!group XMLTiedItem related methods and extensions
117
113
  def xml_options; {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'} end
118
- def parent; @worksheet end
119
- def index; @rowi end
120
- def set_index(value); @rowi=value end
121
- end
122
-
123
- # class Row
124
- # def initialize
125
- # @readonly = :unknown
126
- # @cells = {}
127
- # end
128
- # def worksheet; @parent_array.worksheet end
129
- # def parent_array; @parent_array end # for debug only
130
- # def used_col_range; 1..first_unused_column_index-1 end
131
- # def used_range; used_col_range end
132
- # def first_unused_column_index; raise 'this should be redefined in subclasses' end
133
- # end
134
-
135
-
136
- # --------------------------
137
-
138
-
139
- # # XmlTiedArrayItemGroup is internal representation of repeated items in XmlTiedArray.
140
- # class XmlTiedArrayItemGroup
141
- # # extend Forwardable
142
- # # delegate [:normalize ] => :@row_group
143
- #
144
- # def normalize; @rowgroup.normalize end
145
-
146
- # end
147
-
148
- # array which synchronizes with xml structure and reflects. number-xxx-repeated attributes
149
- # also caches returned objects for indexes.
150
- # options must contain
151
- # :xml_items, :xml_repeated_attribute, :object_type
152
-
153
- # class XmlTiedArray < Array
154
- # def initialize(axmlnode, options={}) # TODO get rid of XmlTiedArray
155
- # @xmlnode = axmlnode
156
- # @options = options
157
- #
158
- # missing_options = [:xml_repeated_attribute,:xml_items_node_name,:object_type]-@options.keys
159
- # raise "Some options missing (#{missing_options.inspect})" unless missing_options.empty?
160
- #
161
- # unless @xmlnode.nil?
162
- # @xmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |group_source_node|
163
- # self << parse_xml_to_group(group_source_node) # it is in @xmlnode so suffices to add object to @rowgroups
164
- # end
165
- # end
166
- # @itemcache=Hash.new()
167
- # end
168
- # def parse_xml_to_group(size_or_xmlnode) # parses xml to new RowGroup which can be added at the end
169
- # # reading params
170
- # if size_or_xmlnode.kind_of? LibXML::XML::Node
171
- # size = (size_or_xmlnode[@options[:xml_repeated_attribute]] || 1).to_i
172
- # node = size_or_xmlnode
173
- # elsif size_or_xmlnode.to_i>0
174
- # size = size_or_xmlnode.to_i
175
- # node = nil
176
- # else
177
- # return nil
178
- # end
179
- # index = first_unused_index
180
- # # construct result
181
- # Rspreadsheet::XmlTiedArrayItemGroup.new(self,index..index+size-1,node)
182
- # end
183
- # def add_item_group(size_or_xmlnode)
184
- # result = parse_xml_to_group(size_or_xmlnode)
185
- # self << result
186
- # @xmlnode << result.xmlnode
187
- # result
188
- # end
189
- # def first_unused_index
190
- # empty? ? 1 : last.range.end+1
191
- # end
192
- # # prolonges the RowArray to cantain rowi and returns it
193
- # def detach_of_bound_item(index)
194
- # fill_row_group_size = index-first_unused_index
195
- # if fill_row_group_size>0
196
- # add_item_group(fill_row_group_size)
197
- # end
198
- # add_item_group(1)
199
- # get_item(index) # aby se odpoved nacacheovala
200
- # end
201
- # def get_item_group(index)
202
- # find{ |item_group| item_group.range.cover?(index) }
203
- # end
204
- # def detach_item(index); get_item(index) end # TODO předělat do lazy podoby, kdy tohle nebude stejny
205
- # def get_item(index)
206
- # if index>= first_unused_index
207
- # nil
208
- # else
209
- # @itemcache[index] ||= Rspreadsheet::XmlTiedArrayItem.new(self,index)
210
- # end
211
- # end
212
- # # This detaches item index from the group and perhaps splits the RowGroup
213
- # # into two pieces. This makes the row individually editable.
214
- # def detach(index)
215
- # group_index = get_group_index(index)
216
- # item_group = self[group_index]
217
- # range = item_group.range
218
- # return self if range==(index..index)
219
- #
220
- # # prepare new components
221
- # replaceby = []
222
- # replaceby << RowGroup.new(self,range.begin..index-1)
223
- # replaceby << (result = SingleRow.new(self,index))
224
- # replaceby << RowGroup.new(self,index+1..range.end)
225
- #
226
- # # put original range somewhere in replaceby and shorten it
227
- #
228
- # if index>range.begin
229
- # replaceby[0] = item_group
230
- # item_group.range = range.begin..index-1
231
- # else
232
- # replaceby[2] = item_group
233
- # item_group.range = index+1..range.end
234
- # end
235
- #
236
- # # normalize and delete empty parts
237
- # replaceby = replaceby.map(&:normalize).compact
238
- #
239
- # # do the replacement in xml
240
- # marker = LibXML::XML::Node.new('temporarymarker')
241
- # item_group.xmlnode.next = marker
242
- # item_group.xmlnode.remove!
243
- # replaceby.each{ |rg|
244
- # marker.prev = rg.xmlnode
245
- # }
246
- # marker.remove!
247
- #
248
- # # do the replacement in array
249
- # self[group_index..group_index]=replaceby
250
- # result
251
- # end
252
- # private
253
- # def get_group_index(index)
254
- # self.find_index{ |rowgroup| rowgroup.range.cover?(index) }
255
- # end
256
- # end
257
-
258
- # class XmlTiedArrayItem
259
- # attr_reader :index
260
- # def initialize(aarray,aindex)
261
- # @array = aarray
262
- # @index = aindex
263
- # if self.virtual?
264
- # @object = nil
265
- # else
266
- # @object = @array.options[:object_type].new(group.xmlnode)
267
- # end
268
- # end
269
- # def group; @array.get_item_group(index) end
270
- # def repeated?; group.repeated? end
271
- # def virtual?; ! self.repeated? end
272
- # def array
273
- # raise 'Group empty' if @group.nil?
274
- # @array
275
- # end
276
- # end
277
-
278
- # class RowArray < XmlTiedArray
279
- # attr_reader :row_array_cache
280
- # def initialize(aworksheet,aworksheet_node)
281
- # @worksheet = aworksheet
282
- # @row_array_cache = Hash.new()
283
- # super(aworksheet_node, :xml_items_node_name => 'table-row', :xml_repeated_attribute => xml_repeated_attribute, :object_type=>Row)
284
- # end
285
- # def get_row(rowi)
286
- # if @row_array_cache.has_key?(rowi)
287
- # return @row_array_cache[rowi]
288
- # end
289
- # item = self.get_item(rowi)
290
- # @row_array_cache[rowi] = if item.nil?
291
- # if rowi>0 then Rspreadsheet::UninitializedEmptyRow.new(self,rowi) else nil end
292
- # else
293
- # if item.repeated?
294
- # Rspreadsheet::MemberOfRowGroup.new(item.index, item.group.to_rowgroup)
295
- # else
296
- # Rspreadsheet::SingleRow.new_from_rowgroup(item.group.to_rowgroup)
297
- # end
298
- # end
299
- # end
300
- # # aliases
301
- # def first_unused_row_index; first_unused_index end
302
- # def worksheet; @worksheet end
303
- # def detach_of_bound_row_group(index)
304
- # super(index)
305
- # return get_row(index)
306
- # end
307
- # end
308
-
309
- # class Row
310
- # def initialize
311
- # @readonly = :unknown
312
- # @cells = {}
313
- # end
314
- # def self.empty_row_node
315
- # LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
316
- # end
317
- # def worksheet; @parent_array.worksheet end
318
- # def parent_array; @parent_array end # for debug only
319
- # def used_col_range; 1..first_unused_column_index-1 end
320
- # def used_range; used_col_range end
321
- # def first_unused_column_index; raise 'this should be redefined in subclasses' end
322
- # def cells(coli)
323
- # coli = coli.to_i
324
- # return nil if coli.to_i<=0
325
- # @cells[coli] ||= get_cell(coli)
326
- # end
327
- # end
328
-
329
- # class RowWithXMLNode < Row
330
- # attr_accessor :xmlnode
331
- # def style_name=(value); Tools.set_ns_attribute(@xmlnode,'table','style-name',value) end
332
- # def get_cell(coli)
333
- # Cell.new(self,coli,cellnodes(coli))
334
- # end
335
- # def nonemptycells
336
- # nonemptycellsindexes.collect{ |index| cells(index) }
337
- # end
338
- # def nonemptycellsindexes
339
- # used_col_range.to_a.select do |coli|
340
- # cellnode = cellnodes(coli)
341
- # !(cellnode.content.nil? or cellnode.content.empty? or cellnode.content =='') or
342
- # !cellnode.attributes.to_a.reject{ |attr| attr.name == 'number-columns-repeated'}.empty?
343
- # end
344
- # end
345
- # def cellnodes(coli)
346
- # cellnode = nil
347
- # while true
348
- # curr_coli=1
349
- # cellnode = @xmlnode.elements.select{|n| n.name=='table-cell'}.find do |el|
350
- # curr_coli += (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i
351
- # curr_coli > coli
352
- # end
353
- # unless cellnode.nil?
354
- # return cellnode
355
- # else
356
- # add_cell
357
- # end
358
- # end
359
- # end
360
- # def add_cell(repeated=1)
361
- # cell = Cell.new(self,first_unused_column_index)
362
- # Tools.set_ns_attribute(cell.xmlnode,'table','number-columns-repeated',repeated) if repeated>1
363
- # @xmlnode << cell.xmlnode
364
- # cell
365
- # end
366
- # def first_unused_column_index
367
- # 1 + @xmlnode.elements.select{|n| n.name=='table-cell'}.reduce(0) do |sum, el|
368
- # sum + (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i
369
- # end
370
- # end
371
- # end
372
-
373
- # class RowGroup < RowWithXMLNode
374
- # @readonly = :yes_always
375
- # attr_reader :range
376
- # attr_accessor :parent_array, :xmlnode
377
- # def initialize(aparent_array,arange,axmlnode=nil)
378
- # super()
379
- # @parent_array = aparent_array
380
- # @range = arange
381
- # if axmlnode.nil?
382
- # axmlnode = Row.empty_row_node
383
- # Tools.set_ns_attribute(axmlnode,'table','number-rows-repeated',range.size) if range.size>1
384
- # end
385
- # @xmlnode = axmlnode
386
- # end
387
- # # returns SingleRow if size of range is 1 and nil if it is 0 or less
388
- # def normalize
389
- # case range.size
390
- # when 2..Float::INFINITY then self
391
- # when 1 then SingleRow.new_from_rowgroup(self)
392
- # else nil
393
- # end
394
- # end
395
- # def repeated; range.size end
396
- # def repeated?; range.size>1 end
397
- # def range=(arange)
398
- # @range=arange
399
- # Tools.set_ns_attribute(@xmlnode,'table','number-rows-repeated',range.size, 1)
400
- # end
401
- # end
402
-
403
- # class SingleRow < RowWithXMLNode
404
- # @readonly = :no
405
- # attr_accessor :xmlnode
406
- # # index Integer
407
- # def initialize(aparent_array,aindex,axmlnode=nil)
408
- # super()
409
- # @parent_array = aparent_array
410
- # @index = aindex
411
- # if axmlnode.nil?
412
- # axmlnode = Row.empty_row_node
413
- # end
414
- # @xmlnode = axmlnode
415
- # end
416
- # def self.new_from_rowgroup(rg)
417
- # anode = rg.xmlnode
418
- # Tools.remove_ns_attribute(anode,'table','number-rows-repeated')
419
- # SingleRow.new(rg.parent_array,rg.range.begin,anode)
420
- # end
421
- # def normalize; self end
422
- # def repeated?; false end
423
- # def repeated; 1 end
424
- # def range; (@index..@index) end
425
- # def detach; self end
426
- # def row; @index end
427
- # def still_out_of_used_range?; false end
428
- # end
429
-
430
- # class LazyDetachableRow < Row
431
- # @readonly = :yes_but_detachable
432
- # def initialize(rowi)
433
- # super()
434
- # @index = rowi.to_i
435
- # end
436
- # def add_cell; detach.add_cell end
437
- # def style_name=(value); detach.style_name=value end
438
- # def row; @index end
439
- # end
440
-
441
- # ## there are not data in this object, they are taken from RowGroup, but this is only readonly
442
- # class MemberOfRowGroup < LazyDetachableRow
443
- # @readonly = :yes_but_detachable
444
- # extend Forwardable
445
- # delegate [:repeated?, :repeated, :xmlnode, :parent_array] => :@row_group
446
- # attr_accessor :row_group # for dubugging
447
- #
448
- # # @index Integer
449
- # # @row_group RepeatedRow
450
- # def initialize(arowi,arow_group)
451
- # super(arowi)
452
- # @row_group = arow_group
453
- # raise 'Wrong parameter given - class is '+@row_group.class.to_a unless @row_group.is_a? RowGroup
454
- # end
455
- # def detach # detaches MemberOfRowGroup from its RowGroup perhaps splitting RowGroup
456
- # @row_group.parent_array.detach(@index)
457
- # end
458
- # def get_cell(coli)
459
- # c = Cell.new(self,coli,@row_group.cellnodes(coli))
460
- # c.mode = :repeated
461
- # c
462
- # end
463
- # def first_unused_column_index
464
- # @row_group.first_unused_column_index
465
- # end
466
- # def nonemptycells
467
- # @row_group.nonemptycellsindexes.collect{ |coli| cells(coli) }
468
- # end
469
- # end
470
114
 
115
+ end
471
116
 
472
117
  end