rspreadsheet 0.2.15 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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