rspreadsheet 0.2.15 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +4 -1
- data/.travis.yml +5 -4
- data/DEVEL_BLOG.md +18 -2
- data/GUIDE.md +5 -0
- data/Guardfile +3 -3
- data/README.md +9 -9
- data/lib/helpers/class_extensions.rb +101 -38
- data/lib/rspreadsheet.rb +2 -1
- data/lib/rspreadsheet/cell.rb +110 -19
- data/lib/rspreadsheet/image.rb +118 -0
- data/lib/rspreadsheet/row.rb +14 -369
- data/lib/rspreadsheet/tools.rb +20 -1
- data/lib/rspreadsheet/version.rb +1 -1
- data/lib/rspreadsheet/workbook.rb +81 -20
- data/lib/rspreadsheet/worksheet.rb +31 -10
- data/lib/rspreadsheet/xml_tied_array.rb +179 -0
- data/lib/rspreadsheet/xml_tied_item.rb +111 -0
- data/lib/rspreadsheet/xml_tied_repeatable.rb +151 -0
- data/reinstall_local_gem.sh +2 -2
- data/rspreadsheet.gemspec +15 -3
- data/spec/cell_spec.rb +82 -1
- data/spec/class_extensions_spec.rb +49 -0
- data/spec/image_spec.rb +104 -0
- data/spec/included_image_spec.rb +8 -0
- data/spec/io_spec.rb +39 -27
- data/spec/row_spec.rb +16 -1
- data/spec/rspreadsheet_spec.rb +94 -35
- data/spec/spec_helper.rb +0 -1
- data/spec/test-image-blue.png +0 -0
- data/spec/test-image.png +0 -0
- data/spec/testfile1.ods +0 -0
- data/spec/testfile2-images.ods +0 -0
- data/spec/workbook_spec.rb +27 -0
- data/spec/worksheet_spec.rb +9 -2
- metadata +20 -47
- data/investigate.rb +0 -19
- data/lib/rspreadsheet/xml_tied.rb +0 -253
data/spec/image_spec.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rspreadsheet::Image do
|
4
|
+
before do
|
5
|
+
@testfile_filename = './spec/testfile2-images.ods'
|
6
|
+
@tmp_testfile_filename = '/tmp/testfile2.ods'
|
7
|
+
File.delete(@tmp_testfile_filename) if File.exists?(@tmp_testfile_filename) # delete temp file
|
8
|
+
|
9
|
+
@testimage_filename = './spec/test-image-blue.png'
|
10
|
+
@testimage2_filename = './spec/test-image.png'
|
11
|
+
@workbook = Rspreadsheet.new(@testfile_filename)
|
12
|
+
@sheet = @workbook.worksheets(1)
|
13
|
+
@sheet2 = @workbook.worksheets(2)
|
14
|
+
end
|
15
|
+
it 'is accesible when included in spreadsheet', :xpending do
|
16
|
+
@sheet.images_count.should == 1
|
17
|
+
@image = @sheet.images(1)
|
18
|
+
|
19
|
+
@image.name.should == 'Obr-a'
|
20
|
+
@sheet.insert_image(@testimage_filename) ## should it be named this way?
|
21
|
+
|
22
|
+
@sheet.images.count.should == 2
|
23
|
+
@image = @sheet.images(2)
|
24
|
+
@image.original_filename.should == @testimage_filename ## should it be named this way? - investigate File object
|
25
|
+
@image.name = 'name1'
|
26
|
+
@image.name.should == 'name1'
|
27
|
+
@image.name = 'name2'
|
28
|
+
@image.name.should_not == 'name1'
|
29
|
+
|
30
|
+
@image.width = '30mm'
|
31
|
+
@image.width.should == '30mm'
|
32
|
+
@image.height = '31mm'
|
33
|
+
@image.height.should == '31mm'
|
34
|
+
@image.x = '32mm'
|
35
|
+
@image.x.should == '32mm'
|
36
|
+
@image.y = '34mm'
|
37
|
+
@image.y.should == '34mm'
|
38
|
+
end
|
39
|
+
it 'can be inserted in sheet without any pictures' do
|
40
|
+
@sheet2.insert_image_to('10mm','10mm',@testimage_filename)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'can be inserted to a sheet and moved around' do
|
44
|
+
x,y = '15mm', '17mm'
|
45
|
+
@image = @sheet.insert_image_to(x,y,@testimage_filename)
|
46
|
+
# moving image
|
47
|
+
@image.x = '21mm'
|
48
|
+
@image.x.should == '21mm'
|
49
|
+
@image.move_to('51mm','52mm')
|
50
|
+
@image.y.should == '52mm'
|
51
|
+
# resizing image
|
52
|
+
@image.width = '30mm'
|
53
|
+
@image.height = '30mm'
|
54
|
+
@image.width.should == '30mm'
|
55
|
+
# copying image
|
56
|
+
@sheet.images_count.should == 2
|
57
|
+
@sheet2.images_count.should == 0
|
58
|
+
@image.copy_to(x,y,@sheet2)
|
59
|
+
@sheet.images_count.should == 2
|
60
|
+
@sheet2.images_count.should == 1
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'can be inserted into file and is saved correctly to it' do
|
64
|
+
tmp_test_image = '/tmp/test-image.png'
|
65
|
+
|
66
|
+
# create new file, insert image into it and save it
|
67
|
+
book = Rspreadsheet.new
|
68
|
+
@sheet = book.add_worksheet
|
69
|
+
@sheet.images_count.should == 0
|
70
|
+
book.worksheets(1).insert_image_to('10mm','10mm',@testimage2_filename)
|
71
|
+
@sheet.images_count.should == 1
|
72
|
+
book.save(@tmp_testfile_filename)
|
73
|
+
|
74
|
+
# reopen it and check the contents
|
75
|
+
book2 = Rspreadsheet.new(@tmp_testfile_filename)
|
76
|
+
@sheet2 = book2.worksheets(1)
|
77
|
+
@sheet2.images_count.should == 1
|
78
|
+
@image = @sheet2.images(1)
|
79
|
+
|
80
|
+
File.delete(tmp_test_image) if File.exists?(tmp_test_image)
|
81
|
+
Zip::File.open(@tmp_testfile_filename) do |zip| ## TODO: this is UGLY - it should not be extracting contents here
|
82
|
+
zip.extract(@image.internal_filename,tmp_test_image)
|
83
|
+
end
|
84
|
+
File.binread(tmp_test_image).unpack("H*").should == File.binread(@testimage2_filename).unpack("H*")
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'generates internal_filename on save randomly and they are different' do
|
88
|
+
i1 = @sheet.insert_image(@testimage_filename)
|
89
|
+
i2 = @sheet.insert_image(@testimage2_filename)
|
90
|
+
i3 = @sheet.insert_image(@testimage2_filename)
|
91
|
+
i1.move_to('40mm','40mm')
|
92
|
+
i2.move_to('40mm','40mm')
|
93
|
+
i3.move_to('40mm','40mm')
|
94
|
+
@workbook.save(@tmp_testfile_filename)
|
95
|
+
|
96
|
+
i1.internal_filename.should_not == i2.internal_filename
|
97
|
+
i1.internal_filename.should_not == i3.internal_filename
|
98
|
+
i2.internal_filename.should_not == i3.internal_filename
|
99
|
+
end
|
100
|
+
|
101
|
+
# it 'has dimensions defaulting to size of the image once it is inserted' do
|
102
|
+
# end
|
103
|
+
|
104
|
+
end
|
data/spec/io_spec.rb
CHANGED
@@ -1,38 +1,50 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
using ClassExtensions if RUBY_VERSION > '2.1'
|
2
3
|
|
3
4
|
describe Rspreadsheet do
|
5
|
+
before do
|
6
|
+
@tmp_filename = '/tmp/testfile.ods'
|
7
|
+
File.delete(@tmp_filename) if File.exists?(@tmp_filename) # delete temp file
|
8
|
+
end
|
9
|
+
|
10
|
+
|
4
11
|
it 'can open spreadsheet and save it to file, resulting file has same content as original' do
|
5
12
|
spreadsheet = Rspreadsheet.new($test_filename) # open a file
|
6
|
-
|
7
|
-
# save it to temp file
|
8
|
-
tmp_filename = '/tmp/testfile1.ods'
|
9
|
-
File.delete(tmp_filename) if File.exists?(tmp_filename) # first delete temp file
|
10
|
-
spreadsheet.save(tmp_filename) # and save spreadsheet as temp file
|
13
|
+
spreadsheet.save(@tmp_filename) # and save spreadsheet as temp file
|
11
14
|
|
12
15
|
# now compare content saved file to original
|
13
|
-
contents_of_files_are_identical($test_filename
|
16
|
+
contents_of_files_are_identical?($test_filename,@tmp_filename).should == true
|
14
17
|
end
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
#
|
19
|
-
# stringio = StringIO.new
|
20
|
-
# spreadsheet.save(stringio)
|
21
|
-
# raise stringio.read
|
22
|
-
#
|
23
|
-
# end
|
24
|
-
end
|
25
|
-
|
26
|
-
def contents_of_files_are_identical(filename1,filename2)
|
27
|
-
@content_xml1 = Zip::File.open(filename1) do |zip|
|
28
|
-
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
29
|
-
end
|
30
|
-
@content_xml2 = Zip::File.open(filename2) do |zip|
|
31
|
-
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
32
|
-
end
|
19
|
+
it 'can open spreadsheet and store it to IO object', :pending => 'Under development' do
|
20
|
+
spreadsheet = Rspreadsheet.new($test_filename) # open a file
|
33
21
|
|
34
|
-
|
35
|
-
|
22
|
+
stringio = StringIO.new
|
23
|
+
spreadsheet.save_to_io(stringio)
|
24
|
+
stringio.size.should > 300000
|
36
25
|
|
37
|
-
|
38
|
-
|
26
|
+
# save it to temp file
|
27
|
+
File.open(@tmp_filename, "w") do |f|
|
28
|
+
f.write stringio.read
|
29
|
+
end
|
30
|
+
|
31
|
+
contents_of_files_are_identical?($test_filename,@tmp_filename).should == true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def contents_of_files_are_identical?(filename1,filename2)
|
36
|
+
@content_xml1 = Zip::File.open(filename1) do |zip|
|
37
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
38
|
+
end
|
39
|
+
@content_xml2 = Zip::File.open(filename2) do |zip|
|
40
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
41
|
+
end
|
42
|
+
return contents_are_identical?(@content_xml1,@content_xml2)
|
43
|
+
end
|
44
|
+
|
45
|
+
def contents_are_identical?(content_xml1,content_xml2)
|
46
|
+
content_xml2.root.first_diff(content_xml1.root).should be_nil
|
47
|
+
content_xml1.root.first_diff(content_xml2.root).should be_nil
|
48
|
+
|
49
|
+
return (content_xml1.root == content_xml2.root)
|
50
|
+
end
|
data/spec/row_spec.rb
CHANGED
@@ -3,7 +3,8 @@ require 'spec_helper'
|
|
3
3
|
describe Rspreadsheet::Row do
|
4
4
|
before do
|
5
5
|
@sheet1 = Rspreadsheet.new.create_worksheet
|
6
|
-
@
|
6
|
+
@book2 = Rspreadsheet.new($test_filename)
|
7
|
+
@sheet2 = @book2.worksheets(1)
|
7
8
|
end
|
8
9
|
it 'allows access to cells in a row' do
|
9
10
|
@row = @sheet2.rows(1)
|
@@ -199,6 +200,16 @@ describe Rspreadsheet::Row do
|
|
199
200
|
@sheet2.add_row_above(1)
|
200
201
|
@sheet2.rows(1).should be_kind_of(Rspreadsheet::Row)
|
201
202
|
end
|
203
|
+
it 'inserted is empty even is surrounded by nonempty rows' do
|
204
|
+
@sheet2.row(4).cells.size.should > 1
|
205
|
+
@sheet2.row(5).cells.size.should > 1
|
206
|
+
@row5 = @sheet2.row(5)
|
207
|
+
@sheet2.add_row_above(5)
|
208
|
+
@sheet2.row(4).cells.size.should > 1
|
209
|
+
@sheet2.row(5).cells.size.should == 0
|
210
|
+
@sheet2.row(6).should == @row5
|
211
|
+
end
|
212
|
+
|
202
213
|
it 'can be deleted' do
|
203
214
|
@sheet1[15,4]='data'
|
204
215
|
@row = @sheet1.rows(15)
|
@@ -255,6 +266,10 @@ describe Rspreadsheet::Row do
|
|
255
266
|
@row.truncate
|
256
267
|
@row.size.should == 0
|
257
268
|
end
|
269
|
+
it 'remembers its parent correctly' do
|
270
|
+
@row = @sheet1.rows(5)
|
271
|
+
@row.worksheet.should == @sheet1
|
272
|
+
end
|
258
273
|
end
|
259
274
|
|
260
275
|
|
data/spec/rspreadsheet_spec.rb
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
using ClassExtensions if RUBY_VERSION > '2.1'
|
2
3
|
|
3
4
|
describe Rspreadsheet do
|
5
|
+
before do
|
6
|
+
@tmp_filename = '/tmp/testfile.ods' # delete temp file before tests
|
7
|
+
File.delete(@tmp_filename) if File.exists?(@tmp_filename)
|
8
|
+
end
|
9
|
+
after do
|
10
|
+
File.delete(@tmp_filename) if File.exists?(@tmp_filename) # delete temp file after tests
|
11
|
+
end
|
12
|
+
|
4
13
|
it 'can open ods testfile and reads its content correctly' do
|
5
14
|
book = Rspreadsheet.new($test_filename)
|
6
15
|
s = book.worksheets(1)
|
@@ -11,13 +20,12 @@ describe Rspreadsheet do
|
|
11
20
|
s[2,2].should === Date.new(2014,1,1)
|
12
21
|
end
|
13
22
|
it 'can open and save file, and saved file has same cells as original' do
|
14
|
-
|
15
|
-
|
16
|
-
book
|
17
|
-
book.save(tmp_filename) # and save it as temp file
|
23
|
+
|
24
|
+
book = Rspreadsheet.new($test_filename) # open test file
|
25
|
+
book.save(@tmp_filename) # and save it as temp file
|
18
26
|
|
19
27
|
book1 = Rspreadsheet.new($test_filename) # now open both again
|
20
|
-
book2 = Rspreadsheet.new(tmp_filename)
|
28
|
+
book2 = Rspreadsheet.new(@tmp_filename)
|
21
29
|
@sheet1 = book1.worksheets(1)
|
22
30
|
@sheet2 = book2.worksheets(1)
|
23
31
|
|
@@ -25,21 +33,38 @@ describe Rspreadsheet do
|
|
25
33
|
@sheet2[cell.rowi,cell.coli].should == cell.value
|
26
34
|
end
|
27
35
|
end
|
36
|
+
|
37
|
+
it 'can open and save file, and saved file is exactly same as original' do
|
38
|
+
book = Rspreadsheet.new($test_filename) # open test file
|
39
|
+
book.save(@tmp_filename) # and save it as temp file
|
40
|
+
|
41
|
+
# now compare them
|
42
|
+
@content_xml1 = Zip::File.open($test_filename) do |zip|
|
43
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
44
|
+
end
|
45
|
+
@content_xml2 = Zip::File.open(@tmp_filename) do |zip|
|
46
|
+
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
47
|
+
end
|
48
|
+
|
49
|
+
@content_xml2.root.first_diff(@content_xml1.root).should be_nil
|
50
|
+
@content_xml1.root.first_diff(@content_xml2.root).should be_nil
|
51
|
+
|
52
|
+
@content_xml1.root.should == @content_xml2.root
|
53
|
+
end
|
54
|
+
|
28
55
|
it 'when open and save file modified, than the file is different' do
|
29
|
-
|
30
|
-
File.delete(tmp_filename) if File.exists?(tmp_filename)
|
31
|
-
book = Rspreadsheet.new($test_filename) # than open test file
|
56
|
+
book = Rspreadsheet.new($test_filename) # open test file
|
32
57
|
book.worksheets(1).rows(1).cells(1).value.should_not == 'xyzxyz'
|
33
58
|
book.worksheets(1).rows(1).cells(1).value ='xyzxyz'
|
34
59
|
book.worksheets(1).rows(1).cells(1).value.should == 'xyzxyz'
|
35
60
|
|
36
|
-
book.save(tmp_filename) # and save it as temp file
|
61
|
+
book.save(@tmp_filename) # and save it as temp file
|
37
62
|
|
38
63
|
# now compare them
|
39
64
|
@content_doc1 = Zip::File.open($test_filename) do |zip|
|
40
65
|
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
41
66
|
end
|
42
|
-
@content_doc2 = Zip::File.open(tmp_filename) do |zip|
|
67
|
+
@content_doc2 = Zip::File.open(@tmp_filename) do |zip|
|
43
68
|
LibXML::XML::Document.io zip.get_input_stream('content.xml')
|
44
69
|
end
|
45
70
|
@content_doc1.eql?(@content_doc2).should == false
|
@@ -52,39 +77,57 @@ describe Rspreadsheet do
|
|
52
77
|
book.create_worksheet
|
53
78
|
end
|
54
79
|
it 'examples from README file are working' do
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
80
|
+
Rspreadsheet.open($test_filename).save(@tmp_filename)
|
81
|
+
@testimage_filename = './spec/test-image-blue.png'
|
82
|
+
def puts(*par); end # supress puts in the example
|
83
|
+
expect do
|
84
|
+
book = Rspreadsheet.open(@tmp_filename)
|
85
|
+
sheet = book.worksheets(1)
|
86
|
+
|
87
|
+
# get value of a cell B5 (there are more ways to do this)
|
88
|
+
sheet.B5 # => 'cell value'
|
89
|
+
sheet[5,2] # => 'cell value'
|
90
|
+
sheet.row(5).cell(2).value # => 'cell value'
|
91
|
+
|
92
|
+
# set value of a cell B5
|
64
93
|
sheet.F5 = 'text'
|
65
94
|
sheet[5,2] = 7
|
66
|
-
sheet.
|
67
|
-
|
68
|
-
sheet.cells(5,2).format.bold = true
|
69
|
-
sheet.cells(5,2).format.background_color = '#FF0000'
|
70
|
-
}.not_to raise_error
|
71
|
-
|
72
|
-
sheet.rows(4).cellvalues.sum{|val| val.to_f}.should eq 4+7*4
|
73
|
-
sheet.rows(4).cells.sum{ |cell| cell.value.to_f }.should eq 4+7*4
|
95
|
+
sheet.cell(5,2).value = 1.78
|
74
96
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
97
|
+
# working with cell format
|
98
|
+
sheet.cell(5,2).format.bold = true
|
99
|
+
sheet.cell(5,2).format.background_color = '#FF0000'
|
100
|
+
|
101
|
+
# calculate sum of cells in row
|
102
|
+
sheet.row(5).cellvalues.sum
|
103
|
+
sheet.row(5).cells.sum{ |cell| cell.value.to_f }
|
104
|
+
|
105
|
+
# or set formula to a cell
|
106
|
+
sheet.cell('A1').formula='=SUM(A2:A9)'
|
107
|
+
|
108
|
+
# insert company logo to the file
|
109
|
+
sheet.insert_image_to('10mm','15mm',@testimage_filename)
|
110
|
+
|
111
|
+
# iterating over list of people and displaying the data
|
112
|
+
total = 0
|
113
|
+
sheet.rows.each do |row|
|
114
|
+
puts "Sponsor #{row[1]} with email #{row[2]} has donated #{row[3]} USD."
|
115
|
+
total += row[3].to_f
|
116
|
+
end
|
117
|
+
puts "Totally fundraised #{total} USD"
|
118
|
+
|
119
|
+
# saving file
|
120
|
+
book.save
|
121
|
+
book.save('/tmp/different_filename.ods')
|
122
|
+
end.not_to raise_error
|
123
|
+
File.delete('/tmp/different_filename.ods') if File.exists?('/tmp/different_filename.ods') # delete after tests
|
81
124
|
end
|
82
125
|
it 'examples from advanced syntax GUIDE are working' do
|
83
126
|
def p(*par); end # supress p in the example
|
84
127
|
expect do
|
85
128
|
book = Rspreadsheet::Workbook.new
|
86
129
|
sheet = book.create_worksheet 'Top icecreams'
|
87
|
-
|
130
|
+
|
88
131
|
sheet[1,1] = 'My top 5'
|
89
132
|
p sheet[1,1].class # => String
|
90
133
|
p sheet[1,1] # => "My top 5"
|
@@ -121,9 +164,25 @@ describe Rspreadsheet do
|
|
121
164
|
# sheet.cells[2,1..5] = ['Vanilla', 'Pistacia', 'Chocolate', 'Annanas', 'Strawbery']
|
122
165
|
# sheet.columns(1).cells(1).format.color = :red
|
123
166
|
|
124
|
-
book.save('testfile.ods')
|
167
|
+
book.save('/tmp/testfile.ods')
|
125
168
|
end.not_to raise_error
|
126
169
|
end
|
170
|
+
it 'can save file to io stream and the content is the same as when saving to file', :skip do
|
171
|
+
book = Rspreadsheet.new($test_filename) # open test file
|
172
|
+
|
173
|
+
file = File.open(@tmp_filename, 'w') # and save the stream to @tmp_filename
|
174
|
+
file.write(book.save_to_io)
|
175
|
+
file.close
|
176
|
+
|
177
|
+
book1 = Rspreadsheet.new($test_filename) # now open both again
|
178
|
+
book2 = Rspreadsheet.new(@tmp_filename)
|
179
|
+
@sheet1 = book1.worksheets(1)
|
180
|
+
@sheet2 = book2.worksheets(1)
|
181
|
+
|
182
|
+
@sheet1.nonemptycells.each do |cell| # and test if they are identical
|
183
|
+
@sheet2[cell.rowi,cell.coli].should == cell.value
|
184
|
+
end
|
185
|
+
end
|
127
186
|
end
|
128
187
|
|
129
188
|
|
data/spec/spec_helper.rb
CHANGED
Binary file
|
data/spec/test-image.png
ADDED
Binary file
|
data/spec/testfile1.ods
CHANGED
Binary file
|
Binary file
|
data/spec/workbook_spec.rb
CHANGED
@@ -56,4 +56,31 @@ describe Rspreadsheet::Workbook do
|
|
56
56
|
book.sheet(1).should == sheet
|
57
57
|
book.sheets(2).should_not == sheet
|
58
58
|
end
|
59
|
+
it 'can access sheet using negative indexes and returns the same object' do
|
60
|
+
book = Rspreadsheet::Workbook.new
|
61
|
+
book.create_worksheet('test')
|
62
|
+
book.create_worksheet('test2')
|
63
|
+
sheet1 = book.worksheets(1)
|
64
|
+
sheet2 = book.worksheets(2)
|
65
|
+
book.worksheet(-1).should == sheet2
|
66
|
+
book.sheet(-2).should == sheet1
|
67
|
+
book[-2].should == sheet1
|
68
|
+
end
|
69
|
+
it 'raises error when attemting to use nonsence index' do
|
70
|
+
book = Rspreadsheet::Workbook.new
|
71
|
+
expect { book.worksheet(Array.new()) }.to raise_error
|
72
|
+
end
|
59
73
|
end
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|