docx_report 0.1.0 → 0.2.3

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
2
  SHA1:
3
- metadata.gz: df8bc8c2d36d553f99684f1d96f2496c2c3200f1
4
- data.tar.gz: 8ff0b77d064a32fae807ed71527057c4d93da371
3
+ metadata.gz: fba6128858dc1881724b7334e94d2fa527979e2e
4
+ data.tar.gz: 2191d4a90ec26b96769b8d3b7870024d6200c81b
5
5
  SHA512:
6
- metadata.gz: dae046f2041647d0fed8de9ff86ba7af46845f80315e3d5999bb2ae3c99f2fa47b6127b6decc3b8134a4b281cf28d4765e1c376b43943902ca8fdf4ffeea764a
7
- data.tar.gz: b2f0a465bd9e348401bd7d450df1266b4ee47935b9daac808f3f508ec7f4569e0d7160d05f348ea0f5f2393a6317d88537e6587cc1319fc7bd223026275230d3
6
+ metadata.gz: 7e4be5bff6c1c47b28913bd71028e25fd938eb82dbcb1d59daa6d3f53aaf09179d3b59c9583243930befad0851a6187cc1fcf645eadb0ea09b95f01067eff911
7
+ data.tar.gz: 84c3319c0f168a716213bdd11d88adef5f6654e926f85b87301babdc306e2d376cacafa9df91a4c626659a4a724ecb79f64267b07f92e7f750fbcb7d79f74321
@@ -6,6 +6,8 @@ require 'docx_report/record'
6
6
  require 'docx_report/field'
7
7
  require 'docx_report/content_file'
8
8
  require 'docx_report/hyperlink'
9
+ require 'docx_report/image'
10
+ require 'docx_report/block_value'
9
11
 
10
12
  module DocxReport
11
13
  def self.create_docx_report(template_path)
@@ -0,0 +1,11 @@
1
+ module DocxReport
2
+ module BlockValue
3
+ def load_value(item)
4
+ if @value.is_a? Proc
5
+ @value.call(item)
6
+ else
7
+ item.is_a?(Hash) ? item[@value] : item.send(@value)
8
+ end
9
+ end
10
+ end
11
+ end
File without changes
@@ -1,6 +1,6 @@
1
1
  module DocxReport
2
2
  module DataItem
3
- attr_reader :fields, :images
3
+ attr_reader :fields, :images, :tables
4
4
 
5
5
  def add_field(name, value, type = :text)
6
6
  field = Field.new name, value, type
@@ -3,33 +3,32 @@ require 'nokogiri'
3
3
 
4
4
  module DocxReport
5
5
  class Document
6
- attr_accessor :template_path, :files
6
+ attr_accessor :template_path, :files, :image_ids, :images, :image_types
7
7
 
8
8
  def initialize(template_path)
9
+ @images = []
10
+ @image_types = []
9
11
  @template_path = template_path
10
12
  zip = Zip::File.open(template_path)
11
13
  load_files zip
14
+ @image_ids = images_ids zip
12
15
  zip.close
13
16
  end
14
17
 
15
18
  def save_to_memory
16
19
  Zip::OutputStream.write_buffer do |output|
20
+ add_images output
17
21
  add_files output
18
22
  end.string
19
23
  end
20
24
 
21
25
  def save_to_file(output_path)
22
26
  Zip::OutputStream.open(output_path) do |output|
27
+ add_images output
23
28
  add_files output
24
29
  end
25
30
  end
26
31
 
27
- def new_uniqe_id(type)
28
- (@files.map do |file|
29
- file.xml.xpath('//*[@id]').map { |e| e[:id].to_i if e.name == type }
30
- end.flatten.compact.max || 0) + 1
31
- end
32
-
33
32
  private
34
33
 
35
34
  def add_files(output)
@@ -47,11 +46,12 @@ module DocxReport
47
46
  end
48
47
 
49
48
  def write_files(name, template, output)
50
- if @files.any? { |f| f.name == name }
51
- add_data name, @files.detect { |f| f.name == name }.xml.to_xml, output
52
- elsif @files.any? { |f| f.rels_name == name }
53
- file = @files.detect { |f| f.rels_name == name }
49
+ if file = @files.detect { |f| f.name == name }
50
+ add_data name, file.xml.to_xml, output
51
+ elsif file = @files.detect { |f| f.rels_name == name }
54
52
  add_data name, file.rels_xml.to_xml, output if file.rels_has_items?
53
+ elsif name == CONTENT_TYPE_NAME
54
+ add_data name, @content_types.to_xml, output
55
55
  else
56
56
  add_data name, template.read(name), output
57
57
  end
@@ -62,17 +62,49 @@ module DocxReport
62
62
  output.write data
63
63
  end
64
64
 
65
+ def add_images(output)
66
+ images.each do |image|
67
+ image.save(output)
68
+ image.new_rels.each do |rels|
69
+ rels[:Target] = format(rels[:Target], image.type)
70
+ end
71
+ add_content_type image.type
72
+ end
73
+ end
74
+
65
75
  def content_types_xpath
66
76
  "//*[@ContentType = '#{CONTENT_TYPES.join("' or @ContentType='")}']"
67
77
  end
68
78
 
69
79
  def load_files(zip)
70
80
  @files = []
71
- content_type_node = Nokogiri::XML zip.read(CONTENT_TYPE_NAME)
72
- content_type_node.xpath(content_types_xpath).each do |e|
81
+ @content_types = Nokogiri::XML zip.read(CONTENT_TYPE_NAME)
82
+ @content_types.xpath(content_types_xpath).each do |e|
73
83
  filename = e['PartName'][1..-1]
74
84
  @files << ContentFile.new(filename, zip)
75
85
  end
86
+ find_image_types.each { |type| @image_types << type[:Extension] }
87
+ end
88
+
89
+ def images_ids(zip)
90
+ zip.entries.map do |e|
91
+ if e.name.start_with?('word/media/image')
92
+ (File.basename(e.name, '.*')[5..-1]).to_i
93
+ end
94
+ end.compact
95
+ end
96
+
97
+ def find_image_types
98
+ @content_types.xpath('//*[starts-with(@ContentType, "image")]')
99
+ end
100
+
101
+ def add_content_type(type)
102
+ unless @image_types.include? type
103
+ @content_types.children.first << Nokogiri::XML(
104
+ format('<Default Extension="%s" ContentType="image/%s"/>',
105
+ type, type)).children.first
106
+ @image_types << type
107
+ end
76
108
  end
77
109
 
78
110
  CONTENT_TYPE_NAME = '[Content_Types].xml'.freeze
@@ -1,5 +1,9 @@
1
+
2
+ require 'docx_report/block_value'
3
+
1
4
  module DocxReport
2
5
  class Field
6
+ include BlockValue
3
7
  attr_reader :name, :value, :type
4
8
 
5
9
  def initialize(name, value = nil, type = :text, &block)
@@ -12,12 +16,8 @@ module DocxReport
12
16
  @value = value || block
13
17
  end
14
18
 
15
- def load_value(item)
16
- Field.new(name[1..-2], if @value.is_a? Proc
17
- @value.call(item)
18
- else
19
- item.is_a?(Hash) ? item[@value] : item.send(@value)
20
- end, type)
19
+ def load_field(item)
20
+ Field.new(name[1..-2], load_value(item), type)
21
21
  end
22
22
  end
23
23
  end
File without changes
@@ -0,0 +1,88 @@
1
+ require 'mini_magick'
2
+
3
+ module DocxReport
4
+ class Image
5
+ attr_accessor :path, :id, :nodes, :files, :new_rels, :type
6
+
7
+ def initialize(path, document)
8
+ @path = path
9
+ @files = {}
10
+ @nodes = []
11
+ @new_rels = []
12
+ global_id(document)
13
+ end
14
+
15
+ def global_id(document)
16
+ @id = document.image_ids.max + 1
17
+ document.image_ids << @id
18
+ document.images << self
19
+ end
20
+
21
+ def file_image_id(file)
22
+ new_image_id(file) if files[file.name].nil?
23
+ files[file.name]
24
+ end
25
+
26
+ def image_ref(id)
27
+ rels = Nokogiri::XML(
28
+ format('<Relationship Id="%s" Type="http://schemas.openxmlformats.org/'\
29
+ 'officeDocument/2006/relationships/image" Target="media/image%s.%s"/>',
30
+ id, @id, '%s')).children.first
31
+ @new_rels << rels
32
+ rels
33
+ end
34
+
35
+ def save(output)
36
+ img = MiniMagick::Image.open(@path)
37
+ @type = img.type.downcase
38
+ fix_rels
39
+ output.put_next_entry "word/media/image#{@id}.#{@type}"
40
+ img.write output
41
+ set_dimentions img.width, img.height, img.resolution
42
+ end
43
+
44
+ private
45
+
46
+ def fix_rels
47
+ new_rels.each { |rels| format(rels, @id, @type) }
48
+ end
49
+
50
+ def new_image_id(file)
51
+ files[file.name] = "rId#{file.new_uniqe_id}"
52
+ file.rels_xml.children.first << image_ref(files[file.name])
53
+ end
54
+
55
+ def set_dimentions(width, height, resolution)
56
+ @width = width.to_f / resolution[0] * EMUS_PER_INCH
57
+ @height = height.to_f / vert_dpi(resolution) * EMUS_PER_INCH
58
+ fit_in_page
59
+ @nodes.each do |node|
60
+ node.xpath('.//*[@cx and @cy]').each do |ext|
61
+ ext[:cx] = @width.to_i
62
+ ext[:cy] = @height.to_i
63
+ end
64
+ end
65
+ end
66
+
67
+ def vert_dpi(resolution)
68
+ resolution[resolution.length == 2 ? 1 : 2]
69
+ end
70
+
71
+ def fit_in_page
72
+ if @width > MAX_WIDTH_EMUS
73
+ ratio = @height / @width
74
+ @width = MAX_WIDTH_EMUS
75
+ @height = MAX_WIDTH_EMUS * ratio
76
+ end
77
+ if @height > MAX_HEIGHT_EMUS
78
+ ratio = @width / @height
79
+ @height = MAX_HEIGHT_EMUS
80
+ @width = MAX_HEIGHT_EMUS * ratio
81
+ end
82
+ end
83
+
84
+ EMUS_PER_INCH = 914400
85
+ MAX_WIDTH_EMUS = 5742360
86
+ MAX_HEIGHT_EMUS = 8229600
87
+ end
88
+ end
@@ -10,6 +10,7 @@ module DocxReport
10
10
  @document.files.each do |file|
11
11
  replace_node_fields(fields, file.xml)
12
12
  replace_node_hyperlinks(fields, file.xml, file)
13
+ replace_node_images(fields, file.xml, file)
13
14
  end
14
15
  end
15
16
 
@@ -27,10 +28,15 @@ module DocxReport
27
28
 
28
29
  def find_hyperlink_nodes(name, parent_node, file)
29
30
  links = file.rels_xml.xpath "//*[@Target='#{name}']"
30
- parent_node.xpath(".//w:hyperlink[@r:id='#{find_by_Id(links)}']")
31
+ parent_node.xpath(".//w:hyperlink[@r:id='#{find_by_id(links)}']")
31
32
  end
32
33
 
33
- def find_by_Id(links)
34
+ def find_image_nodes(name, parent_node)
35
+ parent_node.xpath(
36
+ ".//w:drawing[.//wp:docPr[@title='#{name}']]")
37
+ end
38
+
39
+ def find_by_id(links)
34
40
  links.map { |link| link[:Id] }.join("' or @r:id='")
35
41
  end
36
42
 
@@ -52,9 +58,24 @@ module DocxReport
52
58
  end
53
59
  end
54
60
 
61
+ def replace_node_images(fields, parent_node, file)
62
+ fields.select { |f| f.type == :image }.each do |field|
63
+ image = document_image(field.value)
64
+ find_image_nodes(field.name, parent_node).each do |node|
65
+ node.xpath('.//*[@r:embed]').first['r:embed'] = image
66
+ .file_image_id(file)
67
+ image.nodes << node
68
+ end
69
+ end
70
+ end
71
+
72
+ def document_image(path)
73
+ @document.images.detect { |img| img.path == path } ||
74
+ Image.new(path, @document)
75
+ end
76
+
55
77
  def find_table(name, parent_node)
56
- parent_node.xpath(".//w:tbl[//w:tblCaption[@w:val='#{name}']][1]")
57
- .first
78
+ parent_node.xpath(".//w:tbl[//w:tblCaption[@w:val='#{name}']][1]").first
58
79
  end
59
80
 
60
81
  def find_row(table, table_node)
@@ -75,8 +96,10 @@ module DocxReport
75
96
  table.records.each do |record|
76
97
  new_row = row_node.dup
77
98
  row_node.add_previous_sibling new_row
99
+ fill_tables record.tables, new_row, file
78
100
  replace_node_fields record.fields, new_row
79
101
  replace_node_hyperlinks record.fields, new_row, file, true
102
+ replace_node_images record.fields, new_row, file
80
103
  end
81
104
  row_node.remove
82
105
  end
@@ -6,6 +6,7 @@ module DocxReport
6
6
 
7
7
  def initialize
8
8
  @fields = []
9
+ @tables = []
9
10
  end
10
11
  end
11
12
  end
@@ -3,7 +3,7 @@ require 'docx_report/data_item'
3
3
  module DocxReport
4
4
  class Report
5
5
  include DataItem
6
- attr_reader :fields, :tables
6
+ attr_reader :fields
7
7
 
8
8
  def initialize(template_path)
9
9
  @template_path = template_path
@@ -33,8 +33,8 @@ module DocxReport
33
33
 
34
34
  def apply_changes(document)
35
35
  parser = Parser.new document
36
- parser.replace_all_fields @fields
37
36
  parser.fill_all_tables @tables
37
+ parser.replace_all_fields @fields
38
38
  end
39
39
  end
40
40
  end
@@ -1,17 +1,23 @@
1
+ require 'docx_report/block_value'
2
+
1
3
  module DocxReport
2
4
  class Table
5
+ include BlockValue
3
6
  attr_reader :name, :has_header, :records
4
7
 
5
- def initialize(name, has_header = false)
8
+ def initialize(name, has_header = false, collection = nil, fields = [],
9
+ tables = [])
6
10
  @name = name
7
11
  @has_header = has_header
12
+ @value = collection
8
13
  @records = []
9
- @fields = []
14
+ @fields = fields
15
+ @tables = tables
10
16
  end
11
17
 
12
18
  def new_record
13
19
  new_record = Record.new
14
- records << new_record
20
+ @records << new_record
15
21
  new_record
16
22
  end
17
23
 
@@ -23,12 +29,24 @@ module DocxReport
23
29
  @fields << field
24
30
  end
25
31
 
32
+ def add_table(name, collection = nil, has_header = false, &block)
33
+ raise 'duplicate table name' if @tables.any? { |t| t.name == name }
34
+ table = Table.new name, has_header, collection || block
35
+ @tables << table
36
+ table
37
+ end
38
+
39
+ def load_table(item)
40
+ table = Table.new(name, has_header, nil, @fields, @tables)
41
+ table.load_records(load_value(item))
42
+ table
43
+ end
44
+
26
45
  def load_records(collection)
27
46
  collection.each do |item|
28
47
  record = new_record
29
- @fields.each do |field|
30
- record.fields << field.load_value(item)
31
- end
48
+ @tables.each { |table| record.tables << table.load_table(item) }
49
+ @fields.each { |field| record.fields << field.load_field(item) }
32
50
  end
33
51
  end
34
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docx_report
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmed Abudaqqa
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-08 00:00:00.000000000 Z
11
+ date: 2016-03-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -38,21 +38,37 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: mini_magick
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.5'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.5'
41
55
  description: |-
42
- docx_report is a light weight gem that generates docx files
43
- by replacing strings and inserting images on previously
44
- created .docx template file
56
+ docx_report is a gem that generates docx files by replacing
57
+ strings and inserting images on previously created .docx
58
+ template file
45
59
  email: ahmed@abudaqqa.com
46
60
  executables: []
47
61
  extensions: []
48
62
  extra_rdoc_files: []
49
63
  files:
50
64
  - lib/docx_report.rb
65
+ - lib/docx_report/block_value.rb
51
66
  - lib/docx_report/content_file.rb
52
67
  - lib/docx_report/data_item.rb
53
68
  - lib/docx_report/document.rb
54
69
  - lib/docx_report/field.rb
55
70
  - lib/docx_report/hyperlink.rb
71
+ - lib/docx_report/image.rb
56
72
  - lib/docx_report/parser.rb
57
73
  - lib/docx_report/record.rb
58
74
  - lib/docx_report/report.rb