standard-procedure-consolidate 0.3.9 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb70b369911ba5aa2dc64c21a460b690b722f947e457f3d3efcacf365577046a
4
- data.tar.gz: 010f296150c641bc92be1b7255ae424f43393205baa11384013003fb55f056ba
3
+ metadata.gz: 39e8b33a8f43ddb4a3bff6ccfb91ee9608d741dc9dff3759ea7c45b1c3cf3d3c
4
+ data.tar.gz: 71d357d9e60a98f199c7d67ab3f6a0b5ee8999218162fabf43a4fc4283f04a85
5
5
  SHA512:
6
- metadata.gz: 824f18d218c9489467cfb62ec7567b5400b7f5e08f477864ed1553dbe0a4a278747002f02ee420c1877c2cb659c5b5f456d1716d4a9ffe4b7a8e825641d9bd26
7
- data.tar.gz: f6dabb52a96bea36e61b90fcac99d6aedd2a58090d3ddfe3a84d76b19d1672e969cd6728221b51a9b12e83cf83801481626405ebd89c04632de13dba5c87889f
6
+ metadata.gz: e68a557b00ecb43feb93cdc9a5c9a4c09edd93f8421206be3372e617809bc9ce01cb9ad4928e6f97f49b6db64fd9a329dd2ff7360d5d7b618ce455d0f080c6b1
7
+ data.tar.gz: 94417403770d855c0df0d1927b3de00235a668503790e06388de83cadb3b3d2b533f394fab3ddcda5ace164bbe7394810c8ad6b00092eb553552f74fabaa2e5e
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # [0.4.0] - 2024-12-16
2
+
3
+ Image embedding works
4
+
1
5
  # [0.3.9] - 2024-12-4
2
6
 
3
7
  Image embedding - not fully tested but it seems to work in a few test cases
@@ -0,0 +1 @@
1
+ 58d1597374e775340c60e3e49b9d65437909b482f7be3040fb9fdca5d8d4b4d3020508a9ee01e3521b2c552e1b853d0475335d1ca1c092b4172c1e9ac86df640
@@ -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,47 @@ 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
+ puts "... writing image #{field_name} to #{image.storage_path}" if verbose
60
74
  out.get_output_stream(image.storage_path) { |o| o.write image.contents }
61
75
  end
62
76
 
63
- @zip.each do |entry|
77
+ @relations.each do |relation_name, relations|
78
+ puts "... writing relations #{relation_name}" if verbose
79
+ out.get_output_stream(relation_name) { |o| o.write relations }
80
+ end
81
+
82
+ @zip.reject do |entry|
83
+ @relations.key? entry.name
84
+ end.each do |entry|
64
85
  puts "... writing updated document to #{entry.name}" if verbose
65
86
  out.get_output_stream(entry.name) { |o| o.write(@output[entry.name] || @relations[entry.name] || @zip.read(entry.name)) }
66
87
  end
@@ -71,25 +92,7 @@ module Consolidate
71
92
 
72
93
  attr_reader :verbose
73
94
 
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
95
+ def contents_xml = "[Content_Types].xml"
93
96
 
94
97
  # Regex to find merge fields that contain text
95
98
  def text_tag = /\{\{\s*(?!.*_image\b)(\S+)\s*\}\}/i
@@ -113,30 +116,59 @@ module Consolidate
113
116
  # Extract the image field name(s) from the paragraph
114
117
  def image_field_names_from(tag_node) = (matches = tag_node.content.scan(image_tag)).empty? ? nil : matches.flatten.map(&:strip)
115
118
 
119
+ # Unique number for each image field
120
+ def relation_number_for(field_name) = @mapping.keys.index(field_name) + 1000
121
+
116
122
  # Identifier to use when linking a merge field to the actual image file contents
117
- def relation_id_for(field_name) = "rId#{field_name}"
123
+ def relation_id_for(field_name) = "rId#{relation_number_for(field_name)}"
124
+
125
+ # Empty elations document for documents that do not already have one
126
+ def default_relations_document = %(<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"></Relationships>)
127
+
128
+ def load_documents
129
+ @zip.entries.each_with_object({}) do |entry, results|
130
+ next unless entry.name.match?(/word\/(document|header|footer|footnotes|endnotes).?\.xml/)
131
+ puts "...reading document #{entry.name}" if verbose
132
+ contents = @zip.get_input_stream entry
133
+ results[entry.name] = Nokogiri::XML(contents) { |x| x.noent }
134
+ end
135
+ end
136
+
137
+ def load_relations
138
+ @zip.entries.each_with_object({}) do |entry, results|
139
+ next unless entry.name.match?(/word\/(document|header|footer|footnotes|endnotes).?\.xml/)
140
+ relation_document = entry.name.gsub("word/", "word/_rels/").gsub(".xml", ".xml.rels")
141
+ puts "...reading or building relations for #{relation_document}" if verbose
142
+ contents = @zip.find_entry(relation_document) ? @zip.get_input_stream(relation_document) : default_relations_document
143
+ results[relation_document] = Nokogiri::XML(contents) { |x| x.noent }
144
+ end
145
+ ensure
146
+ @zip.close
147
+ end
118
148
 
119
149
  # 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|
150
+ def load_images_and_link_relations
151
+ load_images.tap do |images|
122
152
  link_relations_to images
123
153
  end
124
154
  end
125
155
 
126
156
  # 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 = {}
157
+ def load_images
128
158
  image_field_names.each_with_object({}) do |field_name, result|
129
- result[field_name] = Consolidate::Docx::Image.new(mapping[field_name])
159
+ result[field_name] = Consolidate::Docx::Image.new(@mapping[field_name])
160
+ puts "... #{field_name} => #{result[field_name].media_path}" if verbose
130
161
  end
131
162
  end
132
163
 
133
164
  # Update all relation documents to include a relationship for each image field and its stored image path
134
165
  def link_relations_to images
135
166
  @relations.each do |name, xml|
167
+ puts "... linking images in #{name}" if verbose
136
168
  images.each do |field_name, image|
137
169
  # 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
170
+ next unless xml.at_xpath("//Relationship[@Target=\"#{image.media_path}\"]").nil?
171
+ puts "... #{relation_id_for(field_name)} => #{image.media_path}" if verbose
140
172
  xml.root << Nokogiri::XML::Node.new("Relationship", xml).tap do |relation|
141
173
  relation["Id"] = relation_id_for(field_name)
142
174
  relation["Type"] = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image"
@@ -146,32 +178,38 @@ module Consolidate
146
178
  end
147
179
  end
148
180
 
181
+ def load_and_update_contents_xml
182
+ puts "...reading and updating #{contents_xml}" if verbose
183
+ content = @zip.get_input_stream(contents_xml)
184
+ Nokogiri::XML(content) { |x| x.noent }.tap do |document|
185
+ add_content_relations_to document
186
+ end
187
+ end
188
+
149
189
  # Go through the given document, replacing any merge fields with the values provided
150
190
  # and storing the results in a new document
151
- def substitute document, document_name:, mapping: {}
191
+ def substitute document, document_name:
192
+ puts "...substituting fields in #{document_name}" if verbose && tag_nodes_for(document).any?
193
+ substitute_text document, document_name: document_name
194
+ substitute_images document, document_name: document_name
195
+ end
196
+
197
+ def substitute_text document, document_name:
152
198
  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) || []
199
+ field_names = text_field_names_from(tag_node) || []
155
200
 
156
201
  # Extract the properties (formatting) nodes if they exist
157
202
  paragraph_properties = tag_node.search ".//w:pPr"
158
203
  run_properties = tag_node.at_xpath ".//w:rPr"
159
204
 
160
- # Get the current contents, then substitute any text fields, followed by any image fields
205
+ # Get the current contents, then substitute any text fields
161
206
  text = tag_node.content
162
207
 
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
208
+ field_names.each do |field_name|
209
+ field_value = @mapping[field_name].to_s
210
+ puts "... substituting '#{field_name}' with '#{field_value}'" if verbose
166
211
  text = text.gsub(tag_for(field_name), field_value)
167
212
  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
213
 
176
214
  # Create a new text node with the substituted text
177
215
  text_node = Nokogiri::XML::Node.new("w:t", tag_node.document)
@@ -182,7 +220,39 @@ module Consolidate
182
220
  run_node << run_properties unless run_properties.nil?
183
221
  run_node << text_node
184
222
  # 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)
223
+ tag_node.children = Nokogiri::XML::NodeSet.new(document, paragraph_properties.to_a + [run_node])
224
+ rescue => ex
225
+ # Have to mangle the exception message otherwise it outputs the entire document
226
+ puts ex.message.to_s[0..255]
227
+ puts ex.backtrace.first
228
+ end
229
+ document
230
+ end
231
+
232
+ # Go through the given document, replacing any merge fields with the values provided
233
+ # and storing the results in a new document
234
+ def substitute_images document, document_name:
235
+ tag_nodes_for(document).each do |tag_node|
236
+ field_names = image_field_names_from(tag_node) || []
237
+ # Extract the properties (formatting) nodes if they exist
238
+ paragraph_properties = tag_node.search ".//w:pPr"
239
+ run_properties = tag_node.at_xpath ".//w:rPr"
240
+
241
+ pieces = tag_node.content.split(image_tag)
242
+ # Split the content into pieces - either text or an image merge field
243
+ # Then replace the text with text nodes or the image merge fields with drawing nodes
244
+ replacement_nodes = pieces.collect do |piece|
245
+ field_name = piece.strip
246
+ if field_names.include? field_name
247
+ image = @images[field_name]
248
+ puts "... substituting '#{field_name}' with '<#{relation_id_for(field_name)}/>'" if verbose
249
+ 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
250
+ else
251
+ Nokogiri::XML::Node.new("w:t", document) { |t| t.content = piece }
252
+ end
253
+ end
254
+ run_nodes = (replacement_nodes.map { |node| Nokogiri::XML::Node.new("w:r", document) { |run_node| run_node.children = node } } + [run_properties]).compact
255
+ tag_node.children = Nokogiri::XML::NodeSet.new(document, paragraph_properties.to_a + run_nodes)
186
256
  rescue => ex
187
257
  # Have to mangle the exception message otherwise it outputs the entire document
188
258
  puts ex.message.to_s[0..255]
@@ -190,6 +260,26 @@ module Consolidate
190
260
  end
191
261
  document
192
262
  end
263
+
264
+ CONTENT_RELATIONS = {
265
+ jpeg: "image/jpg",
266
+ png: "image/png",
267
+ bmp: "image/bmp",
268
+ gif: "image/gif",
269
+ tif: "image/tif",
270
+ pdf: "application/pdf",
271
+ mov: "application/movie"
272
+ }.freeze
273
+
274
+ def add_content_relations_to document
275
+ CONTENT_RELATIONS.each do |file_type, content_type|
276
+ next unless document.at_xpath("//Default[@Extension=\"#{file_type}\"]").nil?
277
+ document.root << Nokogiri::XML::Node.new("Default", document).tap do |relation|
278
+ relation["Extension"] = file_type
279
+ relation["ContentType"] = content_type
280
+ end
281
+ end
282
+ end
193
283
  end
194
284
  end
195
285
  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.0"
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.0
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-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -64,6 +64,7 @@ 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
67
68
  - exe/consolidate
68
69
  - exe/examine
69
70
  - lib/consolidate.rb