odf-report 0.1.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +6 -1
- data/README.textile +86 -48
- data/lib/odf-report.rb +5 -215
- data/lib/odf-report/file_ops.rb +57 -0
- data/lib/odf-report/hash_gsub.rb +20 -0
- data/lib/odf-report/report.rb +127 -0
- data/lib/odf-report/section.rb +126 -0
- data/lib/odf-report/table.rb +144 -0
- data/odf-report.gemspec +5 -6
- data/test/test.odt +0 -0
- data/test/test.rb +57 -19
- metadata +29 -11
- data/Rakefile +0 -14
data/Manifest
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
lib/odf-report.rb
|
2
|
+
lib/odf-report/report.rb
|
3
|
+
lib/odf-report/table.rb
|
4
|
+
lib/odf-report/section.rb
|
5
|
+
lib/odf-report/file_ops.rb
|
6
|
+
lib/odf-report/hash_gsub.rb
|
2
7
|
odf-report.gemspec
|
3
|
-
Rakefile
|
4
8
|
README.textile
|
5
9
|
test/test.odt
|
10
|
+
test/sections.odt
|
6
11
|
test/test.rb
|
7
12
|
Manifest
|
data/README.textile
CHANGED
@@ -1,37 +1,35 @@
|
|
1
1
|
h1. ODF-REPORT
|
2
2
|
|
3
|
-
Gem for generating .odt files by making strings, images and
|
3
|
+
Gem for generating .odt files by making strings, images, tables and sections replacements in a previously created .odt file.
|
4
4
|
|
5
|
-
<hr/>
|
6
5
|
|
7
|
-
|
6
|
+
h2. INSTALL
|
8
7
|
|
9
|
-
|
8
|
+
(sudo) gem install odf-report
|
10
9
|
|
11
|
-
<hr/>
|
12
10
|
|
13
|
-
|
11
|
+
h2. USAGE
|
14
12
|
|
15
|
-
|
13
|
+
h3. Step 1 -- the template
|
16
14
|
|
17
15
|
First of all, you need to create a .odt file to serve as a template
|
18
16
|
|
19
17
|
Templates are normal .odt files with placeholders for Substitutions
|
20
18
|
|
21
|
-
There are now
|
19
|
+
There are now *four* kinds of substitutions available: *fields*, *tables*, *images* and *sections*.
|
22
20
|
|
23
|
-
|
21
|
+
h3. Fields placeholders
|
24
22
|
|
25
23
|
It's just an upcase sentence, surrounded by brackets. It will be replaced for wathever value you supply.
|
26
24
|
|
27
25
|
In the folowing example:
|
28
26
|
|
29
27
|
<pre>
|
30
|
-
report = ODFReport.new("Users/john/my_template.odt") do |r|
|
28
|
+
report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
31
29
|
|
32
30
|
r.add_field :user_name, @user.name
|
33
31
|
r.add_field :address, "My new address"
|
34
|
-
|
32
|
+
|
35
33
|
end
|
36
34
|
</pre>
|
37
35
|
|
@@ -39,25 +37,26 @@ All occurences of @[USER_NAME]@ found in the file will be replaced by the value
|
|
39
37
|
|
40
38
|
It's as simple as that.
|
41
39
|
|
42
|
-
|
40
|
+
|
41
|
+
h3. Table placeholders
|
43
42
|
|
44
43
|
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
44
|
|
46
|
-
If
|
45
|
+
If you inform @:header=>true@, the first row will be treated as a *header* and left untouched. The remaining rows will be used as the template for the table. If you have more than one template row, they will be cycled. This is usefull for making zebra tables.
|
47
46
|
|
48
47
|
As with Field placeholders, just insert a @[FIELD_NAME]@ in each cell and let the magic takes place.
|
49
48
|
|
50
49
|
Taking the folowing example:
|
51
50
|
|
52
51
|
<pre>
|
53
|
-
report = ODFReport.new("Users/john/my_template.odt") do |r|
|
52
|
+
report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
54
53
|
|
55
54
|
r.add_field "USER_NAME", @user.nome
|
56
55
|
r.add_field "ADDRESS", @user.address
|
57
56
|
|
58
|
-
r.add_table("TABLE_1", @list_of_itens) do |
|
59
|
-
|
60
|
-
|
57
|
+
r.add_table("TABLE_1", @list_of_itens, :header=>true) do |t|
|
58
|
+
t.add_column(:item_id, :id)
|
59
|
+
t.add_column(:description) do { |item| "==> #{item.description}" }
|
61
60
|
end
|
62
61
|
|
63
62
|
end
|
@@ -70,9 +69,9 @@ and considering you have a table like this in your template
|
|
70
69
|
| [ITEM_ID] | [DESCRIPTION] |
|
71
70
|
---------------------------------
|
72
71
|
|
73
|
-
* this is my lame attempt to draw a table.
|
74
|
-
you don't suppose to type this.
|
75
|
-
you have to use an actual table.
|
72
|
+
* this is my lame attempt to draw a table.
|
73
|
+
you don't suppose to type this.
|
74
|
+
you have to use an actual table.
|
76
75
|
i don't know... just thought I'd mention it ;-)
|
77
76
|
</pre>
|
78
77
|
|
@@ -80,7 +79,8 @@ and a collection @list_of_itens, it will be created one row for each item in the
|
|
80
79
|
|
81
80
|
Any format applied to the fields in the template will be preserved.
|
82
81
|
|
83
|
-
|
82
|
+
|
83
|
+
h3. Images
|
84
84
|
|
85
85
|
You must put a mock image in your odt template and give it a name. That name will be used to replace the mock image for the actual image.
|
86
86
|
You can also assign any properties you want to the mock image and they will be kept once the image is replaced.
|
@@ -88,61 +88,99 @@ You can also assign any properties you want to the mock image and they will be k
|
|
88
88
|
An image replace would look like this:
|
89
89
|
|
90
90
|
<pre>
|
91
|
-
report = ODFReport.new("Users/john/my_template.odt") do |r|
|
91
|
+
report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
92
92
|
|
93
93
|
r.add_image :graphics1, "/path/to/the/image.jpg"
|
94
94
|
|
95
95
|
end
|
96
96
|
</pre>
|
97
97
|
|
98
|
-
|
98
|
+
|
99
|
+
h3. Sections (NEW!)
|
100
|
+
|
101
|
+
Sometimes, you have to repeat a whole chunk of a document, in a structure a lot more complex than a table. Now you can make a Section in your template and use it in this situations. Creating a Session in OpenOffice is as easy as select menu *Insert* and then *Section...*, and then choose a name for it.
|
102
|
+
|
103
|
+
*Section* 's are lot like Tables, in the sense that you can pass a collection and have that section repeated for each member of the collection. *But*, Sections can have anything inside it, even Tables, as long as you pass the appropriate data structure.
|
104
|
+
|
105
|
+
Let's see an example:
|
106
|
+
|
107
|
+
<pre>
|
108
|
+
|
109
|
+
@invoices = Invoice.find(:all)
|
110
|
+
|
111
|
+
report = ODFReport::Report.new("reports/invoice.odt") do |r|
|
112
|
+
|
113
|
+
r.add_field(:title, "INVOICES REPORT")
|
114
|
+
r.add_field(:date, Date.today)
|
115
|
+
|
116
|
+
r.add_section("SC_INVOICE", @invoices) do |s|
|
117
|
+
|
118
|
+
s.add_field(:number) { |invoice| invoice.number.to_s.rjust(5, '0') }
|
119
|
+
s.add_field(:name, :customer_name)
|
120
|
+
s.add_field(:address, :customer_address)
|
121
|
+
|
122
|
+
s.add_table("TB_ITEMS", :items, :header => true) do |t|
|
123
|
+
t.add_column(:id)
|
124
|
+
t.add_column(:product) {|item| item.product.name }
|
125
|
+
t.add_column(:value, :product_value)
|
126
|
+
end
|
127
|
+
|
128
|
+
s.add_field(:total) do |invoice|
|
129
|
+
if invoice.status == 'CLOSED'
|
130
|
+
invoice.total
|
131
|
+
else
|
132
|
+
invoice.items.sum('product_value')}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
</pre>
|
140
|
+
|
141
|
+
Note that when you add a Table to a Section, you don't pass the collection itself, but the attribute of the item of that section that's gonna return the collection for that particular Table. Sounds complicated, huh? But once you get it, it's quite straightforward.
|
142
|
+
|
143
|
+
In the above example, @s.add_table("TB_ITEMS", :items, :header => true) do |t|@, the @:items@ thing refers to a @invoice.items@. Easy, right?
|
99
144
|
|
100
145
|
<hr/><br/>
|
101
146
|
|
102
|
-
|
147
|
+
h3. Step 2 -- generating the document
|
103
148
|
|
104
149
|
It's fairly simple to generate the document. You can use this inside a Rails application or in a standalone script.
|
105
150
|
|
106
|
-
h4. Generating a document in a Rails application
|
151
|
+
h4. Generating a document in a Rails application
|
107
152
|
|
108
153
|
In a controller, you can have a code like this:
|
109
|
-
|
154
|
+
|
110
155
|
<pre>
|
111
156
|
def print
|
112
157
|
|
113
158
|
@ticket = Ticket.find(params[:id])
|
114
159
|
|
115
|
-
report = ODFReport.new("#{RAILS_ROOT}/app/reports/ticket.odt") do |r|
|
116
|
-
|
117
|
-
r.add_field(:id,
|
160
|
+
report = ODFReport::Report.new("#{RAILS_ROOT}/app/reports/ticket.odt") do |r|
|
161
|
+
|
162
|
+
r.add_field(:id, @ticket.id.to_s)
|
118
163
|
r.add_field(:created_by, @ticket.created_by)
|
119
164
|
r.add_field(:created_at, @ticket.created_at.strftime("%d/%m/%Y - %H:%M"))
|
120
|
-
r.add_field(:type,
|
121
|
-
r.add_field(:status,
|
122
|
-
r.add_field(:date,
|
123
|
-
r.add_field(:solution,
|
165
|
+
r.add_field(:type, @ticket.type.name)
|
166
|
+
r.add_field(:status, @ticket.status_text)
|
167
|
+
r.add_field(:date, Time.now.strftime("%d/%m/%Y - %H:%M"))
|
168
|
+
r.add_field(:solution, (@ticket.solution || ''))
|
124
169
|
|
125
|
-
r.add_table("OPERATORS", @ticket.operators) do |
|
126
|
-
|
170
|
+
r.add_table("OPERATORS", @ticket.operators) do |t|
|
171
|
+
t.add_column(:operator_name) { |op| "#{op.name} (#{op.department.short_name})" }
|
127
172
|
end
|
128
|
-
|
129
|
-
r.add_table("FIELDS", @ticket.fields) do | row, field |
|
130
|
-
|
131
|
-
if field.is_a?(String)
|
132
|
-
row["FIELD_NAME"] = 'Materials'
|
133
|
-
row["FIELD_VALUE"] = field
|
134
|
-
else
|
135
|
-
row["FIELD_NAME"] = field.name
|
136
|
-
row["FIELD_VALUE"] = field.text_value || ''
|
137
|
-
end
|
138
173
|
|
174
|
+
r.add_table("FIELDS", @ticket.fields) do |t|
|
175
|
+
t.add_column(:field_name, :name)
|
176
|
+
t.add_column(:field_value) { |field| field.text_value || "Empty" }
|
139
177
|
end
|
140
178
|
|
141
179
|
end
|
142
180
|
|
143
181
|
report_file_name = report.generate
|
144
182
|
|
145
|
-
send_file(report_file_name)
|
183
|
+
send_file(report_file_name)
|
146
184
|
|
147
185
|
end
|
148
186
|
</pre>
|
@@ -151,12 +189,12 @@ The @generate@ method will, er... generate the document in a temp dir and return
|
|
151
189
|
|
152
190
|
_That's all I have to say about that._
|
153
191
|
|
154
|
-
h4. Generating a document in a standalone script
|
192
|
+
h4. Generating a document in a standalone script
|
155
193
|
|
156
194
|
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.
|
157
195
|
|
158
196
|
<pre>
|
159
|
-
report = ODFReport.new("ticket.odt") do |r|
|
197
|
+
report = ODFReport::Report.new("ticket.odt") do |r|
|
160
198
|
|
161
199
|
... populates the report ...
|
162
200
|
|
data/lib/odf-report.rb
CHANGED
@@ -1,218 +1,8 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'zip/zipfilesystem'
|
3
3
|
require 'fileutils'
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@data={:values=>{}, :tables=>{}, :images => {} }
|
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 add_image(name, path)
|
34
|
-
@data[:images][name] = path
|
35
|
-
end
|
36
|
-
|
37
|
-
def generate(dest = nil)
|
38
|
-
|
39
|
-
if dest
|
40
|
-
|
41
|
-
FileUtils.cp(@template, dest)
|
42
|
-
new_file = dest
|
43
|
-
|
44
|
-
else
|
45
|
-
|
46
|
-
FileUtils.cp(@template, @tmp_dir)
|
47
|
-
new_file = "#{@tmp_dir}/#{File.basename(@template)}"
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
%w(content.xml styles.xml).each do |content_file|
|
52
|
-
|
53
|
-
update_file_from_zip(new_file, content_file) do |txt|
|
54
|
-
|
55
|
-
replace_fields!(txt)
|
56
|
-
replace_tables!(txt)
|
57
|
-
replace_image_refs!(txt)
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
unless @data[:images].empty?
|
63
|
-
image_dir_name = "Pictures"
|
64
|
-
dir = File.join("#{@tmp_dir}", image_dir_name)
|
65
|
-
add_image_files_to_dir(dir)
|
66
|
-
add_dir_to_zip(new_file, dir, image_dir_name)
|
67
|
-
end
|
68
|
-
|
69
|
-
new_file
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
private
|
74
|
-
|
75
|
-
def add_image_files_to_dir(dir)
|
76
|
-
FileUtils.mkdir(dir)
|
77
|
-
@data[:images].each_pair do |name, path|
|
78
|
-
FileUtils.cp(path, File.join(dir, File.basename(path)))
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def add_dir_to_zip(zip_file, dir, entry)
|
83
|
-
Zip::ZipFile.open(zip_file, true) do |z|
|
84
|
-
Dir["#{dir}/**/*"].each { |f| z.add("#{entry}/#{File.basename(f)}", f) }
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def update_file_from_zip(zip_file, content_file, &block)
|
89
|
-
|
90
|
-
Zip::ZipFile.open(zip_file) do |z|
|
91
|
-
cont = "#{@tmp_dir}/#{content_file}"
|
92
|
-
|
93
|
-
z.extract(content_file, cont)
|
94
|
-
|
95
|
-
txt = ''
|
96
|
-
|
97
|
-
File.open(cont, "r") do |f|
|
98
|
-
txt = f.read
|
99
|
-
end
|
100
|
-
|
101
|
-
yield(txt)
|
102
|
-
|
103
|
-
File.open(cont, "w") do |f|
|
104
|
-
f.write(txt)
|
105
|
-
end
|
106
|
-
|
107
|
-
z.replace(content_file, cont)
|
108
|
-
end
|
109
|
-
|
110
|
-
end
|
111
|
-
|
112
|
-
|
113
|
-
def replace_fields!(content)
|
114
|
-
hash_gsub!(content, @data[:values])
|
115
|
-
end
|
116
|
-
|
117
|
-
def replace_image_refs!(content)
|
118
|
-
@data[:images].each_pair do |image_name, path|
|
119
|
-
#Set the new image path
|
120
|
-
new_path = File.join("Pictures", File.basename(path))
|
121
|
-
#Search for the image
|
122
|
-
image_rgx = Regexp.new("draw:name=\"#{image_name}\".*?><draw:image.*?xlink:href=\"([^\s]*)\" .*?/></draw:frame>")
|
123
|
-
content_match = content.match(image_rgx)
|
124
|
-
if content_match
|
125
|
-
replace_path = content_match[1]
|
126
|
-
content.gsub!(content_match[0], content_match[0].gsub(replace_path, new_path))
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def replace_tables!(content)
|
132
|
-
|
133
|
-
@data[:tables].each do |table_name, records|
|
134
|
-
|
135
|
-
# search for the table inside the content
|
136
|
-
table_rgx = Regexp.new("(<table:table table:name=\"#{table_name}.*?>.*?<\/table:table>)", "m")
|
137
|
-
table_match = content.match(table_rgx)
|
138
|
-
|
139
|
-
if table_match
|
140
|
-
table = table_match[0]
|
141
|
-
|
142
|
-
# extract the table from the content
|
143
|
-
content.gsub!(table, "[TABLE_#{table_name}]")
|
144
|
-
|
145
|
-
# search for the table:row's
|
146
|
-
row_rgx = Regexp.new("(<table:table-row.*?<\/table:table-row>)", "m")
|
147
|
-
|
148
|
-
# use scan (instead of match) as the table can have more than one table-row (header and data)
|
149
|
-
# and scan returns all matches
|
150
|
-
row_match = table.scan(row_rgx)
|
151
|
-
|
152
|
-
unless row_match.empty?
|
153
|
-
|
154
|
-
# If there more than one line in the table, takes the second entry (row_match[1])
|
155
|
-
# since the first one represents the column header.
|
156
|
-
# If there just one line, takes the first line. Besides, since the entry is an Array itself,
|
157
|
-
# takes the entry's first element ( entry[0] )
|
158
|
-
model_row = (row_match[1] || row_match[0])[0]
|
159
|
-
|
160
|
-
# extract the row from the table
|
161
|
-
table.gsub!(model_row, "[ROW_#{table_name}]")
|
162
|
-
|
163
|
-
new_rows = ""
|
164
|
-
|
165
|
-
# for each record
|
166
|
-
records.each do |_values|
|
167
|
-
|
168
|
-
# generates one new row (table-row), based in the model extracted
|
169
|
-
# from the original table
|
170
|
-
tmp_row = model_row.dup
|
171
|
-
|
172
|
-
# replace values in the model_row and stores in new_rows
|
173
|
-
hash_gsub!(tmp_row, _values)
|
174
|
-
|
175
|
-
new_rows << tmp_row
|
176
|
-
end
|
177
|
-
|
178
|
-
# replace back the lines into the table
|
179
|
-
table.gsub!("[ROW_#{table_name}]", new_rows)
|
180
|
-
|
181
|
-
end # unless row_match.empty?
|
182
|
-
|
183
|
-
# replace back the table into content
|
184
|
-
content.gsub!("[TABLE_#{table_name}]", table)
|
185
|
-
|
186
|
-
end # if table match
|
187
|
-
|
188
|
-
end # tables each
|
189
|
-
|
190
|
-
end # replace_tables
|
191
|
-
|
192
|
-
def hash_gsub!(_text, hash_of_values)
|
193
|
-
hash_of_values.each do |key, val|
|
194
|
-
_text.gsub!("[#{key.to_s.upcase}]", html_escape(val)) unless val.nil?
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
def random_filename(opts={})
|
199
|
-
opts = {:chars => ('0'..'9').to_a + ('A'..'F').to_a + ('a'..'f').to_a,
|
200
|
-
:length => 24, :prefix => '', :suffix => '',
|
201
|
-
:verify => true, :attempts => 10}.merge(opts)
|
202
|
-
opts[:attempts].times do
|
203
|
-
filename = ''
|
204
|
-
opts[:length].times { filename << opts[:chars][rand(opts[:chars].size)] }
|
205
|
-
filename = opts[:prefix] + filename + opts[:suffix]
|
206
|
-
return filename unless opts[:verify] && File.exists?(filename)
|
207
|
-
end
|
208
|
-
nil
|
209
|
-
end
|
210
|
-
|
211
|
-
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
212
|
-
|
213
|
-
def html_escape(s)
|
214
|
-
return "" unless s
|
215
|
-
s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
|
216
|
-
end
|
217
|
-
|
218
|
-
end
|
4
|
+
require 'odf-report/file_ops'
|
5
|
+
require 'odf-report/hash_gsub'
|
6
|
+
require 'odf-report/section'
|
7
|
+
require 'odf-report/table'
|
8
|
+
require 'odf-report/report'
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module ODFReport
|
2
|
+
|
3
|
+
module FileOps
|
4
|
+
|
5
|
+
def random_filename(opts={})
|
6
|
+
opts = {:chars => ('0'..'9').to_a + ('A'..'F').to_a + ('a'..'f').to_a,
|
7
|
+
:length => 24, :prefix => '', :suffix => '',
|
8
|
+
:verify => true, :attempts => 10}.merge(opts)
|
9
|
+
opts[:attempts].times do
|
10
|
+
filename = ''
|
11
|
+
opts[:length].times { filename << opts[:chars][rand(opts[:chars].size)] }
|
12
|
+
filename = opts[:prefix] + filename + opts[:suffix]
|
13
|
+
return filename unless opts[:verify] && File.exists?(filename)
|
14
|
+
end
|
15
|
+
nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_files_to_dir(files, dir)
|
19
|
+
FileUtils.mkdir(dir)
|
20
|
+
files.each do |path|
|
21
|
+
FileUtils.cp(path, File.join(dir, File.basename(path)))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_dir_to_zip(zip_file, dir, entry)
|
26
|
+
Zip::ZipFile.open(zip_file, true) do |z|
|
27
|
+
Dir["#{dir}/**/*"].each { |f| z.add("#{entry}/#{File.basename(f)}", f) }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_file_from_zip(zip_file, content_file, &block)
|
32
|
+
|
33
|
+
Zip::ZipFile.open(zip_file) do |z|
|
34
|
+
cont = "#{@tmp_dir}/#{content_file}"
|
35
|
+
|
36
|
+
z.extract(content_file, cont)
|
37
|
+
|
38
|
+
txt = ''
|
39
|
+
|
40
|
+
File.open(cont, "r") do |f|
|
41
|
+
txt = f.read
|
42
|
+
end
|
43
|
+
|
44
|
+
yield(txt)
|
45
|
+
|
46
|
+
File.open(cont, "w") do |f|
|
47
|
+
f.write(txt)
|
48
|
+
end
|
49
|
+
|
50
|
+
z.replace(content_file, cont)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ODFReport
|
2
|
+
|
3
|
+
module HashGsub
|
4
|
+
|
5
|
+
def hash_gsub!(_text, hash_of_values)
|
6
|
+
hash_of_values.each do |key, val|
|
7
|
+
_text.gsub!("[#{key.to_s.upcase}]", html_escape(val))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
12
|
+
|
13
|
+
def html_escape(s)
|
14
|
+
return "" unless s
|
15
|
+
s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module ODFReport
|
2
|
+
|
3
|
+
class Report
|
4
|
+
include HashGsub, FileOps
|
5
|
+
|
6
|
+
attr_accessor :values, :tables, :images, :sections
|
7
|
+
|
8
|
+
def initialize(template_name, &block)
|
9
|
+
@template = template_name
|
10
|
+
|
11
|
+
@values = {}
|
12
|
+
@tables = []
|
13
|
+
@images = {}
|
14
|
+
@sections = []
|
15
|
+
|
16
|
+
@tmp_dir = Dir.tmpdir + "/" + random_filename(:prefix=>'odt_')
|
17
|
+
Dir.mkdir(@tmp_dir) unless File.exists? @tmp_dir
|
18
|
+
|
19
|
+
yield(self)
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_section(section_name, collection, &block)
|
24
|
+
sec = Section.new(section_name)
|
25
|
+
@sections << sec
|
26
|
+
|
27
|
+
yield(sec)
|
28
|
+
|
29
|
+
sec.populate(collection)
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_field(field_tag, value)
|
34
|
+
@values[field_tag] = value || ''
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_table(table_name, collection, opts={}, &block)
|
38
|
+
opts[:name] = table_name
|
39
|
+
tab = Table.new(opts)
|
40
|
+
yield(tab)
|
41
|
+
@tables << tab
|
42
|
+
|
43
|
+
tab.populate(collection)
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_image(name, path)
|
48
|
+
@images[name] = path
|
49
|
+
end
|
50
|
+
|
51
|
+
def generate(dest = nil)
|
52
|
+
|
53
|
+
if dest
|
54
|
+
|
55
|
+
FileUtils.cp(@template, dest)
|
56
|
+
new_file = dest
|
57
|
+
|
58
|
+
else
|
59
|
+
|
60
|
+
FileUtils.cp(@template, @tmp_dir)
|
61
|
+
new_file = "#{@tmp_dir}/#{File.basename(@template)}"
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
%w(content.xml styles.xml).each do |content_file|
|
66
|
+
|
67
|
+
update_file_from_zip(new_file, content_file) do |txt|
|
68
|
+
|
69
|
+
replace_fields!(txt)
|
70
|
+
replace_tables!(txt)
|
71
|
+
replace_image_refs!(txt)
|
72
|
+
replace_sections!(txt)
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
unless @images.empty?
|
79
|
+
image_dir_name = "Pictures"
|
80
|
+
dir = File.join("#{@tmp_dir}", image_dir_name)
|
81
|
+
add_files_to_dir(@images.values, dir)
|
82
|
+
add_dir_to_zip(new_file, dir, image_dir_name)
|
83
|
+
end
|
84
|
+
|
85
|
+
new_file
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def replace_fields!(content)
|
92
|
+
hash_gsub!(content, @values)
|
93
|
+
end
|
94
|
+
|
95
|
+
def replace_tables!(content)
|
96
|
+
|
97
|
+
@tables.each do |table|
|
98
|
+
table.replace!(content)
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
def replace_sections!(content)
|
104
|
+
|
105
|
+
@sections.each do |section|
|
106
|
+
section.replace!(content)
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
|
111
|
+
def replace_image_refs!(content)
|
112
|
+
@images.each_pair do |image_name, path|
|
113
|
+
#Set the new image path
|
114
|
+
new_path = File.join("Pictures", File.basename(path))
|
115
|
+
#Search for the image
|
116
|
+
image_rgx = Regexp.new("draw:name=\"#{image_name}\".*?><draw:image.*?xlink:href=\"([^\s]*)\" .*?/></draw:frame>")
|
117
|
+
content_match = content.match(image_rgx)
|
118
|
+
if content_match
|
119
|
+
replace_path = content_match[1]
|
120
|
+
content.gsub!(content_match[0], content_match[0].gsub(replace_path, new_path))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module ODFReport
|
2
|
+
|
3
|
+
class Section
|
4
|
+
include HashGsub
|
5
|
+
|
6
|
+
attr_accessor :fields, :tables, :data, :name
|
7
|
+
|
8
|
+
def initialize(name)
|
9
|
+
@name = name
|
10
|
+
|
11
|
+
@fields = {}
|
12
|
+
@data = []
|
13
|
+
@tables = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_field(name, field=nil, &block)
|
17
|
+
if field
|
18
|
+
@fields[name] = lambda { |item| item.send(field)}
|
19
|
+
else
|
20
|
+
@fields[name] = block
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_table(table_name, collection_field, opts={}, &block)
|
25
|
+
opts.merge!(:name => table_name, :collection_field => collection_field)
|
26
|
+
tab = Table.new(opts)
|
27
|
+
yield(tab)
|
28
|
+
@tables << tab
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
def populate(collection)
|
33
|
+
|
34
|
+
collection.each do |item|
|
35
|
+
row = {}
|
36
|
+
@fields.each do |field_name, block1|
|
37
|
+
row[field_name] = block1.call(item)
|
38
|
+
end
|
39
|
+
|
40
|
+
row[:tables] = {}
|
41
|
+
@tables.each do |table|
|
42
|
+
collection = get_collection_from_item(item, table.collection_field)
|
43
|
+
row[:tables][table.name] = table.values(collection)
|
44
|
+
end
|
45
|
+
|
46
|
+
@data << row
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def replace!(content)
|
52
|
+
|
53
|
+
# search for the table inside the content
|
54
|
+
section_rgx = Regexp.new("(<text:section.*?text:name=\"#{@name}.*?>(.*?)<\/text:section>)", "m")
|
55
|
+
section_match = content.match(section_rgx)
|
56
|
+
|
57
|
+
if section_match
|
58
|
+
section_full = section_match[0]
|
59
|
+
section_content = section_match[2]
|
60
|
+
|
61
|
+
# extract the section from the content
|
62
|
+
content.gsub!(section_full, "[SECTION_#{@name}]")
|
63
|
+
|
64
|
+
new_content = ""
|
65
|
+
|
66
|
+
# for each record
|
67
|
+
@data.each do |_values|
|
68
|
+
|
69
|
+
# generates one new row (table-row), based in the model extracted
|
70
|
+
# from the original table
|
71
|
+
tmp_row = section_content.dup
|
72
|
+
|
73
|
+
# replace values in the section_content and stores in new_content
|
74
|
+
hash_gsub!(tmp_row, _values)
|
75
|
+
|
76
|
+
@tables.each do |t|
|
77
|
+
t.replace!(tmp_row, _values[:tables][t.name])
|
78
|
+
end
|
79
|
+
|
80
|
+
new_content << tmp_row
|
81
|
+
end
|
82
|
+
|
83
|
+
# replace back the table into content
|
84
|
+
content.gsub!("[SECTION_#{@name}]", new_content)
|
85
|
+
|
86
|
+
end # if table match
|
87
|
+
|
88
|
+
end # replace_section
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def get_collection_from_item(item, collection_field)
|
93
|
+
|
94
|
+
if collection_field.is_a?(Array)
|
95
|
+
tmp = item.dup
|
96
|
+
collection_field.each do |f|
|
97
|
+
if f.is_a?(Hash)
|
98
|
+
tmp = tmp.send(f.keys[0], f.values[0])
|
99
|
+
else
|
100
|
+
tmp = tmp.send(f)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
collection = tmp
|
104
|
+
else
|
105
|
+
collection = item.send(collection_field)
|
106
|
+
end
|
107
|
+
|
108
|
+
return collection
|
109
|
+
end
|
110
|
+
|
111
|
+
def hash_gsub!(_text, hash_of_values)
|
112
|
+
hash_of_values.each do |key, val|
|
113
|
+
_text.gsub!("[#{key.to_s.upcase}]", html_escape(val))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
118
|
+
|
119
|
+
def html_escape(s)
|
120
|
+
return "" unless s
|
121
|
+
s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module ODFReport
|
2
|
+
|
3
|
+
class Table
|
4
|
+
include HashGsub
|
5
|
+
|
6
|
+
attr_accessor :fields, :rows, :name, :collection_field, :data, :header
|
7
|
+
|
8
|
+
def initialize(opts)
|
9
|
+
@name = opts[:name]
|
10
|
+
@collection_field = opts[:collection_field]
|
11
|
+
@collection = opts[:collection]
|
12
|
+
@header = opts[:header] || false
|
13
|
+
|
14
|
+
@fields = {}
|
15
|
+
@rows = []
|
16
|
+
@data = []
|
17
|
+
end
|
18
|
+
|
19
|
+
def add_column(name, field=nil, &block)
|
20
|
+
if field
|
21
|
+
@fields[name] = lambda { |item| item.send(field)}
|
22
|
+
elsif block_given?
|
23
|
+
@fields[name] = block
|
24
|
+
else
|
25
|
+
@fields[name] = lambda { |item| item.send(name)}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def values(collection)
|
30
|
+
ret = []
|
31
|
+
collection.each do |item|
|
32
|
+
row = {}
|
33
|
+
@fields.each do |field_name, block1|
|
34
|
+
row[field_name] = block1.call(item) || ''
|
35
|
+
end
|
36
|
+
ret << row
|
37
|
+
end
|
38
|
+
ret
|
39
|
+
end
|
40
|
+
|
41
|
+
def populate(collection)
|
42
|
+
@data = values(collection)
|
43
|
+
end
|
44
|
+
|
45
|
+
def replace!(content, rows = nil)
|
46
|
+
@data = rows if rows
|
47
|
+
|
48
|
+
# search for the table inside the content
|
49
|
+
table_rgx = Regexp.new("(<table:table table:name=\"#{@name}.*?>.*?<\/table:table>)", "m")
|
50
|
+
table_match = content.match(table_rgx)
|
51
|
+
|
52
|
+
if table_match
|
53
|
+
table = table_match[0]
|
54
|
+
|
55
|
+
# extract the table from the content
|
56
|
+
content.gsub!(table, "[TABLE_#{@name}]")
|
57
|
+
|
58
|
+
# search for the table:row's
|
59
|
+
row_rgx = Regexp.new("(<table:table-row.*?<\/table:table-row>)", "m")
|
60
|
+
|
61
|
+
# use scan (instead of match) as the table can have more than one table-row (header and data)
|
62
|
+
# and scan returns all matches
|
63
|
+
row_match = table.scan(row_rgx)
|
64
|
+
|
65
|
+
unless row_match.empty?
|
66
|
+
|
67
|
+
replace_rows!(table, row_match)
|
68
|
+
|
69
|
+
new_rows = ""
|
70
|
+
|
71
|
+
# for each record
|
72
|
+
@data.each do |_values|
|
73
|
+
|
74
|
+
# generates one new row (table-row), based in the model extracted
|
75
|
+
# from the original table
|
76
|
+
tmp_row = get_next_row.dup
|
77
|
+
|
78
|
+
# replace values in the model_row and stores in new_rows
|
79
|
+
hash_gsub!(tmp_row, _values)
|
80
|
+
|
81
|
+
new_rows << tmp_row
|
82
|
+
end
|
83
|
+
|
84
|
+
# replace back the lines into the table
|
85
|
+
table.gsub!("[ROW_#{@name}]", new_rows)
|
86
|
+
|
87
|
+
end # unless row_match.empty?
|
88
|
+
|
89
|
+
# replace back the table into content
|
90
|
+
if @data.empty?
|
91
|
+
content.gsub!("[TABLE_#{@name}]", "")
|
92
|
+
else
|
93
|
+
content.gsub!("[TABLE_#{@name}]", table)
|
94
|
+
end
|
95
|
+
|
96
|
+
end # if table match
|
97
|
+
|
98
|
+
end # replace
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def replace_rows!(table, rows)
|
103
|
+
|
104
|
+
rows.delete_at(0) if @header # ignore the header
|
105
|
+
|
106
|
+
@rows = rows.dup
|
107
|
+
@row_cursor = 0
|
108
|
+
|
109
|
+
# extract the rows from the table
|
110
|
+
first = rows.delete_at(0)[0]
|
111
|
+
table.gsub!(first, "[ROW_#{@name}]")
|
112
|
+
|
113
|
+
rows.each do |r|
|
114
|
+
table.gsub!(r[0], "")
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_next_row
|
120
|
+
ret = @rows[@row_cursor]
|
121
|
+
if @rows.size == @row_cursor + 1
|
122
|
+
@row_cursor = 0
|
123
|
+
else
|
124
|
+
@row_cursor += 1
|
125
|
+
end
|
126
|
+
return ret[0]
|
127
|
+
end
|
128
|
+
|
129
|
+
def hash_gsub!(_text, hash_of_values)
|
130
|
+
hash_of_values.each do |key, val|
|
131
|
+
_text.gsub!("[#{key.to_s.upcase}]", html_escape(val))
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
HTML_ESCAPE = { '&' => '&', '>' => '>', '<' => '<', '"' => '"' }
|
136
|
+
|
137
|
+
def html_escape(s)
|
138
|
+
return "" unless s
|
139
|
+
s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
data/odf-report.gemspec
CHANGED
@@ -2,21 +2,20 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{odf-report}
|
5
|
-
s.version = "0.
|
5
|
+
s.version = "0.3.0"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Sandro Duarte"]
|
9
|
-
s.date = %q{
|
9
|
+
s.date = %q{2010-05-18}
|
10
10
|
s.description = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
|
11
11
|
s.email = %q{sandrods@gmail.com}
|
12
12
|
s.extra_rdoc_files = ["lib/odf-report.rb", "README.textile"]
|
13
|
-
s.files =
|
14
|
-
s.has_rdoc =
|
13
|
+
s.files = %w{lib/odf-report.rb odf-report.gemspec README.textile test/test.odt test/test.rb Manifest lib/odf-report/report.rb lib/odf-report/table.rb lib/odf-report/section.rb lib/odf-report/file_ops.rb lib/odf-report/hash_gsub.rb }
|
14
|
+
s.has_rdoc = false
|
15
15
|
s.homepage = %q{}
|
16
16
|
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Odf-report", "--main", "README.textile"]
|
17
17
|
s.require_paths = ["lib"]
|
18
|
-
s.
|
19
|
-
s.rubygems_version = %q{1.3.1}
|
18
|
+
s.rubygems_version = %q{1.3.6}
|
20
19
|
s.summary = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
|
21
20
|
|
22
21
|
if s.respond_to? :specification_version then
|
data/test/test.odt
CHANGED
Binary file
|
data/test/test.rb
CHANGED
@@ -1,35 +1,36 @@
|
|
1
1
|
require '../lib/odf-report'
|
2
|
+
require 'ostruct'
|
2
3
|
|
3
4
|
col1 = []
|
4
|
-
|
5
|
-
col1 << {:name=>"name
|
6
|
-
|
7
|
-
|
5
|
+
(1..15).each do |i|
|
6
|
+
col1 << OpenStruct.new({:name=>"name #{i}", :id=>i, :address=>"this is address #{i}"})
|
7
|
+
end
|
8
|
+
|
8
9
|
|
9
10
|
col2 = []
|
10
|
-
col2 << {:name=>"josh harnet",
|
11
|
-
col2 << {:name=>"sandro",
|
12
|
-
col2 << {:name=>"ellen bicca",
|
11
|
+
col2 << OpenStruct.new({:name=>"josh harnet", :id=>"02", :address=>"testing <&> ", :phone=>99025668, :zip=>"90420-002"})
|
12
|
+
col2 << OpenStruct.new({:name=>"sandro duarte", :id=>"45", :address=>"address with &", :phone=>88774451, :zip=>"90490-002"})
|
13
|
+
col2 << OpenStruct.new({:name=>"ellen bicca", :id=>"77", :address=>"<address with escaped html>", :phone=>77025668, :zip=>"94420-002"})
|
13
14
|
|
14
|
-
report = ODFReport.new("test.odt") do |r|
|
15
|
+
report = ODFReport::Report.new("test.odt") do |r|
|
15
16
|
|
16
|
-
r.add_field("HEADER_FIELD", "This
|
17
|
+
r.add_field("HEADER_FIELD", "This field was in the HEADER")
|
17
18
|
|
18
19
|
r.add_field("TAG_01", "New tag")
|
19
20
|
r.add_field("TAG_02", "TAG-2 -> New tag")
|
20
21
|
|
21
|
-
r.add_table("TABLE_01", col1) do |
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
r.add_table("TABLE_01", col1, :header=>true) do |t|
|
23
|
+
t.add_column(:field_01, :id)
|
24
|
+
t.add_column(:field_02, :name)
|
25
|
+
t.add_column(:field_03, :address)
|
25
26
|
end
|
26
27
|
|
27
|
-
r.add_table("TABLE_02", col2) do |
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
28
|
+
r.add_table("TABLE_02", col2) do |t|
|
29
|
+
t.add_column(:field_04, :id)
|
30
|
+
t.add_column(:field_05, :name)
|
31
|
+
t.add_column(:field_06, :address)
|
32
|
+
t.add_column(:field_07, :phone)
|
33
|
+
t.add_column(:field_08, :zip)
|
33
34
|
end
|
34
35
|
|
35
36
|
r.add_image("graphics1", File.join(Dir.pwd, 'piriapolis.jpg'))
|
@@ -37,3 +38,40 @@ report = ODFReport.new("test.odt") do |r|
|
|
37
38
|
end
|
38
39
|
|
39
40
|
report.generate("result.odt")
|
41
|
+
|
42
|
+
class Item
|
43
|
+
attr_accessor :name, :sid, :children
|
44
|
+
def initialize(_name, _sid, _children=[])
|
45
|
+
@name=_name
|
46
|
+
@sid=_sid
|
47
|
+
@children=_children
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
items = []
|
52
|
+
items << Item.new("Dexter Morgan", '007', %w(sawyer juliet hurley locke jack freckles))
|
53
|
+
items << Item.new("Danny Crane", '302', %w(sidney sloane jack michael marshal))
|
54
|
+
items << Item.new("Coach Taylor", '220', %w(meredith christina izzie alex george))
|
55
|
+
|
56
|
+
report = ODFReport::Report.new("sections.odt") do |r|
|
57
|
+
|
58
|
+
r.add_field("TAG_01", "New tag")
|
59
|
+
r.add_field("TAG_02", "TAG-2 -> New tag")
|
60
|
+
|
61
|
+
r.add_section("SECTION_01", items) do |s|
|
62
|
+
|
63
|
+
s.add_field('NAME') do |i|
|
64
|
+
i.name
|
65
|
+
end
|
66
|
+
|
67
|
+
s.add_field('SID', :sid)
|
68
|
+
|
69
|
+
s.add_table('TABLE_S1', :children, :header=>true) do |t|
|
70
|
+
t.add_column('NAME1') { |item| "-> #{item}" }
|
71
|
+
t.add_column('INV') { |item| item.to_s.reverse.upcase }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
report.generate("section_result.odt")
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: odf-report
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
version: 0.3.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Sandro Duarte
|
@@ -9,22 +14,28 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date:
|
17
|
+
date: 2010-05-18 00:00:00 -03:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: rubyzip
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
25
|
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
23
29
|
version: "0"
|
24
30
|
- - "="
|
25
31
|
- !ruby/object:Gem::Version
|
32
|
+
segments:
|
33
|
+
- 0
|
34
|
+
- 9
|
35
|
+
- 1
|
26
36
|
version: 0.9.1
|
27
|
-
|
37
|
+
type: :runtime
|
38
|
+
version_requirements: *id001
|
28
39
|
description: Generates ODF files, given a template (.odt) and data, replacing tags
|
29
40
|
email: sandrods@gmail.com
|
30
41
|
executables: []
|
@@ -37,11 +48,15 @@ extra_rdoc_files:
|
|
37
48
|
files:
|
38
49
|
- lib/odf-report.rb
|
39
50
|
- odf-report.gemspec
|
40
|
-
- Rakefile
|
41
51
|
- README.textile
|
42
52
|
- test/test.odt
|
43
53
|
- test/test.rb
|
44
54
|
- Manifest
|
55
|
+
- lib/odf-report/report.rb
|
56
|
+
- lib/odf-report/table.rb
|
57
|
+
- lib/odf-report/section.rb
|
58
|
+
- lib/odf-report/file_ops.rb
|
59
|
+
- lib/odf-report/hash_gsub.rb
|
45
60
|
has_rdoc: true
|
46
61
|
homepage: ""
|
47
62
|
licenses: []
|
@@ -60,18 +75,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
75
|
requirements:
|
61
76
|
- - ">="
|
62
77
|
- !ruby/object:Gem::Version
|
78
|
+
segments:
|
79
|
+
- 0
|
63
80
|
version: "0"
|
64
|
-
version:
|
65
81
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
82
|
requirements:
|
67
83
|
- - ">="
|
68
84
|
- !ruby/object:Gem::Version
|
85
|
+
segments:
|
86
|
+
- 1
|
87
|
+
- 2
|
69
88
|
version: "1.2"
|
70
|
-
version:
|
71
89
|
requirements: []
|
72
90
|
|
73
|
-
rubyforge_project:
|
74
|
-
rubygems_version: 1.3.
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.3.6
|
75
93
|
signing_key:
|
76
94
|
specification_version: 2
|
77
95
|
summary: Generates ODF files, given a template (.odt) and data, replacing tags
|
data/Rakefile
DELETED
@@ -1,14 +0,0 @@
|
|
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
|
-
|