odf-report 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +218 -0
- data/lib/odf-report.rb +1 -1
- data/lib/odf-report/images.rb +1 -1
- data/lib/odf-report/report.rb +14 -26
- data/lib/odf-report/template.rb +68 -0
- data/lib/odf-report/version.rb +1 -1
- data/odf-report.gemspec +1 -2
- data/spec/template_spec.rb +45 -0
- metadata +11 -9
- data/README.textile +0 -223
- data/lib/odf-report/file.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 561d0b3f786d5514b046221b73e9d9056b8ea4b41e74c91bb6ddfd41ce6919fc
|
4
|
+
data.tar.gz: 062e1f340e3923cb3f5e977844a2bc9acc8de1d44496cd3a9dae19d08de92c83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c15b086a652768a8199d45ba92ad67f3d2f1d920757d2605b3903f0b41c2f05af8a23be7234c4ef5805f32f368e8a7c44180d133584216f65f49f8e6d50c0538
|
7
|
+
data.tar.gz: 3b9b7907b90d3316dc63afa19e52df2c0d0b6e5e085f3454077cf78bd30916458b582093d403d998450123300e067a7ebf19bc69833bd6d0ddf2aff608b008f6
|
data/README.md
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
|
2
|
+
# ODF-REPORT
|
3
|
+
|
4
|
+
Gem for generating .odt files by making strings, images, tables and sections replacements in a previously created .odt file.
|
5
|
+
|
6
|
+
### NEW
|
7
|
+
|
8
|
+
* you can provide an `io:` param to the report, so you can use templates stored in the database.
|
9
|
+
* now uses rubyzip ~> **1.2.0**
|
10
|
+
|
11
|
+
## INSTALL
|
12
|
+
|
13
|
+
In your Gemfile
|
14
|
+
```ruby
|
15
|
+
gem 'odf-report'
|
16
|
+
```
|
17
|
+
|
18
|
+
## USAGE
|
19
|
+
|
20
|
+
### Step 1 -- the template
|
21
|
+
|
22
|
+
First of all, you need a `.odt` file to serve as a template.
|
23
|
+
Templates are normal .odt files with `[PLACEHOLDERS]` for *substitutions*.
|
24
|
+
There are *four* kinds of substitutions available:
|
25
|
+
* fields
|
26
|
+
* tables
|
27
|
+
* images
|
28
|
+
* sections
|
29
|
+
|
30
|
+
#### Fields
|
31
|
+
|
32
|
+
It's just an upcase sentence, surrounded by brackets. It will be replaced for wathever value you supply.
|
33
|
+
|
34
|
+
In the folowing example:
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
38
|
+
r.add_field :user_name, @user.name
|
39
|
+
r.add_field :address, "My new address"
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
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`
|
44
|
+
|
45
|
+
|
46
|
+
#### Tables
|
47
|
+
|
48
|
+
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.
|
49
|
+
|
50
|
+
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.
|
51
|
+
|
52
|
+
If you have more than one template row, they will be cycled. This is usefull for making zebra tables.
|
53
|
+
|
54
|
+
As with **Field placeholders**, just insert a `[FIELD_NAME]` in each cell and let the magic takes place.
|
55
|
+
|
56
|
+
Taking the folowing example:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
60
|
+
|
61
|
+
r.add_field "USER_NAME", @user.nome
|
62
|
+
r.add_field "ADDRESS", @user.address
|
63
|
+
|
64
|
+
r.add_table("TABLE_1", @list_of_itens, :header=>true) do |t|
|
65
|
+
t.add_column(:item_id, :id)
|
66
|
+
t.add_column(:description) { |item| "==> #{item.description}" }
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
and considering you have a table like this in your template
|
73
|
+
|
74
|
+
| #ID | Description |
|
75
|
+
|--|--|
|
76
|
+
| [ITEM_ID] | [DESCRIPTION] |
|
77
|
+
|
78
|
+
|
79
|
+
and a collection `@list_of_itens`, it will create one row for each item in the collection, and the replacement will take place accordingly.
|
80
|
+
|
81
|
+
Any format applied to the fields in the template will be preserved.
|
82
|
+
|
83
|
+
|
84
|
+
### Images
|
85
|
+
|
86
|
+
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.
|
87
|
+
You can also assign any properties you want to the mock image and they will be kept once the image is replaced.
|
88
|
+
|
89
|
+
An image replace would look like this:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
report = ODFReport::Report.new("my_template.odt") do |r|
|
93
|
+
r.add_image :graphic1, "/path/to/the/image.jpg"
|
94
|
+
end
|
95
|
+
```
|
96
|
+
|
97
|
+
### Sections
|
98
|
+
|
99
|
+
Sometimes, you have to repeat a whole chunk of a document, in a structure a lot more complex than a table. You can make a Section in your template and use it in this situations. Creating a Section in OpenOffice is as easy as select menu *Insert* and then *Section...*, and then choose a name for it.
|
100
|
+
|
101
|
+
Sections 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 *and nested Sections*, as long as you provide the appropriate data structure.
|
102
|
+
|
103
|
+
Let's see an example:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
@invoices = Invoice.find(:all)
|
107
|
+
|
108
|
+
report = ODFReport::Report.new("reports/invoice.odt") do |r|
|
109
|
+
|
110
|
+
r.add_field(:title, "INVOICES REPORT")
|
111
|
+
r.add_field(:date, Date.today)
|
112
|
+
|
113
|
+
r.add_section("SC_INVOICE", @invoices) do |s|
|
114
|
+
|
115
|
+
s.add_field(:number) { |invoice| invoice.number.to_s.rjust(5, '0') }
|
116
|
+
s.add_field(:name, :customer_name)
|
117
|
+
s.add_field(:address, :customer_address)
|
118
|
+
|
119
|
+
s.add_table("TB_ITEMS", :items, header: true) do |t|
|
120
|
+
t.add_column(:id)
|
121
|
+
t.add_column(:product) {|item| item.product.name }
|
122
|
+
t.add_column(:value, :product_value)
|
123
|
+
end
|
124
|
+
|
125
|
+
s.add_field(:total) do |invoice|
|
126
|
+
if invoice.status == 'CLOSED'
|
127
|
+
invoice.total
|
128
|
+
else
|
129
|
+
invoice.items.sum('product_value')}
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
s.add_section("SUB_NOTES", :notes) do |s1|
|
134
|
+
|
135
|
+
s1.add_field(:note_title) { |n| n.title }
|
136
|
+
|
137
|
+
s1.add_table ...
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
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 will return the collection for that particular Table. Sounds complicated, huh? But once you get it, it's quite straightforward.
|
147
|
+
|
148
|
+
In the above example, `s.add_table("TB_ITEMS", :items, header: true) do |t|`, the `:items` thing refers to a `invoice.items`. Easy, right?
|
149
|
+
|
150
|
+
<hr/>
|
151
|
+
|
152
|
+
### Step 2 -- generating the document
|
153
|
+
|
154
|
+
It's fairly simple to generate the document. You can use this inside a Rails application or in a standalone script.
|
155
|
+
|
156
|
+
#### Generating a document in a Rails application
|
157
|
+
|
158
|
+
In a controller, you can have a code like this:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
def print
|
162
|
+
|
163
|
+
@ticket = Ticket.find(params[:id])
|
164
|
+
|
165
|
+
report = ODFReport::Report.new(Rails.root.join("/app/reports/ticket.odt") do |r|
|
166
|
+
|
167
|
+
r.add_field(:id, @ticket.id.to_s)
|
168
|
+
r.add_field(:created_by, @ticket.created_by)
|
169
|
+
r.add_field(:created_at, @ticket.created_at.strftime("%d/%m/%Y - %H:%M"))
|
170
|
+
r.add_field(:type, @ticket.type.name)
|
171
|
+
r.add_field(:status, @ticket.status_text)
|
172
|
+
r.add_field(:date, Time.now.strftime("%d/%m/%Y - %H:%M"))
|
173
|
+
r.add_field(:solution, (@ticket.solution || ''))
|
174
|
+
|
175
|
+
r.add_table("OPERATORS", @ticket.operators) do |t|
|
176
|
+
t.add_column(:name) { |op| "#{op.name} (#{op.department.short_name})" }
|
177
|
+
end
|
178
|
+
|
179
|
+
r.add_table("FIELDS", @ticket.fields) do |t|
|
180
|
+
t.add_column(:field_name, :name)
|
181
|
+
t.add_column(:field_value) { |field| field.text_value || "Empty" }
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
send_data report.generate,
|
187
|
+
type: 'application/vnd.oasis.opendocument.text',
|
188
|
+
disposition: 'attachment',
|
189
|
+
filename: 'report.odt'
|
190
|
+
|
191
|
+
end
|
192
|
+
```
|
193
|
+
|
194
|
+
#### Generating a document in a standalone script
|
195
|
+
|
196
|
+
It's very similar to a Rails app, but you can inform the path where the file will be saved.
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
report = ODFReport::Report.new("ticket.odt") do |r|
|
200
|
+
... populates the report ...
|
201
|
+
end
|
202
|
+
|
203
|
+
report.generate("./documents/new_ticket.odt")
|
204
|
+
```
|
205
|
+
|
206
|
+
#### Using a template stored in the database (or anywhere besides the file system)
|
207
|
+
You can provide an `io:` param, containing the actual file read into a String.
|
208
|
+
|
209
|
+
```ruby
|
210
|
+
report = ODFReport::Report.new(io: @template.attachment.read) do |r|
|
211
|
+
```
|
212
|
+
|
213
|
+
<hr/>
|
214
|
+
|
215
|
+
#### REQUIREMENTS
|
216
|
+
|
217
|
+
**rubyzip**: for manipulating the contents of the odt file, since it's actually a zip file.
|
218
|
+
**nokogiri**: for parsing and manipulating the document xml files.
|
data/lib/odf-report.rb
CHANGED
@@ -8,7 +8,7 @@ require File.expand_path('../odf-report/parser/default', __FILE__)
|
|
8
8
|
require File.expand_path('../odf-report/images', __FILE__)
|
9
9
|
require File.expand_path('../odf-report/field', __FILE__)
|
10
10
|
require File.expand_path('../odf-report/text', __FILE__)
|
11
|
-
require File.expand_path('../odf-report/
|
11
|
+
require File.expand_path('../odf-report/template', __FILE__)
|
12
12
|
require File.expand_path('../odf-report/nested', __FILE__)
|
13
13
|
require File.expand_path('../odf-report/section', __FILE__)
|
14
14
|
require File.expand_path('../odf-report/table', __FILE__)
|
data/lib/odf-report/images.rb
CHANGED
data/lib/odf-report/report.rb
CHANGED
@@ -3,9 +3,9 @@ module ODFReport
|
|
3
3
|
class Report
|
4
4
|
include Images
|
5
5
|
|
6
|
-
def initialize(template_name,
|
6
|
+
def initialize(template_name = nil, io: nil)
|
7
7
|
|
8
|
-
@
|
8
|
+
@template = ODFReport::Template.new(template_name, io: io)
|
9
9
|
|
10
10
|
@texts = []
|
11
11
|
@fields = []
|
@@ -14,7 +14,7 @@ class Report
|
|
14
14
|
@image_names_replacements = {}
|
15
15
|
@sections = []
|
16
16
|
|
17
|
-
yield(self)
|
17
|
+
yield(self) if block_given?
|
18
18
|
|
19
19
|
end
|
20
20
|
|
@@ -52,45 +52,33 @@ class Report
|
|
52
52
|
|
53
53
|
def generate(dest = nil)
|
54
54
|
|
55
|
-
@
|
55
|
+
@template.update_content do |file|
|
56
56
|
|
57
|
-
file.update_files
|
57
|
+
file.update_files do |doc|
|
58
58
|
|
59
|
-
|
59
|
+
@sections.each { |s| s.replace!(doc) }
|
60
|
+
@tables.each { |t| t.replace!(doc) }
|
60
61
|
|
61
|
-
|
62
|
-
|
62
|
+
@texts.each { |t| t.replace!(doc) }
|
63
|
+
@fields.each { |f| f.replace!(doc) }
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
find_image_name_matches(doc)
|
68
|
-
avoid_duplicate_image_names(doc)
|
69
|
-
|
70
|
-
end
|
65
|
+
find_image_name_matches(doc)
|
66
|
+
avoid_duplicate_image_names(doc)
|
71
67
|
|
72
68
|
end
|
73
69
|
|
74
|
-
|
70
|
+
include_image_files(file)
|
75
71
|
|
76
72
|
end
|
77
73
|
|
78
74
|
if dest
|
79
|
-
::File.open(dest, "wb") {|f| f.write(@
|
75
|
+
::File.open(dest, "wb") {|f| f.write(@template.data) }
|
80
76
|
else
|
81
|
-
@
|
77
|
+
@template.data
|
82
78
|
end
|
83
79
|
|
84
80
|
end
|
85
81
|
|
86
|
-
private
|
87
|
-
|
88
|
-
def parse_document(txt)
|
89
|
-
doc = Nokogiri::XML(txt)
|
90
|
-
yield doc
|
91
|
-
txt.replace(doc.to_xml(:save_with => Nokogiri::XML::Node::SaveOptions::AS_XML))
|
92
|
-
end
|
93
|
-
|
94
82
|
end
|
95
83
|
|
96
84
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module ODFReport
|
2
|
+
class Template
|
3
|
+
|
4
|
+
CONTENT_FILES = ['content.xml', 'styles.xml']
|
5
|
+
|
6
|
+
attr_accessor :output_stream
|
7
|
+
|
8
|
+
def initialize(template = nil, io: nil)
|
9
|
+
raise "You must provide either a filename or an io: string" unless template || io
|
10
|
+
raise "Template [#{template}] not found." unless template.nil? || ::File.exist?(template)
|
11
|
+
|
12
|
+
@template = template
|
13
|
+
@io = io
|
14
|
+
end
|
15
|
+
|
16
|
+
def update_content
|
17
|
+
@buffer = Zip::OutputStream.write_buffer do |out|
|
18
|
+
@output_stream = out
|
19
|
+
yield self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def update_files(&block)
|
24
|
+
|
25
|
+
get_template_entries.each do |entry|
|
26
|
+
|
27
|
+
next if entry.directory?
|
28
|
+
|
29
|
+
entry.get_input_stream do |is|
|
30
|
+
|
31
|
+
data = is.sysread
|
32
|
+
|
33
|
+
if CONTENT_FILES.include?(entry.name)
|
34
|
+
process_entry(data, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
@output_stream.put_next_entry(entry.name)
|
38
|
+
@output_stream.write data
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def data
|
46
|
+
@buffer.string
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def get_template_entries
|
52
|
+
|
53
|
+
if @template
|
54
|
+
Zip::File.open(@template)
|
55
|
+
else
|
56
|
+
Zip::File.open_buffer(@io.force_encoding("ASCII-8BIT"))
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def process_entry(entry)
|
62
|
+
doc = Nokogiri::XML(entry)
|
63
|
+
yield doc
|
64
|
+
entry.replace(doc.to_xml(save_with: Nokogiri::XML::Node::SaveOptions::AS_XML))
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
data/lib/odf-report/version.rb
CHANGED
data/odf-report.gemspec
CHANGED
@@ -9,7 +9,6 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.authors = ["Sandro Duarte"]
|
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
|
-
s.has_rdoc = false
|
13
12
|
s.homepage = %q{http://sandrods.github.com/odf-report/}
|
14
13
|
s.rubygems_version = %q{1.3.7}
|
15
14
|
s.summary = %q{Generates ODF files, given a template (.odt) and data, replacing tags}
|
@@ -19,7 +18,7 @@ Gem::Specification.new do |s|
|
|
19
18
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
20
19
|
s.require_paths = ["lib"]
|
21
20
|
|
22
|
-
s.add_development_dependency "bundler"
|
21
|
+
s.add_development_dependency "bundler"
|
23
22
|
s.add_development_dependency "rake"
|
24
23
|
s.add_development_dependency "rspec", "~> 3.0.0"
|
25
24
|
s.add_development_dependency "faker"
|
@@ -0,0 +1,45 @@
|
|
1
|
+
RSpec.describe "Templates Types" do
|
2
|
+
|
3
|
+
before(:each) do
|
4
|
+
|
5
|
+
@field_01 = Faker::Company.name
|
6
|
+
@field_02 = Faker::Name.name
|
7
|
+
|
8
|
+
report.add_field(:field_01, @field_01)
|
9
|
+
report.add_field(:field_02, @field_02)
|
10
|
+
|
11
|
+
report.generate("spec/result/specs.odt")
|
12
|
+
|
13
|
+
@data = Inspector.new("spec/result/specs.odt")
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
context "template from file" do
|
18
|
+
let(:report) { ODFReport::Report.new("spec/specs.odt") }
|
19
|
+
|
20
|
+
it "works" do
|
21
|
+
|
22
|
+
expect(@data.text).not_to match(/\[FIELD_01\]/)
|
23
|
+
expect(@data.text).not_to match(/\[FIELD_02\]/)
|
24
|
+
|
25
|
+
expect(@data.text).to match @field_01
|
26
|
+
expect(@data.text).to match @field_02
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "template from a String" do
|
32
|
+
let(:report) { ODFReport::Report.new(io: ::File.open("spec/specs.odt").read) }
|
33
|
+
|
34
|
+
it "works" do
|
35
|
+
|
36
|
+
expect(@data.text).not_to match(/\[FIELD_01\]/)
|
37
|
+
expect(@data.text).not_to match(/\[FIELD_02\]/)
|
38
|
+
|
39
|
+
expect(@data.text).to match @field_01
|
40
|
+
expect(@data.text).to match @field_02
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: odf-report
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sandro Duarte
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-01-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -119,17 +119,17 @@ files:
|
|
119
119
|
- Gemfile
|
120
120
|
- MIT-LICENSE
|
121
121
|
- Manifest
|
122
|
-
- README.
|
122
|
+
- README.md
|
123
123
|
- Rakefile
|
124
124
|
- lib/odf-report.rb
|
125
125
|
- lib/odf-report/field.rb
|
126
|
-
- lib/odf-report/file.rb
|
127
126
|
- lib/odf-report/images.rb
|
128
127
|
- lib/odf-report/nested.rb
|
129
128
|
- lib/odf-report/parser/default.rb
|
130
129
|
- lib/odf-report/report.rb
|
131
130
|
- lib/odf-report/section.rb
|
132
131
|
- lib/odf-report/table.rb
|
132
|
+
- lib/odf-report/template.rb
|
133
133
|
- lib/odf-report/text.rb
|
134
134
|
- lib/odf-report/version.rb
|
135
135
|
- odf-report.gemspec
|
@@ -138,6 +138,7 @@ files:
|
|
138
138
|
- spec/spec_helper.rb
|
139
139
|
- spec/specs.odt
|
140
140
|
- spec/tables_spec.rb
|
141
|
+
- spec/template_spec.rb
|
141
142
|
- test/fields_inside_text_test.rb
|
142
143
|
- test/nested_tables_test.rb
|
143
144
|
- test/sections_test.rb
|
@@ -173,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
173
174
|
version: '0'
|
174
175
|
requirements: []
|
175
176
|
rubyforge_project:
|
176
|
-
rubygems_version: 2.6
|
177
|
+
rubygems_version: 2.7.6
|
177
178
|
signing_key:
|
178
179
|
specification_version: 4
|
179
180
|
summary: Generates ODF files, given a template (.odt) and data, replacing tags
|
@@ -183,6 +184,7 @@ test_files:
|
|
183
184
|
- spec/spec_helper.rb
|
184
185
|
- spec/specs.odt
|
185
186
|
- spec/tables_spec.rb
|
187
|
+
- spec/template_spec.rb
|
186
188
|
- test/fields_inside_text_test.rb
|
187
189
|
- test/nested_tables_test.rb
|
188
190
|
- test/sections_test.rb
|
data/README.textile
DELETED
@@ -1,223 +0,0 @@
|
|
1
|
-
h1. ODF-REPORT
|
2
|
-
|
3
|
-
Gem for generating .odt files by making strings, images, tables and sections replacements in a previously created .odt file.
|
4
|
-
|
5
|
-
h3. NEW
|
6
|
-
|
7
|
-
* as per popular request, now uses rubyzip ~> 1.1.0
|
8
|
-
* uses @Zip::OutputStream.write_buffer@ to generate the file. This should avoid corruption issues.
|
9
|
-
* the @.generate@ method now returns the actual report binary, so you can send_data it directly
|
10
|
-
* you can also use @.generate('file.odt')@, which saves the report to the specified file
|
11
|
-
|
12
|
-
h2. INSTALL
|
13
|
-
|
14
|
-
In your Gemfile
|
15
|
-
<pre>gem 'odf-report' </pre>
|
16
|
-
|
17
|
-
h2. USAGE
|
18
|
-
|
19
|
-
h3. Step 1 -- the template
|
20
|
-
|
21
|
-
First of all, you need to create a .odt file to serve as a template.
|
22
|
-
Templates are normal .odt files with placeholders for *substitutions*.
|
23
|
-
There are now *four* kinds of substitutions available: *fields*, *tables*, *images* and *sections*.
|
24
|
-
|
25
|
-
h3. Fields placeholders
|
26
|
-
|
27
|
-
It's just an upcase sentence, surrounded by brackets. It will be replaced for wathever value you supply.
|
28
|
-
|
29
|
-
In the folowing example:
|
30
|
-
|
31
|
-
<pre>
|
32
|
-
report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
33
|
-
|
34
|
-
r.add_field :user_name, @user.name
|
35
|
-
r.add_field :address, "My new address"
|
36
|
-
|
37
|
-
end
|
38
|
-
</pre>
|
39
|
-
|
40
|
-
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@
|
41
|
-
|
42
|
-
It's as simple as that.
|
43
|
-
|
44
|
-
|
45
|
-
h3. Table placeholders
|
46
|
-
|
47
|
-
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.
|
48
|
-
|
49
|
-
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.
|
50
|
-
|
51
|
-
As with Field placeholders, just insert a @[FIELD_NAME]@ in each cell and let the magic takes place.
|
52
|
-
|
53
|
-
Taking the folowing example:
|
54
|
-
|
55
|
-
<pre>
|
56
|
-
report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
57
|
-
|
58
|
-
r.add_field "USER_NAME", @user.nome
|
59
|
-
r.add_field "ADDRESS", @user.address
|
60
|
-
|
61
|
-
r.add_table("TABLE_1", @list_of_itens, :header=>true) do |t|
|
62
|
-
t.add_column(:item_id, :id)
|
63
|
-
t.add_column(:description) { |item| "==> #{item.description}" }
|
64
|
-
end
|
65
|
-
|
66
|
-
end
|
67
|
-
</pre>
|
68
|
-
|
69
|
-
and considering you have a table like this in your template
|
70
|
-
|
71
|
-
<pre>
|
72
|
-
---------------------------------
|
73
|
-
| [ITEM_ID] | [DESCRIPTION] |
|
74
|
-
---------------------------------
|
75
|
-
|
76
|
-
* this is my lame attempt to draw a table.
|
77
|
-
you are not supposed to type this.
|
78
|
-
you have to use an actual table.
|
79
|
-
i don't know... just thought I should mention it ;-)
|
80
|
-
</pre>
|
81
|
-
|
82
|
-
and a collection @list_of_itens, it will create one row for each item in the collection, and the replacement will take place accordingly.
|
83
|
-
|
84
|
-
Any format applied to the fields in the template will be preserved.
|
85
|
-
|
86
|
-
|
87
|
-
h3. Images
|
88
|
-
|
89
|
-
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.
|
90
|
-
You can also assign any properties you want to the mock image and they will be kept once the image is replaced.
|
91
|
-
|
92
|
-
An image replace would look like this:
|
93
|
-
|
94
|
-
<pre>
|
95
|
-
report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
|
96
|
-
|
97
|
-
r.add_image :graphics1, "/path/to/the/image.jpg"
|
98
|
-
|
99
|
-
end
|
100
|
-
</pre>
|
101
|
-
|
102
|
-
|
103
|
-
h3. Sections
|
104
|
-
|
105
|
-
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 Section in OpenOffice is as easy as select menu *Insert* and then *Section...*, and then choose a name for it.
|
106
|
-
|
107
|
-
*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 *and nested Sections*, as long as you pass the appropriate data structure.
|
108
|
-
|
109
|
-
Let's see an example:
|
110
|
-
|
111
|
-
<pre>
|
112
|
-
|
113
|
-
@invoices = Invoice.find(:all)
|
114
|
-
|
115
|
-
report = ODFReport::Report.new("reports/invoice.odt") do |r|
|
116
|
-
|
117
|
-
r.add_field(:title, "INVOICES REPORT")
|
118
|
-
r.add_field(:date, Date.today)
|
119
|
-
|
120
|
-
r.add_section("SC_INVOICE", @invoices) do |s|
|
121
|
-
|
122
|
-
s.add_field(:number) { |invoice| invoice.number.to_s.rjust(5, '0') }
|
123
|
-
s.add_field(:name, :customer_name)
|
124
|
-
s.add_field(:address, :customer_address)
|
125
|
-
|
126
|
-
s.add_table("TB_ITEMS", :items, :header => true) do |t|
|
127
|
-
t.add_column(:id)
|
128
|
-
t.add_column(:product) {|item| item.product.name }
|
129
|
-
t.add_column(:value, :product_value)
|
130
|
-
end
|
131
|
-
|
132
|
-
s.add_field(:total) do |invoice|
|
133
|
-
if invoice.status == 'CLOSED'
|
134
|
-
invoice.total
|
135
|
-
else
|
136
|
-
invoice.items.sum('product_value')}
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
s.add_section("SUB_NOTES", :notes) do |s1|
|
141
|
-
|
142
|
-
s1.add_field(:note_title) { |n| n.title }
|
143
|
-
|
144
|
-
s1.add_table ...
|
145
|
-
|
146
|
-
end
|
147
|
-
|
148
|
-
end
|
149
|
-
|
150
|
-
end
|
151
|
-
</pre>
|
152
|
-
|
153
|
-
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.
|
154
|
-
|
155
|
-
In the above example, @s.add_table("TB_ITEMS", :items, :header => true) do |t|@, the @:items@ thing refers to a @invoice.items@. Easy, right?
|
156
|
-
|
157
|
-
<hr/><br/>
|
158
|
-
|
159
|
-
h3. Step 2 -- generating the document
|
160
|
-
|
161
|
-
It's fairly simple to generate the document. You can use this inside a Rails application or in a standalone script.
|
162
|
-
|
163
|
-
h4. Generating a document in a Rails application
|
164
|
-
|
165
|
-
In a controller, you can have a code like this:
|
166
|
-
|
167
|
-
<pre>
|
168
|
-
def print
|
169
|
-
|
170
|
-
@ticket = Ticket.find(params[:id])
|
171
|
-
|
172
|
-
# For Rails 3 or latest replace #{RAILS_ROOT} to #{Rails.root}
|
173
|
-
report = ODFReport::Report.new("#{RAILS_ROOT}/app/reports/ticket.odt") do |r|
|
174
|
-
|
175
|
-
r.add_field(:id, @ticket.id.to_s)
|
176
|
-
r.add_field(:created_by, @ticket.created_by)
|
177
|
-
r.add_field(:created_at, @ticket.created_at.strftime("%d/%m/%Y - %H:%M"))
|
178
|
-
r.add_field(:type, @ticket.type.name)
|
179
|
-
r.add_field(:status, @ticket.status_text)
|
180
|
-
r.add_field(:date, Time.now.strftime("%d/%m/%Y - %H:%M"))
|
181
|
-
r.add_field(:solution, (@ticket.solution || ''))
|
182
|
-
|
183
|
-
r.add_table("OPERATORS", @ticket.operators) do |t|
|
184
|
-
t.add_column(:operator_name) { |op| "#{op.name} (#{op.department.short_name})" }
|
185
|
-
end
|
186
|
-
|
187
|
-
r.add_table("FIELDS", @ticket.fields) do |t|
|
188
|
-
t.add_column(:field_name, :name)
|
189
|
-
t.add_column(:field_value) { |field| field.text_value || "Empty" }
|
190
|
-
end
|
191
|
-
|
192
|
-
end
|
193
|
-
|
194
|
-
send_data report.generate, type: 'application/vnd.oasis.opendocument.text',
|
195
|
-
disposition: 'attachment',
|
196
|
-
filename: 'report.odt'
|
197
|
-
|
198
|
-
end
|
199
|
-
</pre>
|
200
|
-
|
201
|
-
|
202
|
-
_That's all I have to say about that._
|
203
|
-
|
204
|
-
h4. Generating a document in a standalone script
|
205
|
-
|
206
|
-
It's just the same as in a Rails app, but you can inform the path where the file will be saved.
|
207
|
-
|
208
|
-
<pre>
|
209
|
-
report = ODFReport::Report.new("ticket.odt") do |r|
|
210
|
-
|
211
|
-
... populates the report ...
|
212
|
-
|
213
|
-
end
|
214
|
-
|
215
|
-
report.generate("./documents/new_ticket.odt")
|
216
|
-
</pre>
|
217
|
-
|
218
|
-
<hr/>
|
219
|
-
|
220
|
-
h3. REQUIREMENTS
|
221
|
-
|
222
|
-
* rubyzip*: for manipulating the contents of the odt file, since it's actually a zip file.
|
223
|
-
* nokogiri*: for parsing and manipulating the document xml files.
|
data/lib/odf-report/file.rb
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
module ODFReport
|
2
|
-
class File
|
3
|
-
|
4
|
-
attr_accessor :output_stream
|
5
|
-
|
6
|
-
def initialize(template)
|
7
|
-
raise "Template [#{template}] not found." unless ::File.exist? template
|
8
|
-
@template = template
|
9
|
-
end
|
10
|
-
|
11
|
-
def update_content
|
12
|
-
@buffer = Zip::OutputStream.write_buffer do |out|
|
13
|
-
@output_stream = out
|
14
|
-
yield self
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def update_files(*content_files, &block)
|
19
|
-
|
20
|
-
Zip::File.open(@template) do |file|
|
21
|
-
|
22
|
-
file.each do |entry|
|
23
|
-
|
24
|
-
next if entry.directory?
|
25
|
-
|
26
|
-
entry.get_input_stream do |is|
|
27
|
-
|
28
|
-
data = is.sysread
|
29
|
-
|
30
|
-
if content_files.include?(entry.name)
|
31
|
-
yield data
|
32
|
-
end
|
33
|
-
|
34
|
-
@output_stream.put_next_entry(entry.name)
|
35
|
-
@output_stream.write data
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
end
|
44
|
-
|
45
|
-
def data
|
46
|
-
@buffer.string
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|