odf-report 0.6.1 → 0.7.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.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/README.md +27 -19
- data/bin/odt-extract +10 -0
- data/bin/odt-viewer +18 -0
- data/lib/odf-report.rb +11 -8
- data/lib/odf-report/data_source.rb +64 -0
- data/lib/odf-report/field.rb +7 -40
- data/lib/odf-report/image.rb +52 -0
- data/lib/odf-report/nestable.rb +65 -0
- data/lib/odf-report/nested.rb +13 -8
- data/lib/odf-report/report.rb +21 -13
- data/lib/odf-report/section.rb +15 -42
- data/lib/odf-report/table.rb +54 -59
- data/lib/odf-report/template.rb +23 -3
- data/lib/odf-report/text.rb +1 -3
- data/lib/odf-report/version.rb +1 -1
- data/odf-report.gemspec +2 -1
- data/spec/fields_spec.rb +2 -2
- data/spec/images/image_1.jpg +0 -0
- data/spec/images/image_2.jpg +0 -0
- data/spec/images/image_3.jpg +0 -0
- data/spec/images/piriapolis.jpg +0 -0
- data/spec/images/placeholder.jpg +0 -0
- data/spec/images/rails.png +0 -0
- data/spec/images_spec.rb +80 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/tables_spec.rb +1 -1
- data/spec/template_spec.rb +2 -2
- data/spec/templates/images.odt +0 -0
- data/spec/{specs.odt → templates/specs.odt} +0 -0
- data/test/images_test.rb +32 -0
- data/test/templates/images/image_1.jpg +0 -0
- data/test/templates/images/image_2.jpg +0 -0
- data/test/templates/images/image_3.jpg +0 -0
- data/test/templates/images/placeholder.jpg +0 -0
- data/test/templates/images/placeholder.png +0 -0
- data/test/templates/test_images.odt +0 -0
- data/test/test.rb +262 -0
- metadata +61 -12
- data/lib/odf-report/images.rb +0 -44
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: babb3d76f7401badd099efc108cc000baa9ce38a556447162dd0d31e1ad2b940
|
4
|
+
data.tar.gz: 5ee08b656534f7522c60b3f784343120600761478baba47569c52d108d613237
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 432a082f25cf119cc117d476abee396a1a6e24a144d5792f786aa249adf4b1bcf07a7fabad9d0789ca063484ea33c16fb74b05f6b088364efe3a0be3e5f03d5b
|
7
|
+
data.tar.gz: 07c003adba47dac35a15ef697e750e3a918f42f4db4cb7964c9ec4ce28141a52f74b375fc328d6a4f87ce97df381fa0a7ba14c75598b39bb9c1d435cab4a70ef
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -5,7 +5,8 @@ Gem for generating .odt files by making strings, images, tables and sections rep
|
|
5
5
|
|
6
6
|
### NEW
|
7
7
|
|
8
|
-
*
|
8
|
+
* allow nested images inside tables and sections
|
9
|
+
* allow sections inside tables
|
9
10
|
|
10
11
|
## INSTALL
|
11
12
|
|
@@ -28,7 +29,7 @@ There are *four* kinds of substitutions available:
|
|
28
29
|
|
29
30
|
#### Fields
|
30
31
|
|
31
|
-
It's just an upcase sentence, surrounded by brackets. It will be replaced
|
32
|
+
It's just an upcase sentence, surrounded by brackets. It will be replaced by the value you supply.
|
32
33
|
|
33
34
|
In the folowing example:
|
34
35
|
|
@@ -79,21 +80,7 @@ and a collection `@list_of_itens`, it will create one row for each item in the c
|
|
79
80
|
|
80
81
|
Any format applied to the fields in the template will be preserved.
|
81
82
|
|
82
|
-
|
83
|
-
### Images
|
84
|
-
|
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
|
-
You can also assign any properties you want to the mock image and they will be kept once the image is replaced.
|
87
|
-
|
88
|
-
An image replace would look like this:
|
89
|
-
|
90
|
-
```ruby
|
91
|
-
report = ODFReport::Report.new("my_template.odt") do |r|
|
92
|
-
r.add_image :graphic1, "/path/to/the/image.jpg"
|
93
|
-
end
|
94
|
-
```
|
95
|
-
|
96
|
-
### Sections
|
83
|
+
#### Sections
|
97
84
|
|
98
85
|
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.
|
99
86
|
|
@@ -146,6 +133,26 @@ Note that when you add a Table to a Section, you don't pass the collection itsel
|
|
146
133
|
|
147
134
|
In the above example, `s.add_table("TB_ITEMS", :items, header: true) do |t|`, the `:items` thing refers to a `invoice.items`. Easy, right?
|
148
135
|
|
136
|
+
|
137
|
+
#### Images
|
138
|
+
|
139
|
+
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.
|
140
|
+
You can also assign any properties you want to the mock image and they will be kept once the image is replaced.
|
141
|
+
|
142
|
+
An image replace would look like this:
|
143
|
+
|
144
|
+
```ruby
|
145
|
+
report = ODFReport::Report.new("my_template.odt") do |r|
|
146
|
+
r.add_image :graphic1, "/path/to/the/image.jpg"
|
147
|
+
|
148
|
+
r.add_table("TABLE_WITH_IMAGES", @items) do |t|
|
149
|
+
t.add_column(:id)
|
150
|
+
t.add_column(:product, :product_name)
|
151
|
+
t.add_image('PRODUCT_IMAGE') { |item| item.image_path }
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
149
156
|
<hr/>
|
150
157
|
|
151
158
|
### Step 2 -- generating the document
|
@@ -213,5 +220,6 @@ report = ODFReport::Report.new(io: @template.attachment.read) do |r|
|
|
213
220
|
|
214
221
|
#### REQUIREMENTS
|
215
222
|
|
216
|
-
**rubyzip**:
|
217
|
-
**nokogiri**:
|
223
|
+
**rubyzip**: manipulating the contents of the odt file, since it's actually a zip file.
|
224
|
+
**nokogiri**: parsing and manipulating the document xml files.
|
225
|
+
**mime-types**: identify images mime types
|
data/bin/odt-extract
ADDED
data/bin/odt-viewer
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'launchy'
|
4
|
+
require "nokogiri"
|
5
|
+
require "zip"
|
6
|
+
|
7
|
+
xml = ""
|
8
|
+
|
9
|
+
Zip::File.open(ARGV[0]) do |zip|
|
10
|
+
content = zip.get_entry('content.xml').get_input_stream.read
|
11
|
+
xml = Nokogiri::XML(content).to_xml
|
12
|
+
end
|
13
|
+
|
14
|
+
filename = File.join(Dir.mktmpdir, "#{File.basename(ARGV[0])}.result.xml")
|
15
|
+
|
16
|
+
File.open(filename, 'w') { |f| f.write xml }
|
17
|
+
|
18
|
+
Launchy.open filename
|
data/lib/odf-report.rb
CHANGED
@@ -2,14 +2,17 @@ require 'rubygems'
|
|
2
2
|
require 'zip'
|
3
3
|
require 'fileutils'
|
4
4
|
require 'nokogiri'
|
5
|
+
require 'mime/types'
|
6
|
+
require 'securerandom'
|
5
7
|
|
6
8
|
require File.expand_path('../odf-report/parser/default', __FILE__)
|
7
9
|
|
8
|
-
require File.expand_path('../odf-report/
|
9
|
-
require File.expand_path('../odf-report/field',
|
10
|
-
require File.expand_path('../odf-report/text',
|
11
|
-
require File.expand_path('../odf-report/template',
|
12
|
-
require File.expand_path('../odf-report/
|
13
|
-
require File.expand_path('../odf-report/
|
14
|
-
require File.expand_path('../odf-report/
|
15
|
-
require File.expand_path('../odf-report/
|
10
|
+
require File.expand_path('../odf-report/data_source', __FILE__)
|
11
|
+
require File.expand_path('../odf-report/field', __FILE__)
|
12
|
+
require File.expand_path('../odf-report/text', __FILE__)
|
13
|
+
require File.expand_path('../odf-report/template', __FILE__)
|
14
|
+
require File.expand_path('../odf-report/image', __FILE__)
|
15
|
+
require File.expand_path('../odf-report/nestable', __FILE__)
|
16
|
+
require File.expand_path('../odf-report/section', __FILE__)
|
17
|
+
require File.expand_path('../odf-report/table', __FILE__)
|
18
|
+
require File.expand_path('../odf-report/report', __FILE__)
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module ODFReport
|
2
|
+
class DataSource
|
3
|
+
|
4
|
+
attr_reader :value
|
5
|
+
|
6
|
+
def initialize(opts, &block)
|
7
|
+
@value = opts[:value] || opts[:collection]
|
8
|
+
@data_field = opts[:data_field] || opts[:collection_field] || opts[:name]
|
9
|
+
@block = block
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_source(record)
|
13
|
+
@value = extract_value_from_item(record)
|
14
|
+
end
|
15
|
+
|
16
|
+
def each(&block)
|
17
|
+
@value.each(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def empty?
|
21
|
+
@value.nil? || @value.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def extract_value_from_item(record)
|
27
|
+
|
28
|
+
if @block
|
29
|
+
@block.call(record)
|
30
|
+
|
31
|
+
elsif record.is_a?(Hash)
|
32
|
+
key = @data_field
|
33
|
+
record[key] || record[key.to_s.downcase] || record[key.to_s.upcase] || record[key.to_s.downcase.to_sym]
|
34
|
+
|
35
|
+
elsif @data_field.is_a?(Array)
|
36
|
+
execute_methods_on_item(record)
|
37
|
+
|
38
|
+
elsif @data_field.is_a?(Hash) && record.respond_to?(@data_field.keys[0])
|
39
|
+
record.send(@data_field.keys[0], @data_field.values[0])
|
40
|
+
|
41
|
+
elsif record.respond_to?(@data_field)
|
42
|
+
record.send(@data_field)
|
43
|
+
|
44
|
+
else
|
45
|
+
raise "Can't find [#{@data_field.to_s}] in this #{record.class}"
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def execute_methods_on_item(record)
|
52
|
+
tmp = record.dup
|
53
|
+
@data_field.each do |f|
|
54
|
+
if f.is_a?(Hash)
|
55
|
+
tmp = tmp.send(f.keys[0], f.values[0])
|
56
|
+
else
|
57
|
+
tmp = tmp.send(f)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
tmp
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
data/lib/odf-report/field.rb
CHANGED
@@ -5,56 +5,25 @@ module ODFReport
|
|
5
5
|
|
6
6
|
def initialize(opts, &block)
|
7
7
|
@name = opts[:name]
|
8
|
-
@
|
9
|
-
|
10
|
-
unless @value = opts[:value]
|
11
|
-
|
12
|
-
if block_given?
|
13
|
-
@block = block
|
14
|
-
|
15
|
-
else
|
16
|
-
@block = lambda { |item| self.extract_value(item) }
|
17
|
-
end
|
18
|
-
|
19
|
-
end
|
8
|
+
@data_source = DataSource.new(opts, &block)
|
9
|
+
end
|
20
10
|
|
11
|
+
def set_source(record)
|
12
|
+
@data_source.set_source(record)
|
13
|
+
self
|
21
14
|
end
|
22
15
|
|
23
16
|
def replace!(content, data_item = nil)
|
24
17
|
|
25
18
|
txt = content.inner_html
|
26
19
|
|
27
|
-
|
28
|
-
|
29
|
-
txt.gsub!(to_placeholder, sanitize(val))
|
20
|
+
txt.gsub!(to_placeholder, sanitize(@data_source.value))
|
30
21
|
|
31
22
|
content.inner_html = txt
|
32
23
|
|
33
24
|
end
|
34
25
|
|
35
|
-
|
36
|
-
@value || @block.call(data_item) || ''
|
37
|
-
end
|
38
|
-
|
39
|
-
def extract_value(data_item)
|
40
|
-
return unless data_item
|
41
|
-
|
42
|
-
key = @data_field || @name
|
43
|
-
|
44
|
-
if data_item.is_a?(Hash)
|
45
|
-
data_item[key] || data_item[key.to_s.downcase] || data_item[key.to_s.upcase] || data_item[key.to_s.downcase.to_sym]
|
46
|
-
|
47
|
-
elsif data_item.respond_to?(key.to_s.downcase.to_sym)
|
48
|
-
data_item.send(key.to_s.downcase.to_sym)
|
49
|
-
|
50
|
-
else
|
51
|
-
raise "Can't find field [#{key}] in this #{data_item.class}"
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
private
|
26
|
+
private
|
58
27
|
|
59
28
|
def to_placeholder
|
60
29
|
if DELIMITERS.is_a?(Array)
|
@@ -82,7 +51,5 @@ module ODFReport
|
|
82
51
|
s.to_s.gsub("\n", "<text:line-break/>")
|
83
52
|
end
|
84
53
|
|
85
|
-
|
86
|
-
|
87
54
|
end
|
88
55
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ODFReport
|
2
|
+
class Image < Field
|
3
|
+
|
4
|
+
IMAGE_DIR_NAME = "Pictures"
|
5
|
+
|
6
|
+
attr_reader :files
|
7
|
+
|
8
|
+
def initialize(opts, &block)
|
9
|
+
@files = []
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def replace!(doc, data_item = nil)
|
14
|
+
|
15
|
+
frame = doc.xpath("//draw:frame[@draw:name='#{@name}']").first
|
16
|
+
image = doc.xpath("//draw:frame[@draw:name='#{@name}']/draw:image").first
|
17
|
+
|
18
|
+
return unless image
|
19
|
+
|
20
|
+
file = @data_source.value
|
21
|
+
|
22
|
+
image.attribute('href').content = File.join(IMAGE_DIR_NAME, File.basename(file))
|
23
|
+
frame.attribute('name').content = SecureRandom.uuid
|
24
|
+
|
25
|
+
@files << file
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.include_image_file(zip_file, image_file)
|
29
|
+
return unless image_file
|
30
|
+
|
31
|
+
href = File.join(IMAGE_DIR_NAME, File.basename(image_file))
|
32
|
+
|
33
|
+
zip_file.update_file(href, File.read(image_file))
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.include_manifest_entry(content, image_file)
|
37
|
+
return unless image_file
|
38
|
+
|
39
|
+
return unless root_node = content.at("//manifest:manifest")
|
40
|
+
|
41
|
+
href = File.join(IMAGE_DIR_NAME, File.basename(image_file))
|
42
|
+
|
43
|
+
entry = content.create_element('manifest:file-entry')
|
44
|
+
entry['manifest:full-path'] = href
|
45
|
+
entry['manifest:media-type'] = MIME::Types.type_for(href)[0].content_type
|
46
|
+
|
47
|
+
root_node.add_child entry
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ODFReport
|
2
|
+
class Nestable
|
3
|
+
|
4
|
+
def initialize(opts)
|
5
|
+
@name = opts[:name]
|
6
|
+
|
7
|
+
@data_source = DataSource.new(opts)
|
8
|
+
|
9
|
+
@fields = []
|
10
|
+
@texts = []
|
11
|
+
@tables = []
|
12
|
+
@sections = []
|
13
|
+
@images = []
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
def set_source(data_item)
|
18
|
+
@data_source.set_source(data_item)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_field(name, data_field=nil, &block)
|
23
|
+
opts = { name: name, data_field: data_field }
|
24
|
+
@fields << Field.new(opts, &block)
|
25
|
+
end
|
26
|
+
alias_method :add_column, :add_field
|
27
|
+
|
28
|
+
def add_text(name, data_field=nil, &block)
|
29
|
+
opts = {name: name, data_field: data_field}
|
30
|
+
@texts << Text.new(opts, &block)
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_image(name, data_field=nil, &block)
|
34
|
+
opts = {name: name, data_field: data_field}
|
35
|
+
@images << Image.new(opts, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_table(table_name, collection_field, opts={})
|
39
|
+
opts.merge!(name: table_name, collection_field: collection_field)
|
40
|
+
tab = Table.new(opts)
|
41
|
+
@tables << tab
|
42
|
+
|
43
|
+
yield(tab)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_section(section_name, collection_field, opts={})
|
47
|
+
opts.merge!(name: section_name, collection_field: collection_field)
|
48
|
+
sec = Section.new(opts)
|
49
|
+
@sections << sec
|
50
|
+
|
51
|
+
yield(sec)
|
52
|
+
end
|
53
|
+
|
54
|
+
def all_images
|
55
|
+
(@images.map(&:files) + @sections.map(&:all_images) + @tables.map(&:all_images)).flatten
|
56
|
+
end
|
57
|
+
|
58
|
+
def wrap_with_ns(node)
|
59
|
+
<<-XML
|
60
|
+
<root xmlns:draw="a" xmlns:xlink="b" xmlns:text="c" xmlns:table="d">#{node.to_xml}</root>
|
61
|
+
XML
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
data/lib/odf-report/nested.rb
CHANGED
@@ -3,22 +3,24 @@ module ODFReport
|
|
3
3
|
module Nested
|
4
4
|
|
5
5
|
def add_field(name, data_field=nil, &block)
|
6
|
-
opts = {:
|
7
|
-
|
8
|
-
@fields << field
|
6
|
+
opts = {name: name, data_field: data_field}
|
7
|
+
@fields << Field.new(opts, &block)
|
9
8
|
|
10
9
|
end
|
11
10
|
alias_method :add_column, :add_field
|
12
11
|
|
13
12
|
def add_text(name, data_field=nil, &block)
|
14
|
-
opts = {:
|
15
|
-
|
16
|
-
|
13
|
+
opts = {name: name, data_field: data_field}
|
14
|
+
@texts << Text.new(opts, &block)
|
15
|
+
end
|
17
16
|
|
17
|
+
def add_image(name, data_field=nil, &block)
|
18
|
+
opts = {name: name, data_field: data_field}
|
19
|
+
@images << Image.new(opts, &block)
|
18
20
|
end
|
19
21
|
|
20
22
|
def add_table(table_name, collection_field, opts={})
|
21
|
-
opts.merge!(:
|
23
|
+
opts.merge!(name: table_name, collection_field: collection_field)
|
22
24
|
tab = Table.new(opts)
|
23
25
|
@tables << tab
|
24
26
|
|
@@ -26,13 +28,16 @@ module ODFReport
|
|
26
28
|
end
|
27
29
|
|
28
30
|
def add_section(section_name, collection_field, opts={})
|
29
|
-
opts.merge!(:
|
31
|
+
opts.merge!(name: section_name, collection_field: collection_field)
|
30
32
|
sec = Section.new(opts)
|
31
33
|
@sections << sec
|
32
34
|
|
33
35
|
yield(sec)
|
34
36
|
end
|
35
37
|
|
38
|
+
def all_images
|
39
|
+
(@images.map(&:files) + @sections.map(&:all_images) + @tables.map(&:all_images)).flatten
|
40
|
+
end
|
36
41
|
|
37
42
|
def get_collection_from_item(item, collection_field)
|
38
43
|
|