standard-procedure-consolidate 0.3.9 → 0.4.0

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