standard-procedure-consolidate 0.3.9 → 0.4.1
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/CHANGELOG.md +8 -0
- data/checksums/standard-procedure-consolidate-0.4.0.gem.sha512 +1 -0
- data/checksums/standard-procedure-consolidate-0.4.1.gem.sha512 +1 -0
- data/lib/consolidate/docx/image.rb +11 -3
- data/lib/consolidate/docx/image_reference_node_builder.rb +64 -62
- data/lib/consolidate/docx/merge.rb +147 -47
- data/lib/consolidate/image.rb +4 -1
- data/lib/consolidate/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ffce925315399bd4432936d929e995114acc2dea081f3835eb437fe2787f612
|
4
|
+
data.tar.gz: b64ae5181ae92d5e66be79e5861787c3355fe08870dbb4ee3ab8355ef4ce149d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e56547c32223cf66f807c804cdfff2897f63887729dcf1ff80d51c35a0615115f47b16347b582d58a6ae128c3ecad91a67ca128e0d7c89b6e55a3e88b2d5a6c9
|
7
|
+
data.tar.gz: 4b351ad3a367dd4382abc18e18fbc978095fec92aa8b258a90feb82f2e0a65ffbcca26a8177383d75fab07d711a170b10b541e48c195c4b8b7db0eea2cfd1b26
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
# [0.4.1] - 2024-12-18
|
2
|
+
|
3
|
+
Replace image merge fields with blanks if the image data is not provided
|
4
|
+
|
5
|
+
# [0.4.0] - 2024-12-16
|
6
|
+
|
7
|
+
Image embedding works
|
8
|
+
|
1
9
|
# [0.3.9] - 2024-12-4
|
2
10
|
|
3
11
|
Image embedding - not fully tested but it seems to work in a few test cases
|
@@ -0,0 +1 @@
|
|
1
|
+
58d1597374e775340c60e3e49b9d65437909b482f7be3040fb9fdca5d8d4b4d3020508a9ee01e3521b2c552e1b853d0475335d1ca1c092b4172c1e9ac86df640
|
@@ -0,0 +1 @@
|
|
1
|
+
dfd2ec36154a632f86dd8f99a2b85c59ed6048dbb6b8100f84645ea1cf49e3e77ad73b3367ef89d46e055c860f1b1e82c5b0d0d37c969a19c2644a3e42ebbe21
|
@@ -13,12 +13,20 @@ module Consolidate
|
|
13
13
|
def storage_path = "word/#{media_path}"
|
14
14
|
|
15
15
|
# Convert width from pixels to EMU
|
16
|
-
def width = super *
|
16
|
+
def width = super * emu_per_width_pixel
|
17
17
|
|
18
18
|
# Convert height from pixels to EMU
|
19
|
-
def height = super *
|
19
|
+
def height = super * emu_per_height_pixel
|
20
20
|
|
21
|
-
|
21
|
+
# Get the width of this image in EMU up to a maximum page width (also in EMU)
|
22
|
+
def clamped_width(maximum = 7_772_400) = [width, maximum].min
|
23
|
+
|
24
|
+
# Get the height of this image in EMU adjusted for a maximum page width (also in EMU)
|
25
|
+
def clamped_height(maximum = 7_772_400) = (height * clamped_width(maximum).to_f / width.to_f).to_i
|
26
|
+
|
27
|
+
def emu_per_width_pixel = 914_400 / dpi[:x]
|
28
|
+
|
29
|
+
def emu_per_height_pixel = 914_400 / dpi[:y]
|
22
30
|
end
|
23
31
|
end
|
24
32
|
end
|
@@ -5,74 +5,67 @@ require "nokogiri"
|
|
5
5
|
|
6
6
|
module Consolidate
|
7
7
|
module Docx
|
8
|
-
class ImageReferenceNodeBuilder < Data.define(:field_name, :image, :node_id, :document)
|
8
|
+
class ImageReferenceNodeBuilder < Data.define(:field_name, :image, :node_id, :image_number, :document)
|
9
9
|
def call
|
10
|
-
Nokogiri::XML::Node.new("w:
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
graphic_frame_locks["noChangeAspect"] = "1"
|
31
|
-
end
|
10
|
+
Nokogiri::XML::Node.new("w:drawing", document).tap do |drawing|
|
11
|
+
drawing["xmlns:a"] = "http://schemas.openxmlformats.org/drawingml/2006/main"
|
12
|
+
drawing << Nokogiri::XML::Node.new("wp:inline", document).tap do |inline|
|
13
|
+
inline["distT"] = "0"
|
14
|
+
inline["distB"] = "0"
|
15
|
+
inline["distL"] = "0"
|
16
|
+
inline["distR"] = "0"
|
17
|
+
inline << Nokogiri::XML::Node.new("wp:extent", document).tap do |extent|
|
18
|
+
extent["cx"] = image.clamped_width(max_width_from(document))
|
19
|
+
extent["cy"] = image.clamped_height(max_width_from(document))
|
20
|
+
end
|
21
|
+
inline << Nokogiri::XML::Node.new("wp:effectExtent", document).tap do |effect_extent|
|
22
|
+
effect_extent["l"] = "0"
|
23
|
+
effect_extent["t"] = "0"
|
24
|
+
effect_extent["r"] = "0"
|
25
|
+
effect_extent["b"] = "0"
|
26
|
+
end
|
27
|
+
inline << Nokogiri::XML::Node.new("wp:cNvGraphicFramePr", document).tap do |c_nv_graphic_frame_pr|
|
28
|
+
c_nv_graphic_frame_pr << Nokogiri::XML::Node.new("a:graphicFrameLocks", document).tap do |graphic_frame_locks|
|
29
|
+
graphic_frame_locks["noChangeAspect"] = true
|
32
30
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
end
|
31
|
+
end
|
32
|
+
inline << Nokogiri::XML::Node.new("a:graphic", document).tap do |graphic|
|
33
|
+
graphic["xmlns:a"] = "http://schemas.openxmlformats.org/drawingml/2006/main"
|
34
|
+
graphic << Nokogiri::XML::Node.new("a:graphicData", document).tap do |graphic_data|
|
35
|
+
graphic_data["uri"] = "http://schemas.openxmlformats.org/drawingml/2006/picture"
|
36
|
+
graphic_data << Nokogiri::XML::Node.new("pic:pic", document).tap do |pic|
|
37
|
+
pic["xmlns:pic"] = "http://schemas.openxmlformats.org/drawingml/2006/picture"
|
38
|
+
pic << Nokogiri::XML::Node.new("pic:nvPicPr", document).tap do |nv_pic_pr|
|
39
|
+
nv_pic_pr << Nokogiri::XML::Node.new("pic:cNvPr", document).tap do |c_nv_pr|
|
40
|
+
c_nv_pr["id"] = image_number
|
41
|
+
c_nv_pr["name"] = image.name
|
42
|
+
c_nv_pr["descr"] = image.name
|
43
|
+
c_nv_pr["hidden"] = false
|
44
|
+
c_nv_pr << Nokogiri::XML::Node.new("pic:cNvPicPr", document)
|
49
45
|
end
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
blip << Nokogiri::XML::Node.new("a:extLst", document)
|
55
|
-
end
|
56
|
-
blip_fill << Nokogiri::XML::Node.new("a:stretch", document).tap do |stretch|
|
57
|
-
stretch << Nokogiri::XML::Node.new("a:fillRect", document)
|
58
|
-
end
|
46
|
+
end
|
47
|
+
pic << Nokogiri::XML::Node.new("pic:blipFill", document).tap do |blip_fill|
|
48
|
+
blip_fill << Nokogiri::XML::Node.new("a:blip", document).tap do |blip|
|
49
|
+
blip["r:embed"] = node_id
|
59
50
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
51
|
+
blip_fill << Nokogiri::XML::Node.new("a:stretch", document).tap do |stretch|
|
52
|
+
stretch << Nokogiri::XML::Node.new("a:fillRect", document)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
pic << Nokogiri::XML::Node.new("pic:spPr", document).tap do |sp_pr|
|
56
|
+
sp_pr << Nokogiri::XML::Node.new("a:xfrm", document).tap do |xfrm|
|
57
|
+
xfrm << Nokogiri::XML::Node.new("a:off", document).tap do |off|
|
58
|
+
off["x"] = "0"
|
59
|
+
off["y"] = "0"
|
70
60
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
61
|
+
xfrm << Nokogiri::XML::Node.new("a:ext", document).tap do |ext|
|
62
|
+
ext["cx"] = image.clamped_width(max_width_from(document))
|
63
|
+
ext["cy"] = image.clamped_height(max_width_from(document))
|
74
64
|
end
|
75
|
-
|
65
|
+
end
|
66
|
+
sp_pr << Nokogiri::XML::Node.new("a:prstGeom", document).tap do |prst_geom|
|
67
|
+
prst_geom["prst"] = "rect"
|
68
|
+
prst_geom << Nokogiri::XML::Node.new("a:avLst", document)
|
76
69
|
end
|
77
70
|
end
|
78
71
|
end
|
@@ -81,6 +74,15 @@ module Consolidate
|
|
81
74
|
end
|
82
75
|
end
|
83
76
|
end
|
77
|
+
|
78
|
+
DEFAULT_PAGE_WIDTH = 12_240
|
79
|
+
TWENTIETHS_OF_A_POINT_TO_EMU = 635
|
80
|
+
DEFAULT_PAGE_WIDTH_IN_EMU = DEFAULT_PAGE_WIDTH * TWENTIETHS_OF_A_POINT_TO_EMU
|
81
|
+
|
82
|
+
private def max_width_from document
|
83
|
+
page_width = (document.at_xpath("//w:sectPr/w:pgSz/@w:w")&.value || DEFAULT_PAGE_WIDTH).to_i
|
84
|
+
page_width * TWENTIETHS_OF_A_POINT_TO_EMU
|
85
|
+
end
|
84
86
|
end
|
85
87
|
end
|
86
88
|
end
|
@@ -20,13 +20,16 @@ module Consolidate
|
|
20
20
|
@zip = Zip::File.open(path)
|
21
21
|
@documents = load_documents
|
22
22
|
@relations = load_relations
|
23
|
+
@contents_xml = load_and_update_contents_xml
|
23
24
|
@output = {}
|
24
25
|
@images = {}
|
26
|
+
@mapping = {}
|
25
27
|
end
|
26
28
|
|
27
29
|
# Helper method to display the contents of the document and the merge fields from the CLI
|
28
30
|
def examine
|
29
31
|
puts "Documents: #{document_names.join(", ")}"
|
32
|
+
puts "Content documents: #{content_document_names.join(", ")}"
|
30
33
|
puts "Merge fields: #{text_field_names.join(", ")}"
|
31
34
|
puts "Image fields: #{image_field_names.join(", ")}"
|
32
35
|
end
|
@@ -38,29 +41,48 @@ module Consolidate
|
|
38
41
|
def image_field_names = @image_field_names ||= tag_nodes.collect { |tag_node| image_field_names_from tag_node }.flatten.compact.uniq
|
39
42
|
|
40
43
|
# List the documents stored within this docx
|
41
|
-
def document_names = @zip.entries.
|
44
|
+
def document_names = @zip.entries.map(&:name)
|
45
|
+
|
46
|
+
# List the content within this docx
|
47
|
+
def content_document_names = @documents.keys
|
48
|
+
|
49
|
+
# List the field names that are present in the merge data
|
50
|
+
def merge_field_names = @mapping.keys
|
42
51
|
|
43
52
|
# Set the merge data and erform the substitution - creating copies of any documents that contain merge tags and replacing the tags with the supplied data
|
44
53
|
def data mapping = {}
|
45
|
-
mapping = mapping.transform_keys(&:to_s)
|
46
|
-
|
54
|
+
@mapping = mapping.transform_keys(&:to_s)
|
55
|
+
if verbose
|
56
|
+
puts "...mapping data"
|
57
|
+
puts @mapping.keys.select { |field_name| text_field_names.include?(field_name) }.map { |field_name| "... #{field_name} => #{@mapping[field_name]}" }.join("\n")
|
58
|
+
end
|
47
59
|
|
48
|
-
@images =
|
60
|
+
@images = load_images_and_link_relations
|
49
61
|
|
50
62
|
@documents.each do |name, document|
|
51
|
-
@output[name] = substitute(document.dup,
|
63
|
+
@output[name] = substitute(document.dup, document_name: name).serialize save_with: 0
|
52
64
|
end
|
53
65
|
end
|
54
66
|
|
55
67
|
def write_to path
|
56
68
|
puts "...writing to #{path}" if verbose
|
57
69
|
Zip::File.open(path, Zip::File::CREATE) do |out|
|
70
|
+
@output[contents_xml] = @contents_xml.serialize save_with: 0
|
71
|
+
|
58
72
|
@images.each do |field_name, image|
|
59
|
-
|
73
|
+
next if image.nil?
|
74
|
+
puts "... writing image #{field_name} to #{image.storage_path}" if verbose
|
60
75
|
out.get_output_stream(image.storage_path) { |o| o.write image.contents }
|
61
76
|
end
|
62
77
|
|
63
|
-
@
|
78
|
+
@relations.each do |relation_name, relations|
|
79
|
+
puts "... writing relations #{relation_name}" if verbose
|
80
|
+
out.get_output_stream(relation_name) { |o| o.write relations }
|
81
|
+
end
|
82
|
+
|
83
|
+
@zip.reject do |entry|
|
84
|
+
@relations.key? entry.name
|
85
|
+
end.each do |entry|
|
64
86
|
puts "... writing updated document to #{entry.name}" if verbose
|
65
87
|
out.get_output_stream(entry.name) { |o| o.write(@output[entry.name] || @relations[entry.name] || @zip.read(entry.name)) }
|
66
88
|
end
|
@@ -71,25 +93,7 @@ module Consolidate
|
|
71
93
|
|
72
94
|
attr_reader :verbose
|
73
95
|
|
74
|
-
def
|
75
|
-
@zip.entries.each_with_object({}) do |entry, results|
|
76
|
-
next unless entry.name.match?(/word\/(document|header|footer|footnotes|endnotes).?\.xml/)
|
77
|
-
puts "...reading document #{entry.name}" if verbose
|
78
|
-
contents = @zip.get_input_stream entry
|
79
|
-
results[entry.name] = Nokogiri::XML(contents) { |x| x.noent }
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
def load_relations
|
84
|
-
@zip.entries.each_with_object({}) do |entry, results|
|
85
|
-
next unless entry.name.match?(/word\/_rels\/.*.rels/)
|
86
|
-
puts "...reading relation #{entry.name}" if verbose
|
87
|
-
contents = @zip.get_input_stream entry
|
88
|
-
results[entry.name] = Nokogiri::XML(contents) { |x| x.noent }
|
89
|
-
end
|
90
|
-
ensure
|
91
|
-
@zip.close
|
92
|
-
end
|
96
|
+
def contents_xml = "[Content_Types].xml"
|
93
97
|
|
94
98
|
# Regex to find merge fields that contain text
|
95
99
|
def text_tag = /\{\{\s*(?!.*_image\b)(\S+)\s*\}\}/i
|
@@ -113,30 +117,61 @@ module Consolidate
|
|
113
117
|
# Extract the image field name(s) from the paragraph
|
114
118
|
def image_field_names_from(tag_node) = (matches = tag_node.content.scan(image_tag)).empty? ? nil : matches.flatten.map(&:strip)
|
115
119
|
|
120
|
+
# Unique number for each image field
|
121
|
+
def relation_number_for(field_name) = @mapping.keys.index(field_name) + 1000
|
122
|
+
|
116
123
|
# Identifier to use when linking a merge field to the actual image file contents
|
117
124
|
def relation_id_for(field_name) = "rId#{field_name}"
|
118
125
|
|
126
|
+
# Empty elations document for documents that do not already have one
|
127
|
+
def default_relations_document = %(<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>)
|
128
|
+
|
129
|
+
def load_documents
|
130
|
+
@zip.entries.each_with_object({}) do |entry, results|
|
131
|
+
next unless entry.name.match?(/word\/(document|header|footer|footnotes|endnotes).?\.xml/)
|
132
|
+
puts "...reading document #{entry.name}" if verbose
|
133
|
+
contents = @zip.get_input_stream entry
|
134
|
+
results[entry.name] = Nokogiri::XML(contents) { |x| x.noent }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def load_relations
|
139
|
+
@zip.entries.each_with_object({}) do |entry, results|
|
140
|
+
next unless entry.name.match?(/word\/(document|header|footer|footnotes|endnotes).?\.xml/)
|
141
|
+
relation_document = entry.name.gsub("word/", "word/_rels/").gsub(".xml", ".xml.rels")
|
142
|
+
puts "...reading or building relations for #{relation_document}" if verbose
|
143
|
+
contents = @zip.find_entry(relation_document) ? @zip.get_input_stream(relation_document) : default_relations_document
|
144
|
+
results[relation_document] = Nokogiri::XML(contents) { |x| x.noent }
|
145
|
+
end
|
146
|
+
ensure
|
147
|
+
@zip.close
|
148
|
+
end
|
149
|
+
|
119
150
|
# Create relation links for each image field and store the image data
|
120
|
-
def
|
121
|
-
|
151
|
+
def load_images_and_link_relations
|
152
|
+
load_images.tap do |images|
|
122
153
|
link_relations_to images
|
123
154
|
end
|
124
155
|
end
|
125
156
|
|
126
157
|
# Build a mapping of image paths to the image data so that the image data can be stored in the output docx
|
127
|
-
def
|
158
|
+
def load_images
|
128
159
|
image_field_names.each_with_object({}) do |field_name, result|
|
129
|
-
result[field_name] = Consolidate::Docx::Image.new(mapping[field_name])
|
160
|
+
result[field_name] = @mapping[field_name].nil? ? nil : Consolidate::Docx::Image.new(@mapping[field_name])
|
161
|
+
puts "... #{field_name} => #{result[field_name]&.media_path}" if verbose
|
130
162
|
end
|
131
163
|
end
|
132
164
|
|
133
165
|
# Update all relation documents to include a relationship for each image field and its stored image path
|
134
166
|
def link_relations_to images
|
135
167
|
@relations.each do |name, xml|
|
168
|
+
puts "... linking images in #{name}" if verbose
|
136
169
|
images.each do |field_name, image|
|
170
|
+
# Has an actual image file been supplied?
|
171
|
+
next if image.nil?
|
137
172
|
# Is this image already referenced in this relationship document?
|
138
|
-
next unless xml.at_xpath("//Relationship[@Target
|
139
|
-
puts "...
|
173
|
+
next unless xml.at_xpath("//Relationship[@Target=\"#{image.media_path}\"]").nil?
|
174
|
+
puts "... #{relation_id_for(field_name)} => #{image.media_path}" if verbose
|
140
175
|
xml.root << Nokogiri::XML::Node.new("Relationship", xml).tap do |relation|
|
141
176
|
relation["Id"] = relation_id_for(field_name)
|
142
177
|
relation["Type"] = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
|
@@ -146,32 +181,38 @@ module Consolidate
|
|
146
181
|
end
|
147
182
|
end
|
148
183
|
|
184
|
+
def load_and_update_contents_xml
|
185
|
+
puts "...reading and updating #{contents_xml}" if verbose
|
186
|
+
content = @zip.get_input_stream(contents_xml)
|
187
|
+
Nokogiri::XML(content) { |x| x.noent }.tap do |document|
|
188
|
+
add_content_relations_to document
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
149
192
|
# Go through the given document, replacing any merge fields with the values provided
|
150
193
|
# and storing the results in a new document
|
151
|
-
def substitute document, document_name
|
194
|
+
def substitute document, document_name:
|
195
|
+
puts "...substituting fields in #{document_name}" if verbose && tag_nodes_for(document).any?
|
196
|
+
substitute_text document, document_name: document_name
|
197
|
+
substitute_images document, document_name: document_name
|
198
|
+
end
|
199
|
+
|
200
|
+
def substitute_text document, document_name:
|
152
201
|
tag_nodes_for(document).each do |tag_node|
|
153
|
-
|
154
|
-
image_field_names = image_field_names_from(tag_node) || []
|
202
|
+
field_names = text_field_names_from(tag_node) || []
|
155
203
|
|
156
204
|
# Extract the properties (formatting) nodes if they exist
|
157
205
|
paragraph_properties = tag_node.search ".//w:pPr"
|
158
206
|
run_properties = tag_node.at_xpath ".//w:rPr"
|
159
207
|
|
160
|
-
# Get the current contents, then substitute any text fields
|
208
|
+
# Get the current contents, then substitute any text fields
|
161
209
|
text = tag_node.content
|
162
210
|
|
163
|
-
|
164
|
-
field_value = mapping[field_name].to_s
|
165
|
-
puts "...substituting #{field_name} with #{field_value}
|
211
|
+
field_names.each do |field_name|
|
212
|
+
field_value = @mapping[field_name].to_s
|
213
|
+
puts "... substituting '#{field_name}' with '#{field_value}'" if verbose
|
166
214
|
text = text.gsub(tag_for(field_name), field_value)
|
167
215
|
end
|
168
|
-
image_nodes = image_field_names.collect do |field_name|
|
169
|
-
image = @images[field_name]
|
170
|
-
puts "...substituting #{field_name} in #{document_name}" if verbose
|
171
|
-
# Remove the merge tag and create an image reference node to be added to this node
|
172
|
-
text = text.gsub(tag_for(field_name), "")
|
173
|
-
ImageReferenceNodeBuilder.new(field_name: field_name, image: image, node_id: relation_id_for(field_name), document: document).call
|
174
|
-
end
|
175
216
|
|
176
217
|
# Create a new text node with the substituted text
|
177
218
|
text_node = Nokogiri::XML::Node.new("w:t", tag_node.document)
|
@@ -182,7 +223,46 @@ module Consolidate
|
|
182
223
|
run_node << run_properties unless run_properties.nil?
|
183
224
|
run_node << text_node
|
184
225
|
# Add the paragraph properties and the run node to the tag node
|
185
|
-
tag_node.children = Nokogiri::XML::NodeSet.new(document, paragraph_properties.to_a + [run_node]
|
226
|
+
tag_node.children = Nokogiri::XML::NodeSet.new(document, paragraph_properties.to_a + [run_node])
|
227
|
+
rescue => ex
|
228
|
+
# Have to mangle the exception message otherwise it outputs the entire document
|
229
|
+
puts ex.message.to_s[0..255]
|
230
|
+
puts ex.backtrace.first
|
231
|
+
end
|
232
|
+
document
|
233
|
+
end
|
234
|
+
|
235
|
+
# Go through the given document, replacing any merge fields with the values provided
|
236
|
+
# and storing the results in a new document
|
237
|
+
def substitute_images document, document_name:
|
238
|
+
tag_nodes_for(document).each do |tag_node|
|
239
|
+
field_names = image_field_names_from(tag_node) || []
|
240
|
+
# Extract the properties (formatting) nodes if they exist
|
241
|
+
paragraph_properties = tag_node.search ".//w:pPr"
|
242
|
+
run_properties = tag_node.at_xpath ".//w:rPr"
|
243
|
+
|
244
|
+
pieces = tag_node.content.split(image_tag)
|
245
|
+
# Split the content into pieces - either text or an image merge field
|
246
|
+
# Then replace the text with text nodes or the image merge fields with drawing nodes
|
247
|
+
replacement_nodes = pieces.collect do |piece|
|
248
|
+
field_name = piece.strip
|
249
|
+
if field_names.include? field_name
|
250
|
+
image = @images[field_name]
|
251
|
+
# if no image was provided then insert blank text
|
252
|
+
# otherwise insert a w:drawing node that references the image contents
|
253
|
+
if image.nil?
|
254
|
+
puts "... substituting '#{field_name}' with blank as no image was provided" if verbose
|
255
|
+
Nokogiri::XML::Node.new("w:t", document) { |t| t.content = "" }
|
256
|
+
else
|
257
|
+
puts "... substituting '#{field_name}' with '<#{relation_id_for(field_name)}/>'" if verbose
|
258
|
+
ImageReferenceNodeBuilder.new(field_name: field_name, image: image, node_id: relation_id_for(field_name), image_number: relation_number_for(field_name), document: document).call
|
259
|
+
end
|
260
|
+
else
|
261
|
+
Nokogiri::XML::Node.new("w:t", document) { |t| t.content = piece }
|
262
|
+
end
|
263
|
+
end
|
264
|
+
run_nodes = (replacement_nodes.map { |node| Nokogiri::XML::Node.new("w:r", document) { |run_node| run_node.children = node } } + [run_properties]).compact
|
265
|
+
tag_node.children = Nokogiri::XML::NodeSet.new(document, paragraph_properties.to_a + run_nodes)
|
186
266
|
rescue => ex
|
187
267
|
# Have to mangle the exception message otherwise it outputs the entire document
|
188
268
|
puts ex.message.to_s[0..255]
|
@@ -190,6 +270,26 @@ module Consolidate
|
|
190
270
|
end
|
191
271
|
document
|
192
272
|
end
|
273
|
+
|
274
|
+
CONTENT_RELATIONS = {
|
275
|
+
jpeg: "image/jpg",
|
276
|
+
png: "image/png",
|
277
|
+
bmp: "image/bmp",
|
278
|
+
gif: "image/gif",
|
279
|
+
tif: "image/tif",
|
280
|
+
pdf: "application/pdf",
|
281
|
+
mov: "application/movie"
|
282
|
+
}.freeze
|
283
|
+
|
284
|
+
def add_content_relations_to document
|
285
|
+
CONTENT_RELATIONS.each do |file_type, content_type|
|
286
|
+
next unless document.at_xpath("//Default[@Extension=\"#{file_type}\"]").nil?
|
287
|
+
document.root << Nokogiri::XML::Node.new("Default", document).tap do |relation|
|
288
|
+
relation["Extension"] = file_type
|
289
|
+
relation["ContentType"] = content_type
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
193
293
|
end
|
194
294
|
end
|
195
295
|
end
|
data/lib/consolidate/image.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Consolidate
|
4
4
|
class Image
|
5
|
-
attr_reader :name, :width, :height
|
5
|
+
attr_reader :name, :width, :height, :aspect_ratio, :dpi
|
6
6
|
|
7
7
|
def initialize name:, width:, height:, path: nil, url: nil, contents: nil
|
8
8
|
@name = name
|
@@ -11,6 +11,9 @@ module Consolidate
|
|
11
11
|
@path = path
|
12
12
|
@url = url
|
13
13
|
@contents = contents
|
14
|
+
@aspect_ratio = width.to_f / height.to_f
|
15
|
+
# TODO: Read this from the contents
|
16
|
+
@dpi = {x: 72, y: 72}
|
14
17
|
end
|
15
18
|
|
16
19
|
def to_s = name
|
data/lib/consolidate/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: standard-procedure-consolidate
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rahoul Baruah
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rubyzip
|
@@ -64,6 +64,8 @@ files:
|
|
64
64
|
- checksums/standard-procedure-consolidate-0.3.0.gem.sha512
|
65
65
|
- checksums/standard-procedure-consolidate-0.3.1.gem.sha512
|
66
66
|
- checksums/standard-procedure-consolidate-0.3.9.gem.sha512
|
67
|
+
- checksums/standard-procedure-consolidate-0.4.0.gem.sha512
|
68
|
+
- checksums/standard-procedure-consolidate-0.4.1.gem.sha512
|
67
69
|
- exe/consolidate
|
68
70
|
- exe/examine
|
69
71
|
- lib/consolidate.rb
|