standard-procedure-consolidate 0.3.9 → 0.4.1

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
  SHA256:
3
- metadata.gz: cb70b369911ba5aa2dc64c21a460b690b722f947e457f3d3efcacf365577046a
4
- data.tar.gz: 010f296150c641bc92be1b7255ae424f43393205baa11384013003fb55f056ba
3
+ metadata.gz: 0ffce925315399bd4432936d929e995114acc2dea081f3835eb437fe2787f612
4
+ data.tar.gz: b64ae5181ae92d5e66be79e5861787c3355fe08870dbb4ee3ab8355ef4ce149d
5
5
  SHA512:
6
- metadata.gz: 824f18d218c9489467cfb62ec7567b5400b7f5e08f477864ed1553dbe0a4a278747002f02ee420c1877c2cb659c5b5f456d1716d4a9ffe4b7a8e825641d9bd26
7
- data.tar.gz: f6dabb52a96bea36e61b90fcac99d6aedd2a58090d3ddfe3a84d76b19d1672e969cd6728221b51a9b12e83cf83801481626405ebd89c04632de13dba5c87889f
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 * EMU_PER_PIXEL
16
+ def width = super * emu_per_width_pixel
17
17
 
18
18
  # Convert height from pixels to EMU
19
- def height = super * EMU_PER_PIXEL
19
+ def height = super * emu_per_height_pixel
20
20
 
21
- EMU_PER_PIXEL = (914400 / 72)
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:r", document).tap do |run_node|
11
- run_node << Nokogiri::XML::Node.new("w:drawing", document).tap do |drawing|
12
- drawing << Nokogiri::XML::Node.new("wp:inline", document).tap do |inline|
13
- inline << Nokogiri::XML::Node.new("wp:extend", document).tap do |extent|
14
- extent["cx"] = image.width.to_s
15
- extent["cy"] = image.height.to_s
16
- end
17
- inline << Nokogiri::XML::Node.new("wp:effectExtent", document).tap do |effect_extent|
18
- effect_extent["l"] = "0"
19
- effect_extent["t"] = "0"
20
- effect_extent["r"] = "0"
21
- effect_extent["b"] = "0"
22
- end
23
- inline << Nokogiri::XML::Node.new("wp:docPr", document).tap do |doc_pr|
24
- doc_pr["id"] = node_id
25
- doc_pr["name"] = field_name
26
- end
27
- inline << Nokogiri::XML::Node.new("wp:cNvGraphicFramePr", document) 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["xmlns:a"] = "http://schemas.openxmlformats.org/drawingml/2006/main"
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
- inline << Nokogiri::XML::Node.new("a:graphic", document).tap do |graphic|
34
- graphic["xmlns:a"] = "http://schemas.openxmlformats.org/drawingml/2006/main"
35
- graphic << Nokogiri::XML::Node.new("a:graphicData", document).tap do |graphic_data|
36
- graphic_data["uri"] = "http://schemas.openxmlformats.org/drawingml/2006/picture"
37
- graphic_data << Nokogiri::XML::Node.new("pic:pic", document).tap do |pic|
38
- pic["xmlns:pic"] = "http://schemas.openxmlformats.org/drawingml/2006/picture"
39
- pic << Nokogiri::XML::Node.new("pic:nvPicPr", document).tap do |nv_pic_pr|
40
- nv_pic_pr << Nokogiri::XML::Node.new("pic:cNvPr", document).tap do |c_nv_pr|
41
- c_nv_pr["id"] = node_id
42
- c_nv_pr["name"] = field_name
43
- end
44
- nv_pic_pr << Nokogiri::XML::Node.new("pic:cNvPicPr", document).tap do |c_nv_pic_pr|
45
- c_nv_pic_pr << Nokogiri::XML::Node.new("a:picLocks", document).tap do |pic_locks|
46
- pic_locks["noChangeAspect"] = "1"
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
- pic << Nokogiri::XML::Node.new("pic:blipFill", document).tap do |blip_fill|
51
- blip_fill << Nokogiri::XML::Node.new("a:blip", document).tap do |blip|
52
- blip["r:embed"] = node_id
53
- blip["xmlns:r"] = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
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
- pic << Nokogiri::XML::Node.new("pic:spPr", document).tap do |sp_pr|
61
- sp_pr << Nokogiri::XML::Node.new("a:xfrm", document).tap do |xfrm|
62
- xfrm << Nokogiri::XML::Node.new("a:off", document).tap do |off|
63
- off["x"] = "0"
64
- off["y"] = "0"
65
- end
66
- xfrm << Nokogiri::XML::Node.new("a:ext", document).tap do |ext|
67
- ext["cx"] = image.width.to_s
68
- ext["cy"] = image.height.to_s
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
- sp_pr << Nokogiri::XML::Node.new("a:prstGeom", document).tap do |prst_geom|
72
- prst_geom["prst"] = "rect"
73
- prst_geom << Nokogiri::XML::Node.new("a:avLst", document)
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
- sp_pr << Nokogiri::XML::Node.new("a:effectLst", document)
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.collect { |entry| entry.name }
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
- puts mapping.keys.select { |field_name| text_field_names.include?(field_name) }.map { |field_name| "#{field_name} => #{mapping[field_name]}" }.join("\n") if verbose
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 = load_images_and_link_relations_from mapping
60
+ @images = load_images_and_link_relations
49
61
 
50
62
  @documents.each do |name, document|
51
- @output[name] = substitute(document.dup, mapping: mapping, document_name: name).serialize save_with: 0
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
- puts "... writing #{field_name} to #{image.storage_path}" if verbose
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
- @zip.each do |entry|
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 load_documents
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 load_images_and_link_relations_from mapping
121
- load_images_from(mapping).tap do |images|
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 load_images_from mapping = {}
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='#{image.media_path}']").nil?
139
- puts "...linking #{field_name} to #{image.media_path}" if verbose
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:, mapping: {}
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
- text_field_names = text_field_names_from(tag_node) || []
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, followed by any image fields
208
+ # Get the current contents, then substitute any text fields
161
209
  text = tag_node.content
162
210
 
163
- text_field_names.each do |field_name|
164
- field_value = mapping[field_name].to_s
165
- puts "...substituting #{field_name} with #{field_value} in #{document_name}" if verbose
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] + image_nodes)
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consolidate
4
- VERSION = "0.3.9"
4
+ VERSION = "0.4.1"
5
5
  end
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.3.9
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-04 00:00:00.000000000 Z
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