docx_report 0.0.5 → 0.1.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 +4 -4
- data/lib/docx_report.rb +3 -0
- data/lib/docx_report/content_file.rb +34 -0
- data/lib/docx_report/data_item.rb +11 -0
- data/lib/docx_report/document.rb +55 -20
- data/lib/docx_report/field.rb +23 -0
- data/lib/docx_report/hyperlink.rb +30 -0
- data/lib/docx_report/parser.rb +51 -29
- data/lib/docx_report/record.rb +4 -6
- data/lib/docx_report/report.rb +18 -17
- data/lib/docx_report/table.rb +8 -15
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df8bc8c2d36d553f99684f1d96f2496c2c3200f1
|
4
|
+
data.tar.gz: 8ff0b77d064a32fae807ed71527057c4d93da371
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dae046f2041647d0fed8de9ff86ba7af46845f80315e3d5999bb2ae3c99f2fa47b6127b6decc3b8134a4b281cf28d4765e1c376b43943902ca8fdf4ffeea764a
|
7
|
+
data.tar.gz: b2f0a465bd9e348401bd7d450df1266b4ee47935b9daac808f3f508ec7f4569e0d7160d05f348ea0f5f2393a6317d88537e6587cc1319fc7bd223026275230d3
|
data/lib/docx_report.rb
CHANGED
@@ -3,6 +3,9 @@ require 'docx_report/document'
|
|
3
3
|
require 'docx_report/parser'
|
4
4
|
require 'docx_report/table'
|
5
5
|
require 'docx_report/record'
|
6
|
+
require 'docx_report/field'
|
7
|
+
require 'docx_report/content_file'
|
8
|
+
require 'docx_report/hyperlink'
|
6
9
|
|
7
10
|
module DocxReport
|
8
11
|
def self.create_docx_report(template_path)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module DocxReport
|
2
|
+
class ContentFile
|
3
|
+
attr_reader :name, :xml, :rels_name, :rels_xml, :new_rels
|
4
|
+
|
5
|
+
def initialize(name, zip)
|
6
|
+
@name = name
|
7
|
+
@xml = Nokogiri::XML(zip.read(name))
|
8
|
+
@rels_name = "#{name.sub '/', '/_rels/'}.rels"
|
9
|
+
@new_rels = false
|
10
|
+
@rels_xml = Nokogiri::XML(if zip.entries.any? { |r| r.name == @rels_name }
|
11
|
+
zip.read(@rels_name)
|
12
|
+
else
|
13
|
+
new_rels_xml
|
14
|
+
end)
|
15
|
+
end
|
16
|
+
|
17
|
+
def new_uniqe_id
|
18
|
+
(@rels_xml.xpath('//*[@Id]').map do |e|
|
19
|
+
e[:Id][3..-1].to_i if e.name == 'Relationship'
|
20
|
+
end.compact.max || 0) + 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def new_rels_xml
|
24
|
+
@new_rels = true
|
25
|
+
'<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships'\
|
26
|
+
'xmlns="http://schemas.openxmlformats.org/package/2006/relationships">'\
|
27
|
+
'</Relationships>'
|
28
|
+
end
|
29
|
+
|
30
|
+
def rels_has_items?
|
31
|
+
@rels_xml.xpath('//*[@Id]').any?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module DocxReport
|
2
|
+
module DataItem
|
3
|
+
attr_reader :fields, :images
|
4
|
+
|
5
|
+
def add_field(name, value, type = :text)
|
6
|
+
field = Field.new name, value, type
|
7
|
+
raise 'duplicate field name' if @fields.any? { |f| f.name == field.name }
|
8
|
+
@fields << field
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/docx_report/document.rb
CHANGED
@@ -3,52 +3,87 @@ require 'nokogiri'
|
|
3
3
|
|
4
4
|
module DocxReport
|
5
5
|
class Document
|
6
|
-
|
6
|
+
attr_accessor :template_path, :files
|
7
7
|
|
8
8
|
def initialize(template_path)
|
9
9
|
@template_path = template_path
|
10
10
|
zip = Zip::File.open(template_path)
|
11
|
-
|
11
|
+
load_files zip
|
12
12
|
zip.close
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
|
15
|
+
def save_to_memory
|
16
|
+
Zip::OutputStream.write_buffer do |output|
|
17
|
+
add_files output
|
18
|
+
end.string
|
19
|
+
end
|
20
|
+
|
21
|
+
def save_to_file(output_path)
|
17
22
|
Zip::OutputStream.open(output_path) do |output|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
add_files output
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
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
|
+
private
|
34
|
+
|
35
|
+
def add_files(output)
|
36
|
+
Zip::File.open @template_path do |template|
|
37
|
+
template.each do |entry|
|
38
|
+
write_files entry.name, template, output
|
39
|
+
end
|
40
|
+
@files.each do |file|
|
41
|
+
if file.new_rels && file.rels_has_items?
|
42
|
+
output.put_next_entry file.rels_name
|
43
|
+
output.write file.rels_xml.to_xml
|
24
44
|
end
|
25
45
|
end
|
26
46
|
end
|
27
|
-
zip.close()
|
28
47
|
end
|
29
48
|
|
30
|
-
|
49
|
+
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 }
|
54
|
+
add_data name, file.rels_xml.to_xml, output if file.rels_has_items?
|
55
|
+
else
|
56
|
+
add_data name, template.read(name), output
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_data(name, data, output)
|
61
|
+
output.put_next_entry name
|
62
|
+
output.write data
|
63
|
+
end
|
31
64
|
|
32
65
|
def content_types_xpath
|
33
66
|
"//*[@ContentType = '#{CONTENT_TYPES.join("' or @ContentType='")}']"
|
34
67
|
end
|
35
68
|
|
36
69
|
def load_files(zip)
|
37
|
-
@files =
|
38
|
-
|
39
|
-
|
70
|
+
@files = []
|
71
|
+
content_type_node = Nokogiri::XML zip.read(CONTENT_TYPE_NAME)
|
72
|
+
content_type_node.xpath(content_types_xpath).each do |e|
|
40
73
|
filename = e['PartName'][1..-1]
|
41
|
-
@files
|
74
|
+
@files << ContentFile.new(filename, zip)
|
42
75
|
end
|
43
|
-
@files
|
44
76
|
end
|
45
77
|
|
46
78
|
CONTENT_TYPE_NAME = '[Content_Types].xml'.freeze
|
47
79
|
|
48
80
|
CONTENT_TYPES = [
|
49
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml
|
50
|
-
'
|
51
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml
|
81
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml'\
|
82
|
+
'.document.main+xml',
|
83
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml'\
|
84
|
+
'.header+xml',
|
85
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml'\
|
86
|
+
'.footer+xml'
|
52
87
|
].freeze
|
53
88
|
end
|
54
89
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DocxReport
|
2
|
+
class Field
|
3
|
+
attr_reader :name, :value, :type
|
4
|
+
|
5
|
+
def initialize(name, value = nil, type = :text, &block)
|
6
|
+
@name = "@#{name}@"
|
7
|
+
@type = type
|
8
|
+
set_value(value || block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_value(value = nil, &block)
|
12
|
+
@value = value || block
|
13
|
+
end
|
14
|
+
|
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)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module DocxReport
|
2
|
+
class Hyperlink
|
3
|
+
attr_accessor :target, :file, :id
|
4
|
+
|
5
|
+
def initialize(target, file, id = nil)
|
6
|
+
@target = target
|
7
|
+
@file = file
|
8
|
+
if id.nil?
|
9
|
+
generate_id
|
10
|
+
else
|
11
|
+
@id = id
|
12
|
+
file.rels_xml.xpath("//*[@Id='#{id}']").first[:Target] = target
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def generate_id
|
19
|
+
@id = "rId#{file.new_uniqe_id}"
|
20
|
+
file.rels_xml.children.first << hyperlink_rels
|
21
|
+
end
|
22
|
+
|
23
|
+
def hyperlink_rels
|
24
|
+
Nokogiri::XML(
|
25
|
+
format('<Relationship Id="%s" Type="http://schemas.openxmlformats'\
|
26
|
+
'.org/officeDocument/2006/relationships/hyperlink" Target="%s" '\
|
27
|
+
'TargetMode="External"/>', @id, @target)).children.first
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/docx_report/parser.rb
CHANGED
@@ -7,56 +7,78 @@ module DocxReport
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def replace_all_fields(fields)
|
10
|
-
@document.files.
|
11
|
-
|
10
|
+
@document.files.each do |file|
|
11
|
+
replace_node_fields(fields, file.xml)
|
12
|
+
replace_node_hyperlinks(fields, file.xml, file)
|
12
13
|
end
|
13
14
|
end
|
14
15
|
|
15
16
|
def fill_all_tables(tables)
|
16
|
-
@document.files.
|
17
|
-
|
17
|
+
@document.files.each do |file|
|
18
|
+
fill_tables(tables, file.xml, file)
|
18
19
|
end
|
19
20
|
end
|
20
21
|
|
21
22
|
private
|
22
23
|
|
23
|
-
def
|
24
|
-
|
24
|
+
def find_text_nodes(name, parent_node)
|
25
|
+
parent_node.xpath(".//*[contains(text(), '#{name}')]")
|
25
26
|
end
|
26
27
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def find_hyperlink_nodes(name, parent_node, file)
|
29
|
+
links = file.rels_xml.xpath "//*[@Target='#{name}']"
|
30
|
+
parent_node.xpath(".//w:hyperlink[@r:id='#{find_by_Id(links)}']")
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_by_Id(links)
|
34
|
+
links.map { |link| link[:Id] }.join("' or @r:id='")
|
35
|
+
end
|
36
|
+
|
37
|
+
def replace_node_fields(fields, parent_node)
|
38
|
+
fields.select { |f| f.type == :text }.each do |field|
|
39
|
+
find_text_nodes(field.name, parent_node).map do |node|
|
40
|
+
node.content = node.content.gsub field.name, field.value
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def replace_node_hyperlinks(fields, parent_node, file, create = false)
|
46
|
+
fields.select { |f| f.type == :hyperlink }.each do |field|
|
47
|
+
find_hyperlink_nodes(field.name, parent_node, file).each do |node|
|
48
|
+
hyperlink = Hyperlink.new(field.value, file,
|
49
|
+
(node['r:id'] unless create))
|
50
|
+
node['r:id'] = hyperlink.id
|
31
51
|
end
|
32
52
|
end
|
33
53
|
end
|
34
54
|
|
35
|
-
def find_table(name,
|
36
|
-
|
55
|
+
def find_table(name, parent_node)
|
56
|
+
parent_node.xpath(".//w:tbl[//w:tblCaption[@w:val='#{name}']][1]")
|
57
|
+
.first
|
37
58
|
end
|
38
59
|
|
39
|
-
def find_row(
|
40
|
-
|
60
|
+
def find_row(table, table_node)
|
61
|
+
row_number = table.has_header ? 2 : 1
|
62
|
+
table_node.xpath(".//w:tr[#{row_number}]").first
|
41
63
|
end
|
42
64
|
|
43
|
-
def
|
65
|
+
def fill_tables(tables, parent_node, file)
|
44
66
|
tables.each do |table|
|
45
|
-
tbl = find_table table.name,
|
46
|
-
if tbl
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
67
|
+
tbl = find_table table.name, parent_node
|
68
|
+
next if tbl.nil?
|
69
|
+
tbl_row = find_row table, tbl
|
70
|
+
fill_table_rows(table, tbl_row, file) unless tbl_row.nil?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def fill_table_rows(table, row_node, file)
|
75
|
+
table.records.each do |record|
|
76
|
+
new_row = row_node.dup
|
77
|
+
row_node.add_previous_sibling new_row
|
78
|
+
replace_node_fields record.fields, new_row
|
79
|
+
replace_node_hyperlinks record.fields, new_row, file, true
|
59
80
|
end
|
81
|
+
row_node.remove
|
60
82
|
end
|
61
83
|
end
|
62
84
|
end
|
data/lib/docx_report/record.rb
CHANGED
@@ -1,13 +1,11 @@
|
|
1
|
+
require 'docx_report/data_item'
|
2
|
+
|
1
3
|
module DocxReport
|
2
4
|
class Record
|
3
|
-
|
5
|
+
include DataItem
|
4
6
|
|
5
7
|
def initialize
|
6
|
-
@fields =
|
7
|
-
end
|
8
|
-
|
9
|
-
def add_field(name, value)
|
10
|
-
fields["{@#{name}}"] = value
|
8
|
+
@fields = []
|
11
9
|
end
|
12
10
|
end
|
13
11
|
end
|
data/lib/docx_report/report.rb
CHANGED
@@ -1,20 +1,18 @@
|
|
1
|
-
require '
|
1
|
+
require 'docx_report/data_item'
|
2
2
|
|
3
3
|
module DocxReport
|
4
4
|
class Report
|
5
|
+
include DataItem
|
5
6
|
attr_reader :fields, :tables
|
6
7
|
|
7
8
|
def initialize(template_path)
|
8
9
|
@template_path = template_path
|
9
|
-
@fields =
|
10
|
+
@fields = []
|
10
11
|
@tables = []
|
11
12
|
end
|
12
13
|
|
13
|
-
def add_field(name, value)
|
14
|
-
@fields["{@#{name}}"] = value
|
15
|
-
end
|
16
|
-
|
17
14
|
def add_table(name, collection = nil, has_header = false)
|
15
|
+
raise 'duplicate table name' if @tables.any? { |t| t.name == name }
|
18
16
|
table = Table.new name, has_header
|
19
17
|
@tables << table
|
20
18
|
yield table
|
@@ -22,18 +20,21 @@ module DocxReport
|
|
22
20
|
end
|
23
21
|
|
24
22
|
def generate_docx(filename = nil, template_path = nil)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
begin
|
32
|
-
doc.save docx_path
|
33
|
-
File.read docx_path if filename.nil?
|
34
|
-
ensure
|
35
|
-
temp.close! if temp
|
23
|
+
document = Document.new template_path || @template_path
|
24
|
+
apply_changes document
|
25
|
+
if filename.nil?
|
26
|
+
document.save_to_memory
|
27
|
+
else
|
28
|
+
document.save_to_file filename
|
36
29
|
end
|
37
30
|
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def apply_changes(document)
|
35
|
+
parser = Parser.new document
|
36
|
+
parser.replace_all_fields @fields
|
37
|
+
parser.fill_all_tables @tables
|
38
|
+
end
|
38
39
|
end
|
39
40
|
end
|
data/lib/docx_report/table.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module DocxReport
|
2
2
|
class Table
|
3
|
-
|
3
|
+
attr_reader :name, :has_header, :records
|
4
4
|
|
5
5
|
def initialize(name, has_header = false)
|
6
6
|
@name = name
|
@@ -15,28 +15,21 @@ module DocxReport
|
|
15
15
|
new_record
|
16
16
|
end
|
17
17
|
|
18
|
-
def add_field(name,
|
19
|
-
|
18
|
+
def add_field(name, value = nil, type = :text, &block)
|
19
|
+
field = Field.new(name, value || block, type)
|
20
|
+
raise 'duplicate field name' if @fields.any? do |f|
|
21
|
+
f.name == field.name
|
22
|
+
end
|
23
|
+
@fields << field
|
20
24
|
end
|
21
25
|
|
22
26
|
def load_records(collection)
|
23
27
|
collection.each do |item|
|
24
28
|
record = new_record
|
25
29
|
@fields.each do |field|
|
26
|
-
|
27
|
-
record.add_field field[:name],
|
28
|
-
mapped_value(item, field[:mapped_field])
|
29
|
-
else
|
30
|
-
record.add_field field[:name], field[:block].call(item)
|
31
|
-
end
|
30
|
+
record.fields << field.load_value(item)
|
32
31
|
end
|
33
32
|
end
|
34
33
|
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def mapped_value(item, field_name)
|
39
|
-
item.is_a?(Hash) ? item[field_name] : item.send(field_name)
|
40
|
-
end
|
41
34
|
end
|
42
35
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: docx_report
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ahmed Abudaqqa
|
@@ -40,14 +40,19 @@ dependencies:
|
|
40
40
|
version: '1.2'
|
41
41
|
description: |-
|
42
42
|
docx_report is a light weight gem that generates docx files
|
43
|
-
by replacing strings
|
43
|
+
by replacing strings and inserting images on previously
|
44
|
+
created .docx template file
|
44
45
|
email: ahmed@abudaqqa.com
|
45
46
|
executables: []
|
46
47
|
extensions: []
|
47
48
|
extra_rdoc_files: []
|
48
49
|
files:
|
49
50
|
- lib/docx_report.rb
|
51
|
+
- lib/docx_report/content_file.rb
|
52
|
+
- lib/docx_report/data_item.rb
|
50
53
|
- lib/docx_report/document.rb
|
54
|
+
- lib/docx_report/field.rb
|
55
|
+
- lib/docx_report/hyperlink.rb
|
51
56
|
- lib/docx_report/parser.rb
|
52
57
|
- lib/docx_report/record.rb
|
53
58
|
- lib/docx_report/report.rb
|
@@ -75,5 +80,6 @@ rubyforge_project:
|
|
75
80
|
rubygems_version: 2.4.5.1
|
76
81
|
signing_key:
|
77
82
|
specification_version: 4
|
78
|
-
summary: docx_report generate docx files based on previously created .docx
|
83
|
+
summary: docx_report generate docx files based on previously created .docx template
|
84
|
+
file
|
79
85
|
test_files: []
|