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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9603b557fe4954d39ebb8b15e64672a2fccf54a9
4
- data.tar.gz: 0f7960beb896714a00d75d09db73781b176ede6f
2
+ SHA256:
3
+ metadata.gz: babb3d76f7401badd099efc108cc000baa9ce38a556447162dd0d31e1ad2b940
4
+ data.tar.gz: 5ee08b656534f7522c60b3f784343120600761478baba47569c52d108d613237
5
5
  SHA512:
6
- metadata.gz: c31b6efa59a4563028c434738547fddd7bf9520b77c5020343b5c1e2bd3a24d4324ef77ea22328a487a86a8d261ef5784a066122de3d2368b34991a9c40f283a
7
- data.tar.gz: f2c540d70f4562ede248beac9e7631c8e158cdfbb0f8de353e31760d3d6578a4ccf552f52f9cde6559a0b9763c9749425b071d20f85c0a14bb14384b8b74709c
6
+ metadata.gz: 432a082f25cf119cc117d476abee396a1a6e24a144d5792f786aa249adf4b1bcf07a7fabad9d0789ca063484ea33c16fb74b05f6b088364efe3a0be3e5f03d5b
7
+ data.tar.gz: 07c003adba47dac35a15ef697e750e3a918f42f4db4cb7964c9ec4ce28141a52f74b375fc328d6a4f87ce97df381fa0a7ba14c75598b39bb9c1d435cab4a70ef
data/.gitignore CHANGED
@@ -5,3 +5,4 @@ doc
5
5
  test/result
6
6
  spec/result
7
7
  Gemfile.lock
8
+ .DS_Store
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
- * uses newer rubyzip >= **1.3.0**
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 for wathever value you supply.
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**: for manipulating the contents of the odt file, since it's actually a zip file.
217
- **nokogiri**: for parsing and manipulating the document xml files.
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
@@ -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/template', __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,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
@@ -5,56 +5,25 @@ module ODFReport
5
5
 
6
6
  def initialize(opts, &block)
7
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
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
- val = get_value(data_item)
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
- 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
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
@@ -3,22 +3,24 @@ module ODFReport
3
3
  module Nested
4
4
 
5
5
  def add_field(name, data_field=nil, &block)
6
- opts = {:name => name, :data_field => data_field}
7
- field = Field.new(opts, &block)
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 = {:name => name, :data_field => data_field}
15
- field = Text.new(opts, &block)
16
- @texts << field
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!(:name => table_name, :collection_field => collection_field)
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!(:name => section_name, :collection_field => collection_field)
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