rspreadsheet 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,14 +3,14 @@ module Rspreadsheet
3
3
  # this module contains methods used bz several objects
4
4
  module Tools
5
5
  # converts cell adress like 'F12' to pair od integers [row,col]
6
- def self.convert_cell_address(*coords)
7
- if coords.length == 1
8
- coords[0].match(/^([A-Z]{1,3})(\d{1,8})$/)
6
+ def self.convert_cell_address_to_coordinates(*addr)
7
+ if addr.length == 1
8
+ addr[0].match(/^([A-Z]{1,3})(\d{1,8})$/)
9
9
  colname = $~[1]
10
10
  rowname = $~[2]
11
- elsif coords.length == 2
12
- colname = coords[0]
13
- rowname = coords[1]
11
+ elsif addr.length == 2
12
+ colname = addr[0]
13
+ rowname = addr[1]
14
14
  else
15
15
  raise 'Wrong number of arguments'
16
16
  end
@@ -20,6 +20,27 @@ module Tools
20
20
  row = rowname.to_i
21
21
  return [row,col]
22
22
  end
23
+ def self.convert_cell_coordinates_to_address(*coords)
24
+ coords = coords[0] if coords.length == 1
25
+ raise 'Wrong number of arguments' if coords.length != 2
26
+ row = coords[0].to_i # security against string arguments
27
+ col = coords[1].to_i
28
+ colstring = ''
29
+ if col > 702
30
+ pom = (col-703).div(26*26)+1
31
+ colstring += (pom+64).chr
32
+ col -= pom*26*26
33
+ end
34
+ if col > 26
35
+ pom = (col-27).div(26)+1
36
+ colstring += (pom+64).chr
37
+ col -= pom*26
38
+ end
39
+ colstring += (col+64).chr
40
+ return colstring+row.to_s
41
+ end
42
+ def self.c2a(*x); convert_cell_coordinates_to_address(*x) end
43
+ def self.a2c(*x); convert_cell_address_to_coordinates(*x) end
23
44
  def self.get_namespace(prefix)
24
45
  ns_array = {
25
46
  'office'=>"urn:oasis:names:tc:opendocument:xmlns:office:1.0",
@@ -85,7 +106,7 @@ module Tools
85
106
  end
86
107
  end
87
108
  def self.get_ns_attribute(node,ns_prefix,key)
88
- node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
109
+ node.nil? ? nil : node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
89
110
  end
90
111
  def self.get_ns_attribute_value(node,ns_prefix,key)
91
112
  Tools.get_ns_attribute(node,ns_prefix,key).andand.value
@@ -94,6 +115,9 @@ module Tools
94
115
  node.attributes.get_attribute_ns(Tools.get_namespace(ns_prefix).href,key)
95
116
  attr.remove! unless attr.nil?
96
117
  end
118
+ def self.create_ns_node(nodename,ns_prefix)
119
+ LibXML::XML::Node.new(nodename,nil, Tools.get_namespace(ns_prefix))
120
+ end
97
121
  end
98
122
 
99
123
  end
@@ -1,3 +1,3 @@
1
1
  module Rspreadsheet
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -2,7 +2,7 @@ require 'rspreadsheet/row'
2
2
  require 'rspreadsheet/tools'
3
3
  # require 'forwardable'
4
4
 
5
- module Rspreadsheet
5
+ module Rspreadsheet
6
6
 
7
7
  class Worksheet
8
8
  attr_accessor :name, :xmlnode
@@ -10,6 +10,7 @@ class Worksheet
10
10
  # def_delegators :nonemptycells
11
11
 
12
12
  def initialize(xmlnode_or_sheet_name)
13
+ @rowcache=[]
13
14
  # set up the @xmlnode according to parameter
14
15
  case xmlnode_or_sheet_name
15
16
  when LibXML::XML::Node
@@ -21,10 +22,119 @@ class Worksheet
21
22
  @xmlnode['table:name'] = xmlnode_or_sheet_name
22
23
  else raise 'Provide name or xml node to create a Worksheet object'
23
24
  end
24
-
25
- ## initialize rows
26
- @spredsheetrows=RowArray.new(self,@xmlnode)
27
25
  end
26
+
27
+ def rowxmlnode(rowi)
28
+ find_subnode_respect_repeated(@xmlnode, rowi, {:xml_items_node_name => 'table-row', :xml_repeated_attribute => 'number-rows-repeated'})
29
+ 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
42
+ end
43
+
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'})
55
+ end
56
+
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'})
59
+ end
60
+
61
+ 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'})
67
+ end
68
+
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)
115
+ end
116
+
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
137
+
28
138
  def cells(r,c)
29
139
  rows(r).andand.cells(c)
30
140
  end
@@ -32,7 +142,7 @@ class Worksheet
32
142
  used_rows_range.collect{ |rowi| rows(rowi) }.collect { |row| row.nonemptycells }.flatten
33
143
  end
34
144
  def rows(rowi)
35
- @spredsheetrows.get_row(rowi)
145
+ @rowcache[rowi] ||= Row.new(self,rowi) unless rowi<=0
36
146
  end
37
147
  ## syntactic sugar follows
38
148
  def [](r,c)
@@ -44,7 +154,7 @@ class Worksheet
44
154
  # allows syntax like sheet.F15
45
155
  def method_missing method_name, *args, &block
46
156
  if method_name.to_s.match(/^([A-Z]{1,3})(\d{1,8})(=?)$/)
47
- row,col = Rspreadsheet::Tools.convert_cell_address($~[1],$~[2])
157
+ row,col = Rspreadsheet::Tools.convert_cell_address_to_coordinates($~[1],$~[2])
48
158
  assignchar = $~[3]
49
159
  if assignchar == '='
50
160
  self.cells(row,col).value = args.first
@@ -56,7 +166,7 @@ class Worksheet
56
166
  end
57
167
  end
58
168
  def used_rows_range
59
- 1..@spredsheetrows.first_unused_row_index-1
169
+ 1..self.first_unused_row_index-1
60
170
  end
61
171
  end
62
172
 
data/reinstall.sh CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/bin/sh
2
2
 
3
3
  cd rspreadsheet
4
+ rm rspreadsheet-0.*.gem
4
5
  gem build rspreadsheet.gemspec
5
- gem install rspreadsheet-0.1.0.gem
6
+ sudo gem install rspreadsheet-0.*.gem
6
7
  cd ..
data/rspreadsheet.gemspec CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["Jakub A.Těšínský"]
10
10
  spec.email = ["jAkub.cz (A is at)"]
11
11
  spec.summary = %q{Manipulating spreadsheets with Ruby (read / create / modify OpenDocument Spreadsheet).}
12
- spec.description = %q{Manipulating spreadsheets with Ruby (read / create / modify OpenDocument Spreadsheet).}
12
+ spec.description = %q{Manipulating OpenDocument spreadsheets with Ruby. This gem can create new, read existing files abd modify them. When modyfying files, it tries to change as little as possible, making it as much forward compatifle as possible.}
13
13
  spec.homepage = "https://github.com/gorn/rspreadsheet"
14
14
  spec.license = "GPL"
15
15
 
data/spec/cell_spec.rb CHANGED
@@ -9,13 +9,13 @@ describe Rspreadsheet::Cell do
9
9
  end
10
10
  it 'contains good row and col coordinates' do
11
11
  @cell = @sheet1.cells(1,3)
12
- @cell.row.should == 1
13
- @cell.col.should == 3
12
+ @cell.rowi.should == 1
13
+ @cell.coli.should == 3
14
14
  @cell.coordinates.should == [1,3]
15
15
 
16
16
  @cell = @sheet2.cells(7,2)
17
- @cell.row.should == 7
18
- @cell.col.should == 2
17
+ @cell.rowi.should == 7
18
+ @cell.coli.should == 2
19
19
  @cell.coordinates.should == [7,2]
20
20
  end
21
21
  it 'can be referenced by more vars and both are synchronized' do
@@ -38,8 +38,8 @@ describe Rspreadsheet::Cell do
38
38
  it 'contains good row and col coordinates even after table:number-columns-repeated cells' do
39
39
  @cell = @sheet2.cells(13,5)
40
40
  @cell.value.should == 'afterrepeated'
41
- @cell.row.should == 13
42
- @cell.col.should == 5
41
+ @cell.rowi.should == 13
42
+ @cell.coli.should == 5
43
43
  end
44
44
  it 'does not accept negative and zero coordinates' do
45
45
  @sheet2.cells(0,5).should be(nil)
@@ -48,15 +48,41 @@ describe Rspreadsheet::Cell do
48
48
  end
49
49
  it 'has nonempty parents' do
50
50
  @cell = @sheet2.cells(13,5)
51
- @cell.parent_row.should_not be_nil
51
+ @cell.row.should_not be_nil
52
52
  @cell.worksheet.should_not be_nil
53
53
 
54
54
  @cell = @sheet1.cells(2,2)
55
- @cell.parent_row.should_not be_nil
55
+ @cell.row.should_not be_nil
56
56
  @cell.worksheet.should_not be_nil
57
57
  end
58
58
  it 'handles relative correctly' do
59
59
  @sheet2.cells(3,3).relative(-1,+2).coordinates.should == [2,5]
60
60
  @sheet2.cells(3,3).relative(0,0).coordinates.should == [3,3]
61
61
  end
62
+ it 'is automatically "unrepeated" on value assignement' do
63
+ @cell = @sheet2.cells(13,2)
64
+ @cell.is_repeated?.should == true
65
+ @cell.value = 'cokoli'
66
+ @cell.is_repeated?.should == false
67
+ @cell.value.should == 'cokoli'
68
+ @sheet2.cells(13,1).should_not == 'cokoli'
69
+ @sheet2.cells(13,3).should_not == 'cokoli'
70
+ @sheet2.cells(13,4).should_not == 'cokoli'
71
+ end
72
+ it 'returns type for the cell' do
73
+ book = Rspreadsheet.new($test_filename)
74
+ s = book.worksheets[1]
75
+ s.cells(1,2).type.should === :string
76
+ s.cells(2,2).type.should === :date
77
+ s.cells(3,1).type.should === :float
78
+ s.cells(3,2).type.should === :percentage
79
+ s.cells(4,2).type.should === :string
80
+ s.cells(200,200).type.should === :unassigned
81
+ end
82
+
62
83
  end
84
+
85
+
86
+
87
+
88
+
data/spec/row_spec.rb CHANGED
@@ -9,22 +9,28 @@ describe Rspreadsheet::Row do
9
9
  @row = @sheet2.rows(1)
10
10
  @c = @row.cells(1)
11
11
  @c.value = 3
12
- @c.value.should == 3
12
+ @c.value.should == 3
13
13
  end
14
- it 'creates minimal row on demand, which contains correctly namespaced attributes only'do
15
- @sheet1.rows(1).cells(2).value = 'nejakydata'
16
- @row = @sheet1.rows(1)
17
- @xmlnode = @row.xmlnode
18
- @xmlnode.namespaces.to_a.size.should >5
19
- @xmlnode.namespaces.namespace.should_not be_nil
20
- @xmlnode.namespaces.namespace.prefix.should == 'table'
14
+ it 'can be detached and changes to unrepeated if done' do
15
+ @row = @sheet1.rows(5)
16
+ @row2 = @row.detach
17
+ @row2.is_repeated?.should == false
18
+ end
19
+ it 'is the synchronized object, now matter how you access it' do
20
+ @row1 = @sheet1.rows(5)
21
+ @row2 = @sheet1.rows(5)
22
+ @row1.should equal(@row2)
23
+
24
+ @sheet1.rows(5).cells(2).value = 'nejakydata'
25
+ @row1 = @sheet1.rows(5)
26
+ @row2 = @sheet1.rows(5)
27
+ @row1.should equal(@row2)
28
+
21
29
  end
22
30
  it 'cells in row are settable through sheet' do
23
- @sheet1.rows(9).add_cell
24
31
  @sheet1.rows(9).cells(1).value = 2
25
32
  @sheet1.rows(9).cells(1).value.should == 2
26
33
 
27
- @sheet1.rows(7).add_cell
28
34
  @sheet1.rows(7).cells(1).value = 7
29
35
  @sheet1.rows(7).cells(1).value.should == 7
30
36
 
@@ -46,27 +52,44 @@ describe Rspreadsheet::Row do
46
52
  @sheet1.rows(10).should be_kind_of(Rspreadsheet::Row)
47
53
  @sheet1.rows(11).should be_kind_of(Rspreadsheet::Row)
48
54
  end
49
- it 'detachment works as expected' do
55
+ it 'detachment creates correct repeated groups' do
50
56
  @sheet1.rows(5).detach
51
57
  @sheet1.rows(5).repeated?.should == false
52
58
  @sheet1.rows(5).repeated.should == 1
53
59
  @sheet1.rows(5).xmlnode.should_not == nil
60
+
54
61
  @sheet1.rows(3).repeated?.should == true
55
62
  @sheet1.rows(3).repeated.should == 4
56
63
  @sheet1.rows(3).xmlnode.should_not == nil
57
- @table_ns_href = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"
58
- @sheet1.rows(3).xmlnode.attributes.get_attribute_ns(@table_ns_href,'number-rows-repeated').value.should == '4'
64
+ @sheet1.rows(3).xmlnode.attributes.get_attribute_ns("urn:oasis:names:tc:opendocument:xmlns:table:1.0",'number-rows-repeated').value.should == '4'
65
+ end
66
+ it 'detachment assigns correct namespaces to node' do
67
+ @sheet1.rows(5).detach
68
+ @xmlnode = @sheet1.rows(5).xmlnode
69
+ @xmlnode.namespaces.to_a.size.should >5
70
+ @xmlnode.namespaces.namespace.should_not be_nil
71
+ @xmlnode.namespaces.namespace.prefix.should == 'table'
59
72
  end
60
73
  it 'by assigning value, the repeated row is automatically detached' do
61
- row = @sheet1.rows(5)
62
- row.detach
63
- @table_ns_href = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"
74
+ @sheet1.rows(15).detach
75
+
76
+ @sheet1.rows(2).repeated?.should == true
77
+ @sheet1.rows(2).cells(2).value = 'nejakydata'
78
+ @sheet1.rows(2).repeated?.should == false
79
+
80
+ @sheet1.rows(22).repeated?.should == true
81
+ @sheet1.rows(22).cells(7).value = 'nejakydata'
82
+ @sheet1.rows(22).repeated?.should == false
83
+ end
84
+ it 'styles can be assigned to rows' do
85
+ @sheet1.rows(5).detach
86
+ table_ns_href = "urn:oasis:names:tc:opendocument:xmlns:table:1.0"
64
87
 
65
88
  @sheet1.rows(5).style_name = 'newstylename'
66
- @sheet1.rows(5).xmlnode.attributes.get_attribute_ns(@table_ns_href,'style-name').value.should == 'newstylename'
89
+ @sheet1.rows(5).xmlnode.attributes.get_attribute_ns(table_ns_href,'style-name').value.should == 'newstylename'
67
90
 
68
91
  @sheet1.rows(17).style_name = 'newstylename2'
69
- @sheet1.rows(17).xmlnode.attributes.get_attribute_ns(@table_ns_href,'style-name').value.should == 'newstylename2'
92
+ @sheet1.rows(17).xmlnode.attributes.get_attribute_ns(table_ns_href,'style-name').value.should == 'newstylename2'
70
93
  end
71
94
  it 'ignores negative any zero row indexes' do
72
95
  @sheet1.rows(0).should be_nil
@@ -75,7 +98,7 @@ describe Rspreadsheet::Row do
75
98
  it 'has correct rowindex' do
76
99
  @sheet1.rows(5).detach
77
100
  (4..6).each do |i|
78
- @sheet1.rows(i).row.should == i
101
+ @sheet1.rows(i).rowi.should == i
79
102
  end
80
103
  end
81
104
  it 'can open ods testfile and read its content' do
@@ -89,12 +112,12 @@ describe Rspreadsheet::Row do
89
112
  s[1,2].should === 'text'
90
113
  s[2,2].should === Date.new(2014,1,1)
91
114
  end
92
- it 'normalizes to itself if single line' do
93
- @sheet1.rows(5).detach
94
- @sheet1.rows(5).cells(4).value='test'
95
- @sheet1.rows(5).normalize
96
- @sheet1.rows(5).cells(4).value.should == 'test'
97
- end
115
+ # it 'normalizes to itself if single line' do
116
+ # @sheet1.rows(5).detach
117
+ # @sheet1.rows(5).cells(4).value='test'
118
+ # @sheet1.rows(5).normalize
119
+ # @sheet1.rows(5).cells(4).value.should == 'test'
120
+ # end
98
121
  it 'cell manipulation does not contain attributes without namespace nor doubled attributes' do # inspired by a real bug regarding namespaces manipulation
99
122
  @sheet2.rows(1).xmlnode.attributes.each { |attr| attr.ns.should_not be_nil}
100
123
  @sheet2.rows(1).cells(1).value.should_not == 'xyzxyz'
@@ -117,10 +140,10 @@ describe Rspreadsheet::Row do
117
140
  end
118
141
  it 'nonempty cells work properly' do
119
142
  nec = @sheet2.rows(1).nonemptycells
120
- nec.collect{ |c| [c.row,c.col]}.should == [[1,1],[1,2]]
143
+ nec.collect{ |c| c.coordinates}.should == [[1,1],[1,2]]
121
144
 
122
145
  nec = @sheet2.rows(19).nonemptycells
123
- nec.collect{ |c| [c.row,c.col]}.should == [[19,6]]
146
+ nec.collect{ |c| c.coordinates}.should == [[19,6]]
124
147
  end
125
148
  end
126
149