rspreadsheet 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -2
- data/DEVEL_BLOG.md +12 -2
- data/GUIDE.md +3 -3
- data/Guardfile +1 -1
- data/investigate.rb +19 -0
- data/lib/class_extensions.rb +29 -17
- data/lib/rspreadsheet/cell.rb +152 -40
- data/lib/rspreadsheet/empty_file_template.ods +0 -0
- data/lib/rspreadsheet/empty_file_template.old.ods +0 -0
- data/lib/rspreadsheet/row.rb +283 -4
- data/lib/rspreadsheet/tools.rb +79 -5
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/workbook.rb +28 -20
- data/lib/rspreadsheet/worksheet.rb +23 -29
- data/rspreadsheet.gemspec +3 -2
- data/spec/cell_spec.rb +49 -0
- data/spec/row_spec.rb +127 -0
- data/spec/rspreadsheet_spec.rb +40 -109
- data/spec/spec_helper.rb +7 -0
- data/spec/testfile1.ods +0 -0
- data/spec/workbook_spec.rb +19 -0
- data/spec/worksheet_spec.rb +50 -0
- metadata +20 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c147702da70bb4a26183164ed522e854d65ffb60
|
4
|
+
data.tar.gz: 7bac02b0ac665aebb70232c06d02e07a487a3f61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27c8e0ca9ea36ee65a8316a42ba2f993b10c4282ffe1447b3be4e2db4123a682e00c4a161b32f97cd2aa39d4e04402b521404950f8b29afdb9d456d22d63892e
|
7
|
+
data.tar.gz: fc92b43e46ba1157b294810dfecd524eba13e95c773357a9d65590a866b315200fc9fd616c5dcbc7bdfb9f5d73d50a667eababff5af7135f3e3f74ae83519e30
|
data/.gitignore
CHANGED
@@ -22,6 +22,7 @@ build/
|
|
22
22
|
/doc/
|
23
23
|
/rdoc/
|
24
24
|
.~lock*
|
25
|
+
.*kate-swp
|
25
26
|
|
26
27
|
## Environment normalisation:
|
27
28
|
/.bundle/
|
@@ -31,8 +32,8 @@ build/
|
|
31
32
|
# intended to run in multiple environments; otherwise, check them in:
|
32
33
|
|
33
34
|
Gemfile.lock
|
34
|
-
|
35
|
-
|
35
|
+
.ruby-version
|
36
|
+
.ruby-gemset
|
36
37
|
|
37
38
|
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
38
39
|
.rvmrc
|
data/DEVEL_BLOG.md
CHANGED
@@ -2,6 +2,7 @@ See [GUIDE.md](GUIDE.md#conventions) for syntax conventions.
|
|
2
2
|
|
3
3
|
## Ideas/wishlist
|
4
4
|
|
5
|
+
* What should be returned when asking for row/cell outside used range? Currently it creates new row/cell on fly, but maybe it should only return nil, so the user needs to insert apropriate rows/cells himself before using them. However it spoils little bit syntax like spreadsheet.rows(5).cells(3) because if rows returns nil, that would fail and ugly constructs like spreadsheet.andand.rows(5).andand.cells(3) would be needed. The only way this could be acoided is by using something like "UncreatedRow" object. This concern falls to category "clash of worlds" like the indexing issue (0 vs 1 based) and many others.
|
5
6
|
* In future inntroduce syntax like ``sheet.range('C3:E4')`` for mass operations. Also maybe ``sheet.cells('C3')`` or ``sheet.cells(3, 'C')`` etc.
|
6
7
|
* Trying to make row Enumerable - perhaps skipping empty or undefined cells.
|
7
8
|
* Accessors for nonempty/defined cells.
|
@@ -9,12 +10,17 @@ See [GUIDE.md](GUIDE.md#conventions) for syntax conventions.
|
|
9
10
|
* Allow any of these:
|
10
11
|
* ``book['Spring 2014']`` as alternative to ``book.worksheets('Spring 2014')`` ?
|
11
12
|
* ``sheet.cells.F13`` as alternative to ``sheet.cells[14,5]`` ?
|
12
|
-
|
13
|
+
|
14
|
+
Guiding ideas
|
15
|
+
* xml document is always synchronized with the data. So the save is trivial.
|
16
|
+
* no duplication of data. Objects like RowArray should containg minimum information. This one exists solely to speed up cell search. Taken to extream it is questionable, whether we need such objects at all, it might be possible to always work with xml directly.
|
17
|
+
|
18
|
+
|
13
19
|
## Developing this gem
|
14
20
|
|
15
21
|
### Automated testing
|
16
22
|
|
17
|
-
* ``guard`` will get tested any changes as soon as they are summitted
|
23
|
+
* ``budele exec guard`` will get tested any changes as soon as they are summitted
|
18
24
|
* ``rake spec`` runs all test manually
|
19
25
|
|
20
26
|
### Automated utilities
|
@@ -37,3 +43,7 @@ alternative way using ``gem`` command
|
|
37
43
|
gem push rspreadsheet-x.y.z.gem
|
38
44
|
|
39
45
|
|
46
|
+
### Naming conventions
|
47
|
+
|
48
|
+
* create_xxx - creates object and inserts it where necessary
|
49
|
+
* prepare_xxx - create object, but does not insert it anywhere
|
data/GUIDE.md
CHANGED
@@ -13,13 +13,13 @@ p sheet[1,1].class # => String
|
|
13
13
|
p sheet[1,1] # => "My top 5"
|
14
14
|
|
15
15
|
# These are all the same values - alternative syntax
|
16
|
+
p sheet.rows(1).cells(0).value
|
17
|
+
p sheet.cells(1,1).value
|
16
18
|
p sheet.A1
|
17
19
|
p sheet[1,1]
|
18
|
-
p sheet.cells(0,0).value
|
19
|
-
p sheet.rows(0).cells(0).value
|
20
20
|
|
21
21
|
# How to inspect/manipulate the Cell object
|
22
|
-
sheet.cells(1,1)
|
22
|
+
sheet.cells(1,1) # => Rspreadsheet::Cell
|
23
23
|
sheet.cells(1,1).format
|
24
24
|
sheet.cells(1,1).format.size = 15
|
25
25
|
sheet.cells(1,1).format.weight = bold
|
data/Guardfile
CHANGED
data/investigate.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'xml'
|
2
|
+
|
3
|
+
ns = XML::Namespace.new(XML::Node.new('xxx'), 'soap', 'http://schemas.xmlsoap.org/soap/envelope/')
|
4
|
+
|
5
|
+
|
6
|
+
d = XML::Document.new
|
7
|
+
nr = XML::Node.new('root', 'text')
|
8
|
+
d.root = nr
|
9
|
+
n2 = XML::Node.new('element', 'content')
|
10
|
+
nr << n2
|
11
|
+
puts d.to_s
|
12
|
+
|
13
|
+
n2.namespaces.namespace = ns
|
14
|
+
nr.namespaces.namespace = ns
|
15
|
+
puts d.to_s
|
16
|
+
|
17
|
+
n2.namespaces.namespace = ns
|
18
|
+
puts d.to_s
|
19
|
+
|
data/lib/class_extensions.rb
CHANGED
@@ -1,36 +1,48 @@
|
|
1
1
|
class LibXML::XML::Node
|
2
|
+
def new_with_ns(namespace,name)
|
3
|
+
ns = self.namespaces.find_by_prefix('table') || LibXML::XML::Namespace.new(self, 'table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0')
|
4
|
+
self.namespaces.namespace = ns
|
5
|
+
self
|
6
|
+
LibXML::XML::Node.new('table-row')
|
7
|
+
end
|
2
8
|
def elements
|
3
9
|
result = []
|
4
10
|
each_element { |e| result << e }
|
5
11
|
return result
|
6
12
|
end
|
7
13
|
# if node2 contains at least all that I do
|
8
|
-
def
|
9
|
-
|
14
|
+
def simplification_of?(node2)
|
15
|
+
first_diff(node2).nil?
|
16
|
+
end
|
17
|
+
# return first difference where self has something more than node2 does
|
18
|
+
def first_diff(node2)
|
19
|
+
where = self.path.split('/').last
|
20
|
+
|
21
|
+
return "#{where}> Equivalent node does not exist: #{self.name} != NOTHING" if node2.nil?
|
22
|
+
return "#{where}> Names are different: #{self.name} != #{node2.name}" if (self.name != node2.name)
|
10
23
|
self.attributes.each do |attr|
|
11
|
-
return
|
24
|
+
return "#{where}> Attribute #{attr} have diffent values: #{attr.value} != #{node2.attributes[attr.name]}" unless node2.attributes[attr.name] == attr.value
|
12
25
|
end
|
13
26
|
|
14
27
|
elems1 = self.elements
|
15
28
|
elems2 = node2.elements
|
16
|
-
|
29
|
+
# return "#{where}> elements have different number of subelements #{elems1.length} != #{elems2.length}" if (elems1.length != elems2.length)
|
17
30
|
elems1.length.times do |i|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
when 'element'
|
23
|
-
elems1[i].simpifation_of?(elems2[i])
|
24
|
-
else true
|
25
|
-
end
|
26
|
-
then
|
27
|
-
return false
|
31
|
+
if (elems1[i].node_type_name == 'text') && ((elems1[i].to_s != elems2[i].to_s) )
|
32
|
+
return "#{where}> #{i+1}th text subelements are different: #{elems1[i].to_s} != #{elems2[i].to_s}"
|
33
|
+
elsif (elems1[i].node_type_name == 'element') && (!elems1[i].simplification_of?(elems2[i]))
|
34
|
+
return "#{where}/[#{i+1}]#{elems1[i].first_diff(elems2[i])}"
|
28
35
|
end
|
29
36
|
end
|
30
37
|
|
31
|
-
return
|
38
|
+
return nil
|
39
|
+
end
|
40
|
+
def equals?(node2) #TODO redefine == with this
|
41
|
+
self.simplification_of?(node2) and node2.simplification_of?(self)
|
32
42
|
end
|
33
|
-
def
|
34
|
-
|
43
|
+
def add_table_namesepace
|
44
|
+
ns = self.namespaces.find_by_prefix('table') || LibXML::XML::Namespace.new(self, 'table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0')
|
45
|
+
self.namespaces.namespace = ns
|
46
|
+
self
|
35
47
|
end
|
36
48
|
end
|
data/lib/rspreadsheet/cell.rb
CHANGED
@@ -1,54 +1,166 @@
|
|
1
1
|
require 'andand'
|
2
2
|
|
3
3
|
module Rspreadsheet
|
4
|
+
|
4
5
|
class Cell
|
5
|
-
attr_reader :
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
when 'float'
|
18
|
-
@source_node.attributes['value'].to_f
|
19
|
-
when 'string'
|
20
|
-
@source_node.elements.first.andand.content.to_s
|
21
|
-
when 'date'
|
22
|
-
Date.strptime(@source_node.attributes['date-value'].to_s, '%Y-%m-%d')
|
23
|
-
when 'percentage'
|
24
|
-
@source_node.attributes['value'].to_f
|
25
|
-
else
|
26
|
-
if @source_node.children.size == 0
|
27
|
-
nil
|
28
|
-
else
|
29
|
-
nil
|
30
|
-
# raise "Unknown type from #{@source_node.to_s} / children size=#{@source_node.children.size.to_s} / type=#{@type}"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
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'))
|
11
|
+
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
|
34
18
|
end
|
19
|
+
@xmlnode = asource_node
|
20
|
+
@col = coli
|
35
21
|
end
|
36
|
-
def
|
37
|
-
|
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 xml; self.source_node.to_s; end
|
27
|
+
def value_xml; self.source_node.children.first.children.first.to_s; end
|
28
|
+
def coordinates; [row,col]; end
|
29
|
+
def row; @parent_row.row; end
|
30
|
+
def value
|
31
|
+
gt = guess_cell_type
|
32
|
+
if (@mode == :regular) or (@mode == @repeated)
|
33
|
+
case
|
34
|
+
when gt == nil then nil
|
35
|
+
when gt == Float then @xmlnode.attributes['value'].to_f
|
36
|
+
when gt == String then @xmlnode.elements.first.andand.content.to_s
|
37
|
+
when gt == Date then Date.strptime(@xmlnode.attributes['date-value'].to_s, '%Y-%m-%d')
|
38
|
+
when gt == 'percentage' then @xmlnode.attributes['value'].to_f
|
39
|
+
end
|
40
|
+
elsif @mode == :outbound # needs to check whether it is still unbound
|
41
|
+
if parent_row.still_out_of_used_range?
|
42
|
+
nil
|
43
|
+
else
|
44
|
+
parent_row.normalize.cells(@col).value
|
45
|
+
end
|
46
|
+
else
|
47
|
+
raise "Unknown cell mode #{@mode}"
|
48
|
+
end
|
38
49
|
end
|
39
50
|
def value=(avalue)
|
40
|
-
@
|
41
|
-
|
51
|
+
if @mode == :regular
|
52
|
+
gt = guess_cell_type(avalue)
|
53
|
+
case
|
54
|
+
when gt == nil then raise 'This value type is not storable to cell'
|
55
|
+
when gt == Float
|
56
|
+
set_type_attribute('float')
|
57
|
+
remove_all_value_attributes_and_content(@xmlnode)
|
58
|
+
Tools.set_ns_attribute(@xmlnode,'office','value', avalue.to_s)
|
59
|
+
@xmlnode << LibXML::XML::Node.new('p', avalue.to_f.to_s, ns_text)
|
60
|
+
when gt == String then
|
61
|
+
set_type_attribute('string')
|
62
|
+
remove_all_value_attributes_and_content(@xmlnode)
|
63
|
+
@xmlnode << LibXML::XML::Node.new('p', avalue.to_s, ns_text)
|
64
|
+
when gt == Date then
|
65
|
+
set_type_attribute('date')
|
66
|
+
remove_all_value_attributes_and_content(@xmlnode)
|
67
|
+
Tools.set_ns_attribute(@xmlnode,'office','date-value', avalue.strftime('%Y-%m-%d'))
|
68
|
+
@xmlnode << LibXML::XML::Node.new('p', avalue.strftime('%Y-%m-%d'), ns_text)
|
69
|
+
when gt == 'percentage'
|
70
|
+
set_type_attribute('float')
|
71
|
+
remove_all_value_attributes_and_content(@xmlnode)
|
72
|
+
Tools.set_ns_attribute(@xmlnode,'office','value', avalue.to_f.to_s)
|
73
|
+
@xmlnode << LibXML::XML::Node.new('p', (avalue.to_f*100).round.to_s+'%', ns_text)
|
74
|
+
end
|
75
|
+
elsif (@mode == :repeated) or (@mode == :outbound ) # Cell did not exist individually yet, detach row and create editable cell
|
76
|
+
row = @parent_row.detach
|
77
|
+
row.cells(@col).value = avalue
|
78
|
+
else
|
79
|
+
raise "Unknown cell mode #{@mode}"
|
80
|
+
end
|
42
81
|
end
|
43
|
-
def
|
44
|
-
|
82
|
+
def remove_all_value_attributes_and_content(node)
|
83
|
+
if att = Tools.get_ns_attribute(@xmlnode, 'office','value') then att.remove! end
|
84
|
+
if att = Tools.get_ns_attribute(@xmlnode, 'office','date-value') then att.remove! end
|
85
|
+
@xmlnode.content=''
|
45
86
|
end
|
46
|
-
def
|
47
|
-
|
48
|
-
self.source_node.children.first.children.first.to_s
|
87
|
+
def set_type_attribute(typestring)
|
88
|
+
Tools.set_ns_attribute(@xmlnode,'office','value-type',typestring)
|
49
89
|
end
|
50
|
-
|
51
|
-
|
90
|
+
|
91
|
+
# based on @xmlnode and optionally value which is about to be assigned, guesses which type the result should be
|
92
|
+
def guess_cell_type(avalue=nil)
|
93
|
+
# try guessing by value
|
94
|
+
valueguess = case avalue
|
95
|
+
when Numeric then Float
|
96
|
+
when Date then Date
|
97
|
+
when String,nil then nil
|
98
|
+
else nil
|
99
|
+
end
|
100
|
+
result = valueguess
|
101
|
+
|
102
|
+
if valueguess.nil? # valueguess is most important
|
103
|
+
# if not succesfull then try guessing by type
|
104
|
+
type = @xmlnode.attributes['value-type'].to_s
|
105
|
+
typeguess = case type
|
106
|
+
when 'float' then Float
|
107
|
+
when 'string' then String
|
108
|
+
when 'date' then Date
|
109
|
+
when 'percentage' then 'percentage'
|
110
|
+
else
|
111
|
+
if @xmlnode.children.size == 0
|
112
|
+
nil
|
113
|
+
else
|
114
|
+
raise "Unknown type from #{@xmlnode.to_s} / children size=#{@xmlnode.children.size.to_s} / type=#{type}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
result =
|
119
|
+
if !typeguess.nil? # if not certain by value, but have a typeguess
|
120
|
+
if !avalue.nil? # with value we may try converting
|
121
|
+
if (typeguess(avalue) rescue false) # if convertible then it is typeguess
|
122
|
+
typeguess
|
123
|
+
elsif (String(avalue) rescue false) # otherwise try string
|
124
|
+
String
|
125
|
+
else # if not convertible to anyhing concious then nil
|
126
|
+
nil
|
127
|
+
end
|
128
|
+
else # without value we just beleive typeguess
|
129
|
+
typeguess
|
130
|
+
end
|
131
|
+
else # it not have a typeguess
|
132
|
+
if (String(avalue) rescue false) # convertible to String
|
133
|
+
String
|
134
|
+
else
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
result
|
140
|
+
end
|
141
|
+
def inspect
|
142
|
+
"#<Cell:[#{row},#{col}]=#{value}(#{guess_cell_type.to_s})"
|
52
143
|
end
|
53
144
|
end
|
145
|
+
|
54
146
|
end
|
147
|
+
|
148
|
+
# ## initialize cells
|
149
|
+
# @cells = Hash.new do |hash, coords|
|
150
|
+
# # we create empty cell and place it to hash, we do not have to check whether there is a cell in XML already, because it would be in hash as well
|
151
|
+
# hash[coords]=Cell.new(coords[0],coords[1])
|
152
|
+
# # TODO: create XML empty node here or upon save?
|
153
|
+
# end
|
154
|
+
# rowi = 1
|
155
|
+
# unless @xmlnode.nil?
|
156
|
+
# @xmlnode.elements.select{ |node| node.name == 'table-row'}.each do |row_source_node|
|
157
|
+
# coli = 1
|
158
|
+
# row_source_node.elements.select{ |node| node.name == 'table-cell'}.each do |cell_source_node|
|
159
|
+
# initialize_cell(rowi,coli,cell_source_node)
|
160
|
+
# coli += 1
|
161
|
+
# end
|
162
|
+
# rowi += 1
|
163
|
+
# end
|
164
|
+
# end
|
165
|
+
|
166
|
+
|
Binary file
|
Binary file
|