odf-report 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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