sandrods-odf-report 0.1.1
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.
- data/Manifest +7 -0
- data/README.textile +150 -0
- data/Rakefile +14 -0
- data/lib/odf-report.rb +174 -0
- data/odf-report.gemspec +34 -0
- data/test/test.odt +0 -0
- data/test/test.rb +36 -0
- metadata +78 -0
data/Manifest
ADDED
data/README.textile
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
h1. ODF-REPORT
|
|
2
|
+
|
|
3
|
+
Gem for generating .odt files by making strings substitution in a previously created .odt file.
|
|
4
|
+
|
|
5
|
+
<hr/>
|
|
6
|
+
|
|
7
|
+
h3. INSTALL
|
|
8
|
+
|
|
9
|
+
@gem install sandrods-odf-report --source=http://gems.github.com@
|
|
10
|
+
|
|
11
|
+
<hr/>
|
|
12
|
+
|
|
13
|
+
h3. USAGE
|
|
14
|
+
|
|
15
|
+
h4. Step 1 -- the template
|
|
16
|
+
|
|
17
|
+
First of all, you need to create a .odt file to serve as a template
|
|
18
|
+
|
|
19
|
+
Templates are normal .odt files with placeholders for Substitutions
|
|
20
|
+
|
|
21
|
+
There are two kinds of substitutions available: *fields* and *tables*
|
|
22
|
+
|
|
23
|
+
h4. Fields placeholders
|
|
24
|
+
|
|
25
|
+
It's just an upcase sentence, surrounded by brackets. It will be replaced for wathever value you supply.
|
|
26
|
+
|
|
27
|
+
In the folowing example:
|
|
28
|
+
|
|
29
|
+
<pre>
|
|
30
|
+
report = ODFReport.new("Users/john/my_template.odt") do |r|
|
|
31
|
+
|
|
32
|
+
r.add_field :user_name, @user.name
|
|
33
|
+
r.add_field :address, "My new address"
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
</pre>
|
|
37
|
+
|
|
38
|
+
All occurences of @[USER_NAME]@ found in the file will be replaced by the value of @@user.name@ whereas all @[ADDRESS]@ 'es will contains @My new address@
|
|
39
|
+
|
|
40
|
+
It's as simple as that.
|
|
41
|
+
|
|
42
|
+
h4. Table placeholders
|
|
43
|
+
|
|
44
|
+
To use table placeholders, you should create a Table in your document and give it a name. In OpenOffice, it's just a matter of right-clicking the table you just created, choose _Table Properties..._ and type a name in the Name field.
|
|
45
|
+
|
|
46
|
+
If the table has two rows, the first one will be treated as a *header* and left untouched. Otherwise you should use a table with one row only.
|
|
47
|
+
|
|
48
|
+
As with Field placeholders, just insert a @[FIELD_NAME]@ in each cell and let the magic takes place.
|
|
49
|
+
|
|
50
|
+
Taking the folowing example:
|
|
51
|
+
|
|
52
|
+
<pre>
|
|
53
|
+
report = ODFReport.new("Users/john/my_template.odt") do |r|
|
|
54
|
+
|
|
55
|
+
r.add_field "USER_NAME", @user.nome
|
|
56
|
+
r.add_field "ADDRESS", @user.address
|
|
57
|
+
|
|
58
|
+
r.add_table("TABLE_1", @list_of_itens) do |row, item|
|
|
59
|
+
row["ITEM_ID"] = item.id
|
|
60
|
+
row["DESCRIPTION"] = "==> #{item.description}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
</pre>
|
|
65
|
+
|
|
66
|
+
and considering you have a table like this in your template
|
|
67
|
+
|
|
68
|
+
<pre>
|
|
69
|
+
----------------------------
|
|
70
|
+
| [ITEM_ID] | [DESCRIPTON] |
|
|
71
|
+
----------------------------
|
|
72
|
+
</pre>
|
|
73
|
+
|
|
74
|
+
and a collection @list_of_itens, it will be created one row for each item in the collection, and the replacement will take place accordingly.
|
|
75
|
+
|
|
76
|
+
Any format applied to the fields in the template will be preserved.
|
|
77
|
+
|
|
78
|
+
<hr/><br/>
|
|
79
|
+
|
|
80
|
+
h4. Step 2 -- generating the document
|
|
81
|
+
|
|
82
|
+
It's fairly simple to generate the document. You can use this inside a Rails application or in a standalone script.
|
|
83
|
+
|
|
84
|
+
h4. Generating a document in a Rails application
|
|
85
|
+
|
|
86
|
+
In a controller, you can have a code like this:
|
|
87
|
+
|
|
88
|
+
<pre>
|
|
89
|
+
def print
|
|
90
|
+
|
|
91
|
+
report = ODFReport.new("#{RAILS_ROOT}/app/reports/ticket.odt") do |r|
|
|
92
|
+
|
|
93
|
+
r.add_field(:id, @ticket.id.to_s)
|
|
94
|
+
r.add_field(:created_by, @ticket.created_by)
|
|
95
|
+
r.add_field(:created_at, @ticket.created_at.strftime("%d/%m/%Y - %H:%M"))
|
|
96
|
+
r.add_field(:type, @ticket.type.name)
|
|
97
|
+
r.add_field(:status, @ticket.status_text)
|
|
98
|
+
r.add_field(:date, Time.now.strftime("%d/%m/%Y - %H:%M"))
|
|
99
|
+
r.add_field(:solution, (@ticket.solution || ''))
|
|
100
|
+
|
|
101
|
+
r.add_table("OPERATORS", @ticket.operators) do | row, op |
|
|
102
|
+
row["OPERATOR_NAME"] = "#{op.name} (#{op.department.short_name})"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
r.add_table("FIELDS", @ticket.fields) do | row, field |
|
|
106
|
+
|
|
107
|
+
if field.is_a?(String)
|
|
108
|
+
row["FIELD_NAME"] = 'Materials'
|
|
109
|
+
row["FIELD_VALUE"] = field
|
|
110
|
+
else
|
|
111
|
+
row["FIELD_NAME"] = field.name
|
|
112
|
+
row["FIELD_VALUE"] = field.text_value || ''
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
report_file_name = report.generate
|
|
120
|
+
|
|
121
|
+
send_file(report_file_name)
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
</pre>
|
|
125
|
+
|
|
126
|
+
The @generate@ method will, er... generate the document in a temp dir and returns the full path of the generated file, so you can send it back to the user.
|
|
127
|
+
|
|
128
|
+
_That's all I have to say about that._
|
|
129
|
+
|
|
130
|
+
h4. Generating a document in a standalone script
|
|
131
|
+
|
|
132
|
+
It's just the same as in a Rails app, but you can inform the path where the file will be generated instead of using a temp dir.
|
|
133
|
+
|
|
134
|
+
<pre>
|
|
135
|
+
report = ODFReport.new("ticket.odt") do |r|
|
|
136
|
+
|
|
137
|
+
... populates the report ...
|
|
138
|
+
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
report.generate("./documents/")
|
|
142
|
+
</pre>
|
|
143
|
+
|
|
144
|
+
<hr/>
|
|
145
|
+
|
|
146
|
+
h3. REQUIREMENTS
|
|
147
|
+
|
|
148
|
+
*rubyzip*: for manipulating the contents of the odt file, since it's actually a zip file.
|
|
149
|
+
|
|
150
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'rake'
|
|
3
|
+
require 'echoe'
|
|
4
|
+
|
|
5
|
+
Echoe.new('odf-report', '0.1.1') do |p|
|
|
6
|
+
p.description = "Generates ODF files, given a template (.odt) and data, replacing tags"
|
|
7
|
+
p.url = ""
|
|
8
|
+
p.author = "Sandro Duarte"
|
|
9
|
+
p.email = "sandrods@gmail.com"
|
|
10
|
+
p.ignore_pattern = ["tmp/*", "script/*"]
|
|
11
|
+
p.development_dependencies = []
|
|
12
|
+
p.runtime_dependencies = ['rubyzip >= 0.9.1']
|
|
13
|
+
end
|
|
14
|
+
|
data/lib/odf-report.rb
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
require 'rubygems'
|
|
2
|
+
require 'zip/zipfilesystem'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
class ODFReport
|
|
6
|
+
|
|
7
|
+
def initialize(template_name, &block)
|
|
8
|
+
@template = template_name
|
|
9
|
+
@data={:values=>{}, :tables=>{}}
|
|
10
|
+
|
|
11
|
+
@tmp_dir = Dir.tmpdir + "/" + random_filename(:prefix=>'odt_')
|
|
12
|
+
Dir.mkdir(@tmp_dir) unless File.exists? @tmp_dir
|
|
13
|
+
|
|
14
|
+
yield self
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def add_field(field_tag, value)
|
|
18
|
+
@data[:values][field_tag] = value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_table(table_tag, collection, &block)
|
|
22
|
+
|
|
23
|
+
@data[:tables][table_tag] = []
|
|
24
|
+
|
|
25
|
+
collection.each do |item|
|
|
26
|
+
row = {}
|
|
27
|
+
yield(row, item)
|
|
28
|
+
@data[:tables][table_tag] << row
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def generate(dest = nil)
|
|
34
|
+
|
|
35
|
+
if dest
|
|
36
|
+
|
|
37
|
+
FileUtils.cp(@template, dest)
|
|
38
|
+
new_file = dest
|
|
39
|
+
|
|
40
|
+
else
|
|
41
|
+
|
|
42
|
+
FileUtils.cp(@template, @tmp_dir)
|
|
43
|
+
new_file = "#{@tmp_dir}/#{File.basename(@template)}"
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
%w(content.xml styles.xml).each do |content_file|
|
|
48
|
+
|
|
49
|
+
update_file_from_zip(new_file, content_file) do |txt|
|
|
50
|
+
|
|
51
|
+
replace_fields!(txt)
|
|
52
|
+
replace_tables!(txt)
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
new_file
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def update_file_from_zip(zip_file, content_file, &block)
|
|
65
|
+
|
|
66
|
+
Zip::ZipFile.open(zip_file) do |z|
|
|
67
|
+
cont = "#{@tmp_dir}/#{content_file}"
|
|
68
|
+
|
|
69
|
+
z.extract(content_file, cont)
|
|
70
|
+
|
|
71
|
+
txt = ''
|
|
72
|
+
|
|
73
|
+
File.open(cont, "r") do |f|
|
|
74
|
+
txt = f.read
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
yield(txt)
|
|
78
|
+
|
|
79
|
+
File.open(cont, "w") do |f|
|
|
80
|
+
f.write(txt)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
z.replace(content_file, cont)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def replace_fields!(content)
|
|
90
|
+
hash_gsub!(content, @data[:values])
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def replace_tables!(content)
|
|
95
|
+
|
|
96
|
+
@data[:tables].each do |table_name, records|
|
|
97
|
+
|
|
98
|
+
# search for the table inside the content
|
|
99
|
+
table_rgx = Regexp.new("(<table:table table:name=\"#{table_name}.*?>.*?<\/table:table>)", "m")
|
|
100
|
+
table_match = content.match(table_rgx)
|
|
101
|
+
|
|
102
|
+
if table_match
|
|
103
|
+
table = table_match[0]
|
|
104
|
+
|
|
105
|
+
# extract the table from the content
|
|
106
|
+
content.gsub!(table, "[TABLE_#{table_name}]")
|
|
107
|
+
|
|
108
|
+
# search for the table:row's
|
|
109
|
+
row_rgx = Regexp.new("(<table:table-row.*?<\/table:table-row>)", "m")
|
|
110
|
+
|
|
111
|
+
# use scan (instead of match) as the table can have more than one table-row (header and data)
|
|
112
|
+
# and scan returns all matches
|
|
113
|
+
row_match = table.scan(row_rgx)
|
|
114
|
+
|
|
115
|
+
unless row_match.empty?
|
|
116
|
+
|
|
117
|
+
# If there more than one line in the table, takes the second entry (row_match[1])
|
|
118
|
+
# since the first one represents the column header.
|
|
119
|
+
# If there just one line, takes the first line. Besides, since the entry is an Array itself,
|
|
120
|
+
# takes the entry's first element ( entry[0] )
|
|
121
|
+
model_row = (row_match[1] || row_match[0])[0]
|
|
122
|
+
|
|
123
|
+
# extract the row from the table
|
|
124
|
+
table.gsub!(model_row, "[ROW_#{table_name}]")
|
|
125
|
+
|
|
126
|
+
new_rows = ""
|
|
127
|
+
|
|
128
|
+
# for each record
|
|
129
|
+
records.each do |_values|
|
|
130
|
+
|
|
131
|
+
# generates one new row (table-row), based in the model extracted
|
|
132
|
+
# from the original table
|
|
133
|
+
tmp_row = model_row.dup
|
|
134
|
+
|
|
135
|
+
# replace values in the model_row and stores in new_rows
|
|
136
|
+
hash_gsub!(tmp_row, _values)
|
|
137
|
+
|
|
138
|
+
new_rows << tmp_row
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# replace back the lines into the table
|
|
142
|
+
table.gsub!("[ROW_#{table_name}]", new_rows)
|
|
143
|
+
|
|
144
|
+
end # unless row_match.empty?
|
|
145
|
+
|
|
146
|
+
# replace back the table into content
|
|
147
|
+
content.gsub!("[TABLE_#{table_name}]", table)
|
|
148
|
+
|
|
149
|
+
end # if table match
|
|
150
|
+
|
|
151
|
+
end # tables each
|
|
152
|
+
|
|
153
|
+
end # replace_tables
|
|
154
|
+
|
|
155
|
+
def hash_gsub!(_text, hash_of_values)
|
|
156
|
+
hash_of_values.each do |key, val|
|
|
157
|
+
_text.gsub!("[#{key.to_s.upcase}]", val.to_s) unless val.nil?
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def random_filename(opts={})
|
|
162
|
+
opts = {:chars => ('0'..'9').to_a + ('A'..'F').to_a + ('a'..'f').to_a,
|
|
163
|
+
:length => 24, :prefix => '', :suffix => '',
|
|
164
|
+
:verify => true, :attempts => 10}.merge(opts)
|
|
165
|
+
opts[:attempts].times do
|
|
166
|
+
filename = ''
|
|
167
|
+
opts[:length].times { filename << opts[:chars][rand(opts[:chars].size)] }
|
|
168
|
+
filename = opts[:prefix] + filename + opts[:suffix]
|
|
169
|
+
return filename unless opts[:verify] && File.exists?(filename)
|
|
170
|
+
end
|
|
171
|
+
nil
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
end
|
data/odf-report.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |s|
|
|
4
|
+
s.name = %q{odf-report}
|
|
5
|
+
s.version = "0.1.1"
|
|
6
|
+
|
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
|
8
|
+
s.authors = ["Sandro Duarte"]
|
|
9
|
+
s.date = %q{2009-07-28}
|
|
10
|
+
s.description = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
|
|
11
|
+
s.email = %q{sandrods@gmail.com}
|
|
12
|
+
s.extra_rdoc_files = ["lib/odf-report.rb", "README.textile"]
|
|
13
|
+
s.files = ["lib/odf-report.rb", "odf-report.gemspec", "Rakefile", "README.textile", "test/test.odt", "test/test.rb", "Manifest"]
|
|
14
|
+
s.has_rdoc = true
|
|
15
|
+
s.homepage = %q{}
|
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Odf-report", "--main", "README.textile"]
|
|
17
|
+
s.require_paths = ["lib"]
|
|
18
|
+
s.rubyforge_project = %q{odf-report}
|
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
|
20
|
+
s.summary = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
|
|
21
|
+
|
|
22
|
+
if s.respond_to? :specification_version then
|
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
24
|
+
s.specification_version = 2
|
|
25
|
+
|
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
|
27
|
+
s.add_runtime_dependency(%q<rubyzip>, [">= 0", "= 0.9.1"])
|
|
28
|
+
else
|
|
29
|
+
s.add_dependency(%q<rubyzip>, [">= 0", "= 0.9.1"])
|
|
30
|
+
end
|
|
31
|
+
else
|
|
32
|
+
s.add_dependency(%q<rubyzip>, [">= 0", "= 0.9.1"])
|
|
33
|
+
end
|
|
34
|
+
end
|
data/test/test.odt
ADDED
|
Binary file
|
data/test/test.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require '../lib/odf-report'
|
|
2
|
+
|
|
3
|
+
col1 = []
|
|
4
|
+
col1 << {:nome=>"Campo zero um", :id=>"Campo dois", :ender=>"tres tres"}
|
|
5
|
+
col1 << {:nome=>"zero um", :id=>"dois", :ender=>"tres"}
|
|
6
|
+
col1 << {:nome=>"Campo um", :id=>"Campo erthe dois", :ender=>"tres tres tres"}
|
|
7
|
+
|
|
8
|
+
col2 = []
|
|
9
|
+
col2 << {:nome=>"Campo zero um", :id=>"Campo dois", :ender=>"tres tres", :fone=>99025668, :cep=>"90420-002"}
|
|
10
|
+
col2 << {:nome=>"sandro", :id=>"45", :ender=>"minha casa", :fone=>88774451, :cep=>"90490-002"}
|
|
11
|
+
col2 << {:nome=>"ellen bicca", :id=>"77", :ender=>"casa dela", :fone=>77025668, :cep=>"94420-002"}
|
|
12
|
+
|
|
13
|
+
report = ODFReport.new("test.odt") do |r|
|
|
14
|
+
|
|
15
|
+
r.add_field("CAMPO_CAB", "Este campo estava no cabecalho")
|
|
16
|
+
|
|
17
|
+
r.add_field("TAG_01", "Nova tag")
|
|
18
|
+
r.add_field("TAG_02", "TAG-2 -> Nova tag")
|
|
19
|
+
|
|
20
|
+
r.add_table("TABELA_01", col1) do |row, item|
|
|
21
|
+
row["CAMPO_01"] = item[:id]
|
|
22
|
+
row["CAMPO_02"] = item[:nome]
|
|
23
|
+
row["CAMPO_03"] = item[:ender]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
r.add_table("TABELA_02", col2) do |row, item|
|
|
27
|
+
row["CAMPO_04"] = item[:id]
|
|
28
|
+
row["CAMPO_05"] = item[:nome]
|
|
29
|
+
row["CAMPO_06"] = item[:ender]
|
|
30
|
+
row["CAMPO_07"] = item[:fone]
|
|
31
|
+
row["CAMPO_08"] = item[:cep]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
report.generate("result.odt")
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sandrods-odf-report
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sandro Duarte
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-07-28 00:00:00 -07:00
|
|
13
|
+
default_executable:
|
|
14
|
+
dependencies:
|
|
15
|
+
- !ruby/object:Gem::Dependency
|
|
16
|
+
name: rubyzip
|
|
17
|
+
type: :runtime
|
|
18
|
+
version_requirement:
|
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
20
|
+
requirements:
|
|
21
|
+
- - ">="
|
|
22
|
+
- !ruby/object:Gem::Version
|
|
23
|
+
version: "0"
|
|
24
|
+
- - "="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.9.1
|
|
27
|
+
version:
|
|
28
|
+
description: Generates ODF files, given a template (.odt) and data, replacing tags
|
|
29
|
+
email: sandrods@gmail.com
|
|
30
|
+
executables: []
|
|
31
|
+
|
|
32
|
+
extensions: []
|
|
33
|
+
|
|
34
|
+
extra_rdoc_files:
|
|
35
|
+
- lib/odf-report.rb
|
|
36
|
+
- README.textile
|
|
37
|
+
files:
|
|
38
|
+
- lib/odf-report.rb
|
|
39
|
+
- odf-report.gemspec
|
|
40
|
+
- Rakefile
|
|
41
|
+
- README.textile
|
|
42
|
+
- test/test.odt
|
|
43
|
+
- test/test.rb
|
|
44
|
+
- Manifest
|
|
45
|
+
has_rdoc: true
|
|
46
|
+
homepage: ""
|
|
47
|
+
licenses:
|
|
48
|
+
post_install_message:
|
|
49
|
+
rdoc_options:
|
|
50
|
+
- --line-numbers
|
|
51
|
+
- --inline-source
|
|
52
|
+
- --title
|
|
53
|
+
- Odf-report
|
|
54
|
+
- --main
|
|
55
|
+
- README.textile
|
|
56
|
+
require_paths:
|
|
57
|
+
- lib
|
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
59
|
+
requirements:
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: "0"
|
|
63
|
+
version:
|
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: "1.2"
|
|
69
|
+
version:
|
|
70
|
+
requirements: []
|
|
71
|
+
|
|
72
|
+
rubyforge_project: odf-report
|
|
73
|
+
rubygems_version: 1.3.5
|
|
74
|
+
signing_key:
|
|
75
|
+
specification_version: 2
|
|
76
|
+
summary: Generates ODF files, given a template (.odt) and data, replacing tags
|
|
77
|
+
test_files: []
|
|
78
|
+
|