rspreadsheet 0.2.15 → 0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|