cure-odf-report 0.5.1b

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +4 -0
  4. data/Gemfile +4 -0
  5. data/MIT-LICENSE +20 -0
  6. data/Manifest +12 -0
  7. data/README.textile +223 -0
  8. data/Rakefile +9 -0
  9. data/lib/odf-report.rb +16 -0
  10. data/lib/odf-report/actions/remove_section.rb +22 -0
  11. data/lib/odf-report/field.rb +88 -0
  12. data/lib/odf-report/file.rb +50 -0
  13. data/lib/odf-report/images.rb +44 -0
  14. data/lib/odf-report/nested.rb +62 -0
  15. data/lib/odf-report/parser/default.rb +91 -0
  16. data/lib/odf-report/report.rb +103 -0
  17. data/lib/odf-report/section.rb +64 -0
  18. data/lib/odf-report/table.rb +88 -0
  19. data/lib/odf-report/text.rb +43 -0
  20. data/lib/odf-report/version.rb +3 -0
  21. data/odf-report.gemspec +31 -0
  22. data/spec/fields_spec.rb +77 -0
  23. data/spec/result/specs.odt +0 -0
  24. data/spec/result/tables.rb +38 -0
  25. data/spec/spec_helper.rb +45 -0
  26. data/spec/specs.odt +0 -0
  27. data/spec/tables_spec.rb +39 -0
  28. data/test/fields_inside_text_test.rb +38 -0
  29. data/test/nested_tables_test.rb +43 -0
  30. data/test/sections_test.rb +44 -0
  31. data/test/sub_sections_test.rb +58 -0
  32. data/test/table_headers_test.rb +41 -0
  33. data/test/tables_test.rb +67 -0
  34. data/test/templates/piriapolis.jpg +0 -0
  35. data/test/templates/rails.png +0 -0
  36. data/test/templates/test_fields_inside_text.odt +0 -0
  37. data/test/templates/test_nested_tables.odt +0 -0
  38. data/test/templates/test_sections.odt +0 -0
  39. data/test/templates/test_sub_sections.odt +0 -0
  40. data/test/templates/test_table_headers.odt +0 -0
  41. data/test/templates/test_tables.odt +0 -0
  42. data/test/templates/test_text.odt +0 -0
  43. data/test/text_test.rb +56 -0
  44. metadata +204 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d36288e169f5ae0e36674e1a5ddb3d4d0ea25253
4
+ data.tar.gz: 163d75c4ca04321dcf403f257febd24d05b7a271
5
+ SHA512:
6
+ metadata.gz: 6e50976d86bd701d27f013ebeaf93c267e89a0ed8a2d44d1fe05e4ab5b03eeda1b160c1e19324f491fcc7b871eb6e04ca968ff96720fbac8820f81fa6c0aa24d
7
+ data.tar.gz: 65ce3ca1adbe04827703793d042d1422215f048cce1e86c6eaafd2c2d0ab560ccc4f542520f3756b15dcb5da4d01df6ade6462929f69ee960e1a0f78c6677520
@@ -0,0 +1,6 @@
1
+ pkg
2
+ pckg
3
+ doc
4
+ *.gem
5
+ test/result
6
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,4 @@
1
+ --color
2
+ --warnings
3
+ --require spec_helper
4
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in odf-report.gemspec
4
+ gemspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Sandro Duarte
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,12 @@
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
7
+ odf-report.gemspec
8
+ README.textile
9
+ test/test.odt
10
+ test/sections.odt
11
+ test/test.rb
12
+ Manifest
@@ -0,0 +1,223 @@
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.
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require 'launchy'
3
+ task :test do
4
+ Dir.glob('./test/*_test.rb').each { |file| require file}
5
+ end
6
+
7
+ task :open do
8
+ Dir.glob('./test/result/*.odt').each { |file| Launchy.open(file) }
9
+ end
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'zip'
3
+ require 'fileutils'
4
+ require 'nokogiri'
5
+
6
+ require File.expand_path('../odf-report/parser/default', __FILE__)
7
+
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__)
16
+ require File.expand_path('../odf-report/actions/remove_section', __FILE__)
@@ -0,0 +1,22 @@
1
+ module ODFReport
2
+ module Actions
3
+ class RemoveSection
4
+ def initialize(section_name)
5
+ @section_name = section_name.upcase
6
+ end
7
+
8
+ def process!(doc)
9
+ section_node = section_node(doc)
10
+
11
+ section_node.remove if section_node
12
+ end
13
+
14
+ private
15
+
16
+ def section_node(doc)
17
+ sections = doc.xpath(".//text:section[@text:name='#{@section_name}']")
18
+ sections.empty? ? nil : sections.first
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,88 @@
1
+ module ODFReport
2
+ class Field
3
+
4
+ DELIMITERS = %w([ ])
5
+
6
+ def initialize(opts, &block)
7
+ @name = opts[:name]
8
+ @data_field = opts[:data_field]
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
20
+
21
+ end
22
+
23
+ def replace!(content, data_item = nil)
24
+
25
+ txt = content.inner_html
26
+
27
+ val = get_value(data_item)
28
+
29
+ txt.gsub!(to_placeholder, sanitize(val))
30
+
31
+ content.inner_html = txt
32
+
33
+ end
34
+
35
+ def get_value(data_item = nil)
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
58
+
59
+ def to_placeholder
60
+ if DELIMITERS.is_a?(Array)
61
+ "#{DELIMITERS[0]}#{@name.to_s.upcase}#{DELIMITERS[1]}"
62
+ else
63
+ "#{DELIMITERS}#{@name.to_s.upcase}#{DELIMITERS}"
64
+ end
65
+ end
66
+
67
+ def sanitize(txt)
68
+ txt = html_escape(txt)
69
+ txt = odf_linebreak(txt)
70
+ txt
71
+ end
72
+
73
+ HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;' }
74
+
75
+ def html_escape(s)
76
+ return "" unless s
77
+ s.to_s.gsub(/[&"><]/) { |special| HTML_ESCAPE[special] }
78
+ end
79
+
80
+ def odf_linebreak(s)
81
+ return "" unless s
82
+ s.to_s.gsub("\n", "<text:line-break/>")
83
+ end
84
+
85
+
86
+
87
+ end
88
+ end
@@ -0,0 +1,50 @@
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.exists? 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