text_alignment 0.6.3 → 0.7.3

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: 6bed1eba72da626227ab727ce22129d226539bcfae5ca22006ac26258b184d8c
4
- data.tar.gz: d2c121ea072186fd25fd61fb90c5ffacb886c1d109b82c044a1666220b8f7d8b
3
+ metadata.gz: '0788bdb6d161499f5a5258757b9f61faee96b60246b422b57b17ba953b4a2c87'
4
+ data.tar.gz: 4564fd15e1e1d673932438206989dc706aa67f2467698f16946e2635c562ec90
5
5
  SHA512:
6
- metadata.gz: 6e526995325e79fdde8ecd729c04e2e6a21e13f0166acc39b341133055275a1bbd5a3318f78dd5af4a72237c140fa8eb06270441a16e2426e58a57183b91ca6a
7
- data.tar.gz: ec423d59036b1ee5595141428fe320f0e9ca16b8b2660d46a0f59f376c3845ad70196d006c2f83390ac12f98b35ff14a1098fcd24cda0ee1c6534f36915def81
6
+ metadata.gz: c93882cc28e8bfdbdeed325b282b63fd3f3644d1739ed979ef92a7d8a133e26f4a8ffd0bda22da0fc2e0a31c77c6d49ce89f8f0fae802c9ae2041a7db60a2a4e
7
+ data.tar.gz: 238ed44ac0c0a178a64743846550639c626d3a300a79f05fedb016e71a890f11025b52d260ce2f46f00d68b1c4b9c13c48a8be8712f737bb2b57b0274b174b8b
@@ -105,9 +105,7 @@ lost_annotations = []
105
105
  target_annotations = if source_annotations.class == Array
106
106
  align_mdoc(source_annotations, {text: target_text})
107
107
  else
108
- alignment = TextAlignment::TextAlignment.new(source_annotations[:text], target_text)
109
-
110
- # pp alignment
108
+ alignment = TextAlignment::TextAlignment.new(source_annotations[:text], target_text, source_annotations[:denotations])
111
109
 
112
110
  # verification
113
111
  # source_text = source_annotations[:text]
@@ -142,22 +140,7 @@ else
142
140
  puts "====="
143
141
  # exit
144
142
 
145
- # verification of source denotations
146
- puts "[Invalid source denotations]"
147
- source_annotations[:denotations] do |d|
148
- p d unless d[:span][:begin] && d[:span][:end] && d[:span][:begin] < d[:span][:end] && d[:span][:begin] >= 0 && d[:span][:end] < source_text.length
149
- end
150
- puts "====="
151
- puts
152
-
153
143
  denotations = alignment.transform_hdenotations(source_annotations[:denotations])
154
- puts "[Invalid transformation]"
155
- denotations.each do |d|
156
- p d unless d[:span][:begin] && d[:span][:end] && d[:span][:begin] < d[:span][:end] && d[:span][:begin] >= 0 && d[:span][:end] < target_text.length
157
- end
158
- puts "====="
159
- puts
160
-
161
144
  lost_annotations += alignment.lost_annotations if alignment.lost_annotations
162
145
 
163
146
  source_annotations.merge({text:target_text, denotations:denotations})
@@ -194,7 +177,9 @@ warn "denotations:\t#{target_annotations[:denotations].nil? ? 0 : target_annotat
194
177
 
195
178
  if lost_annotations
196
179
  warn "\n[lost annotations]"
197
- warn "#{lost_annotations.length}"
180
+ lost_annotations.each do |a|
181
+ p a
182
+ end
198
183
  end
199
184
 
200
185
  #puts target_annotations.to_json
@@ -1,7 +1,7 @@
1
1
  module TextAlignment; end unless defined? TextAlignment
2
2
 
3
3
  TextAlignment::SIZE_NGRAM = 8 unless defined? TextAlignment::SIZE_NGRAM
4
- TextAlignment::SIZE_WINDOW = 60 unless defined? TextAlignment::SIZE_WINDOW
4
+ TextAlignment::SIZE_WINDOW = 30 unless defined? TextAlignment::SIZE_WINDOW
5
5
  TextAlignment::BUFFER_RATE = 0.1 unless defined? TextAlignment::BUFFER_RATE
6
6
  TextAlignment::BUFFER_MIN = 20 unless defined? TextAlignment::BUFFER_MIN
7
7
  TextAlignment::TEXT_SIMILARITY_THRESHOLD = 0.9 unless defined? TextAlignment::TEXT_SIMILARITY_THRESHOLD
@@ -63,6 +63,7 @@ TextAlignment::MAPPINGS = [
63
63
  [" ", " "], #U+200A (hair space)
64
64
  [" ", " "], #U+00A0 (no-break space)
65
65
  [" ", " "], #U+3000 (ideographic space)
66
+ ["‑", "-"], #U+2211 (Non-Breaking Hyphen)
66
67
  ["−", "-"], #U+2212 (minus sign)
67
68
  ["–", "-"], #U+2013 (en dash)
68
69
  ["′", "'"], #U+2032 (prime)
@@ -12,43 +12,46 @@ class TextAlignment::TextAlignment
12
12
  attr_reader :similarity
13
13
  attr_reader :lost_annotations
14
14
 
15
- def initialize(str1, str2, _size_ngram = nil, _size_window = nil, _text_similiarity_threshold = nil)
15
+ def initialize(str1, str2, denotations = nil, _size_ngram = nil, _size_window = nil, _text_similiarity_threshold = nil)
16
16
  raise ArgumentError, "nil string" if str1.nil? || str2.nil?
17
17
 
18
18
  @block_alignment = {source_text:str1, target_text:str2}
19
+ @str1 = str1
20
+ @str2 = str2
19
21
 
20
- # try exact match
22
+ ## Block exact match
21
23
  block_begin = str2.index(str1)
22
24
  unless block_begin.nil?
23
25
  @block_alignment[:blocks] = [{source:{begin:0, end:str1.length}, target:{begin:block_begin, end:block_begin + str1.length}, delta:block_begin, alignment: :block}]
24
- return @block_alignment
26
+ return
25
27
  end
26
28
 
27
- # try exact match
28
29
  block_begin = str2.downcase.index(str1.downcase)
29
30
  unless block_begin.nil?
30
31
  @block_alignment[:blocks] = [{source:{begin:0, end:str1.length}, target:{begin:block_begin, end:block_begin + str1.length}, delta:block_begin, alignment: :block}]
31
- return @block_alignment
32
+ return
32
33
  end
33
34
 
35
+
36
+ ## to find block alignments
34
37
  anchor_finder = TextAlignment::AnchorFinder.new(str1, str2, _size_ngram, _size_window, _text_similiarity_threshold)
35
38
 
36
- # To collect matched blocks
37
- mblocks = []
38
- while anchor = anchor_finder.get_next_anchor
39
- last = mblocks.last
40
- if last && (anchor[:source][:begin] == last[:source][:end] + 1) && (anchor[:target][:begin] == last[:target][:end] + 1)
41
- last[:source][:end] = anchor[:source][:end]
42
- last[:target][:end] = anchor[:target][:end]
39
+ blocks = []
40
+ while block = anchor_finder.get_next_anchor
41
+ last = blocks.last
42
+ if last && (block[:source][:begin] == last[:source][:end] + 1) && (block[:target][:begin] == last[:target][:end] + 1)
43
+ last[:source][:end] = block[:source][:end]
44
+ last[:target][:end] = block[:target][:end]
43
45
  else
44
- mblocks << anchor
46
+ blocks << block.merge(alignment: :block, delta: block[:target][:begin] - block[:source][:begin])
45
47
  end
46
48
  end
47
49
 
48
- # pp mblocks
50
+ # pp blocks
49
51
  # puts "-----"
50
52
  # puts
51
- # mblocks.each do |b|
53
+ # exit
54
+ # blocks.each do |b|
52
55
  # p [b[:source], b[:target]]
53
56
  # puts "---"
54
57
  # puts str1[b[:source][:begin] ... b[:source][:end]]
@@ -60,114 +63,202 @@ class TextAlignment::TextAlignment
60
63
  # puts "-=-=-=-=-"
61
64
  # puts
62
65
 
63
- ## To find block alignments
64
- @block_alignment[:blocks] = []
65
- return if mblocks.empty?
66
-
67
- # Initial step
68
- if mblocks[0][:source][:begin] > 0
69
- e1 = mblocks[0][:source][:begin]
70
- e2 = mblocks[0][:target][:begin]
66
+ ## to fill the gaps
67
+ last_block = nil
68
+ blocks2 = blocks.inject([]) do |sum, block|
69
+ b1 = last_block ? last_block[:source][:end] : 0
70
+ e1 = block[:source][:begin]
71
71
 
72
- if mblocks[0][:target][:begin] == 0
73
- @block_alignment[:blocks] << {source:{begin:0, end:e1}, target:{begin:0, end:0}, alignment: :empty}
72
+ sum += if b1 == e1
73
+ [block]
74
74
  else
75
- _str1 = str1[0 ... e1]
76
- _str2 = str2[0 ... e2]
75
+ b2 = last_block ? last_block[:target][:end] : 0
76
+ e2 = block[:target][:begin]
77
+
78
+ if b2 == e2
79
+ [
80
+ {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty},
81
+ block
82
+ ]
83
+ else
84
+ if b1 == 0 && b2 == 0
85
+ len_buffer = (e1 * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
86
+ b2 = e2 - len_buffer if e2 > len_buffer
87
+ end
88
+
89
+ _str1 = str1[b1 ... e1]
90
+ _str2 = str2[b2 ... e2]
77
91
 
78
- unless _str1.strip.empty?
79
- if _str2.strip.empty?
80
- @block_alignment[:blocks] << {source:{begin:0, end:e1}, target:{begin:0, end:e2}, alignment: :empty}
92
+ if _str1.strip.empty? || _str2.strip.empty?
93
+ [
94
+ {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty},
95
+ block
96
+ ]
81
97
  else
82
- len_min = [_str1.length, _str2.length].min
83
- len_buffer = (len_min * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
84
- b1 = _str1.length < len_buffer ? 0 : e1 - len_buffer
85
- b2 = _str2.length < len_buffer ? 0 : e2 - len_buffer
86
-
87
- @block_alignment[:blocks] << {source:{begin:0, end:b1}, target:{begin:0, end:b2}, alignment: :empty} if b1 > 0
88
-
89
- _str1 = str1[b1 ... e1]
90
- _str2 = str2[b2 ... e2]
91
- alignment = TextAlignment::MixedAlignment.new(_str1.downcase, _str2.downcase)
92
- if alignment.similarity < 0.5
93
- @block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:0, end:e2}, alignment: :empty, similarity: alignment.similarity}
94
- else
95
- @block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:0, end:e2}, alignment:alignment, similarity: alignment.similarity}
96
- end
98
+ local_alignment_blocks(str1, b1, e1, str2, b2, e2, denotations) << block
97
99
  end
98
100
  end
99
101
  end
102
+
103
+ last_block = block
104
+ sum
100
105
  end
101
- @block_alignment[:blocks] << mblocks[0].merge(alignment: :block)
102
-
103
- (1 ... mblocks.length).each do |i|
104
- b1 = mblocks[i - 1][:source][:end]
105
- b2 = mblocks[i - 1][:target][:end]
106
- e1 = mblocks[i][:source][:begin]
107
- e2 = mblocks[i][:target][:begin]
108
- _str1 = str1[b1 ... e1]
109
- _str2 = str2[b2 ... e2]
110
- unless _str1.strip.empty?
111
- if _str2.strip.empty?
112
- @block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty}
106
+
107
+ # the last step
108
+ blocks2 += if last_block.nil?
109
+ local_alignment_blocks(str1, 0, str1.length, str2, 0, str2.length, denotations)
110
+ else
111
+ b1 = last_block[:source][:end]
112
+ if b1 < str1.length
113
+ e1 = str1.length
114
+
115
+ b2 = last_block[:target][:end]
116
+ if b2 < str2.length
117
+ len_buffer = ((e1 - b1) * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
118
+ e2 = (str2.length - b2) > len_buffer ? b2 + len_buffer : str2.length
119
+ local_alignment_blocks(str1, b1, e1, str2, b2, e2, denotations)
113
120
  else
114
- alignment = TextAlignment::MixedAlignment.new(_str1.downcase, _str2.downcase)
115
- if alignment.similarity < 0.5
116
- @block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty, similarity: alignment.similarity}
117
- else
118
- @block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment:alignment, similarity: alignment.similarity}
121
+ [{source:{begin:last_block[:source][:end], end:str1.length}, alignment: :empty}]
122
+ end
123
+ else
124
+ []
125
+ end
126
+ end
127
+
128
+ @block_alignment[:blocks] = blocks2
129
+ end
130
+
131
+ def local_alignment_blocks(str1, b1, e1, str2, b2, e2, denotations = nil)
132
+ block2 = str2[b2 ... e2]
133
+
134
+ ## term-based alignment
135
+ tblocks = if denotations
136
+ ds_in_scope = denotations.select{|d| d[:span][:begin] >= b1 && d[:span][:end] <= e1}.
137
+ sort{|d1, d2| d1[:span][:begin] <=> d2[:span][:begin] || d2[:span][:end] <=> d1[:span][:end] }.
138
+ map{|d| d.merge(lex:str1[d[:span][:begin] ... d[:span][:end]])}
139
+
140
+ position = 0
141
+ tblocks = ds_in_scope.map do |term|
142
+ lex = term[:lex]
143
+ r = block2.index(lex, position)
144
+ if r.nil?
145
+ position = nil
146
+ break
147
+ end
148
+ position = r + lex.length
149
+ {source:term[:span], target:{begin:r + b2, end:r + b2 + lex.length}, alignment: :term, delta: r - term[:span][:begin]}
150
+ end
151
+
152
+ # missing term found
153
+ tblocks = [] if position.nil?
154
+
155
+ # redundant matching found
156
+ unless position.nil?
157
+ ds_in_scope.each do |term|
158
+ lex = term[:lex]
159
+ look_forward = block2.index(lex, position)
160
+ unless look_forward.nil?
161
+ puts lex
162
+ tblocks = []
163
+ break
119
164
  end
120
165
  end
121
166
  end
122
- @block_alignment[:blocks] << mblocks[i].merge(alignment: :block)
167
+
168
+ tblocks
123
169
  end
124
170
 
125
- # Final step
126
- if mblocks[-1][:source][:end] < str1.length && mblocks[-1][:target][:end] < str2.length
127
- b1 = mblocks[-1][:source][:end]
128
- b2 = mblocks[-1][:target][:end]
129
- _str1 = str1[b1 ... str1.length]
130
- _str2 = str2[b2 ... str2.length]
171
+ if tblocks.empty?
172
+ if b1 == 0 && e1 == str1.length
173
+ if (e1 > 2000) || (e2 > 2000)
174
+ [{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty}]
175
+ else
176
+ block1 = str1[b1 ... e1]
177
+ block2 = str2[b2 ... e2]
131
178
 
132
- unless _str1.strip.empty?
133
- if _str2.strip.empty?
134
- @block_alignment[:blocks] << {source:{begin:b1, end:str1.length}, target:{begin:b2, end:str2.length}, alignment: :empty}
179
+ ## character-based alignment
180
+ alignment = TextAlignment::MixedAlignment.new(block1.downcase, block2.downcase)
181
+ if alignment.sdiff.nil?
182
+ [{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty}]
183
+ else
184
+ [{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: alignment, similarity: alignment.similarity}]
185
+ end
186
+ end
187
+ else
188
+ block1 = str1[b1 ... e1]
189
+ block2 = str2[b2 ... e2]
190
+
191
+ ## character-based alignment
192
+ alignment = TextAlignment::MixedAlignment.new(block1.downcase, block2.downcase)
193
+ if alignment.sdiff.nil?
194
+ [{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty}]
135
195
  else
136
- len_min = [_str1.length, _str2.length].min
137
- len_buffer = (len_min * (1 + TextAlignment::BUFFER_RATE)).to_i + TextAlignment::BUFFER_MIN
138
- e1 = _str1.length < len_buffer ? str1.length : b1 + len_buffer
139
- e2 = _str2.length < len_buffer ? str2.length : b2 + len_buffer
140
- _str1 = str1[b1 ... e1]
141
- _str2 = str2[b2 ... e2]
196
+ [{source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: alignment, similarity: alignment.similarity}]
197
+ end
198
+ end
199
+ else
200
+ last_tblock = nil
201
+ lblocks = tblocks.inject([]) do |sum, tblock|
202
+ tb1 = last_tblock ? last_tblock[:source][:end] : b1
203
+ te1 = tblock[:source][:begin]
142
204
 
143
- alignment = TextAlignment::MixedAlignment.new(_str1.downcase, _str2.downcase)
144
- if alignment.similarity < 0.5
145
- @block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment: :empty, similarity: alignment.similarity}
205
+ sum += if te1 == tb1
206
+ [tblock]
207
+ else
208
+ tb2 = last_tblock ? last_tblock[:target][:end] : b2
209
+ te2 = tblock[:target][:begin]
210
+
211
+ if b2 == e2
212
+ [
213
+ {source:{begin:tb1, end:te1}, alignment: :empty},
214
+ tblock
215
+ ]
146
216
  else
147
- @block_alignment[:blocks] << {source:{begin:b1, end:e1}, target:{begin:b2, end:e2}, alignment:alignment, similarity: alignment.similarity}
217
+ [
218
+ {source:{begin:tb1, end:te1}, target:{begin:tb2, end:te2}, alignment: :empty},
219
+ tblock
220
+ ]
148
221
  end
222
+ end
149
223
 
150
- @block_alignment[:blocks] << {source:{begin:e1, end:-1}, target:{begin:e2, end:-1}, alignment: :empty} if e1 < str1.length
224
+ last_tblock = tblock
225
+ sum
226
+ end
227
+
228
+ if last_tblock[:source][:end] < e1
229
+ if last_tblock[:target][:end] < e2
230
+ lblocks << {source:{begin:last_tblock[:source][:end], end:e1}, target:{begin:last_tblock[:target][:end], end:e2}, alignment: :empty}
231
+ else
232
+ lblocks << {source:{begin:last_tblock[:source][:end], end:e1}, alignment: :empty}
151
233
  end
152
234
  end
153
- end
154
235
 
155
- @block_alignment[:blocks].each do |a|
156
- a[:delta] = a[:target][:begin] - a[:source][:begin]
236
+ lblocks
157
237
  end
158
238
  end
159
239
 
240
+
241
+ def indices(str, target)
242
+ position = 0
243
+ len = target.len
244
+ Enumerator.new do |yielder|
245
+ while idx = str.index(target, position)
246
+ yielder << idx
247
+ position = idx + len
248
+ end
249
+ end
250
+ end
251
+
160
252
  def transform_begin_position(begin_position)
161
253
  i = @block_alignment[:blocks].index{|b| b[:source][:end] > begin_position}
162
254
  block = @block_alignment[:blocks][i]
163
255
 
164
- b = if block[:alignment] == :block
256
+ b = if block[:alignment] == :block || block[:alignment] == :term
165
257
  begin_position + block[:delta]
166
258
  elsif block[:alignment] == :empty
167
259
  if begin_position == block[:source][:begin]
168
260
  block[:target][:begin]
169
261
  else
170
- # raise "lost annotation"
171
262
  nil
172
263
  end
173
264
  else
@@ -180,13 +271,12 @@ class TextAlignment::TextAlignment
180
271
  i = @block_alignment[:blocks].index{|b| b[:source][:end] >= end_position}
181
272
  block = @block_alignment[:blocks][i]
182
273
 
183
- e = if block[:alignment] == :block
274
+ e = if block[:alignment] == :block || block[:alignment] == :term
184
275
  end_position + block[:delta]
185
276
  elsif block[:alignment] == :empty
186
277
  if end_position == block[:source][:end]
187
278
  block[:target][:end]
188
279
  else
189
- # raise "lost annotation"
190
280
  nil
191
281
  end
192
282
  else
@@ -208,14 +298,14 @@ class TextAlignment::TextAlignment
208
298
  @lost_annotations = []
209
299
 
210
300
  denotations.each do |d|
211
- begin
212
- d.begin = transform_begin_position(d.begin);
213
- d.end = transform_end_position(d.end);
214
- rescue
215
- @lost_annotations << d
216
- d.begin = nil
217
- d.end = nil
218
- end
301
+ source = {begin:d.begin, end:d.end}
302
+ d.begin = transform_begin_position(d.begin);
303
+ d.end = transform_end_position(d.end);
304
+ raise "invalid transform" unless !d.begin.nil? && !d.end.nil? && d.begin >= 0 && d.end > d.begin && d.end <= @str2.length
305
+ rescue
306
+ @lost_annotations << {source: source, target:{begin:d.begin, end:d.end}}
307
+ d.begin = nil
308
+ d.end = nil
219
309
  end
220
310
 
221
311
  @lost_annotations
@@ -226,12 +316,12 @@ class TextAlignment::TextAlignment
226
316
  @lost_annotations = []
227
317
 
228
318
  r = hdenotations.collect do |d|
229
- new_d = begin
230
- d.dup.merge({span:transform_a_span(d[:span])})
231
- rescue
232
- @lost_annotations << d
233
- nil
234
- end
319
+ t = transform_a_span(d[:span])
320
+ raise "invalid transform" unless !t[:begin].nil? && !t[:end].nil? && t[:begin] >= 0 && t[:end] > t[:begin] && t[:end] <= @str2.length
321
+ new_d = d.dup.merge({span:t})
322
+ rescue
323
+ @lost_annotations << {source: d[:span], target:t}
324
+ nil
235
325
  end.compact
236
326
 
237
327
  r
@@ -245,7 +335,10 @@ class TextAlignment::TextAlignment
245
335
  @block_alignment[:blocks].each do |a|
246
336
  show += case a[:alignment]
247
337
  when :block
248
- "===== common ===== [#{a[:source][:begin]} - #{a[:source][:end]}] [#{a[:target][:begin]} - #{a[:target][:end]}]\n" +
338
+ "===== common (block) ===== [#{a[:source][:begin]} - #{a[:source][:end]}] [#{a[:target][:begin]} - #{a[:target][:end]}]\n" +
339
+ stext[a[:source][:begin] ... a[:source][:end]] + "\n\n"
340
+ when :term
341
+ "===== common (term) ===== [#{a[:source][:begin]} - #{a[:source][:end]}] [#{a[:target][:begin]} - #{a[:target][:end]}]\n" +
249
342
  stext[a[:source][:begin] ... a[:source][:end]] + "\n\n"
250
343
  when :empty
251
344
  "xxxxx disparate texts (similarity: #{a[:similarity]})\n" +
@@ -1,3 +1,3 @@
1
1
  class TextAlignment
2
- VERSION = '0.6.3'
2
+ VERSION = '0.7.3'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: text_alignment
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jin-Dong Kim
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-06 00:00:00.000000000 Z
11
+ date: 2020-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-dictionary