odf-report 0.5.2 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/gem-push.yml +40 -0
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +62 -0
  5. data/README.md +220 -0
  6. data/bin/odt-extract +10 -0
  7. data/bin/odt-viewer +18 -0
  8. data/lib/odf-report.rb +11 -8
  9. data/lib/odf-report/data_source.rb +65 -0
  10. data/lib/odf-report/field.rb +7 -40
  11. data/lib/odf-report/image.rb +57 -0
  12. data/lib/odf-report/nestable.rb +65 -0
  13. data/lib/odf-report/report.rb +24 -28
  14. data/lib/odf-report/section.rb +15 -42
  15. data/lib/odf-report/table.rb +54 -59
  16. data/lib/odf-report/template.rb +88 -0
  17. data/lib/odf-report/text.rb +1 -3
  18. data/lib/odf-report/version.rb +1 -1
  19. data/odf-report.gemspec +4 -4
  20. data/spec/fields_spec.rb +4 -4
  21. data/spec/images/image_1.jpg +0 -0
  22. data/spec/images/image_2.jpg +0 -0
  23. data/spec/images/image_3.jpg +0 -0
  24. data/spec/images/piriapolis.jpg +0 -0
  25. data/spec/images/placeholder.jpg +0 -0
  26. data/spec/images/rails.png +0 -0
  27. data/spec/images_spec.rb +159 -0
  28. data/spec/sections_spec.rb +51 -0
  29. data/spec/spec_helper.rb +2 -4
  30. data/spec/tables_spec.rb +1 -1
  31. data/spec/template_spec.rb +45 -0
  32. data/spec/templates/images.odt +0 -0
  33. data/spec/templates/specs.odt +0 -0
  34. data/test/images_test.rb +32 -0
  35. data/test/templates/images/image_1.jpg +0 -0
  36. data/test/templates/images/image_2.jpg +0 -0
  37. data/test/templates/images/image_3.jpg +0 -0
  38. data/test/templates/images/placeholder.jpg +0 -0
  39. data/test/templates/images/placeholder.png +0 -0
  40. data/test/templates/test_images.odt +0 -0
  41. data/test/test.rb +262 -0
  42. metadata +77 -23
  43. data/README.textile +0 -223
  44. data/lib/odf-report/file.rb +0 -50
  45. data/lib/odf-report/images.rb +0 -44
  46. data/lib/odf-report/nested.rb +0 -62
  47. data/spec/specs.odt +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 639774f385b52ff5939a37b9e5d46f0ef32883fa
4
- data.tar.gz: 518447cce1c1ccae65960ffd0d75b617f3bb48bf
2
+ SHA256:
3
+ metadata.gz: c8d30a8580192ffaf7fa4908a5b674e69ecef19a5c3fe054baeaf1e72c3770ad
4
+ data.tar.gz: e402b3a61216a3070cdfcc0799930038ed9cd37687c7a844eaf76ead2b55a704
5
5
  SHA512:
6
- metadata.gz: 823da1a19f5c7fa4d44d2f130fd208658fe38472ea252c71427e2a5580126f882c40ba5a3ccb0d4bcc55e93e54d739fd22142fc3cfb540696fa0d52539227fdd
7
- data.tar.gz: ba034ba239cd822fccb1c08e2586d33aa3c7ffd9268dc372fbac54cdd5b7f2b09f63d7aa69e48b83bcc1056cd64d4e4ced757c226771eaf999428f5144f33518
6
+ metadata.gz: 72e8c9581c0f655dd1ba4ef5dcfd11706f36a2e69d810261fb7f233d72f0cb982a298da7f24055d7561551f3ac737abf018809ea982d495661e0386895ee75bf
7
+ data.tar.gz: 9113f76a643655aa46227a0390c425cca828ba2c05835eb33d3894c7cb2219253bda16c109bf885148e63736671be3389abe804782a196ca860fb958b786ecf5
@@ -0,0 +1,40 @@
1
+ name: Ruby Gem
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build + Publish
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - name: Set up Ruby 2.6
15
+ uses: actions/setup-ruby@v1
16
+ with:
17
+ version: 2.6.x
18
+
19
+ # - name: Publish to GPR
20
+ # run: |
21
+ # mkdir -p $HOME/.gem
22
+ # touch $HOME/.gem/credentials
23
+ # chmod 0600 $HOME/.gem/credentials
24
+ # printf -- "---\n:github: Bearer ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
25
+ # gem build *.gemspec
26
+ # gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem
27
+ # env:
28
+ # GEM_HOST_API_KEY: ${{secrets.GPR_AUTH_TOKEN}}
29
+ # OWNER: username
30
+
31
+ - name: Publish to RubyGems
32
+ run: |
33
+ mkdir -p $HOME/.gem
34
+ touch $HOME/.gem/credentials
35
+ chmod 0600 $HOME/.gem/credentials
36
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
37
+ gem build *.gemspec
38
+ gem push *.gem
39
+ env:
40
+ GEM_HOST_API_KEY: ${{secrets.RUBYGEMS_AUTH_TOKEN}}
data/.gitignore CHANGED
@@ -5,3 +5,4 @@ doc
5
5
  test/result
6
6
  spec/result
7
7
  Gemfile.lock
8
+ .DS_Store
@@ -0,0 +1,62 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
+
7
+ ## Unreleased
8
+
9
+ ### Breaking Changes
10
+
11
+ - None
12
+
13
+ ### Added
14
+
15
+ - None
16
+
17
+ ### Fixed
18
+
19
+ - None
20
+
21
+ ## 0.7.2
22
+
23
+ ### Fixed
24
+
25
+ - files being recognized as "broken file" in Microsoft Word
26
+
27
+
28
+ ## 0.7.1
29
+
30
+ ### Added
31
+
32
+ - remove image if path is null
33
+ - remove section if collection is empty/null
34
+
35
+
36
+ ## 0.7.0
37
+
38
+ ### Added
39
+
40
+ - allow nested images inside tables and sections
41
+ - allow sections inside tables
42
+
43
+ ### Dependencies
44
+
45
+ - rubyzip >= 1.3.0 (was ~> 1.2.0)
46
+
47
+
48
+ ## 0.6.0
49
+
50
+ ### Breaking Changes
51
+
52
+ - `ODFReport::File` renamed to `ODFReport::Template`
53
+ - `ODFReport::Report` constructor signature changed
54
+
55
+ ### Dependencies
56
+
57
+ - rubyzip ~> 1.2.0 (was ~> 1.1.0)
58
+
59
+
60
+ ## Earlier Versions
61
+
62
+ - No docs yet. Contributions welcome!
@@ -0,0 +1,220 @@
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
+ ## INSTALL
7
+
8
+ In your Gemfile
9
+ ```ruby
10
+ gem 'odf-report'
11
+ ```
12
+
13
+ ## USAGE
14
+
15
+ ### Step 1 -- the template
16
+
17
+ First of all, you need a `.odt` file to serve as a template.
18
+ Templates are normal .odt files with `[PLACEHOLDERS]` for *substitutions*.
19
+ There are *four* kinds of substitutions available:
20
+ * fields
21
+ * tables
22
+ * images
23
+ * sections
24
+
25
+ #### Fields
26
+
27
+ It's just an upcase sentence, surrounded by brackets. It will be replaced by the value you supply.
28
+
29
+ In the folowing example:
30
+
31
+ ```ruby
32
+ report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
33
+ r.add_field :user_name, @user.name
34
+ r.add_field :address, "My new address"
35
+ end
36
+ ```
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
+
41
+ #### Tables
42
+
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.
44
+
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.
46
+
47
+ If you have more than one template row, they will be cycled. This is usefull for making zebra tables.
48
+
49
+ As with **Field placeholders**, just insert a `[FIELD_NAME]` in each cell and let the magic takes place.
50
+
51
+ Taking the folowing example:
52
+
53
+ ```ruby
54
+ report = ODFReport::Report.new("Users/john/my_template.odt") do |r|
55
+
56
+ r.add_field "USER_NAME", @user.nome
57
+ r.add_field "ADDRESS", @user.address
58
+
59
+ r.add_table("TABLE_1", @list_of_itens, :header=>true) do |t|
60
+ t.add_column(:item_id, :id)
61
+ t.add_column(:description) { |item| "==> #{item.description}" }
62
+ end
63
+
64
+ end
65
+ ```
66
+
67
+ and considering you have a table like this in your template
68
+
69
+ | #ID | Description |
70
+ |--|--|
71
+ | [ITEM_ID] | [DESCRIPTION] |
72
+
73
+
74
+ and a collection `@list_of_itens`, it will create 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
+ #### Sections
79
+
80
+ 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.
81
+
82
+ 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.
83
+
84
+ Let's see an example:
85
+
86
+ ```ruby
87
+ @invoices = Invoice.find(:all)
88
+
89
+ report = ODFReport::Report.new("reports/invoice.odt") do |r|
90
+
91
+ r.add_field(:title, "INVOICES REPORT")
92
+ r.add_field(:date, Date.today)
93
+
94
+ r.add_section("SC_INVOICE", @invoices) do |s|
95
+
96
+ s.add_field(:number) { |invoice| invoice.number.to_s.rjust(5, '0') }
97
+ s.add_field(:name, :customer_name)
98
+ s.add_field(:address, :customer_address)
99
+
100
+ s.add_table("TB_ITEMS", :items, header: true) do |t|
101
+ t.add_column(:id)
102
+ t.add_column(:product) {|item| item.product.name }
103
+ t.add_column(:value, :product_value)
104
+ end
105
+
106
+ s.add_field(:total) do |invoice|
107
+ if invoice.status == 'CLOSED'
108
+ invoice.total
109
+ else
110
+ invoice.items.sum('product_value')}
111
+ end
112
+ end
113
+
114
+ s.add_section("SUB_NOTES", :notes) do |s1|
115
+
116
+ s1.add_field(:note_title) { |n| n.title }
117
+
118
+ s1.add_table ...
119
+
120
+ end
121
+
122
+ end
123
+
124
+ end
125
+ ```
126
+
127
+ 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.
128
+
129
+ In the above example, `s.add_table("TB_ITEMS", :items, header: true) do |t|`, the `:items` thing refers to a `invoice.items`. Easy, right?
130
+
131
+
132
+ #### Images
133
+
134
+ 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.
135
+ You can also assign any properties you want to the mock image and they will be kept once the image is replaced.
136
+
137
+ An image replace would look like this:
138
+
139
+ ```ruby
140
+ report = ODFReport::Report.new("my_template.odt") do |r|
141
+ r.add_image :graphic1, "/path/to/the/image.jpg"
142
+
143
+ r.add_table("TABLE_WITH_IMAGES", @items) do |t|
144
+ t.add_column(:id)
145
+ t.add_column(:product, :product_name)
146
+ t.add_image('PRODUCT_IMAGE') { |item| item.image_path }
147
+ end
148
+ end
149
+ ```
150
+
151
+ <hr/>
152
+
153
+ ### Step 2 -- generating the document
154
+
155
+ It's fairly simple to generate the document. You can use this inside a Rails application or in a standalone script.
156
+
157
+ #### Generating a document in a Rails application
158
+
159
+ In a controller, you can have a code like this:
160
+
161
+ ```ruby
162
+ def print
163
+
164
+ @ticket = Ticket.find(params[:id])
165
+
166
+ report = ODFReport::Report.new(Rails.root.join("/app/reports/ticket.odt")) do |r|
167
+
168
+ r.add_field(:id, @ticket.id.to_s)
169
+ r.add_field(:created_by, @ticket.created_by)
170
+ r.add_field(:created_at, @ticket.created_at.strftime("%d/%m/%Y - %H:%M"))
171
+ r.add_field(:type, @ticket.type.name)
172
+ r.add_field(:status, @ticket.status_text)
173
+ r.add_field(:date, Time.now.strftime("%d/%m/%Y - %H:%M"))
174
+ r.add_field(:solution, (@ticket.solution || ''))
175
+
176
+ r.add_table("OPERATORS", @ticket.operators) do |t|
177
+ t.add_column(:name) { |op| "#{op.name} (#{op.department.short_name})" }
178
+ end
179
+
180
+ r.add_table("FIELDS", @ticket.fields) do |t|
181
+ t.add_column(:field_name, :name)
182
+ t.add_column(:field_value) { |field| field.text_value || "Empty" }
183
+ end
184
+
185
+ end
186
+
187
+ send_data report.generate,
188
+ type: 'application/vnd.oasis.opendocument.text',
189
+ disposition: 'attachment',
190
+ filename: 'report.odt'
191
+
192
+ end
193
+ ```
194
+
195
+ #### Generating a document in a standalone script
196
+
197
+ It's very similar to a Rails app, but you can inform the path where the file will be saved.
198
+
199
+ ```ruby
200
+ report = ODFReport::Report.new("ticket.odt") do |r|
201
+ ... populates the report ...
202
+ end
203
+
204
+ report.generate("./documents/new_ticket.odt")
205
+ ```
206
+
207
+ #### Using a template stored in the database (or anywhere besides the file system)
208
+ You can provide an `io:` param, containing the actual file read into a String.
209
+
210
+ ```ruby
211
+ report = ODFReport::Report.new(io: @template.attachment.read) do |r|
212
+ ```
213
+
214
+ <hr/>
215
+
216
+ #### REQUIREMENTS
217
+
218
+ **rubyzip**: manipulating the contents of the odt file, since it's actually a zip file.
219
+ **nokogiri**: parsing and manipulating the document xml files.
220
+ **mime-types**: identify images mime types
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'launchy'
4
+
5
+ arg = ARGV[0]
6
+ dir = File.basename(arg, File.extname(arg))
7
+
8
+ %x( rm -rf #{dir}; unzip -d #{dir} #{arg} )
9
+
10
+ Launchy.open dir
@@ -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
@@ -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/images', __FILE__)
9
- require File.expand_path('../odf-report/field', __FILE__)
10
- require File.expand_path('../odf-report/text', __FILE__)
11
- require File.expand_path('../odf-report/file', __FILE__)
12
- require File.expand_path('../odf-report/nested', __FILE__)
13
- require File.expand_path('../odf-report/section', __FILE__)
14
- require File.expand_path('../odf-report/table', __FILE__)
15
- require File.expand_path('../odf-report/report', __FILE__)
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,65 @@
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
+ return unless @value
18
+ @value.each(&block)
19
+ end
20
+
21
+ def empty?
22
+ @value.nil? || @value.empty?
23
+ end
24
+
25
+ private
26
+
27
+ def extract_value_from_item(record)
28
+
29
+ if @block
30
+ @block.call(record)
31
+
32
+ elsif record.is_a?(Hash)
33
+ key = @data_field
34
+ record[key] || record[key.to_s.downcase] || record[key.to_s.upcase] || record[key.to_s.downcase.to_sym]
35
+
36
+ elsif @data_field.is_a?(Array)
37
+ execute_methods_on_item(record)
38
+
39
+ elsif @data_field.is_a?(Hash) && record.respond_to?(@data_field.keys[0])
40
+ record.send(@data_field.keys[0], @data_field.values[0])
41
+
42
+ elsif record.respond_to?(@data_field)
43
+ record.send(@data_field)
44
+
45
+ else
46
+ raise "Can't find [#{@data_field.to_s}] in this #{record.class}"
47
+
48
+ end
49
+
50
+ end
51
+
52
+ def execute_methods_on_item(record)
53
+ tmp = record.dup
54
+ @data_field.each do |f|
55
+ if f.is_a?(Hash)
56
+ tmp = tmp.send(f.keys[0], f.values[0])
57
+ else
58
+ tmp = tmp.send(f)
59
+ end
60
+ end
61
+ tmp
62
+ end
63
+
64
+ end
65
+ end