yalab-ruby-ods 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
1
+ = README
2
+
3
+ release:: 0.0.0
4
+ copyright:: copyright(c) 2006-2009 ya-lab.org all rights reserved.
5
+
6
+
7
+ == About yalab-ruby-ods
8
+
9
+ This library is read and write OpenOffice Document SpreadSheet(ods) file.
10
+ This version only use string of cell format.
11
+
12
+
13
+ == Installation
14
+
15
+ $ sudo gem install yalab-ruby-ods -s http://gemcutter.org
16
+
17
+ == Usage
18
+
19
+ require 'rubygems'
20
+ require 'ods'
21
+
22
+ ods = Ods.new('some_document.ods')
23
+
24
+ sheet = ods.sheets[0]
25
+ sheet[3, :A].text #=> get A3 cell value
26
+ sheet[4, :B].text = 'foobar'
27
+ sheet[4, :B].text #=> foobar
28
+
29
+ values = []
30
+ sheet.rows.each do |row|
31
+ row.each{|cell|
32
+ values.push cell.text
33
+ }
34
+ end
35
+
36
+ new_sheet = ods.create_sheet
37
+ new_sheet[1, :A].annotation = 'hint'
38
+ new_sheet[1, :A].text = 'baz'
39
+
40
+ ods.save
41
+
42
+ == License
43
+
44
+ MIT License
45
+
46
+
47
+ == Author
48
+
49
+ yalab <rudeboyjet@gmail.com>
@@ -0,0 +1,12 @@
1
+ require 'jeweler'
2
+ Jeweler::Tasks.new do |gemspec|
3
+ gemspec.name = "yalab-ruby-ods"
4
+ gemspec.summary = "This is using OpenOffice SpreadSheet document file via ruby"
5
+ gemspec.email = "rudeboyjet@gmail.com"
6
+ gemspec.homepage = "http://github.com/yalab/ruby-ods"
7
+ gemspec.description = ""
8
+ gemspec.authors = ["yalab"]
9
+ gemspec.add_dependency "nokogiri", ">= 1.4.0"
10
+ gemspec.add_dependency "rubyzip", ">= 0.9.1"
11
+ end
12
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1,26 @@
1
+ # -*- coding: utf-8 -*-
2
+ Nokogiri::XML::Element.module_eval do
3
+ def add_element(name, attributes={})
4
+ (prefix, name) = name.split(':') if name.include?(':')
5
+ node = Nokogiri::XML::Node.new(name, self)
6
+ attributes.each do |attr, val|
7
+ node.set_attribute(attr, val)
8
+ end
9
+ ns = node.add_namespace_definition(prefix, Ods::NAMESPACES[prefix])
10
+ node.namespace = ns
11
+ self.add_child(node)
12
+ node
13
+ end
14
+
15
+ def fetch(xpath)
16
+ if node = self.xpath(xpath).first
17
+ return node
18
+ end
19
+
20
+ return self.add_element(xpath) unless xpath.include?('/')
21
+
22
+ xpath = xpath.split('/')
23
+ last_path = xpath.pop
24
+ fetch(xpath.join('/')).fetch(last_path)
25
+ end
26
+ end
@@ -0,0 +1,208 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'forwardable'
3
+ require 'rubygems'
4
+ require 'nokogiri'
5
+ require 'nokogiri_ext'
6
+ require 'zip/zip'
7
+ require 'fileutils'
8
+
9
+ class Ods
10
+ attr_reader :content, :sheets
11
+ XPATH_SHEETS = '//office:body/office:spreadsheet/table:table'
12
+
13
+ NAMESPACES = {
14
+ 'office' => 'urn:oasis:names:tc:opendocument:xmlns:office:1.0',
15
+ 'style' => 'urn:oasis:names:tc:opendocument:xmlns:style:1.0',
16
+ 'text' => 'urn:oasis:names:tc:opendocument:xmlns:text:1.0',
17
+ 'table' => 'urn:oasis:names:tc:opendocument:xmlns:table:1.0',
18
+ 'draw' => 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0',
19
+ 'fo' => 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0',
20
+ 'xlink' => 'http://www.w3.org/1999/xlink',
21
+ 'dc' => 'http://purl.org/dc/elements/1.1/',
22
+ 'meta' => 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0',
23
+ 'number' => 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0',
24
+ 'presentation' => 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0',
25
+ 'svg' => 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0',
26
+ 'chart' => 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0',
27
+ 'dr3d' => 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0',
28
+ 'math' => 'http://www.w3.org/1998/Math/MathML',
29
+ 'form' => 'urn:oasis:names:tc:opendocument:xmlns:form:1.0',
30
+ 'script' => 'urn:oasis:names:tc:opendocument:xmlns:script:1.0',
31
+ 'ooo' => 'http://openoffice.org/2004/office',
32
+ 'ooow' => 'http://openoffice.org/2004/writer',
33
+ 'oooc' => 'http://openoffice.org/2004/calc',
34
+ 'dom' => 'http://www.w3.org/2001/xml-events',
35
+ 'xforms' => 'http://www.w3.org/2002/xforms',
36
+ 'xsd' => 'http://www.w3.org/2001/XMLSchema',
37
+ 'xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
38
+ 'rpt' => 'http://openoffice.org/2005/report',
39
+ 'of' => 'urn:oasis:names:tc:opendocument:xmlns:of:1.2',
40
+ 'rdfa' => 'http://docs.oasis-open.org/opendocument/meta/rdfa#',
41
+ 'field' => 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0',
42
+ 'formx' => 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0'
43
+ }
44
+
45
+ def initialize(path)
46
+ @path = path
47
+ Zip::ZipFile.open(@path) do |zip|
48
+ @content = Nokogiri::XML::Document.parse(zip.read('content.xml'))
49
+ end
50
+ @sheets = []
51
+ @content.root.xpath(XPATH_SHEETS).each do |sheet|
52
+ @sheets.push(Sheet.new(sheet))
53
+ end
54
+ @content
55
+ end
56
+
57
+ def save(dest=nil)
58
+ if dest
59
+ FileUtils.cp(@path, dest)
60
+ else
61
+ dest = @path
62
+ end
63
+
64
+ @sheets.each do |sheet|
65
+ column = sheet.column
66
+ max_length = 0
67
+ column.content.parent.xpath('table:table-row').each do |row|
68
+ length = row.xpath('table:table-cell').length
69
+ max_length = length if max_length < length
70
+ end
71
+ column.set_attr('repeated', max_length)
72
+ end
73
+
74
+ Zip::ZipFile.open(dest) do |zip|
75
+ zip.get_output_stream('content.xml') do |io|
76
+ io << @content.to_s
77
+ end
78
+ end
79
+ end
80
+
81
+ def create_sheet
82
+ parent = @content.root.xpath(XPATH_SHEETS.split('/')[0..-2].join('/'))[0]
83
+ table = parent.add_element('table:table',
84
+ 'name' => "Sheet#{@sheets.length + 1}",
85
+ 'style-name' => 'ta1',
86
+ 'print' => 'false')
87
+ table.add_element('table:table-column',
88
+ 'style-name' => 'co1',
89
+ 'default-cell-style-name' => 'Default')
90
+ new_sheet = Sheet.new(table)
91
+ @sheets.push(new_sheet)
92
+ new_sheet
93
+ end
94
+
95
+ class Sheet
96
+ attr_reader :content
97
+ def initialize(content)
98
+ @content = content
99
+ end
100
+
101
+ def name
102
+ @content.attribute('name').to_s
103
+ end
104
+
105
+ def name=(name)
106
+ @content.set_attribute('table:name', name)
107
+ end
108
+
109
+ def [](row, col)
110
+ (row - rows.length).times do
111
+ rows.push(Row.new(@content.add_element('table:table-row',
112
+ 'table:style-name' => 'ro1'), rows.length+1))
113
+ end
114
+ row = rows[row-1]
115
+ col = ('A'..col.to_s).to_a.index(col.to_s)
116
+ cols = row.cols
117
+ (col - cols.length + 1).times do
118
+ no = (cols.last) ? cols.last.no.to_s.succ : 'A'
119
+ cols.push(Cell.new(row.add_element('table:table-cell', 'office:value-type' => 'string'), no))
120
+ end
121
+ cols[col]
122
+ end
123
+
124
+ def rows
125
+ return @rows if @rows
126
+ @rows = []
127
+ @content.xpath('./table:table-row').each_with_index{|row, index|
128
+ @rows << Row.new(row, index+1)
129
+ }
130
+ @rows
131
+ end
132
+
133
+ def column
134
+ Column.new(@content.xpath('table:table-column').first)
135
+ end
136
+ end
137
+
138
+ class Row
139
+ extend Forwardable
140
+
141
+ def_delegator :@content, :xpath, :xpath
142
+ def_delegator :@content, :add_element, :add_element
143
+ attr_reader :no
144
+
145
+ def initialize(content, no)
146
+ @content = content
147
+ @no = no
148
+ end
149
+
150
+ def cols
151
+ return @cols if @cols
152
+ @cols = []
153
+ no = 'A'
154
+ xpath('table:table-cell').each{|cell|
155
+ @cols << Cell.new(cell, no)
156
+ no.succ!
157
+ }
158
+ @cols
159
+ end
160
+
161
+ def create_cell
162
+
163
+ end
164
+ end
165
+
166
+ class Cell
167
+ extend Forwardable
168
+
169
+ def_delegator :@content, :fetch, :fetch
170
+ attr_reader :no
171
+
172
+ def initialize(content, no)
173
+ @content = content
174
+ @no = no.to_sym
175
+ end
176
+
177
+ def value
178
+ fetch('text:p').content
179
+ end
180
+
181
+ def value=(value)
182
+ fetch('text:p').content = value
183
+ end
184
+
185
+ def annotation
186
+ fetch('office:annotation/text:p').content
187
+ end
188
+
189
+ def annotation=(value)
190
+ fetch('office:annotation/text:p').content = value
191
+ end
192
+ end
193
+
194
+ class Column
195
+ attr_reader :content
196
+ def initialize(content)
197
+ @content = content
198
+ end
199
+
200
+ def attr(name)
201
+ @content['number-columns-' + name]
202
+ end
203
+
204
+ def set_attr(name, value)
205
+ @content['table:number-columns-' + name] = value.to_s
206
+ end
207
+ end
208
+ end
Binary file
@@ -0,0 +1,132 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'test/unit'
3
+ require File.dirname(File.expand_path(__FILE__)) + '/../lib/ods'
4
+
5
+ class OdsTest < Test::Unit::TestCase
6
+ BASE_DIR = File.dirname(File.expand_path(__FILE__))
7
+ def setup
8
+ @ods = Ods.new(BASE_DIR + '/cook.ods')
9
+ @file_path = BASE_DIR + '/modified.ods'
10
+ end
11
+
12
+ def teardown
13
+ File.unlink(@file_path) if File.exists?(@file_path)
14
+ end
15
+
16
+ def test_sheet_count
17
+ assert_equal 3, @ods.sheets.length
18
+ end
19
+
20
+ def test_sheet_name
21
+ assert_equal 'さつま揚げとキャベツのお味噌汁', @ods.sheets[0].name
22
+ end
23
+
24
+ def test_sheet_name_modify
25
+ modified_name = 'hogehoge'
26
+ offset = 2
27
+
28
+ assert_not_equal modified_name, @ods.sheets[offset].name
29
+ @ods.sheets[offset].name = modified_name
30
+ @ods.save(@file_path)
31
+ modified_ods = Ods.new(@file_path)
32
+ assert_equal modified_name, modified_ods.sheets[offset].name
33
+ end
34
+
35
+ def test_get_column
36
+ sheet = @ods.sheets[0]
37
+ assert_equal 'だし汁', sheet[2, :A].value
38
+ assert_equal '適量', sheet[6, :B].value
39
+ end
40
+
41
+ def test_modify_column
42
+ sheet_offset = 0
43
+ row = 2
44
+ col = :B
45
+ sheet = @ods.sheets[sheet_offset]
46
+ modified_text = '酢味噌'
47
+ assert_not_equal modified_text, sheet[row, col].value
48
+ sheet[row, col].value = modified_text
49
+ @ods.save(@file_path)
50
+ modified_ods = Ods.new(@file_path)
51
+ assert_equal modified_text, modified_ods.sheets[sheet_offset][row, col].value
52
+ end
53
+
54
+ def test_access_not_existed_sheet
55
+ ods_length = @ods.sheets.length
56
+ new_sheet = @ods.create_sheet
57
+ assert_equal "Sheet#{ods_length+1}", new_sheet.name
58
+ assert_equal '', new_sheet[1, :A].value
59
+ (col, row) = [100, :CC]
60
+ assert_nothing_raised { new_sheet[col, row].value = 'hoge' }
61
+ assert_equal 'hoge', new_sheet[col, row].value
62
+ end
63
+
64
+ def test_read_annotation
65
+ cell = @ods.sheets[0][2, :A]
66
+ assert_equal '昆布だし', cell.annotation
67
+ end
68
+
69
+ def test_write_annotation
70
+ sheet_offset = 0
71
+ row = 3
72
+ col = :A
73
+ cell = @ods.sheets[sheet_offset][row, col]
74
+ assert_equal '', cell.annotation
75
+ text = 'foobar'
76
+ cell.annotation = text
77
+ assert_equal text, @ods.sheets[sheet_offset][row, col].annotation
78
+ end
79
+
80
+ def test_columns_repeated
81
+ sheet = @ods.create_sheet
82
+ row = 10
83
+ col = :C
84
+ sheet[row, col].value = 'hoge'
85
+ @ods.save(@file_path)
86
+
87
+ modified_ods = Ods.new(@file_path)
88
+ sheet = modified_ods.sheets[modified_ods.sheets.length-1]
89
+ assert_equal "3", sheet.column.attr('repeated')
90
+ end
91
+
92
+ def test_each_rows_and_cols
93
+ sheet = @ods.create_sheet
94
+ row_offset = 10
95
+ col_offset = :C
96
+ sheet[row_offset, col_offset].value = 'foo'
97
+ count = 0
98
+ sheet.rows.each do |row|
99
+ count += 1
100
+ end
101
+ assert_instance_of Ods::Row, sheet.rows[0]
102
+ assert_equal row_offset, count
103
+
104
+ count = 0
105
+ sheet.rows[row_offset-1].cols.each do |col|
106
+ count += 1
107
+ end
108
+ assert_instance_of Ods::Cell, sheet.rows[row_offset-1].cols[0]
109
+ assert_equal ('A'..col_offset.to_s).to_a.length, count
110
+ end
111
+
112
+ def test_row_no
113
+ sheet = @ods.sheets[0]
114
+ count = 0
115
+ sheet.rows.each_with_index do |row, index|
116
+ assert_equal index + 1, row.no
117
+ count += 1
118
+ end
119
+
120
+ sheet[count+1, :A].value = 'hoge'
121
+ assert_equal count+1, sheet.rows.last.no
122
+ end
123
+
124
+ def test_cell_no
125
+ sheet = @ods.create_sheet
126
+ sheet[1, :A].value = 'hoge'
127
+ sheet[2, :B].value = 'foo'
128
+ assert_equal :A, sheet.rows.first.cols.first.no
129
+ assert_equal :A, sheet.rows.first.cols.last.no
130
+ assert_equal :B, sheet.rows.last.cols.last.no
131
+ end
132
+ end
@@ -0,0 +1,53 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{yalab-ruby-ods}
8
+ s.version = "0.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["yalab"]
12
+ s.date = %q{2009-11-27}
13
+ s.description = %q{}
14
+ s.email = %q{rudeboyjet@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.txt"
17
+ ]
18
+ s.files = [
19
+ "README.txt",
20
+ "Rakefile",
21
+ "VERSION",
22
+ "lib/nokogiri_ext.rb",
23
+ "lib/ods.rb",
24
+ "test/cook.ods",
25
+ "test/ods_test.rb",
26
+ "yalab-ruby-ods.gemspec"
27
+ ]
28
+ s.homepage = %q{http://github.com/yalab/ruby-ods}
29
+ s.rdoc_options = ["--charset=UTF-8"]
30
+ s.require_paths = ["lib"]
31
+ s.rubygems_version = %q{1.3.5}
32
+ s.summary = %q{This is using OpenOffice SpreadSheet document file via ruby}
33
+ s.test_files = [
34
+ "test/ods_test.rb"
35
+ ]
36
+
37
+ if s.respond_to? :specification_version then
38
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
42
+ s.add_runtime_dependency(%q<nokogiri>, [">= 1.4.0"])
43
+ s.add_runtime_dependency(%q<rubyzip>, [">= 0.9.1"])
44
+ else
45
+ s.add_dependency(%q<nokogiri>, [">= 1.4.0"])
46
+ s.add_dependency(%q<rubyzip>, [">= 0.9.1"])
47
+ end
48
+ else
49
+ s.add_dependency(%q<nokogiri>, [">= 1.4.0"])
50
+ s.add_dependency(%q<rubyzip>, [">= 0.9.1"])
51
+ end
52
+ end
53
+
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: yalab-ruby-ods
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - yalab
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-27 00:00:00 +09:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: nokogiri
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.4.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rubyzip
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.1
34
+ version:
35
+ description: ""
36
+ email: rudeboyjet@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - README.txt
43
+ files:
44
+ - README.txt
45
+ - Rakefile
46
+ - VERSION
47
+ - lib/nokogiri_ext.rb
48
+ - lib/ods.rb
49
+ - test/cook.ods
50
+ - test/ods_test.rb
51
+ - yalab-ruby-ods.gemspec
52
+ has_rdoc: true
53
+ homepage: http://github.com/yalab/ruby-ods
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --charset=UTF-8
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.5
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: This is using OpenOffice SpreadSheet document file via ruby
80
+ test_files:
81
+ - test/ods_test.rb