standard-procedure-consolidate 0.2.0 → 0.3.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: f61fdd9cdf73d8609574952bca3a57839983f74f100aea6f8b54a4248b526efa
4
- data.tar.gz: 0a64110c54d435f10c1465b96dd61e150c2ff56ae887788b5880d7bf489e93d5
3
+ metadata.gz: e7a086625fa1f07169c2f9eba544538df266fbd83702efeedd13998d1a9e83b3
4
+ data.tar.gz: f80f0c11aa839c9dba9c4dc41758471ccfc31d7d38159e902e11d48cc8016c82
5
5
  SHA512:
6
- metadata.gz: ac0a18d02265eb79b2c9ffda09b2a13825e520a08faef45c1a0f3fd29877b11abf46776554a7c7e2e4157e560f0269889a95f3a9dc3dc4f49b66bda51848e37c
7
- data.tar.gz: 1fce15f81e519792c058dc731cd298db46dc4163d7c0c6cd753c87c2ec7a672aa4b9e350d526d8a641ebdb13bd3dd98b1a9ebd8bb854cd6d0e2342899103b883
6
+ metadata.gz: 72b26cbc5f54cc481ad49cbb604338591e27f1a503330372ab41920a05f3cbde4b1d6b6aa4b607b1189fbd8fd5ed30102d9741b8b6cf762c0cb39c900ac19327
7
+ data.tar.gz: 33ab5fbe36794cf24ca73ce70ec6da8f91cfe5430a438d5794da54bd668e72dd956331401ac983f5d15b7f77d60ef8ac6a3d438e4a0fb6279736c6dd79342931
@@ -1,7 +1,23 @@
1
1
  // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
2
  // README at: https://github.com/devcontainers/templates/tree/main/src/ruby
3
3
  {
4
- "name": "Ruby",
5
- "image": "mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye",
6
- "postCreateCommand": "bundle install"
7
- }
4
+ "name": "Ruby",
5
+ "image": "mcr.microsoft.com/devcontainers/ruby:1-3.2-bullseye",
6
+ "postCreateCommand": "bundle install",
7
+ "customizations": {
8
+ "vscode": {
9
+ "extensions": [
10
+ "Shopify.ruby-extensions-pack",
11
+ "testdouble.vscode-standard-ruby",
12
+ "manuelpuyol.erb-linter",
13
+ "Shopify.ruby-lsp",
14
+ "aki77.rails-db-schema",
15
+ "miguel-savignano.ruby-symbols",
16
+ "sibiraj-s.vscode-scss-formatter",
17
+ "Thadeu.vscode-run-rspec-file",
18
+ "Cronos87.yaml-symbols",
19
+ "aliariff.vscode-erb-beautify"
20
+ ]
21
+ }
22
+ }
23
+ }
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.2.2
1
+ 3.2.5
data/.standard.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  # For available configuration options, see:
2
2
  # https://github.com/testdouble/standard
3
- ruby_version: 2.6
3
+ ruby_version: 3.2.5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## [0.3.1] - 2024-11-22
2
+
3
+ Ensure that the substituted nodes are reinserted correctly into the output document, attempting to restore formatting at the paragraph level (although it does lose formatting at lower levels than this - so-called "run" nodes which represent arbitrary spans of characters within the paragraph).
4
+
5
+
6
+ ## [0.3.0] - 2024-11-21
7
+
8
+ Updated the code that examines the docx file for merge fields to deal with Word formatting tags being inserted in the middle of the merge fields.
9
+
1
10
  ## [0.2.0] - 2023-09-13
2
11
 
3
12
  Thrown away the mail-merge implementation and replaced it with a simple search/replace.
data/README.md CHANGED
@@ -78,7 +78,7 @@ Consolidate looks for word/document.xml files, plus any files that match word/he
78
78
 
79
79
  ## Development
80
80
 
81
- The repo contains a .devcontainer folder - this contains instructions for a development container that has everything needed to build the project. Once the container has started, you can use `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
81
+ The repo contains a .devcontainer folder - this contains instructions for a development container that has everything needed to build the project. Once the container has started, you can use `bin/setup` to install dependencies. Then, run `bundle exec rake` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
82
82
 
83
83
  `bundle exec rake install` will install the gem on your local machine (obviously not from within the devcontainer though). To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
84
84
 
data/Rakefile CHANGED
@@ -7,4 +7,4 @@ RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  require "standard/rake"
9
9
 
10
- task default: %i[spec standard]
10
+ task default: %i[standard:fix spec]
@@ -0,0 +1 @@
1
+ 098078b13e3bb4e3370cbc6bdb0195300489dfb33a5c27a201a4c5f1f0184bba73557264e674f49da41dbc62ad991e8ae99bef1debf417a5b1a363b1d56829f1
@@ -0,0 +1 @@
1
+ ac1393002b538b8f000c1e4f1f7f4514f689434dad0889a77a0470bd839c58cb49d6218daafd4a8ca48675e220ab9c42147de78a40ec0fd59d5a19371b699eb0
@@ -11,6 +11,15 @@ module Consolidate
11
11
  path
12
12
  end
13
13
 
14
+ def initialize(path, verbose: false, &block)
15
+ @verbose = verbose
16
+ @output = {}
17
+ @zip = Zip::File.open(path)
18
+ @documents = load_documents
19
+ block&.call self
20
+ end
21
+
22
+ # Helper method to display the contents of the document and the merge fields from the CLI
14
23
  def examine
15
24
  documents = document_names.join(", ")
16
25
  fields = field_names.join(", ")
@@ -18,39 +27,37 @@ module Consolidate
18
27
  puts "Merge fields: #{fields}"
19
28
  end
20
29
 
30
+ # Read all documents within the docx and extract any merge fields
21
31
  def field_names
22
- documents.collect do |name, document|
23
- (document / "//w:t").collect do |text_node|
24
- next unless (matches = text_node.content.match(/{{\s*(\S+)\s*}}/))
25
- field_name = matches[1].strip
26
- puts "...field #{field_name} found in #{name}" if verbose
27
- field_name
28
- end.compact
29
- end.flatten
32
+ tag_nodes.collect do |tag_node|
33
+ field_names_from tag_node
34
+ end.flatten.compact.uniq
30
35
  end
31
36
 
37
+ # List the documents stored within this docx
32
38
  def document_names
33
39
  @zip.entries.collect { |entry| entry.name }
34
40
  end
35
41
 
36
- def data fields = {}
37
- fields = fields.transform_keys(&:to_s)
42
+ # Substitute the data from the merge fields with the values provided
43
+ def data mapping = {}
44
+ mapping = mapping.transform_keys(&:to_s)
38
45
 
39
46
  if verbose
40
47
  puts "...substitutions..."
41
- fields.each do |key, value|
48
+ mapping.each do |key, value|
42
49
  puts " #{key} => #{value}"
43
50
  end
44
51
  end
45
52
 
46
53
  @documents.each do |name, document|
47
- result = document.dup
48
- result = substitute result, fields, name
54
+ output_document = substitute document.dup, mapping: mapping, document_name: name
49
55
 
50
- @output[name] = result.serialize save_with: 0
56
+ @output[name] = output_document.serialize save_with: 0
51
57
  end
52
58
  end
53
59
 
60
+ # Write the new document to the given path
54
61
  def write_to path
55
62
  puts "...writing to #{path}" if verbose
56
63
  Zip::File.open(path, Zip::File::CREATE) do |out|
@@ -62,51 +69,84 @@ module Consolidate
62
69
  end
63
70
  end
64
71
 
65
- protected
72
+ private
66
73
 
67
74
  attr_reader :verbose
68
75
  attr_reader :zip
69
76
  attr_reader :xml
70
77
  attr_reader :documents
71
78
  attr_accessor :output
79
+ TAG = /\{\{\s*(\S+)\s*\}\}/
72
80
 
73
- def initialize(path, verbose: false, &block)
74
- raise "No block given" unless block
75
- @verbose = verbose
76
- @output = {}
77
- @documents = {}
78
- begin
79
- @zip = Zip::File.open(path)
80
- @zip.entries.each do |entry|
81
- next unless entry.name.match?(/word\/(document|header|footer|footnotes|endnotes).?\.xml/)
82
- puts "...reading #{entry.name}" if verbose
83
- xml = @zip.get_input_stream entry
84
- @documents[entry.name] = Nokogiri::XML(xml) { |x| x.noent }
85
- end
86
- yield self
87
- ensure
88
- @zip.close
81
+ def load_documents
82
+ @zip.entries.each_with_object({}) do |entry, documents|
83
+ next unless entry.name.match?(/word\/(document|header|footer|footnotes|endnotes).?\.xml/)
84
+ puts "...reading #{entry.name}" if verbose
85
+ xml = @zip.get_input_stream entry
86
+ documents[entry.name] = Nokogiri::XML(xml) { |x| x.noent }
89
87
  end
88
+ ensure
89
+ @zip.close
90
90
  end
91
91
 
92
- def substitute document, fields, document_name
93
- (document / "//w:t").each do |text_node|
94
- next unless (matches = text_node.content.match(/{{\s*(\S+)\s*}}/))
95
- field_name = matches[1].strip
96
- if fields.has_key? field_name
97
- field_value = fields[field_name]
92
+ # Collect all the nodes that contain merge fields
93
+ def tag_nodes
94
+ documents.collect do |name, document|
95
+ tag_nodes_for document
96
+ end.flatten
97
+ end
98
+
99
+ # go through all w:t (Word Text???) nodes of the document
100
+ # find any nodes that contain "{{"
101
+ # then find the ancestor node that also includes the ending "}}"
102
+ # This collection of nodes contains all the merge fields for this document
103
+ def tag_nodes_for document
104
+ (document / "//w:p").select do |paragraph|
105
+ paragraph.content.match(TAG)
106
+ end
107
+ end
108
+
109
+ # Extract the merge field name from the node
110
+ def field_names_from(tag_node)
111
+ matches = tag_node.content.scan(TAG)
112
+ matches.empty? ? nil : matches.flatten.map(&:strip)
113
+ end
114
+
115
+ # Go through the given document, replacing any merge fields with the values provided
116
+ # and storing the results in a new document
117
+ def substitute document, document_name:, mapping: {}
118
+ tag_nodes_for(document).each do |tag_node|
119
+ field_names = field_names_from tag_node
120
+ puts "Original Node for #{field_names} is #{tag_node}" if verbose
121
+
122
+ # Extract the paragraph properties node if it exists
123
+ paragraph_properties = tag_node.search ".//w:pPr"
124
+ run_properties = tag_node.at_xpath ".//w:rPr"
125
+
126
+ text = tag_node.content
127
+ field_names.each do |field_name|
128
+ field_value = mapping[field_name].to_s
98
129
  puts "...substituting #{field_name} with #{field_value} in #{document_name}" if verbose
99
- text_node.content = text_node.content.gsub(matches[1], field_value).gsub("{{", "").gsub("}}", "")
100
- elsif verbose
101
- puts "...found #{field_name} but no replacement value"
130
+ text = text.gsub(/{{\s*#{field_name}\s*}}/, field_value)
102
131
  end
132
+
133
+ # Create a new text node with the substituted text
134
+ text_node = Nokogiri::XML::Node.new("w:t", tag_node.document)
135
+ text_node.content = text
136
+
137
+ # Create a new run node to hold the substituted text and the paragraph properties
138
+ run_node = Nokogiri::XML::Node.new("w:r", tag_node.document)
139
+ run_node << run_properties if run_properties
140
+ run_node << text_node
141
+ tag_node.children = Nokogiri::XML::NodeSet.new(document, paragraph_properties.to_a + [run_node])
142
+
143
+ puts "TAG NODE FOR #{field_names} IS #{tag_node}" if verbose
144
+ rescue => ex
145
+ # Have to mangle the exception message otherwise it outputs the entire document
146
+ puts ex.message.to_s[0..255]
103
147
  end
104
148
  document
105
149
  end
106
-
107
- def close
108
- zip.close
109
- end
110
150
  end
111
151
  end
112
152
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Consolidate
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.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.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rahoul Baruah
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-13 00:00:00.000000000 Z
11
+ date: 2024-11-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -48,7 +48,6 @@ extensions: []
48
48
  extra_rdoc_files: []
49
49
  files:
50
50
  - ".devcontainer/devcontainer.json"
51
- - ".nova/Configuration.json"
52
51
  - ".rspec"
53
52
  - ".ruby-version"
54
53
  - ".standard.yml"
@@ -62,6 +61,8 @@ files:
62
61
  - checksums/standard-procedure-consolidate-0.1.3.gem.sha512
63
62
  - checksums/standard-procedure-consolidate-0.1.4.gem.sha512
64
63
  - checksums/standard-procedure-consolidate-0.2.0.gem.sha512
64
+ - checksums/standard-procedure-consolidate-0.3.0.gem.sha512
65
+ - checksums/standard-procedure-consolidate-0.3.1.gem.sha512
65
66
  - exe/consolidate
66
67
  - exe/examine
67
68
  - lib/consolidate.rb
@@ -77,7 +78,7 @@ metadata:
77
78
  homepage_uri: https://github.com/standard-procedure/standard-procedure-consolidate
78
79
  source_code_uri: https://github.com/standard-procedure/standard-procedure-consolidate
79
80
  changelog_uri: https://github.com/standard-procedure/standard-procedure-consolidate/blob/main/CHANGELOG.md
80
- post_install_message:
81
+ post_install_message:
81
82
  rdoc_options: []
82
83
  require_paths:
83
84
  - lib
@@ -92,8 +93,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
92
93
  - !ruby/object:Gem::Version
93
94
  version: '0'
94
95
  requirements: []
95
- rubygems_version: 3.4.10
96
- signing_key:
96
+ rubygems_version: 3.4.19
97
+ signing_key:
97
98
  specification_version: 4
98
99
  summary: Simple ruby mailmerge for Microsoft Word .docx files.
99
100
  test_files: []
@@ -1,3 +0,0 @@
1
- {
2
-
3
- }