combine_pdf 0.2.21 → 0.2.27
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 +54 -0
- data/LICENSE.txt +2 -1
- data/README.md +30 -0
- data/lib/combine_pdf.rb +12 -18
- data/lib/combine_pdf/api.rb +156 -153
- data/lib/combine_pdf/basic_writer.rb +41 -53
- data/lib/combine_pdf/decrypt.rb +235 -228
- data/lib/combine_pdf/filter.rb +79 -90
- data/lib/combine_pdf/fonts.rb +451 -459
- data/lib/combine_pdf/page_methods.rb +864 -877
- data/lib/combine_pdf/parser.rb +649 -578
- data/lib/combine_pdf/pdf_protected.rb +377 -218
- data/lib/combine_pdf/pdf_public.rb +490 -462
- data/lib/combine_pdf/renderer.rb +157 -163
- data/lib/combine_pdf/version.rb +1 -1
- data/test/automated +79 -0
- data/test/console +4 -4
- data/test/named_dest +84 -0
- metadata +6 -2
@@ -5,223 +5,382 @@
|
|
5
5
|
## is subject to the same license.
|
6
6
|
########################################################
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
8
|
module CombinePDF
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
9
|
+
class PDF
|
10
|
+
protected
|
11
|
+
|
12
|
+
include Renderer
|
13
|
+
|
14
|
+
# RECORSIVE_PROTECTION = { Parent: true, Last: true}.freeze
|
15
|
+
|
16
|
+
# @private
|
17
|
+
# Some PDF objects contain references to other PDF objects.
|
18
|
+
#
|
19
|
+
# this function adds the references contained in `@objects`.
|
20
|
+
#
|
21
|
+
# this is used for internal operations, such as injectng data using the << operator.
|
22
|
+
def add_referenced
|
23
|
+
# add references but not root
|
24
|
+
should_resolve = @objects.dup
|
25
|
+
dup_pages = nil
|
26
|
+
resolved = [].to_set
|
27
|
+
while should_resolve.any?
|
28
|
+
obj = should_resolve.pop
|
29
|
+
if obj.is_a?(Hash)
|
30
|
+
next if resolved.include? obj.object_id
|
31
|
+
resolved << obj.object_id
|
32
|
+
if obj[:referenced_object]
|
33
|
+
tmp = @objects.find_index(obj[:referenced_object])
|
34
|
+
if tmp
|
35
|
+
tmp = @objects[tmp]
|
36
|
+
obj[:referenced_object] = tmp
|
37
|
+
else
|
38
|
+
tmp = obj[:referenced_object]
|
39
|
+
should_resolve << tmp
|
40
|
+
@objects << tmp
|
41
|
+
end
|
42
|
+
else
|
43
|
+
obj.keys.each { |k| should_resolve << obj[k] unless k == :Parent || resolved.include?(obj[k].object_id) || !obj[k].is_a?(Enumerable) }
|
44
|
+
end
|
45
|
+
elsif obj.is_a?(Array)
|
46
|
+
next if resolved.include? obj.object_id
|
47
|
+
resolved << obj.object_id
|
48
|
+
should_resolve.concat obj
|
49
|
+
end
|
50
|
+
end
|
51
|
+
resolved.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
# # @private
|
55
|
+
# # Some PDF objects contain references to other PDF objects.
|
56
|
+
# #
|
57
|
+
# # this function adds the references contained in "object", but DOESN'T add the object itself.
|
58
|
+
# #
|
59
|
+
# # this is used for internal operations, such as injectng data using the << operator.
|
60
|
+
# def add_referenced(object, dup_pages = true)
|
61
|
+
# # add references but not root
|
62
|
+
# if object.is_a?(Array)
|
63
|
+
# object.each { |it| add_referenced(it, dup_pages) }
|
64
|
+
# return true
|
65
|
+
# elsif object.is_a?(Hash)
|
66
|
+
# # first if statement is actually a workaround for a bug in Acrobat Reader, regarding duplicate pages.
|
67
|
+
# if dup_pages && object[:is_reference_only] && object[:referenced_object] && object[:referenced_object].is_a?(Hash) && object[:referenced_object][:Type] == :Page
|
68
|
+
# if @objects.find_index object[:referenced_object]
|
69
|
+
# @objects << (object[:referenced_object] = object[:referenced_object].dup)
|
70
|
+
# else
|
71
|
+
# @objects << object[:referenced_object]
|
72
|
+
# end
|
73
|
+
# elsif object[:is_reference_only] && object[:referenced_object]
|
74
|
+
# found_at = @objects.find_index object[:referenced_object]
|
75
|
+
# if found_at
|
76
|
+
# # if the objects are equal, they might still be different objects!
|
77
|
+
# # so, we need to make sure they are the same object for the pointers to effect id numbering
|
78
|
+
# # and formatting operations.
|
79
|
+
# object[:referenced_object] = @objects[found_at]
|
80
|
+
# # stop this path, there is no need to run over the Hash's keys and values
|
81
|
+
# return true
|
82
|
+
# else
|
83
|
+
# # stop if page propegation is false
|
84
|
+
# return true if !dup_pages && object[:referenced_object][:Type] == :Page
|
85
|
+
# # @objects.include? object[:referenced_object] is bound to be false
|
86
|
+
# # the object wasn't found - add it to the @objects array
|
87
|
+
# @objects << object[:referenced_object]
|
88
|
+
# end
|
89
|
+
#
|
90
|
+
# end
|
91
|
+
# object.each do |k, v|
|
92
|
+
# add_referenced(v, dup_pages) unless RECORSIVE_PROTECTION[k]
|
93
|
+
# end
|
94
|
+
# else
|
95
|
+
# return false
|
96
|
+
# end
|
97
|
+
# true
|
98
|
+
# end
|
99
|
+
|
100
|
+
# @private
|
101
|
+
def rebuild_catalog(*with_pages)
|
102
|
+
# # build page list v.1 Slow but WORKS
|
103
|
+
# # Benchmark testing value: 26.708394
|
104
|
+
# old_catalogs = @objects.select {|obj| obj.is_a?(Hash) && obj[:Type] == :Catalog}
|
105
|
+
# old_catalogs ||= []
|
106
|
+
# page_list = []
|
107
|
+
# PDFOperations._each_object(old_catalogs,false) { |p| page_list << p if p.is_a?(Hash) && p[:Type] == :Page }
|
108
|
+
|
109
|
+
# build page list v.2 faster, better, and works
|
110
|
+
# Benchmark testing value: 0.215114
|
111
|
+
page_list = pages
|
112
|
+
|
113
|
+
# add pages to catalog, if requested
|
114
|
+
page_list.concat(with_pages) unless with_pages.empty?
|
115
|
+
|
116
|
+
# build new Pages object
|
117
|
+
pages_object = { Type: :Pages, Count: page_list.length, Kids: page_list.map { |p| { referenced_object: p, is_reference_only: true } } }
|
118
|
+
|
119
|
+
# rebuild/rename the names dictionary
|
120
|
+
rebuild_names
|
121
|
+
# build new Catalog object
|
122
|
+
catalog_object = { Type: :Catalog,
|
123
|
+
Pages: { referenced_object: pages_object, is_reference_only: true },
|
124
|
+
Names: { referenced_object: @names, is_reference_only: true },
|
125
|
+
Outlines: { referenced_object: @outlines, is_reference_only: true } }
|
126
|
+
catalog_object[:ViewerPreferences] = @viewer_preferences unless @viewer_preferences.empty?
|
127
|
+
|
128
|
+
# rebuild/rename the forms dictionary
|
129
|
+
if @forms_data.nil? || @forms_data.empty?
|
130
|
+
@forms_data = nil
|
131
|
+
else
|
132
|
+
@forms_data = { referenced_object: (@forms_data[:referenced_object] || @forms_data), is_reference_only: true }
|
133
|
+
catalog_object[:AcroForm] = @forms_data
|
134
|
+
end
|
135
|
+
|
136
|
+
# point old Pages pointers to new Pages object
|
137
|
+
## first point known pages objects - enough?
|
138
|
+
pages.each { |p| p[:Parent] = { referenced_object: pages_object, is_reference_only: true } }
|
139
|
+
## or should we, go over structure? (fails)
|
140
|
+
# each_object {|obj| obj[:Parent][:referenced_object] = pages_object if obj.is_a?(Hash) && obj[:Parent].is_a?(Hash) && obj[:Parent][:referenced_object] && obj[:Parent][:referenced_object][:Type] == :Pages}
|
141
|
+
|
142
|
+
# remove old catalog and pages objects
|
143
|
+
@objects.reject! { |obj| obj.is_a?(Hash) && (obj[:Type] == :Catalog || obj[:Type] == :Pages) }
|
144
|
+
|
145
|
+
# inject new catalog and pages objects
|
146
|
+
@objects << pages_object
|
147
|
+
@objects << catalog_object
|
148
|
+
|
149
|
+
catalog_object
|
150
|
+
end
|
151
|
+
|
152
|
+
def names_object
|
153
|
+
@names
|
154
|
+
end
|
155
|
+
|
156
|
+
def outlines_object
|
157
|
+
@outlines
|
158
|
+
end
|
159
|
+
# def forms_data
|
160
|
+
# @forms_data
|
161
|
+
# end
|
162
|
+
|
163
|
+
# @private
|
164
|
+
# this is an alternative to the rebuild_catalog catalog method
|
165
|
+
# this method is used by the to_pdf method, for streamlining the PDF output.
|
166
|
+
# there is no point is calling the method before preparing the output.
|
167
|
+
def rebuild_catalog_and_objects
|
168
|
+
catalog = rebuild_catalog
|
169
|
+
@objects.clear
|
170
|
+
@objects << @info
|
171
|
+
@objects << catalog
|
172
|
+
# fix Acrobat Reader issue with page reference uniqueness (must be unique or older Acrobat Reader fails)
|
173
|
+
catalog[:Pages][:referenced_object][:Kids].each do |page|
|
174
|
+
tmp = page[:referenced_object]
|
175
|
+
tmp = page[:referenced_object] = tmp.dup if @objects.include? tmp
|
176
|
+
@objects << tmp
|
177
|
+
end
|
178
|
+
# adds every referenced object to the @objects (root), addition is performed as pointers rather then copies
|
179
|
+
# puts (Benchmark.measure do
|
180
|
+
add_referenced
|
181
|
+
# end)
|
182
|
+
# @objects << @info
|
183
|
+
# add_referenced @info
|
184
|
+
# add_referenced catalog
|
185
|
+
# add_referenced catalog[:Pages]
|
186
|
+
# add_referenced catalog[:Names], false
|
187
|
+
# add_referenced catalog[:Outlines], false
|
188
|
+
# add_referenced catalog[:AcroForm], false
|
189
|
+
catalog
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_existing_catalogs
|
193
|
+
(@objects.select { |obj| obj.is_a?(Hash) && obj[:Type] == :Catalog }) || (@objects.select { |obj| obj.is_a?(Hash) && obj[:Type] == :Page })
|
194
|
+
end
|
195
|
+
|
196
|
+
# end
|
197
|
+
# @private
|
198
|
+
def renumber_object_ids(start = nil)
|
199
|
+
@set_start_id = start || @set_start_id
|
200
|
+
start = @set_start_id
|
201
|
+
history = {}
|
202
|
+
@objects.each do |obj|
|
203
|
+
obj[:indirect_reference_id] = start
|
204
|
+
start += 1
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def remove_old_ids
|
209
|
+
@objects.each { |obj| obj.delete(:indirect_reference_id); obj.delete(:indirect_generation_number) }
|
210
|
+
end
|
211
|
+
|
212
|
+
POSSIBLE_NAME_TREES = [:Dests, :AP, :Pages, :IDS, :Templates, :URLS, :Pages].to_set.freeze
|
213
|
+
|
214
|
+
def rebuild_names(name_tree = nil, base = 'CombinePDF_0000000')
|
215
|
+
if name_tree
|
216
|
+
return nil unless name_tree.is_a?(Hash)
|
217
|
+
name_tree = name_tree[:referenced_object] || name_tree
|
218
|
+
dic = []
|
219
|
+
# map a names tree and return a valid name tree. Do not recourse.
|
220
|
+
should_resolve = [name_tree[:Kids], name_tree[:Names]]
|
221
|
+
resolved = [].to_set
|
222
|
+
while should_resolve.any?
|
223
|
+
pos = should_resolve.pop
|
224
|
+
if pos.is_a? Array
|
225
|
+
next if resolved.include?(pos.object_id)
|
226
|
+
if pos[0].is_a? String
|
227
|
+
(pos.length / 2).times do |i|
|
228
|
+
dic << (pos[i * 2].clear << base.next!)
|
229
|
+
dic << (pos[(i * 2) + 1].is_a?(Array) ? { is_reference_only: true, referenced_object: { indirect_without_dictionary: pos[(i * 2) + 1] } } : pos[(i * 2) + 1])
|
230
|
+
# dic << pos[(i * 2) + 1]
|
231
|
+
end
|
232
|
+
else
|
233
|
+
should_resolve.concat pos
|
234
|
+
end
|
235
|
+
elsif pos.is_a? Hash
|
236
|
+
pos = pos[:referenced_object] || pos
|
237
|
+
next if resolved.include?(pos.object_id)
|
238
|
+
should_resolve << pos[:Kids] if pos[:Kids]
|
239
|
+
should_resolve << pos[:Names] if pos[:Names]
|
240
|
+
end
|
241
|
+
resolved << pos.object_id
|
242
|
+
end
|
243
|
+
return { referenced_object: { Names: dic }, is_reference_only: true }
|
244
|
+
end
|
245
|
+
@names ||= @names[:referenced_object]
|
246
|
+
new_names = { Type: :Names }.dup
|
247
|
+
POSSIBLE_NAME_TREES.each do |ntree|
|
248
|
+
if @names[ntree]
|
249
|
+
new_names[ntree] = rebuild_names(@names[ntree], base)
|
250
|
+
@names[ntree].clear
|
251
|
+
end
|
252
|
+
end
|
253
|
+
@names.clear
|
254
|
+
@names = new_names
|
255
|
+
end
|
256
|
+
|
257
|
+
# @private
|
258
|
+
# this method reviews a Hash an updates it by merging Hash data,
|
259
|
+
# preffering the new over the old.
|
260
|
+
def self.hash_merge_new_no_page(_key, old_data, new_data)
|
261
|
+
if old_data.is_a? Hash
|
262
|
+
return old_data if old_data[:Type] == :Page
|
263
|
+
old_data.merge(new_data, &(@hash_merge_new_no_page_proc ||= method(:hash_merge_new_no_page)))
|
264
|
+
elsif old_data.is_a? Array
|
265
|
+
old_data + new_data
|
266
|
+
else
|
267
|
+
new_data
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
# Merges 2 outlines by appending one to the end or start of the other.
|
272
|
+
# old_data - the main outline, which is also the one that will be used in the resulting PDF.
|
273
|
+
# new_data - the outline to be appended
|
274
|
+
# position - an integer representing the position where a PDF is being inserted.
|
275
|
+
# This method only differentiates between inserted at the beginning, or not.
|
276
|
+
# Not at the beginning, means the new outline will be added to the end of the original outline.
|
277
|
+
# An outline base node (tree base) has :Type, :Count, :First, :Last
|
278
|
+
# Every node within the outline base node's :First or :Last can have also have the following pointers to other nodes:
|
279
|
+
# :First or :Last (only if the node has a subtree / subsection)
|
280
|
+
# :Parent (the node's parent)
|
281
|
+
# :Prev, :Next (previous and next node)
|
282
|
+
# Non-node-pointer data in these nodes:
|
283
|
+
# :Title - the node's title displayed in the PDF outline
|
284
|
+
# :Count - Number of nodes in it's subtree (0 if no subtree)
|
285
|
+
# :Dest - node link destination (if the node is linking to something)
|
286
|
+
def merge_outlines(old_data, new_data, position)
|
287
|
+
old_data = actual_object(old_data)
|
288
|
+
new_data = actual_object(new_data)
|
289
|
+
if old_data.nil? || old_data.empty? || old_data[:First].nil?
|
290
|
+
# old_data is a reference to the actual object,
|
291
|
+
# so if we update old_data, we're done, no need to take any further action
|
292
|
+
old_data.update new_data
|
293
|
+
elsif new_data.nil? || new_data.empty? || new_data[:First].nil?
|
294
|
+
return old_data
|
295
|
+
else
|
296
|
+
new_data = new_data.dup # avoid old data corruption
|
297
|
+
# number of outline nodes, after the merge
|
298
|
+
old_data[:Count] = old_data[:Count].to_i + new_data[:Count].to_i
|
299
|
+
# walk the Hash here ...
|
300
|
+
# I'm just using the start / end insert-position for now...
|
301
|
+
# first - is going to be the start of the outline base node's :First, after the merge
|
302
|
+
# last - is going to be the end of the outline base node's :Last, after the merge
|
303
|
+
# median - the start of what will be appended to the end of the outline base node's :First
|
304
|
+
# parent - the outline base node of the resulting merged outline
|
305
|
+
# FIXME implement the possibility to insert somewhere in the middle of the outline
|
306
|
+
prev = nil
|
307
|
+
pos = first = actual_object(((position != 0) ? old_data : new_data)[:First])
|
308
|
+
last = actual_object(((position != 0) ? new_data : old_data)[:Last])
|
309
|
+
median = { is_reference_only: true, referenced_object: actual_object(((position != 0) ? new_data : old_data)[:First]) }
|
310
|
+
old_data[:First] = { is_reference_only: true, referenced_object: first }
|
311
|
+
old_data[:Last] = { is_reference_only: true, referenced_object: last }
|
312
|
+
parent = { is_reference_only: true, referenced_object: old_data }
|
313
|
+
while pos
|
314
|
+
# walking through old_data here and updating the :Parent as we go,
|
315
|
+
# this updates the inserted new_data :Parent's as well once it is appended and the
|
316
|
+
# loop keeps walking the appended data.
|
317
|
+
pos[:Parent] = parent if pos[:Parent]
|
318
|
+
# connect the two outlines
|
319
|
+
# if there is no :Next, the end of the outline base node's :First is reached and this is
|
320
|
+
# where the new data gets appended, the same way you would append to a two-way linked list.
|
321
|
+
if pos[:Next].nil?
|
322
|
+
median[:referenced_object][:Prev] = { is_reference_only: true, referenced_object: prev } if median
|
323
|
+
pos[:Next] = median
|
324
|
+
# midian becomes 'nil' because this loop keeps going after the appending is done,
|
325
|
+
# to update the parents of the appended tree and we wouldn't want to keep appending it infinitely.
|
326
|
+
median = nil
|
327
|
+
end
|
328
|
+
# iterating over the outlines main nodes (this is not going into subtrees)
|
329
|
+
# while keeping every rotations previous node saved
|
330
|
+
prev = pos
|
331
|
+
pos = actual_object(pos[:Next])
|
332
|
+
end
|
333
|
+
# make sure the last object doesn't have the :Next and the first no :Prev property
|
334
|
+
prev.delete :Next
|
335
|
+
actual_object(old_data[:First]).delete :Prev
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
# Prints the whole outline hash to a file,
|
340
|
+
# with basic indentation and replacing raw streams with "RAW STREAM"
|
341
|
+
# (subbing doesn't allways work that great for big streams)
|
342
|
+
# outline - outline hash
|
343
|
+
# file - "filename.filetype" string
|
344
|
+
def print_outline_to_file(outline, file)
|
345
|
+
outline_subbed_str = outline.to_s.gsub(/\:raw_stream_content=\>"(?:(?!"}).)*+"\}\}/, ':raw_stream_content=> RAW STREAM}}')
|
346
|
+
brace_cnt = 0
|
347
|
+
formatted_outline_str = ''
|
348
|
+
outline_subbed_str.each_char do |c|
|
349
|
+
if c == '{'
|
350
|
+
formatted_outline_str << "\n" << "\t" * brace_cnt << c
|
351
|
+
brace_cnt += 1
|
352
|
+
elsif c == '}'
|
353
|
+
brace_cnt -= 1
|
354
|
+
brace_cnt = 0 if brace_cnt < 0
|
355
|
+
formatted_outline_str << c << "\n" << "\t" * brace_cnt
|
356
|
+
elsif c == '\n'
|
357
|
+
formatted_outline_str << c << "\t" * brace_cnt
|
358
|
+
else
|
359
|
+
formatted_outline_str << c
|
360
|
+
end
|
361
|
+
end
|
362
|
+
formatted_outline_str << "\n" * 10
|
363
|
+
File.open(file, 'w') { |file| file.write(formatted_outline_str) }
|
364
|
+
end
|
365
|
+
|
366
|
+
private
|
367
|
+
|
368
|
+
def renaming_dictionary(object = nil, dictionary = {})
|
369
|
+
object ||= @names
|
370
|
+
case object
|
371
|
+
when Array
|
372
|
+
object.length.times { |i| object[i].is_a?(String) ? (dictionary[object[i]] = (dictionary.last || 'Random_0001').next) : renaming_dictionary(object[i], dictionary) }
|
373
|
+
when Hash
|
374
|
+
object.values.each { |v| renaming_dictionary v, dictionary }
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def rename_object(object, _dictionary)
|
379
|
+
case object
|
380
|
+
when Array
|
381
|
+
object.length.times { |i| }
|
382
|
+
when Hash
|
383
|
+
end
|
384
|
+
end
|
385
|
+
end
|
227
386
|
end
|