docx_report 0.1.0 → 0.2.3

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
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