rspreadsheet 0.2.0 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,9 +14,18 @@ module Tools
14
14
  else
15
15
  raise 'Wrong number of arguments'
16
16
  end
17
-
18
- colname=colname.rjust(3,'@')
19
- col = (colname[-1].ord-64)+(colname[-2].ord-64)*26+(colname[-3].ord-64)*26*26
17
+
18
+ ## first possibility how to implement it
19
+ # colname=colname.rjust(3,'@')
20
+ # col = (colname[-1].ord-64)+(colname[-2].ord-64)*26+(colname[-3].ord-64)*26*26
21
+
22
+ ## second possibility how to implement it
23
+ # col=(colname.to_i(36)-('A'*colname.size).to_i(36)).to_s(36).to_i(26)+('1'*colname.size).to_i(26)
24
+
25
+ ## third possibility how to implement it (second one little shortened)
26
+ s=colname.size
27
+ col=(colname.to_i(36)-(36**s-1).div(3.5)).to_s(36).to_i(26)+(26**s-1)/25
28
+
20
29
  row = rowname.to_i
21
30
  return [row,col]
22
31
  end
@@ -115,8 +124,8 @@ module Tools
115
124
  node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
116
125
  attr.remove! unless attr.nil?
117
126
  end
118
- def self.create_ns_node(nodename,ns_prefix)
119
- LibXML::XML::Node.new(nodename,nil, Tools.get_namespace(ns_prefix))
127
+ def self.create_ns_node(ns_prefix,nodename,value=nil)
128
+ LibXML::XML::Node.new(nodename,value, Tools.get_namespace(ns_prefix))
120
129
  end
121
130
  end
122
131
 
@@ -1,3 +1,3 @@
1
1
  module Rspreadsheet
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.3"
3
3
  end
@@ -5,145 +5,53 @@ require 'rspreadsheet/tools'
5
5
  module Rspreadsheet
6
6
 
7
7
  class Worksheet
8
+ include XMLTiedArray
8
9
  attr_accessor :name, :xmlnode
9
- # extend Forwardable
10
- # def_delegators :nonemptycells
10
+ def subitem_xml_options; {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'} end
11
11
 
12
12
  def initialize(xmlnode_or_sheet_name)
13
- @rowcache=[]
13
+ @itemcache = Hash.new #TODO: move to module XMLTiedArray
14
14
  # set up the @xmlnode according to parameter
15
15
  case xmlnode_or_sheet_name
16
16
  when LibXML::XML::Node
17
17
  @xmlnode = xmlnode_or_sheet_name
18
18
  when String
19
- @xmlnode = LibXML::XML::Node.new('table')
20
- ns = LibXML::XML::Namespace.new(@xmlnode, 'table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0')
21
- @xmlnode .namespaces.namespace = ns
22
- @xmlnode['table:name'] = xmlnode_or_sheet_name
19
+ @xmlnode = Tools.create_ns_node('table','table')
20
+ Tools.set_ns_attribute(@xmlnode,'table','name', xmlnode_or_sheet_name)
23
21
  else raise 'Provide name or xml node to create a Worksheet object'
24
22
  end
25
23
  end
26
24
 
27
25
  def rowxmlnode(rowi)
28
- find_subnode_respect_repeated(@xmlnode, rowi, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
26
+ find_my_subnode_respect_repeated(rowi, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
29
27
  end
30
-
31
- def rowrange(rowi)
32
- find_subnode_range_respect_repeated(@xmlnode, rowi, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
33
- end
34
-
35
- def row_nonempty_cells_col_indexes(rowi)
36
- arowxmlnode = rowxmlnode(rowi)
37
- if arowxmlnode.nil?
38
- []
39
- else
40
- find_nonempty_subnode_indexes(arowxmlnode, {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'})
41
- end
28
+
29
+ def first_unused_row_index
30
+ find_first_unused_index_respect_repeated({:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
42
31
  end
43
32
 
44
- def cellxmlnode(rowi,coli)
45
- arowxmlnode = rowxmlnode(rowi)
46
- if arowxmlnode.nil?
47
- nil
48
- else
49
- find_subnode_respect_repeated(arowxmlnode, coli, {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'})
50
- end
51
- end
52
-
53
- def cellrange(coli)
54
- find_subnode_range_respect_repeated(@xmlnode, rowi, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
33
+ def insert_row_above(arowi)
34
+ insert_subitem_before(arowi)
55
35
  end
56
36
 
57
- def first_unused_row_index
58
- find_first_unused_index_respect_repeated(xmlnode, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
37
+ def insert_cell_before(arowi,acoli)
38
+ detach_row_in_xml(arowi)
39
+ rows(arowi).insert_subitem_before(acoli)
59
40
  end
60
41
 
61
42
  def detach_row_in_xml(rowi)
62
- return detach_subnode_respect_repeated(xmlnode, rowi, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
63
- end
64
- def detach_cell_in_xml(rowi,coli)
65
- rownode = detach_row_in_xml(rowi)
66
- return detach_subnode_respect_repeated(rownode, coli, {:xml_items_node_name => 'table-cell', :xml_repeated_attribute => 'number-columns-repeated'})
43
+ return detach_my_subnode_respect_repeated(rowi, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
67
44
  end
68
45
 
69
- def detach_subnode_respect_repeated(axmlnode,aindex, options)
70
- index = 0
71
- axmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
72
- repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
73
- oldindex = index
74
- index = index+repeated
75
- if index>= aindex # found the node, now do the detachement
76
- ranges = [oldindex+1..aindex-1,aindex..aindex,aindex+1..index].reject {|range| range.size<1}
77
- ranges.each do |range|
78
- newnode = node.copy(true)
79
- Tools.set_ns_attribute(newnode,'table',options[:xml_repeated_attribute],range.size,1)
80
- node.prev = newnode
81
- end
82
- node.remove!
83
- return find_subnode_respect_repeated(axmlnode, aindex, options)
84
- end
85
- end
86
- # add outbound xmlnode
87
- [index+1..aindex-1,aindex..aindex].reject {|range| range.size<1}.each do |range|
88
- node = LibXML::XML::Node.new(options[:xml_items_node_name],nil, Tools.get_namespace('table'))
89
- Tools.set_ns_attribute(node,'table',options[:xml_repeated_attribute],range.size, 1)
90
- axmlnode << node
91
- end
92
- find_subnode_respect_repeated(axmlnode, aindex, options)
93
- end
94
-
95
- def find_subnode_respect_repeated(axmlnode, aindex, options)
96
- index = 0
97
- axmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
98
- repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
99
- index = index+repeated
100
- return node if index>= aindex
101
- end
102
- return nil
103
- end
104
- def find_subnode_range_respect_repeated(axmlnode, aindex, options)
105
- index = 0
106
- axmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
107
- repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
108
- if index+repeated >= aindex
109
- return (index+1..index+repeated)
110
- else
111
- index = index+repeated
112
- end
113
- end
114
- return (index+1..Float::INFINITY)
46
+ def nonemptycells
47
+ used_rows_range.collect{ |rowi| rows(rowi).nonemptycells }.flatten
115
48
  end
116
49
 
117
- def find_nonempty_subnode_indexes(axmlnode, options)
118
- index = 0
119
- result = []
120
- axmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
121
- repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
122
- index = index + repeated
123
- if !(node.content.nil? or node.content.empty? or node.content =='') and (repeated==1)
124
- result << index
125
- end
126
- end
127
- return result
128
- end
129
- def find_first_unused_index_respect_repeated(axmlnode, options)
130
- index = 0
131
- axmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
132
- repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
133
- index = index+repeated
134
- end
135
- return index+1
136
- end
50
+ # rozšíření XMLTiedArray
51
+ def rows(rowi); subitem(rowi) end
52
+ def prepare_subitem(rowi); Row.new(self,rowi) end
53
+ def rowcache; @itemcache end
137
54
 
138
- def cells(r,c)
139
- rows(r).andand.cells(c)
140
- end
141
- def nonemptycells
142
- used_rows_range.collect{ |rowi| rows(rowi) }.collect { |row| row.nonemptycells }.flatten
143
- end
144
- def rows(rowi)
145
- @rowcache[rowi] ||= Row.new(self,rowi) unless rowi<=0
146
- end
147
55
  ## syntactic sugar follows
148
56
  def [](r,c)
149
57
  cells(r,c).andand.value
@@ -151,6 +59,9 @@ class Worksheet
151
59
  def []=(r,c,avalue)
152
60
  cells(r,c).andand.value=avalue
153
61
  end
62
+ def cells(r,c)
63
+ rows(r).andand.cells(c)
64
+ end
154
65
  # allows syntax like sheet.F15
155
66
  def method_missing method_name, *args, &block
156
67
  if method_name.to_s.match(/^([A-Z]{1,3})(\d{1,8})(=?)$/)
@@ -0,0 +1,234 @@
1
+ module Rspreadsheet
2
+
3
+ class XMLTied
4
+ def xml
5
+ xmlnode.to_s
6
+ end
7
+ end
8
+
9
+ # abstrac class. All successort MUST implement: set_index,xml_options,parent,index
10
+ class XMLTiedItem < XMLTied
11
+ def mode
12
+ case
13
+ when xmlnode.nil? then :outbound
14
+ when repeated>1 then :repeated
15
+ else :regular
16
+ end
17
+ end
18
+ def repeated; (Tools.get_ns_attribute_value(xmlnode, 'table', xml_repeated_attribute) || 1 ).to_i end
19
+ def repeated?; mode==:repeated || mode==:outbound end
20
+ alias :is_repeated? :repeated?
21
+ def xmlnode
22
+ parentnode = parent.xmlnode
23
+ if parentnode.nil?
24
+ nil
25
+ else
26
+ parent.find_my_subnode_respect_repeated(index, xml_options)
27
+ end
28
+ end
29
+ def detach_if_needed
30
+ detach if repeated? # item did not exist individually yet, detach it within its parent and therefore make it individally editable
31
+ end
32
+ def detach
33
+ parent.detach_if_needed if parent.respond_to?(:detach_if_needed)
34
+ parent.detach_my_subnode_respect_repeated(index, xml_options)
35
+ self
36
+ end
37
+ def shift_by(diff)
38
+ set_index(index + diff)
39
+ end
40
+ def range
41
+ parent.find_my_subnode_range_respect_repeated(index,xml_options)
42
+ end
43
+ def invalid_reference?; false end
44
+ # destroys the object so it can not be used, this is necessarry to prevent
45
+ # accessing cells and rows which has been long time ago deleted and do not represent
46
+ # any physical object anymore
47
+ def invalidate_myself
48
+ raise_destroyed_cell_error = Proc.new {|*params| raise "Calling method of already destroyed Cell."}
49
+ (self.methods - Object.methods + [:nil?]).each do |method| # "undefine" all methods
50
+ self.singleton_class.send(:define_method, method, raise_destroyed_cell_error)
51
+ end
52
+ self.singleton_class.send(:define_method, :inspect, -> { "#<%s:0x%x destroyed cell>" % [self.class,object_id] }) # define descriptive inspect
53
+ self.singleton_class.send(:define_method, :invalid_reference?, -> { true }) # define invalid_reference? method
54
+ # self.instance_variables.each do |variable|
55
+ # instance_variable_set(variable,nil)
56
+ # end
57
+ end
58
+ def delete
59
+ parent.delete_subitem(index)
60
+ invalidate_myself
61
+ end
62
+ end
63
+
64
+ # abstrac class. All successort MUST implement: prepare_subitem
65
+ # terminology
66
+ # item, subitem is object from @itemcache (quite often subclass of XMLTiedItem)
67
+ # node, subnode is LibXML::XML::Node object
68
+
69
+ module XMLTiedArray
70
+ attr_reader :itemcache
71
+
72
+ def find_my_subnode_range_respect_repeated(aindex, options)
73
+ index = 0
74
+ xmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
75
+ repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
76
+ if index+repeated >= aindex
77
+ return (index+1..index+repeated)
78
+ else
79
+ index = index+repeated
80
+ end
81
+ end
82
+ return (index+1..Float::INFINITY)
83
+ end
84
+
85
+ # vrátí xmlnode na souřadnici aindex
86
+ def find_my_subnode_respect_repeated(aindex, options)
87
+ find_subnode_respect_repeated(xmlnode,aindex, options)
88
+ end
89
+ # vrátí item na souřadnici aindex
90
+ def subitem(aindex)
91
+ aindex = aindex.to_i
92
+ if aindex.to_i<=0
93
+ nil
94
+ else
95
+ @itemcache[aindex] ||= prepare_subitem(aindex)
96
+ end
97
+ end
98
+
99
+ def subitems_array
100
+ (1..(find_first_unused_index_respect_repeated(subitem_xml_options)-1)).collect do |i|
101
+ subitem(i)
102
+ end
103
+ end
104
+
105
+ def find_subnode_respect_repeated(axmlnode, aindex, options)
106
+ result1, result2 = find_subnode_with_range_respect_repeated(axmlnode, aindex, options)
107
+ return result1
108
+ end
109
+
110
+ def find_subnode_with_range_respect_repeated(axmlnode, aindex, options)
111
+ index = 0
112
+ axmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
113
+ repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
114
+ oldindex = index
115
+ index = index+repeated
116
+ if index>= aindex
117
+ return node, oldindex..index
118
+ end
119
+ end
120
+ return nil, index..Float::INFINITY
121
+ end
122
+
123
+ def prepare_repeated_subnode(times_repeated,options)
124
+ result = LibXML::XML::Node.new(options[:xml_items_node_name],nil, Tools.get_namespace('table'))
125
+ Tools.set_ns_attribute(result,'table',options[:xml_repeated_attribute],times_repeated, 1)
126
+ result
127
+ end
128
+
129
+ def clone_before_and_set_repeated_attribute(node,times_repeated,options)
130
+ newnode = node.copy(true)
131
+ Tools.set_ns_attribute(newnode,'table',options[:xml_repeated_attribute],times_repeated,1)
132
+ node.prev = newnode
133
+ end
134
+
135
+ # detaches subnode with aindex
136
+ def detach_my_subnode_respect_repeated(aindex, options)
137
+ axmlnode = xmlnode
138
+ node,index_range = find_subnode_with_range_respect_repeated(axmlnode, aindex, options)
139
+ if index_range.size > 1 # pokud potřebuje vůbec detachovat
140
+ if !node.nil? # detach subnode
141
+ [index_range.begin+1..aindex-1,aindex..aindex,aindex+1..index_range.end].reject {|range| range.size<1}.each do |range| # create new structure by cloning
142
+ clone_before_and_set_repeated_attribute(node,range.size,options)
143
+ end
144
+ node.remove! # remove the original node
145
+ else # add outbound xmlnode
146
+ [index_range.begin+1..aindex-1,aindex..aindex].reject {|range| range.size<1}.each do |range|
147
+ axmlnode << prepare_repeated_subnode(range.size, options)
148
+ end
149
+ end
150
+ end
151
+ return find_subnode_respect_repeated(axmlnode, aindex, options)
152
+ end
153
+
154
+ def insert_my_subnode_before_respect_repeated(aindex, options)
155
+ axmlnode = xmlnode
156
+
157
+ node,index_range = find_subnode_with_range_respect_repeated(axmlnode, aindex, options)
158
+
159
+ if !node.nil? # found the node, now do the insert
160
+ [index_range.begin+1..aindex-1,aindex..index_range.end].reject {|range| range.size<1}.each do |range| # split original node by cloning
161
+ clone_before_and_set_repeated_attribute(node,range.size,options)
162
+ end
163
+ clone_before_and_set_repeated_attribute(node.prev,1,options) # insert new node
164
+ node.remove! # remove the original node
165
+ else # insert outbound xmlnode
166
+ [index+1..aindex-1,aindex..aindex].reject {|range| range.size<1}.each do |range|
167
+ axmlnode << XMLTiedArray.prepare_repeated_subnode(range.size, options)
168
+ end
169
+ end
170
+ return find_subnode_respect_repeated(axmlnode, aindex, options)
171
+ end
172
+
173
+ def delete_my_subnode_respect_repeated(aindex,options)
174
+ detach_my_subnode_respect_repeated(aindex,options) #TODO: tohle neni uplne spravne, protoze to zanecha skupinu rozdelenou na dve casti
175
+ subitem(aindex).xmlnode.remove!
176
+ end
177
+
178
+ def find_first_unused_index_respect_repeated(options)
179
+ index = 0
180
+ xmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
181
+ repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
182
+ index = index+repeated
183
+ end
184
+ return index+1
185
+ end
186
+
187
+ def insert_subitem_before(aindex)
188
+ insert_subitem_before_with_options(aindex,subitem_xml_options)
189
+ end
190
+ def insert_subitem_before_with_options(aindex,options)
191
+ @itemcache.keys.sort.reverse.select{|i| i>=aindex }.each do |i|
192
+ @itemcache[i+1]=@itemcache.delete(i)
193
+ @itemcache[i+1].shift_by(1)
194
+ end
195
+ insert_my_subnode_before_respect_repeated(aindex,options) # nyní vlož node do xml
196
+ @itemcache[aindex] = subitem(aindex)
197
+ end
198
+
199
+ # clean up item from xml (handle possible detachments) and itemcache. leave the object invalidation on the object
200
+ # this should not be called from nowhere but XMLTiedItem.delete
201
+ def delete_subitem(aindex)
202
+ options = subitem_xml_options
203
+ delete_my_subnode_respect_repeated(aindex,options) # vymaž node z xml
204
+ @itemcache.delete(aindex)
205
+ @itemcache.keys.sort.select{|i| i>=aindex+1 }.each do |i|
206
+ @itemcache[i-1]=@itemcache.delete(i)
207
+ @itemcache[i-1].shift_by(-1)
208
+ end
209
+ end
210
+
211
+ def delete
212
+ @itemcache.each do |key,item|
213
+ item.delete # delete item - this destroys its subitems, xmlnode and invalidates it
214
+ @itemcache.delete(key) # delete the entry from the hash, normally this would mean this ceases to exist, if user does not have reference stored somewhere. Of he does, the object is invalidated anyways
215
+ end
216
+ super # this for example for Row objects calls XMLTiedItem.delete
217
+ end
218
+
219
+ def find_nonempty_subnode_indexes(axmlnode, options)
220
+ index = 0
221
+ result = []
222
+ axmlnode.elements.select{|node| node.name == options[:xml_items_node_name]}.each do |node|
223
+ repeated = (node.attributes[options[:xml_repeated_attribute]] || 1).to_i
224
+ index = index + repeated
225
+ if !(node.content.nil? or node.content.empty? or node.content =='') and (repeated==1)
226
+ result << index
227
+ end
228
+ end
229
+ return result
230
+ end
231
+
232
+ end
233
+
234
+ end
data/lib/rspreadsheet.rb CHANGED
@@ -1,4 +1,4 @@
1
- require "rspreadsheet/version"
1
+ require 'rspreadsheet/version'
2
2
  require 'rspreadsheet/workbook'
3
3
  require 'rspreadsheet/worksheet'
4
4
  require 'class_extensions'
data/reinstall2.sh ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ cd rspreadsheet
4
+ #rm rspreadsheet-0.*.gem
5
+ #rake build
6
+ sudo rake install
7
+ cd ..
data/rspreadsheet.gemspec CHANGED
@@ -22,19 +22,18 @@ Gem::Specification.new do |spec|
22
22
  spec.add_runtime_dependency 'libxml-ruby', '~>2.7' # parsing XML files
23
23
  spec.add_runtime_dependency 'rubyzip', '~>1.1' # opening zip files
24
24
  spec.add_runtime_dependency 'andand', '~>1.3'
25
-
26
-
25
+
27
26
  # development dependencies
28
27
  spec.add_development_dependency "bundler", "~> 1.5"
29
28
  spec.add_development_dependency "rake", '~>0.9'
30
29
  # testig - see http://bit.ly/1n5yM51
31
- spec.add_development_dependency "rspec", '~>2' # testing
32
- spec.add_development_dependency 'pry-nav', '~>0' # enables pry 'next', 'step' commands
33
-
30
+ spec.add_development_dependency "rspec", '~>2' # testing
31
+ spec.add_development_dependency 'pry-nav', '~>0' # enables pry 'next', 'step' commands
32
+
34
33
  # optional and testing
35
34
  spec.add_development_dependency "coveralls", '~>0.7'
36
35
  spec.add_development_dependency "guard", '~>2.6'
37
36
  spec.add_development_dependency "guard-rspec", '~>2.6'
38
- # spec.add_development_dependency 'equivalent-xml' # implementing xml diff
39
-
37
+ # spec.add_development_dependency 'equivalent-xml' # implementing xml diff
38
+
40
39
  end