rspreadsheet 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b667a14fc5845f86995ec8f260650b47f92fc8b8
4
- data.tar.gz: d8311461aeb22d2d801f8615bc8d3e43a1b35707
3
+ metadata.gz: a45451dc3599fbe6519acf7c0d27bb277743a782
4
+ data.tar.gz: 25b449a6d2efd44fcbf5396275bd0ebb22dbc85f
5
5
  SHA512:
6
- metadata.gz: e742ce5a232d4d1e4a46c64bbffab4d4607ed3024cb327ad514f9c27f30053d86e2edaef6b3755927136719f667ea9076b9c79f35baf166431315e1fde0e18b7
7
- data.tar.gz: 778a31e6b50ee4d15873ec6c416361f0f4ea24476608b2555004fb54fac070117612b4bff1747678ea6a25a6e3001176bcdb22866891a6ed13f6889074a5e50a
6
+ metadata.gz: b6ed38a381b781d6b543285a783fc54346a70f61d6c99c4e1c7e18ea5e1ab3674ec0fc27cc8af647298fcbc5cc2bc66c58dda0af127967eec2db9647894e46d7
7
+ data.tar.gz: 341cce44687eeb226acc0b27df38b00951d5d2067a28898962cf9aaee4216d1a5318cb3b54784a8661a4083b5f9ec8d9660701c1656a96666a9fc42cf14a793a
data/DEVEL_BLOG.md CHANGED
@@ -10,6 +10,8 @@ See [GUIDE.md](GUIDE.md#conventions) for syntax conventions.
10
10
  * Allow any of these:
11
11
  * ``book['Spring 2014']`` as alternative to ``book.worksheets('Spring 2014')`` ?
12
12
  * ``sheet.cells.F13`` as alternative to ``sheet.cells[14,5]`` ?
13
+ * We may have problem with the @parent_row concept in cells. If cell has repeated row as parent and it is detached in some other code, the cell never finds out that its parent_row was changed. A cure to this might be, when there are no "unatacched" cells, and when all cells belong to some row. This way if row detaches it can let know to all its cells.
14
+ * Document that there is a little distinction betwean RSpreadsheet and RSpreadsheet::Workbook. The former delegates everythink to the other.
13
15
 
14
16
  Guiding ideas
15
17
  * xml document is always synchronized with the data. So the save is trivial.
@@ -31,15 +33,17 @@ Guiding ideas
31
33
 
32
34
  ### Local manual testing and releasing (to github released, ).
33
35
 
34
- gem build rspreadsheet.gemspec
35
- sudo gem install rspreadsheet-x.y.z.gem
36
- gem push rspreadsheet-x.y.z.gem
36
+ gem build rspreadsheet.gemspec -\ These two lines together are in install.sh
37
+ sudo gem install rspreadsheet-x.y.z.gem -/ which should be invoked from parent directory
38
+ gem push rspreadsheet-x.y.z.gem releases the gem, do not forgot to update version in rspreadsheet.gemspec before doing this
37
39
 
38
40
  alternative way using ``rake`` command - release is more automatic
39
41
 
40
- * ``rake build`` - builds the gem to pkg directory.
41
- * ``sudo rake install`` - If this fails with "mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h" you may want to ``sudo aptitude install ruby-dev``
42
- * Now can locally and manually use/test the gem. This should not be replacement for automated testing.
42
+ 1. build and install locally
43
+ * ``rake build`` - builds the gem to pkg directory.
44
+ * ``sudo rake install`` - If this fails with "mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h" you may want to ``sudo aptitude install ruby-dev``
45
+ * Now can locally and manually use/test the gem. This should not be replacement for automated testing. If you make some changes, repeat step 1.
46
+ * When happy, increment the version number and deploy in next step.
43
47
  * ``rake release`` - creates a version tag in git and pushes the code to github + Rubygems. After this is succesfull the new version appears as release in Github and RubyGems.
44
48
 
45
49
 
data/README.md CHANGED
@@ -53,6 +53,12 @@ Or install it yourself as:
53
53
 
54
54
  gem is hosted on Rubygems - https://rubygems.org/gems/rspreadsheet
55
55
 
56
+ If you get this or similar error
57
+
58
+ mkmf.rb can't find header files for ruby at /usr/lib/ruby/include/ruby.h
59
+
60
+ then you might not have installed libxml for ruby. I.e. in debian something like <code>sudo aptitude install ruby-libxml</code> or using equivalent command in other package managers.
61
+
56
62
  ## Motivation and Ideas
57
63
 
58
64
  This project arised from the necessity. Alhought it is not true that there are no ruby gems allowing to acess OpenDOcument spreadsheet, I did not find another decent one which would suit my needs. Most of them also look abandoned and inactive. I have investigated these options:
@@ -2,110 +2,44 @@ require 'andand'
2
2
 
3
3
  module Rspreadsheet
4
4
 
5
- class Cell
6
- attr_reader :col, :xmlnode
7
- attr_reader :parent_row # for debug only
8
- attr_accessor :mode
9
- def self.empty_cell_node
10
- LibXML::XML::Node.new('table-cell',nil, Tools.get_namespace('table'))
5
+ class RowOrNode
6
+ def mode
7
+ case
8
+ when xmlnode.nil? then :outbound
9
+ when repeated>1 then :repeated
10
+ else :regular
11
+ end
11
12
  end
12
- def initialize(aparent_row,coli,asource_node=nil)
13
- raise "First parameter should be Row object not #{aparent_row.class}" unless aparent_row.kind_of?(Rspreadsheet::Row)
14
- @mode = :regular
15
- @parent_row = aparent_row
16
- if asource_node.nil?
17
- asource_node = Cell.empty_cell_node
18
- end
19
- @xmlnode = asource_node
20
- @col = coli
13
+ end
14
+
15
+ class Cell < RowOrNode
16
+ attr_accessor :worksheet, :rowi, :coli
17
+ def initialize(aworksheet,arowi,acoli)
18
+ @worksheet = aworksheet
19
+ @rowi = arowi
20
+ @coli = acoli
21
21
  end
22
- def ns_table; @parent_row.xmlnode.doc.root.namespaces.find_by_prefix('table') end
23
- def ns_office; @parent_row.xmlnode.doc.root.namespaces.find_by_prefix('office') end
24
- def ns_text; @parent_row.xmlnode.doc.root.namespaces.find_by_prefix('text') end
25
- def to_s; value end
26
- def cell_xml; self.xmlnode.to_s; end
27
- def xml; self.xmlnode.children.first.andand.inner_xml end
28
- def coordinates; [row,col]; end
29
- def row; @parent_row.row; end
30
- def worksheet; @parent_row.worksheet; end
22
+ def row; @worksheet.rows(rowi) end
23
+ def coordinates; [rowi,coli] end
24
+
25
+ def xmlnode; @worksheet.cellxmlnode(@rowi,@coli) end
31
26
  def value
32
27
  gt = guess_cell_type
33
- if (@mode == :regular) or (@mode == @repeated)
28
+ if (self.mode == :regular) or (self.mode == :repeated)
34
29
  case
35
30
  when gt == nil then nil
36
- when gt == Float then @xmlnode.attributes['value'].to_f
37
- when gt == String then @xmlnode.elements.first.andand.content.to_s
38
- when gt == Date then Date.strptime(@xmlnode.attributes['date-value'].to_s, '%Y-%m-%d')
39
- when gt == 'percentage' then @xmlnode.attributes['value'].to_f
31
+ when gt == Float then xmlnode.attributes['value'].to_f
32
+ when gt == String then xmlnode.elements.first.andand.content.to_s
33
+ when gt == Date then Date.strptime(xmlnode.attributes['date-value'].to_s, '%Y-%m-%d')
34
+ when gt == 'percentage' then xmlnode.attributes['value'].to_f
40
35
  end
41
- elsif @mode == :outbound # needs to check whether it is still unbound
42
- if parent_row.still_out_of_used_range?
43
- nil
44
- else
45
- parent_row.normalize.cells(@col).value
46
- end
36
+ elsif self.mode == :outbound
37
+ nil
47
38
  else
48
- raise "Unknown cell mode #{@mode}"
49
- end
50
- end
51
- def value=(avalue)
52
- if @mode == :regular
53
- gt = guess_cell_type(avalue)
54
- case
55
- when gt == nil then raise 'This value type is not storable to cell'
56
- when gt == Float
57
- set_type_attribute('float')
58
- remove_all_value_attributes_and_content(@xmlnode)
59
- Tools.set_ns_attribute(@xmlnode,'office','value', avalue.to_s)
60
- @xmlnode << LibXML::XML::Node.new('p', avalue.to_f.to_s, ns_text)
61
- when gt == String then
62
- set_type_attribute('string')
63
- remove_all_value_attributes_and_content(@xmlnode)
64
- @xmlnode << LibXML::XML::Node.new('p', avalue.to_s, ns_text)
65
- when gt == Date then
66
- set_type_attribute('date')
67
- remove_all_value_attributes_and_content(@xmlnode)
68
- Tools.set_ns_attribute(@xmlnode,'office','date-value', avalue.strftime('%Y-%m-%d'))
69
- @xmlnode << LibXML::XML::Node.new('p', avalue.strftime('%Y-%m-%d'), ns_text)
70
- when gt == 'percentage'
71
- set_type_attribute('float')
72
- remove_all_value_attributes_and_content(@xmlnode)
73
- Tools.set_ns_attribute(@xmlnode,'office','value', avalue.to_f.to_s)
74
- @xmlnode << LibXML::XML::Node.new('p', (avalue.to_f*100).round.to_s+'%', ns_text)
75
- end
76
- elsif (@mode == :repeated) or (@mode == :outbound ) # Cell did not exist individually yet, detach row and create editable cell
77
- row = @parent_row.detach
78
- row.cells(@col).value = avalue
79
- else
80
- raise "Unknown cell mode #{@mode}"
81
- end
82
- end
83
- def remove_all_value_attributes_and_content(node)
84
- if att = Tools.get_ns_attribute(@xmlnode, 'office','value') then att.remove! end
85
- if att = Tools.get_ns_attribute(@xmlnode, 'office','date-value') then att.remove! end
86
- @xmlnode.content=''
87
- end
88
- def set_type_attribute(typestring)
89
- Tools.set_ns_attribute(@xmlnode,'office','value-type',typestring)
90
- end
91
- def type
92
- case
93
- when guess_cell_type == Float then :float
94
- when guess_cell_type == String then :string
95
- when guess_cell_type == Date then :date
96
- when guess_cell_type == 'percentage' then :percentage
97
- when guess_cell_type == nil then :empty
98
- else :unknown
39
+ raise "Unknown cell mode #{self.mode}"
99
40
  end
100
41
  end
101
- # use this to find node in cell xml. ex. xmlfind('.//text:a') finds all link nodes
102
- def xmlfindall(path)
103
- xmlnode.find(path)
104
- end
105
- def xmlfindfirst(path)
106
- xmlfindall(path).first
107
- end
108
- # based on @xmlnode and optionally value which is about to be assigned, guesses which type the result should be
42
+ def repeated; (Tools.get_ns_attribute_value(xmlnode, 'table', 'number-columns-repeated') || 1 ).to_i end
109
43
  def guess_cell_type(avalue=nil)
110
44
  # try guessing by value
111
45
  valueguess = case avalue
@@ -115,23 +49,25 @@ class Cell
115
49
  else nil
116
50
  end
117
51
  result = valueguess
118
-
52
+
119
53
  if valueguess.nil? # valueguess is most important
120
- # if not succesfull then try guessing by type
121
- type = @xmlnode.attributes['value-type'].to_s
122
- typeguess = case type
54
+ # if not succesfull then try guessing by type from node xml
55
+ typ = xmlnode.nil? ? 'N/A' : xmlnode.attributes['value-type']
56
+ typeguess = case typ
57
+ when nil then nil
123
58
  when 'float' then Float
124
59
  when 'string' then String
125
60
  when 'date' then Date
126
61
  when 'percentage' then :percentage
62
+ when 'N/A' then :unassigned
127
63
  else
128
- if @xmlnode.children.size == 0
64
+ if xmlnode.children.size == 0
129
65
  nil
130
66
  else
131
- raise "Unknown type from #{@xmlnode.to_s} / children size=#{@xmlnode.children.size.to_s} / type=#{type}"
67
+ raise "Unknown type at #{coordinates.to_s} from #{xmlnode.to_s} / children size=#{xmlnode.children.size.to_s} / type=#{xmlnode.attributes['value-type'].to_s}"
132
68
  end
133
69
  end
134
-
70
+
135
71
  result =
136
72
  if !typeguess.nil? # if not certain by value, but have a typeguess
137
73
  if !avalue.nil? # with value we may try converting
@@ -139,7 +75,7 @@ class Cell
139
75
  typeguess
140
76
  elsif (String(avalue) rescue false) # otherwise try string
141
77
  String
142
- else # if not convertible to anyhing concious then nil
78
+ else # if not convertible to anything concious then nil
143
79
  nil
144
80
  end
145
81
  else # without value we just beleive typeguess
@@ -155,13 +91,111 @@ class Cell
155
91
  end
156
92
  result
157
93
  end
158
- def inspect
159
- "#<Cell:[#{row},#{col}]=#{value}(#{guess_cell_type.to_s})"
94
+ def detach_if_needed
95
+ if (self.mode == :repeated) or (self.mode == :outbound ) # Cell did not exist individually yet, detach row and create editable cell
96
+ @worksheet.detach_cell_in_xml(rowi,coli)
97
+ end
98
+ end
99
+ def value=(avalue)
100
+ detach_if_needed
101
+ if self.mode == :regular
102
+ gt = guess_cell_type(avalue)
103
+ case
104
+ when gt == nil then raise 'This value type is not storable to cell'
105
+ when gt == Float
106
+ set_type_attribute('float')
107
+ remove_all_value_attributes_and_content(xmlnode)
108
+ Tools.set_ns_attribute(xmlnode,'office','value', avalue.to_s)
109
+ xmlnode << LibXML::XML::Node.new('p', avalue.to_f.to_s, ns_text)
110
+ when gt == String then
111
+ set_type_attribute('string')
112
+ remove_all_value_attributes_and_content(xmlnode)
113
+ xmlnode << LibXML::XML::Node.new('p', avalue.to_s, ns_text)
114
+ when gt == Date then
115
+ set_type_attribute('date')
116
+ remove_all_value_attributes_and_content(xmlnode)
117
+ Tools.set_ns_attribute(xmlnode,'office','date-value', avalue.strftime('%Y-%m-%d'))
118
+ xmlnode << LibXML::XML::Node.new('p', avalue.strftime('%Y-%m-%d'), ns_text)
119
+ when gt == 'percentage'
120
+ set_type_attribute('float')
121
+ remove_all_value_attributes_and_content(xmlnode)
122
+ Tools.set_ns_attribute(xmlnode,'office','value', avalue.to_f.to_s)
123
+ xmlnode << LibXML::XML::Node.new('p', (avalue.to_f*100).round.to_s+'%', ns_text)
124
+ end
125
+ else
126
+ raise "Unknown cell mode #{self.mode}"
127
+ end
128
+ end
129
+ def set_type_attribute(typestring)
130
+ Tools.set_ns_attribute(xmlnode,'office','value-type',typestring)
160
131
  end
132
+ def remove_all_value_attributes_and_content(node)
133
+ if att = Tools.get_ns_attribute(node, 'office','value') then att.remove! end
134
+ if att = Tools.get_ns_attribute(node, 'office','date-value') then att.remove! end
135
+ node.content=''
136
+ end
137
+ def ns_table; xmlnode.doc.root.namespaces.find_by_prefix('table') end
138
+ def ns_office; xmlnode.doc.root.namespaces.find_by_prefix('office') end
139
+ def ns_text; xmlnode.doc.root.namespaces.find_by_prefix('text') end
161
140
  def relative(rowdiff,coldiff)
162
- worksheet.cells(self.row+rowdiff, self.col+coldiff)
141
+ @worksheet.cells(self.rowi+rowdiff, self.coli+coldiff)
142
+ end
143
+ def is_repeated?; mode == :repeated end
144
+ def type
145
+ gct = guess_cell_type
146
+ case
147
+ when gct == Float then :float
148
+ when gct == String then :string
149
+ when gct == Date then :date
150
+ when gct == :percentage then :percentage
151
+ when gct == :unassigned then :unassigned
152
+ when gct == nil then :unknown
153
+ else :unknown
154
+ end
163
155
  end
164
156
  end
157
+
158
+
159
+ # class Cell
160
+ # attr_reader :col, :xmlnode
161
+ # attr_reader :parent_row # for debug only
162
+ # attr_accessor :mode
163
+ # def self.empty_cell_node
164
+ # LibXML::XML::Node.new('table-cell',nil, Tools.get_namespace('table'))
165
+ # end
166
+ # def initialize(aparent_row,coli,axmlnode=nil)
167
+ # raise "First parameter should be Row object not #{aparent_row.class}" unless aparent_row.kind_of?(Rspreadsheet::Row)
168
+ # @parent_row = aparent_row
169
+ # if axmlnode.nil?
170
+ # axmlnode = Cell.empty_cell_node
171
+ # end
172
+ # @xmlnode = axmlnode
173
+ # @col = coli
174
+ # # set @mode
175
+ # @mode = case
176
+ # when !@parent_row.used_col_range.include?(coli) then :outbound
177
+ # when Tools.get_ns_attribute_value(@xmlnode, 'table', 'number-columns-repeated').to_i>1 then :repeated
178
+ # else:regular
179
+ # end
180
+ # end
181
+ # def to_s; value end
182
+ # def cell_xml; self.xmlnode.to_s end
183
+ # def xml; self.xmlnode.children.first.andand.inner_xml end
184
+ # def address; Rspreadsheet::Tools.c2a(row,col) end
185
+ # def row; @parent_row.row end
186
+ # def worksheet; @parent_row.worksheet end
187
+ # # use this to find node in cell xml. ex. xmlfind('.//text:a') finds all link nodes
188
+ # def xmlfindall(path)
189
+ # xmlnode.find(path)
190
+ # end
191
+ # def xmlfindfirst(path)
192
+ # xmlfindall(path).first
193
+ # end
194
+ # # based on @xmlnode and optionally value which is about to be assigned, guesses which type the result should be
195
+ # def inspect
196
+ # "#<Rspreadsheet::Cell:Cell\n row:#{row}, col:#{col} address:#{address}\n type: #{guess_cell_type.to_s}, value:#{value}\n mode: #{mode}\n>"
197
+ # end
198
+ # end
165
199
 
166
200
  end
167
201
 
@@ -1,28 +1,120 @@
1
1
  require('rspreadsheet/cell')
2
2
  require('forwardable')
3
3
 
4
-
5
4
  # Currently this is only syntax sugar for cells and contains no functionality
6
5
 
7
6
  module Rspreadsheet
8
7
 
9
- class RowArray
10
- def initialize(aworksheet,aworksheet_node)
8
+ class Row < RowOrNode
9
+ attr_reader :worksheet, :rowi
10
+ def initialize(aworksheet,arowi)
11
11
  @worksheet = aworksheet
12
- @worksheet_node = aworksheet_node
12
+ @rowi = arowi
13
+ end
14
+ def xmlnode; @worksheet.rowxmlnode(@rowi) end
15
+ def cells(coli)
16
+ coli = coli.to_i
17
+ if coli.to_i<=0
18
+ nil
19
+ else
20
+ Cell.new(@worksheet,@rowi,coli)
21
+ end
22
+ end
23
+ def self.empty_xmlnode(repeats=1)
24
+ node = LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
25
+ Tools.set_ns_attribute(node,'table','number-rows-repeated',repeats, 1)
26
+ node
27
+ end
28
+ def detach_if_needed;
29
+ detach if repeated?
30
+ end
31
+ def detach
32
+ @worksheet.detach_row_in_xml(@rowi)
33
+ self
34
+ end
35
+ def repeated
36
+ (Tools.get_ns_attribute_value(self.xmlnode, 'table', 'number-rows-repeated') || 1).to_i
37
+ end
38
+ def repeated?; mode==:repeated || mode==:outbound end
39
+ alias :is_repeated? :repeated?
40
+ def style_name=(value);
41
+ detach_if_needed
42
+ Tools.set_ns_attribute(xmlnode,'table','style-name',value)
43
+ end
44
+ def used_range
45
+ @worksheet.rowrange(@rowi)
46
+ end
47
+ def nonemptycells
48
+ nonemptycellsindexes.collect{ |index| cells(index) }
49
+ end
50
+ def nonemptycellsindexes
51
+ @worksheet.row_nonempty_cells_col_indexes(@rowi)
52
+ end
53
+ end
13
54
 
14
- # initialize @rowgroups from @worksheet_node
15
- @rowgroups = []
16
- unless @worksheet_node.nil?
17
- @worksheet_node.elements.select{|node| node.name == 'table-row'}.each do |row_source_node|
18
- @rowgroups << prepare_row_group(row_source_node) # it is in @worksheet_node so suffices to add object to @rowgroups
55
+ # class Row
56
+ # def initialize
57
+ # @readonly = :unknown
58
+ # @cells = {}
59
+ # end
60
+ # def worksheet; @parent_array.worksheet end
61
+ # def parent_array; @parent_array end # for debug only
62
+ # def used_col_range; 1..first_unused_column_index-1 end
63
+ # def used_range; used_col_range end
64
+ # def first_unused_column_index; raise 'this should be redefined in subclasses' end
65
+ # end
66
+
67
+
68
+ # --------------------------
69
+
70
+
71
+ # XmlTiedArrayItemGroup is internal representation of repeated items in XmlTiedArray.
72
+ class XmlTiedArrayItemGroup
73
+ # extend Forwardable
74
+ # delegate [:normalize ] => :@row_group
75
+
76
+ def normalize; @rowgroup.normalize end
77
+ def range; @rowgroup.range end
78
+ def repeated?; self.range.size>1 end
79
+ def xmlnode; @rowgroup.xmlnode end
80
+
81
+ def initialize(aparent_array,arange,axmlnode=nil)
82
+ @rowgroup = RowGroup.new(aparent_array,arange,axmlnode)
83
+ end
84
+ def self.new_from_xml
85
+ end
86
+ def to_rowgroup
87
+ @rowgroup
88
+ end
89
+ def range=(arange)
90
+
91
+ end
92
+ end
93
+
94
+ # array which synchronizes with xml structure and reflects. number-xxx-repeated attributes
95
+ # also caches returned objects for indexes.
96
+ # options must contain
97
+ # :xml_items, :xml_repeated_attribute, :object_type
98
+
99
+ class XmlTiedArray < Array
100
+ def initialize(axmlnode, options={}) # TODO get rid of XmlTiedArray
101
+ @xmlnode = axmlnode
102
+ @options = options
103
+
104
+ missing_options = [:xml_repeated_attribute,:xml_items_node_name,:object_type]-@options.keys
105
+ raise "Some options missing (#{missing_options.inspect})" unless missing_options.empty?
106
+
107
+ unless @xmlnode.nil?
108
+ @xmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |group_source_node|
109
+ self << parse_xml_to_group(group_source_node) # it is in @xmlnode so suffices to add object to @rowgroups
19
110
  end
20
111
  end
112
+ @itemcache=Hash.new()
21
113
  end
22
- def prepare_row_group(size_or_xmlnode) # appends new RowGroup at the end
114
+ def parse_xml_to_group(size_or_xmlnode) # parses xml to new RowGroup which can be added at the end
23
115
  # reading params
24
116
  if size_or_xmlnode.kind_of? LibXML::XML::Node
25
- size = (size_or_xmlnode['number-rows-repeated'] || 1).to_i
117
+ size = (size_or_xmlnode[@options[:xml_repeated_attribute]] || 1).to_i
26
118
  node = size_or_xmlnode
27
119
  elsif size_or_xmlnode.to_i>0
28
120
  size = size_or_xmlnode.to_i
@@ -30,66 +122,61 @@ class RowArray
30
122
  else
31
123
  return nil
32
124
  end
33
- index = first_unused_row_index
34
-
125
+ index = first_unused_index
35
126
  # construct result
36
- RowGroup.new(self,index..index+size-1,node).normalize
127
+ Rspreadsheet::XmlTiedArrayItemGroup.new(self,index..index+size-1,node)
37
128
  end
38
- def add_row_group(size_or_xmlnode)
39
- result = prepare_row_group(size_or_xmlnode)
40
- @rowgroups << result
41
- @worksheet_node << result.xmlnode
129
+ def add_item_group(size_or_xmlnode)
130
+ result = parse_xml_to_group(size_or_xmlnode)
131
+ self << result
132
+ @xmlnode << result.xmlnode
42
133
  result
43
134
  end
44
- def get_row_group(rowi)
45
- @rowgroups.find{ |rowgroup| rowgroup.range.cover?(rowi) }
46
- end
47
- def get_row(rowi)
48
- rg = get_row_group(rowi).andand.normalize
49
- case rg
50
- when SingleRow then rg
51
- when RowGroup then MemberOfRowGroup.new(rowi, rg)
52
- when nil
53
- if rowi>0 then UninitializedEmptyRow.new(self,rowi) else nil end
54
- else raise
55
- end
135
+ def first_unused_index
136
+ empty? ? 1 : last.range.end+1
56
137
  end
57
138
  # prolonges the RowArray to cantain rowi and returns it
58
- def detach_of_bound_row_group(rowi)
59
- fill_row_group_size = rowi-first_unused_row_index
139
+ def detach_of_bound_item(index)
140
+ fill_row_group_size = index-first_unused_index
60
141
  if fill_row_group_size>0
61
- add_row_group(fill_row_group_size)
142
+ add_item_group(fill_row_group_size)
62
143
  end
63
- add_row_group(1)
64
- return get_row(rowi)
144
+ add_item_group(1)
145
+ get_item(index) # aby se odpoved nacacheovala
146
+ end
147
+ def get_item_group(index)
148
+ find{ |item_group| item_group.range.cover?(index) }
65
149
  end
66
- def first_unused_row_index
67
- if @rowgroups.empty?
68
- 1
150
+ def detach_item(index); get_item(index) end # TODO předělat do lazy podoby, kdy tohle nebude stejny
151
+ def get_item(index)
152
+ if index>= first_unused_index
153
+ nil
69
154
  else
70
- @rowgroups.last.range.end+1
155
+ @itemcache[index] ||= Rspreadsheet::XmlTiedArrayItem.new(self,index)
71
156
  end
72
157
  end
73
- # This detaches row rowi from the group and perhaps splits the RowGroup
158
+ # This detaches item index from the group and perhaps splits the RowGroup
74
159
  # into two pieces. This makes the row individually editable.
75
- def detach(rowi)
76
- index = get_row_group_index(rowi)
77
- row_group = @rowgroups[index]
78
- range = row_group.range
160
+ def detach(index)
161
+ group_index = get_group_index(index)
162
+ item_group = self[group_index]
163
+ range = item_group.range
164
+ return self if range==(index..index)
79
165
 
80
166
  # prepare new components
81
167
  replaceby = []
82
- replaceby << RowGroup.new(self,range.begin..rowi-1)
83
- replaceby << (result = SingleRow.new(self,rowi))
84
- replaceby << RowGroup.new(self,rowi+1..range.end)
168
+ replaceby << RowGroup.new(self,range.begin..index-1)
169
+ replaceby << (result = SingleRow.new(self,index))
170
+ replaceby << RowGroup.new(self,index+1..range.end)
85
171
 
86
172
  # put original range somewhere in replaceby and shorten it
87
- if rowi>range.begin
88
- replaceby[0] = row_group
89
- row_group.range = range.begin..rowi-1
173
+
174
+ if index>range.begin
175
+ replaceby[0] = item_group
176
+ item_group.range = range.begin..index-1
90
177
  else
91
- replaceby[2] = row_group
92
- row_group.range = rowi+1..range.end
178
+ replaceby[2] = item_group
179
+ item_group.range = index+1..range.end
93
180
  end
94
181
 
95
182
  # normalize and delete empty parts
@@ -97,40 +184,98 @@ class RowArray
97
184
 
98
185
  # do the replacement in xml
99
186
  marker = LibXML::XML::Node.new('temporarymarker')
100
- row_group.xmlnode.next = marker
101
- row_group.xmlnode.remove!
187
+ item_group.xmlnode.next = marker
188
+ item_group.xmlnode.remove!
102
189
  replaceby.each{ |rg|
103
190
  marker.prev = rg.xmlnode
104
191
  }
105
192
  marker.remove!
106
193
 
107
194
  # do the replacement in array
108
- @rowgroups[index..index]=replaceby
195
+ self[group_index..group_index]=replaceby
109
196
  result
110
197
  end
111
- def worksheet; @worksheet end
112
- private
113
- def get_row_group_index(rowi)
114
- @rowgroups.find_index{ |rowgroup| rowgroup.range.cover?(rowi) }
198
+ private
199
+ def get_group_index(index)
200
+ self.find_index{ |rowgroup| rowgroup.range.cover?(index) }
115
201
  end
116
202
  end
117
203
 
118
- class Row
119
- @readonly = :unknown
120
- # ? @rowindex
121
- def self.empty_row_node
122
- LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
123
- end
124
- def worksheet; @parent_array.worksheet end
125
- def parent_array; @parent_array end # for debug only
204
+ class XmlTiedArrayItem
205
+ attr_reader :index
206
+ def initialize(aarray,aindex)
207
+ @array = aarray
208
+ @index = aindex
209
+ if self.virtual?
210
+ @object = nil
211
+ else
212
+ @object = @array.options[:object_type].new(group.xmlnode)
213
+ end
214
+ end
215
+ def group; @array.get_item_group(index) end
216
+ def repeated?; group.repeated? end
217
+ def virtual?; ! self.repeated? end
218
+ def array
219
+ raise 'Group empty' if @group.nil?
220
+ @array
221
+ end
126
222
  end
127
223
 
224
+ class RowArray < XmlTiedArray
225
+ attr_reader :row_array_cache
226
+ def initialize(aworksheet,aworksheet_node)
227
+ @worksheet = aworksheet
228
+ @row_array_cache = Hash.new()
229
+ super(aworksheet_node, :xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated', :object_type=>Row)
230
+ end
231
+ def get_row(rowi)
232
+ if @row_array_cache.has_key?(rowi)
233
+ return @row_array_cache[rowi]
234
+ end
235
+ item = self.get_item(rowi)
236
+ @row_array_cache[rowi] = if item.nil?
237
+ if rowi>0 then Rspreadsheet::UninitializedEmptyRow.new(self,rowi) else nil end
238
+ else
239
+ if item.repeated?
240
+ Rspreadsheet::MemberOfRowGroup.new(item.index, item.group.to_rowgroup)
241
+ else
242
+ Rspreadsheet::SingleRow.new_from_rowgroup(item.group.to_rowgroup)
243
+ end
244
+ end
245
+ end
246
+ # aliases
247
+ def first_unused_row_index; first_unused_index end
248
+ def worksheet; @worksheet end
249
+ def detach_of_bound_row_group(index)
250
+ super(index)
251
+ return get_row(index)
252
+ end
253
+ end
254
+
255
+ # class Row
256
+ # def initialize
257
+ # @readonly = :unknown
258
+ # @cells = {}
259
+ # end
260
+ # def self.empty_row_node
261
+ # LibXML::XML::Node.new('table-row',nil, Tools.get_namespace('table'))
262
+ # end
263
+ # def worksheet; @parent_array.worksheet end
264
+ # def parent_array; @parent_array end # for debug only
265
+ # def used_col_range; 1..first_unused_column_index-1 end
266
+ # def used_range; used_col_range end
267
+ # def first_unused_column_index; raise 'this should be redefined in subclasses' end
268
+ # def cells(coli)
269
+ # coli = coli.to_i
270
+ # return nil if coli.to_i<=0
271
+ # @cells[coli] ||= get_cell(coli)
272
+ # end
273
+ # end
274
+
128
275
  class RowWithXMLNode < Row
129
276
  attr_accessor :xmlnode
130
277
  def style_name=(value); Tools.set_ns_attribute(@xmlnode,'table','style-name',value) end
131
- def cells(coli)
132
- coli = coli.to_i
133
- return nil if coli.to_i<=0
278
+ def get_cell(coli)
134
279
  Cell.new(self,coli,cellnodes(coli))
135
280
  end
136
281
  def nonemptycells
@@ -143,9 +288,6 @@ class RowWithXMLNode < Row
143
288
  !cellnode.attributes.to_a.reject{ |attr| attr.name == 'number-columns-repeated'}.empty?
144
289
  end
145
290
  end
146
- def used_col_range
147
- 1..first_unused_column_index-1
148
- end
149
291
  def cellnodes(coli)
150
292
  cellnode = nil
151
293
  while true
@@ -167,10 +309,6 @@ class RowWithXMLNode < Row
167
309
  @xmlnode << cell.xmlnode
168
310
  cell
169
311
  end
170
- def used_range
171
- fu = first_unused_column_index
172
- (fu>1) ? 1..fu : nil
173
- end
174
312
  def first_unused_column_index
175
313
  1 + @xmlnode.elements.select{|n| n.name=='table-cell'}.reduce(0) do |sum, el|
176
314
  sum + (Tools.get_ns_attribute_value(el, 'table', 'number-columns-repeated') || 1).to_i
@@ -183,6 +321,7 @@ class RowGroup < RowWithXMLNode
183
321
  attr_reader :range
184
322
  attr_accessor :parent_array, :xmlnode
185
323
  def initialize(aparent_array,arange,axmlnode=nil)
324
+ super()
186
325
  @parent_array = aparent_array
187
326
  @range = arange
188
327
  if axmlnode.nil?
@@ -212,6 +351,7 @@ class SingleRow < RowWithXMLNode
212
351
  attr_accessor :xmlnode
213
352
  # index Integer
214
353
  def initialize(aparent_array,aindex,axmlnode=nil)
354
+ super()
215
355
  @parent_array = aparent_array
216
356
  @index = aindex
217
357
  if axmlnode.nil?
@@ -222,21 +362,21 @@ class SingleRow < RowWithXMLNode
222
362
  def self.new_from_rowgroup(rg)
223
363
  anode = rg.xmlnode
224
364
  Tools.remove_ns_attribute(anode,'table','number-rows-repeated')
225
-
226
365
  SingleRow.new(rg.parent_array,rg.range.begin,anode)
227
366
  end
228
367
  def normalize; self end
229
368
  def repeated?; false end
230
369
  def repeated; 1 end
231
370
  def range; (@index..@index) end
232
- def detach; true end
371
+ def detach; self end
233
372
  def row; @index end
234
-
373
+ def still_out_of_used_range?; false end
235
374
  end
236
375
 
237
376
  class LazyDetachableRow < Row
238
377
  @readonly = :yes_but_detachable
239
378
  def initialize(rowi)
379
+ super()
240
380
  @index = rowi.to_i
241
381
  end
242
382
  def add_cell; detach.add_cell end
@@ -256,13 +396,18 @@ class MemberOfRowGroup < LazyDetachableRow
256
396
  def initialize(arowi,arow_group)
257
397
  super(arowi)
258
398
  @row_group = arow_group
259
- raise 'Wrong parameter given' unless @row_group.is_a? RowGroup
399
+ raise 'Wrong parameter given - class is '+@row_group.class.to_a unless @row_group.is_a? RowGroup
260
400
  end
261
401
  def detach # detaches MemberOfRowGroup from its RowGroup perhaps splitting RowGroup
262
402
  @row_group.parent_array.detach(@index)
263
403
  end
264
- def cells(coli)
265
- Cell.new(self,coli,@row_group.cellnodes(coli)).tap{|n| n.mode = :repeated}
404
+ def get_cell(coli)
405
+ c = Cell.new(self,coli,@row_group.cellnodes(coli))
406
+ c.mode = :repeated
407
+ c
408
+ end
409
+ def first_unused_column_index
410
+ @row_group.first_unused_column_index
266
411
  end
267
412
  def nonemptycells
268
413
  @row_group.nonemptycellsindexes.collect{ |coli| cells(coli) }
@@ -277,9 +422,9 @@ class UninitializedEmptyRow < LazyDetachableRow
277
422
  super(arowi)
278
423
  @parent_array = aparent_array
279
424
  end
280
- def cells(coli)
425
+ def get_cell(coli)
281
426
  if still_out_of_used_range?
282
- Cell.new(self,coli,Cell.empty_cell_node).tap{|n| n.mode = :outbound}
427
+ Cell.new(self,coli,nil)
283
428
  else
284
429
  @parent_array.get_row(@index).cells(coli)
285
430
  end
@@ -291,10 +436,13 @@ class UninitializedEmptyRow < LazyDetachableRow
291
436
  @parent_array.get_row(@index)
292
437
  end
293
438
  end
294
- def detach; @parent_array.detach_of_bound_row_group(@index) end
439
+ def detach; @parent_array.detach_item(@index) end
440
+ def detach_cell(col)
441
+ self.detach.detach_cell(col)
442
+ end
295
443
  def still_out_of_used_range?; @index >= @parent_array.first_unused_row_index end
296
444
  def xmlnode; Row.empty_row_node end
297
- def nonemptycells; [] end
445
+ def first_unused_column_index; 1 end
298
446
  end
299
447
 
300
448
  end