yalab-ruby-ods 0.0.0

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.
@@ -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