plain_text 0.1
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 +7 -0
- data/.gitignore +51 -0
- data/ChangeLog +5 -0
- data/Makefile +23 -0
- data/README.en.rdoc +172 -0
- data/Rakefile +9 -0
- data/bin/countchar +89 -0
- data/lib/plain_text/parse_rule.rb +474 -0
- data/lib/plain_text/part/boundary.rb +44 -0
- data/lib/plain_text/part/paragraph.rb +35 -0
- data/lib/plain_text/part.rb +973 -0
- data/lib/plain_text/split.rb +103 -0
- data/lib/plain_text/util.rb +104 -0
- data/lib/plain_text.rb +839 -0
- data/plain_text.gemspec +49 -0
- data/test/test_plain_text.rb +280 -0
- data/test/test_plain_text_parse_rule.rb +146 -0
- data/test/test_plain_text_part.rb +353 -0
- data/test/test_plain_text_split.rb +78 -0
- metadata +72 -0
@@ -0,0 +1,973 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require "plain_text/util"
|
4
|
+
|
5
|
+
module PlainText
|
6
|
+
|
7
|
+
# Class to represent a Chapter-like entity like an Array
|
8
|
+
#
|
9
|
+
# An instance of this class contains always an even number of elements,
|
10
|
+
# either another {Part} instance or {Paragraph}-type String-like instance,
|
11
|
+
# followed by a {Boundary}-type String-like instance. The first element is
|
12
|
+
# always a former and the last element is always a latter.
|
13
|
+
#
|
14
|
+
# Essentially, the instance of this class holds the order information between sub-{Part}-s (< Array)
|
15
|
+
# and/or {Paragraph}-s (< String) and {Boundary}-s (< String).
|
16
|
+
#
|
17
|
+
# An example instance looks like this:
|
18
|
+
#
|
19
|
+
# Part (
|
20
|
+
# (0) Paragraph::Empty,
|
21
|
+
# (1) Boundary::General,
|
22
|
+
# (2) Part::ArticleHeader(
|
23
|
+
# (0) Paragraph::Title,
|
24
|
+
# (1) Boundary::Empty
|
25
|
+
# ),
|
26
|
+
# (3) Boundary::TitleMain,
|
27
|
+
# (4) Part::ArticleMain(
|
28
|
+
# (0) Part::ArticleSection(
|
29
|
+
# (0) Paragraph::Title,
|
30
|
+
# (1) Boundary::General,
|
31
|
+
# (2) Paragraph::General,
|
32
|
+
# (3) Boundary::General,
|
33
|
+
# (4) Part::ArticleSubSection(...),
|
34
|
+
# (5) Boundary::General,
|
35
|
+
# (6) Paragraph::General,
|
36
|
+
# (7) Boundary::Empty
|
37
|
+
# ),
|
38
|
+
# (1) Boundary::General,
|
39
|
+
# (2) Paragraph::General,
|
40
|
+
# (3) Boundary::Empty
|
41
|
+
# ),
|
42
|
+
# (5) Boundary::General
|
43
|
+
# )
|
44
|
+
#
|
45
|
+
# A Section (Part) always has an even number of elements: pairs of ({Part}|{Paragraph}) and {Boundary} in this order.
|
46
|
+
#
|
47
|
+
# Note some standard destructive Array operations, most notably +#delete+, +#delete_if+, +#reject!+,
|
48
|
+
# +#select!+, +#filter!+, +#keep_if+, +#flatten!+, +#uniq!+ may alter the content in a way
|
49
|
+
# it breaks the self-inconsistency of the object.
|
50
|
+
# Use it at your own risk, if you wish (or don't).
|
51
|
+
#
|
52
|
+
# An instance of this class is always *non-equal* to that of the standard Array class.
|
53
|
+
# To compare it at the Array level, convert a {Part} class instance into Array with #to_a first and compare them.
|
54
|
+
#
|
55
|
+
# For CRUD of elements (contents) of an instance, the following methods are most basic:
|
56
|
+
#
|
57
|
+
# * Create:
|
58
|
+
# * Insert/Append: {#insert} to insert. If the specified index is #size}, it means "append". For primitive operations, specify +primitive: true+ to skip various checks performed to guarantee the self-consistency as an instance of this class.
|
59
|
+
# * {#<<} is disabled.
|
60
|
+
# * Read:
|
61
|
+
# * Read: #to_a gives the standard Array, and then you can do whatever manipulation allowed for Array. For example, if you delete an element in the returned Array, that does not affect the original {Part} instance. However, it is a shallow copy, and hence if you alter an element of it destructively (such as String#replace), the original instance, too, is affected.
|
62
|
+
# * The methods {#[]} (or its alias {#slice}) have some restrictions, such as, an odd number of elements cannot be retrieved, so as to conform the returned value is a valid instance of this class.
|
63
|
+
# * Update:
|
64
|
+
# * Replace: {#[]=} has some restrictions, such as, if multiple elements are replaced, they have to be pairs of Paragraph and Boundary. To skip all the checks, do {#insert} with +primitive: true+
|
65
|
+
# * Delete:
|
66
|
+
# * Delete: {#slice!} to delete. For primitive operations, specify +primitive: true+ to skip various checks performed to guarantee the self-consistency as an instance of this class.
|
67
|
+
# * +#delete_at+ is disabled. +#delete+, +#delete_if+, +#reject!+, +#select!+, +#filter!+, +#keep_if+ (and +#drop_while+ and +#take_whie+ in recent Ruby) remain enabled, but if you use them, make sure to use them carefully at your own risk, as no self-consistency checks would be performed automatically.
|
68
|
+
#
|
69
|
+
# @author Masa Sakano (Wise Babel Ltd)
|
70
|
+
#
|
71
|
+
# @todo methods
|
72
|
+
# * flatten
|
73
|
+
# * SAFE level for command-line tools?
|
74
|
+
#
|
75
|
+
class Part < Array
|
76
|
+
|
77
|
+
include PlainText::Util
|
78
|
+
|
79
|
+
# Error messages
|
80
|
+
ERR_MSGS = {
|
81
|
+
even_num: 'even number of elements must be specified.',
|
82
|
+
use_to_a: 'To handle it as an Array, use to_a first.',
|
83
|
+
}
|
84
|
+
|
85
|
+
# @param arin [Array] of [Paragraph1, Boundary1, Para2, Bd2, ...] or Part/Paragraph if boundaries is given
|
86
|
+
# @param boundaries [Array] of Boundary
|
87
|
+
# @option recursive: [Boolean] if true (Default), normalize recursively.
|
88
|
+
# @option compact: [Boolean] if true (Default), pairs of nil paragraph and boundary are removed. Otherwise, nil is converted to an empty string.
|
89
|
+
# @option compacter: [Boolean] if true (Default), pairs of nil or empty paragraph and boundary are removed.
|
90
|
+
# @return [self]
|
91
|
+
def initialize(arin, boundaries=nil, recursive: true, compact: true, compacter: true)
|
92
|
+
if !boundaries
|
93
|
+
super(arin)
|
94
|
+
else
|
95
|
+
|
96
|
+
armain = []
|
97
|
+
arin.each_with_index do |ea_e, i|
|
98
|
+
armain << ea_e
|
99
|
+
armain << (boundaries[i] || Boundary.new(''))
|
100
|
+
end
|
101
|
+
super armain
|
102
|
+
end
|
103
|
+
normalize!(recursive: recursive, compact: compact, compacter: compacter)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Parses a given string (or {Part}) and returns this class of instance.
|
107
|
+
#
|
108
|
+
# @param inprm [String, Array, Part]
|
109
|
+
# @option rule: [PlainText::ParseRule]
|
110
|
+
# @return [PlainText::Part]
|
111
|
+
def self.parse(inprm, rule: PlainText::ParseRule::RuleConsecutiveLbs)
|
112
|
+
arin = rule.apply(inprm)
|
113
|
+
self.new(arin)
|
114
|
+
end
|
115
|
+
|
116
|
+
####################################################
|
117
|
+
# Instance methods
|
118
|
+
####################################################
|
119
|
+
|
120
|
+
##########
|
121
|
+
# Unique instance methods (not existing in Array)
|
122
|
+
##########
|
123
|
+
|
124
|
+
# Returns an array of boundary parts (odd-number-index parts), consisting of Boundaries
|
125
|
+
#
|
126
|
+
# @return [Array<Boundary>]
|
127
|
+
# @see #parts
|
128
|
+
def boundaries
|
129
|
+
select.with_index { |_, i| i.odd? } rescue select.each_with_index { |_, i| i.odd? } # Rescue for Ruby 2.1 or earlier
|
130
|
+
end
|
131
|
+
|
132
|
+
# returns all the Boundaries immediately before the index and at it as an Array
|
133
|
+
#
|
134
|
+
# See {#squash_boundary_at!} to squash them.
|
135
|
+
#
|
136
|
+
# @param index [Integer]
|
137
|
+
# @return [Array, nil] nil if a too large index is specified.
|
138
|
+
def boundary_extended_at(index)
|
139
|
+
(i_pos = get_valid_ipos_for_boundary(index)) || return
|
140
|
+
arret = []
|
141
|
+
prt = self[i_pos-1]
|
142
|
+
arret = prt.public_send(__method__, -1) if prt.class.method_defined? __method__
|
143
|
+
arret << self[index]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a dup-ped instance with all the Arrays and Strings dup-ped.
|
147
|
+
#
|
148
|
+
# @return [Part]
|
149
|
+
def deepcopy
|
150
|
+
dup.map!{ |i| i.class.method_defined?(:deepcopy) ? i.deepcopy : i.dup }
|
151
|
+
end
|
152
|
+
|
153
|
+
# each method for boundaries only, providing also the index (always an odd number) to the block.
|
154
|
+
#
|
155
|
+
# For just looping over the elements of {#boundaries}, do simply
|
156
|
+
#
|
157
|
+
# boundaries.each do |ec|
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# The indices provided in this method are for the main Array,
|
161
|
+
# and hence different from {#boundaries}.each_with_index
|
162
|
+
#
|
163
|
+
# @param (see #map_boundaries_with_index)
|
164
|
+
# @return as self
|
165
|
+
def each_boundaries_with_index(**kwd, &bl)
|
166
|
+
map_boundaries_core(map: false, with_index: true, **kwd, &bl)
|
167
|
+
end
|
168
|
+
|
169
|
+
# each method for parts only, providing also the index (always an even number) to the block.
|
170
|
+
#
|
171
|
+
# For just looping over the elements of {#parts}, do simply
|
172
|
+
#
|
173
|
+
# parts.each do |ec|
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
# The indices provided in this method are for the main Array,
|
177
|
+
# and hence different from {#parts}.each_with_index
|
178
|
+
#
|
179
|
+
# @param (see #map_parts_with_index)
|
180
|
+
# @return as self
|
181
|
+
def each_parts_with_index(**kwd, &bl)
|
182
|
+
map_parts_core(map: false, with_index: false, **kwd, &bl)
|
183
|
+
end
|
184
|
+
|
185
|
+
# The first significant (=non-empty) element.
|
186
|
+
#
|
187
|
+
# If the returned value is non-nil and destructively altered, self changes.
|
188
|
+
#
|
189
|
+
# @return [Integer, nil] if self.empty? nil is returned.
|
190
|
+
def first_significant_element
|
191
|
+
(i = first_significant_index) || return
|
192
|
+
self[i]
|
193
|
+
end
|
194
|
+
|
195
|
+
# Index of the first significant (=non-empty) element.
|
196
|
+
#
|
197
|
+
# If every element is empty, the last index is returned.
|
198
|
+
#
|
199
|
+
# @return [Integer, nil] if self.empty? nil is returned.
|
200
|
+
def first_significant_index
|
201
|
+
return nil if empty?
|
202
|
+
each_index do |i|
|
203
|
+
return i if self[i] && !self[i].empty? # self for sanity
|
204
|
+
end
|
205
|
+
return size-1
|
206
|
+
end
|
207
|
+
|
208
|
+
# True if the index should be semantically for Paragraph?
|
209
|
+
#
|
210
|
+
# @param i [Integer] index for the array of self
|
211
|
+
# @option skip_check: [Boolean] if true (Default: false), skip conversion of the negative index to positive.
|
212
|
+
# @see #parts
|
213
|
+
def index_para?(i, skip_check: false)
|
214
|
+
skip_check ? i.even? : positive_array_index_checked(i, self).even?
|
215
|
+
end
|
216
|
+
|
217
|
+
# The last significant (=non-empty) element.
|
218
|
+
#
|
219
|
+
# If the returned value is non-nil and destructively altered, self changes.
|
220
|
+
#
|
221
|
+
# @return [Integer, nil] if self.empty? nil is returned.
|
222
|
+
def last_significant_element
|
223
|
+
(i = last_significant_index) || return
|
224
|
+
self[i]
|
225
|
+
end
|
226
|
+
|
227
|
+
# Index of the last significant (=non-empty) element.
|
228
|
+
#
|
229
|
+
# If every element is empty, 0 is returned.
|
230
|
+
#
|
231
|
+
# @return [Integer, nil] if self.empty? nil is returned.
|
232
|
+
def last_significant_index
|
233
|
+
return nil if empty?
|
234
|
+
(0..(size-1)).to_a.reverse.each do |i|
|
235
|
+
return i if self[i] && !self[i].empty? # self for sanity
|
236
|
+
end
|
237
|
+
return 0
|
238
|
+
end
|
239
|
+
|
240
|
+
# map method for boundaries only, returning a copied self.
|
241
|
+
#
|
242
|
+
# If recursive is true (Default), any Boundaries in the descendant Parts are also handled.
|
243
|
+
#
|
244
|
+
# If a Boundary is set nil or empty, along with the preceding Paragraph,
|
245
|
+
# the pair is removed from the returned instance in Default (:compact and :compacter options
|
246
|
+
# - see {#initialize} for detail)
|
247
|
+
#
|
248
|
+
# @option recursive: [Boolean] if true (Default), map is performed recursively.
|
249
|
+
# @return as self
|
250
|
+
# @see #initialize for the other options (:compact and :compacter)
|
251
|
+
def map_boundaries(**kwd, &bl)
|
252
|
+
map_boundaries_core(with_index: false, **kwd, &bl)
|
253
|
+
end
|
254
|
+
|
255
|
+
# map method for boundaries only, providing also the index (always an odd number) to the block, returning a copied self.
|
256
|
+
#
|
257
|
+
# @param (see #map_boundaries)
|
258
|
+
# @return as self
|
259
|
+
def map_boundaries_with_index(**kwd, &bl)
|
260
|
+
map_boundaries_core(with_index: true, **kwd, &bl)
|
261
|
+
end
|
262
|
+
|
263
|
+
# map method for parts only, returning a copied self.
|
264
|
+
#
|
265
|
+
# If recursive is true (Default), any Paragraphs in the descendant Parts are also handled.
|
266
|
+
#
|
267
|
+
# If a Paragraph is set nil or empty, along with the following Boundary,
|
268
|
+
# the pair is removed from the returned instance in Default (:compact and :compacter options
|
269
|
+
# - see {#initialize} for detail)
|
270
|
+
#
|
271
|
+
# @option recursive: [Boolean] if true (Default), map is performed recursively.
|
272
|
+
# @return as self
|
273
|
+
# @see #initialize for the other options (:compact and :compacter)
|
274
|
+
def map_parts(**kwd, &bl)
|
275
|
+
map_parts_core(with_index: false, **kwd, &bl)
|
276
|
+
end
|
277
|
+
|
278
|
+
# map method for parts only, providing also the index (always an even number) to the block, returning a copied self.
|
279
|
+
#
|
280
|
+
# @param (see #map_parts)
|
281
|
+
# @return as self
|
282
|
+
def map_parts_with_index(**kwd, &bl)
|
283
|
+
map_parts_core(with_index: false, **kwd, &bl)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Normalize the content, making sure it has an even number of elements
|
287
|
+
#
|
288
|
+
# The even and odd number elements are, if bare Strings or Array, converted into
|
289
|
+
# Paeagraph and Boundary, or Part, respectively. If not, Exception is raised.
|
290
|
+
# Note nil is conveted into either an empty Paragraph or Boundary.
|
291
|
+
#
|
292
|
+
# @option recursive: [Boolean] if true (Default), normalize recursively.
|
293
|
+
# @option ignore_array_boundary: [Boolean] if true (Default), even if a Boundary element (odd-numbered index) is an Array, ignore it.
|
294
|
+
# @option compact: [Boolean] if true (Default), pairs of nil paragraph and boundary are removed. Otherwise, nil is converted to an empty string.
|
295
|
+
# @option compacter: [Boolean] if true (Default), pairs of nil or empty paragraph and boundary are removed.
|
296
|
+
# @return [self]
|
297
|
+
def normalize!(recursive: true, ignore_array_boundary: true, compact: true, compacter: true)
|
298
|
+
# Trim pairs of consecutive Paragraph and Boundary of nil
|
299
|
+
size_parity = (size.even? ? 0 : 1)
|
300
|
+
if (compact || compacter) && (size > 0+size_parity)
|
301
|
+
((size-2-size_parity)..0).each do |i|
|
302
|
+
# Loop over every Paragraph
|
303
|
+
next if i.odd?
|
304
|
+
slice! i, 2 if compact && !self[i] && !self[i+1]
|
305
|
+
slice! i, 2 if compacter && (!self[i] || self[i].empty?) && (!self[i+1] || self[i+1].empty?)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
i = -1
|
310
|
+
map!{ |ea|
|
311
|
+
i += 1
|
312
|
+
normalize_core(ea, i, recursive: recursive)
|
313
|
+
}
|
314
|
+
insert_original_b4_part(size, Boundary.new('')) if size.odd?
|
315
|
+
self
|
316
|
+
end
|
317
|
+
|
318
|
+
# Non-destructive version of {#normalize!}
|
319
|
+
#
|
320
|
+
# @option recursive: [Boolean] if true (Default), normalize recursively.
|
321
|
+
# @option ignore_array_boundary: [Boolean] if true (Default), even if a Boundary element (odd-numbered index) is an Array, ignore it.
|
322
|
+
# @option compact: [Boolean] if true (Default), pairs of nil paragraph and boundary are removed. Otherwise, nil is converted to an empty string.
|
323
|
+
# @option compacter: [Boolean] if true (Default), pairs of nil or empty paragraph and boundary are removed.
|
324
|
+
# @return as self
|
325
|
+
# @see #normalize!
|
326
|
+
def normalize(recursive: true, ignore_array_boundary: true, compact: true, compacter: true)
|
327
|
+
# Trims pairs of consecutive Paragraph and Boundary of nil
|
328
|
+
arall = to_a
|
329
|
+
size_parity = (size.even? ? 0 : 1)
|
330
|
+
if (compact || compacter) && (size > 0+size_parity)
|
331
|
+
((size-2-size_parity)..0).each do |i|
|
332
|
+
# Loop over every Paragraph
|
333
|
+
next if i.odd?
|
334
|
+
arall.slice! i, 2 if compact && !self[i] && !self[i+1]
|
335
|
+
arall.slice! i, 2 if compacter && (!self[i] || self[i].empty?) && (!self[i+1] || self[i+1].empty?)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
i = -1
|
340
|
+
self.class.new(
|
341
|
+
arall.map{ |ea|
|
342
|
+
i += 1
|
343
|
+
normalize_core(ea, i, recursive: recursive)
|
344
|
+
} + (arall.size.odd? ? [Boundary.new('')] : [])
|
345
|
+
)
|
346
|
+
end
|
347
|
+
|
348
|
+
|
349
|
+
# Returns an array of substantial parts (even-number-index parts), consisting of Part and/or Paragraph
|
350
|
+
#
|
351
|
+
# @return [Array<Part, Paragraph>]
|
352
|
+
# @see #boundaries
|
353
|
+
def parts
|
354
|
+
select.with_index { |_, i| i.even? } rescue select.each_with_index { |_, i| i.even? } # Rescue for Ruby 2.1 or earlier
|
355
|
+
# ret.freeze
|
356
|
+
end
|
357
|
+
|
358
|
+
# Reparses self or a part of it.
|
359
|
+
#
|
360
|
+
# @param str [String]
|
361
|
+
# @option rule: [PlainText::ParseRule] (PlainText::ParseRule::RuleConsecutiveLbs)
|
362
|
+
# @option name: [String, Symbol, Integer, nil] Identifier of rule, if need to specify.
|
363
|
+
# @option range: [Range, nil] Range of indices of self to reparse. In Default, the entire self.
|
364
|
+
# @return [self]
|
365
|
+
def reparse!(rule: PlainText::ParseRule::RuleConsecutiveLbs, name: nil, range: (0..-1))
|
366
|
+
insert range.begin, self.class.parse((range ? self[range] : self), rule: rule, name: name)
|
367
|
+
self
|
368
|
+
end
|
369
|
+
|
370
|
+
# Non-destructive version of {reparse!}
|
371
|
+
#
|
372
|
+
# @param (see #reparse!)
|
373
|
+
# @return [PlainText::Part]
|
374
|
+
def reparse(**kwd)
|
375
|
+
ret = self.dup
|
376
|
+
ret.reparse!(**kwd)
|
377
|
+
ret
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
# Emptifies all the Boundaries immediately before the Boundary at the index and squashes it to the one at it.
|
382
|
+
#
|
383
|
+
# See {#boundary_extended_at} to view them.
|
384
|
+
#
|
385
|
+
# @param index [Integer]
|
386
|
+
# @return [Boundary, nil] nil if a too large index is specified.
|
387
|
+
def squash_boundary_at!(index)
|
388
|
+
(i_pos = get_valid_ipos_for_boundary(index)) || return
|
389
|
+
prt = self[i_pos-1]
|
390
|
+
m = :emptify_last_boundaries!
|
391
|
+
self[i_pos] << prt.public_send(m) if prt.class.method_defined? m
|
392
|
+
self[i_pos]
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
# Wrapper of {#squash_boundary_at!} to loop over the whole {Part}
|
397
|
+
#
|
398
|
+
# @return [self]
|
399
|
+
def squash_boundaryies!
|
400
|
+
each_boundaries_with_index do |ec, i|
|
401
|
+
squash_boundary_at!(i)
|
402
|
+
end
|
403
|
+
self
|
404
|
+
end
|
405
|
+
|
406
|
+
|
407
|
+
# Boundary sub-class name only
|
408
|
+
#
|
409
|
+
# Make sure your class is a child class of Part
|
410
|
+
# Otherwise this method would not be inherited, obviously.
|
411
|
+
#
|
412
|
+
# @example
|
413
|
+
# class PlainText::Part
|
414
|
+
# class Section < self
|
415
|
+
# class Subsection < self; end # It must be a child class!
|
416
|
+
# end
|
417
|
+
# end
|
418
|
+
# ss = PlainText::Part::Section::Subsection.new ["abc"]
|
419
|
+
# ss.subclass_name # => "Section::Subsection"
|
420
|
+
#
|
421
|
+
# @return [String]
|
422
|
+
# @see PlainText::Part#subclass_name
|
423
|
+
def subclass_name
|
424
|
+
printf "__method__=(%s)\n", __method__
|
425
|
+
self.class.name.split(/\A#{Regexp.quote method(__method__).owner.name}::/)[1] || ''
|
426
|
+
end
|
427
|
+
|
428
|
+
##########
|
429
|
+
# Overwriting instance methods of the parent Object or Array class
|
430
|
+
##########
|
431
|
+
|
432
|
+
# Original equal and plus operators of Array
|
433
|
+
hsmethod = {
|
434
|
+
:equal_original_b4_part => :==,
|
435
|
+
:substitute_original_b4_part => :[]=,
|
436
|
+
:insert_original_b4_part => :insert,
|
437
|
+
:delete_at_original_b4_part => :delete_at,
|
438
|
+
:slice_original_b4_part => :slice,
|
439
|
+
:slice_original_b4_part! => :slice!,
|
440
|
+
}
|
441
|
+
|
442
|
+
hsmethod.each_pair do |k, ea_orig|
|
443
|
+
if self.method_defined?(k)
|
444
|
+
# To Developer: If you see this message, switch the DEBUG flag on (-d option) and run it.
|
445
|
+
warn sprintf("WARNING: Method %s#%s has been already defined, which should not be. Contact the code developer. Line %d in %s%s", self.name, k.to_s, __FILE__, __LINE__, ($DEBUG ? "\n"+caller_locations.join("\n").map{|i| " "+i} : ""))
|
446
|
+
else
|
447
|
+
alias_method k, ea_orig
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
451
|
+
alias_method :substit, :substitute_original_b4_part
|
452
|
+
|
453
|
+
########## Most basic methods (Object) ##########
|
454
|
+
|
455
|
+
# @return [String]
|
456
|
+
def inspect
|
457
|
+
self.class.name + super
|
458
|
+
end
|
459
|
+
|
460
|
+
# # clone
|
461
|
+
# #
|
462
|
+
# # Redefines Array#clone so the instance variables are also cloned.
|
463
|
+
# #
|
464
|
+
# # @return [self]
|
465
|
+
# def clone
|
466
|
+
# copied = super
|
467
|
+
# val = (@sep.clone rescue @sep) # rescue in case of immutable.
|
468
|
+
# copied.instance_eval{ @sep = val }
|
469
|
+
# copied
|
470
|
+
# end
|
471
|
+
|
472
|
+
# Equal operator
|
473
|
+
#
|
474
|
+
# Unless both are kind of Part instances, false is returned.
|
475
|
+
# If you want to make comparison in the Array level, do
|
476
|
+
# p1.to_a == a1.to_a
|
477
|
+
#
|
478
|
+
# @param other [Object]
|
479
|
+
def ==(other)
|
480
|
+
return false if !other.class.method_defined?(:to_ary)
|
481
|
+
%i(parts boundaries).each do |ea_m| # %i(...) defined in Ruby 2.0 and later
|
482
|
+
return false if !other.class.method_defined?(ea_m) || (self.public_send(ea_m) != other.public_send(ea_m)) # public_send() defined in Ruby 2.0 (1.9?) and later
|
483
|
+
end
|
484
|
+
super
|
485
|
+
end
|
486
|
+
|
487
|
+
|
488
|
+
# # Multiplication operator
|
489
|
+
# #
|
490
|
+
# # @param other [Integer, String]
|
491
|
+
# # @return as self
|
492
|
+
# def *(other)
|
493
|
+
# super
|
494
|
+
# end
|
495
|
+
|
496
|
+
|
497
|
+
# Plus operator
|
498
|
+
#
|
499
|
+
# @param other [Object]
|
500
|
+
# @return as self
|
501
|
+
def +(other)
|
502
|
+
# ## The following is strict, but a bit redundant.
|
503
|
+
# # is_part = true # Whether "other" is a Part class instance.
|
504
|
+
# # %i(to_ary parts boundaries).each do |ea_m| # %i(...) defined in Ruby 2.0 and later
|
505
|
+
# # is_part &&= other.class.method_defined?(ea_m)
|
506
|
+
# # end
|
507
|
+
|
508
|
+
# begin
|
509
|
+
# other_even_odd =
|
510
|
+
# ([other.parts, other.boundaries] rescue even_odd_arrays(self, size_even: true, filler: ""))
|
511
|
+
# rescue NoMethodError
|
512
|
+
# raise TypeError, sprintf("no implicit conversion of %s into %s", other.class.name, self.class.name)
|
513
|
+
# end
|
514
|
+
|
515
|
+
# # eg., if self is PlainText::Part::Section, the returned object is the same.
|
516
|
+
# ret = self.class.new(self.parts+other_even_odd[0], self.boundaries+other_even_odd[1])
|
517
|
+
ret = self.class.new super
|
518
|
+
ret.normalize!
|
519
|
+
end
|
520
|
+
|
521
|
+
|
522
|
+
# Minus operator
|
523
|
+
#
|
524
|
+
# @param other [Object]
|
525
|
+
# @return as self
|
526
|
+
def -(other)
|
527
|
+
ret = self.class.new super
|
528
|
+
ret.normalize!
|
529
|
+
end
|
530
|
+
|
531
|
+
# Array#<< is now undefined
|
532
|
+
# (because the instances of this class must take always an even number of elements).
|
533
|
+
undef_method(:<<) if method_defined?(:<<)
|
534
|
+
|
535
|
+
# Array#delete_at is now undefined
|
536
|
+
# (because the instances of this class must have always an even number of elements).
|
537
|
+
undef_method(:delete_at) if method_defined?(:delete_at)
|
538
|
+
|
539
|
+
# Returns a partial Part-Array (or Object, if a single Integer is specified)
|
540
|
+
#
|
541
|
+
# Because the returned object is this class of instance (when a pair of Integer or Range
|
542
|
+
# is specified), only an even number of elements, starting from an even number of index,
|
543
|
+
# is allowed.
|
544
|
+
#
|
545
|
+
# @param arg1 [Integer, Range]
|
546
|
+
# @option arg2 [Integer, NilClass]
|
547
|
+
# @return [Object]
|
548
|
+
def [](arg1, *rest)
|
549
|
+
arg2 = rest[0]
|
550
|
+
return super(arg1) if !arg2 && !arg1.class.method_defined?(:exclude_end?)
|
551
|
+
|
552
|
+
check_bracket_args_type_error(arg1, arg2) # Args are now either (Int, Int) or (Range)
|
553
|
+
|
554
|
+
if arg2
|
555
|
+
size2ret = size2extract(arg1, arg2, ignore_error: true) # maybe nil (if the index is too small).
|
556
|
+
raise ArgumentError, ERR_MSGS[:even_num]+" "+ERR_MSGS[:use_to_a] if size2ret.odd?
|
557
|
+
begin
|
558
|
+
raise ArgumentError, "odd index is not allowed as the starting index for #{self.class.name}. It must be even. "+ERR_MSGS[:use_to_a] if positive_array_index_checked(arg1, self).odd?
|
559
|
+
rescue TypeError, IndexError
|
560
|
+
# handled by super
|
561
|
+
end
|
562
|
+
return super
|
563
|
+
end
|
564
|
+
|
565
|
+
begin
|
566
|
+
rang = normalize_index_range(arg1)
|
567
|
+
rescue IndexError #=> err
|
568
|
+
return nil
|
569
|
+
# raise RangeError, err.message
|
570
|
+
end
|
571
|
+
|
572
|
+
raise RangeError if rang.begin < 0 || rang.end < 0
|
573
|
+
|
574
|
+
# The end is smaller than the begin in the positive index. Empty instance of this class is returned.
|
575
|
+
if rang.end < rang.begin
|
576
|
+
return super
|
577
|
+
end
|
578
|
+
|
579
|
+
raise RangeError, "odd index is not allowed as the starting Range for #{sefl.class.name}. It must be even. "+ERR_MSGS[:use_to_a] if rang.begin.odd?
|
580
|
+
size2ret = size2extract(rang, skip_renormalize: true)
|
581
|
+
raise ArgumentError, ERR_MSGS[:even_num]+" "+ERR_MSGS[:use_to_a] if size2ret.odd?
|
582
|
+
super
|
583
|
+
end
|
584
|
+
|
585
|
+
|
586
|
+
# Replaces some of the Array content.
|
587
|
+
#
|
588
|
+
# @param arg1 [Integer, Range]
|
589
|
+
# @option arg2 [Integer, NilClass]
|
590
|
+
# @return [Object]
|
591
|
+
def []=(arg1, *rest)
|
592
|
+
if rest.size == 1
|
593
|
+
arg2, val = [nil, rest[-1]]
|
594
|
+
else
|
595
|
+
arg2, val = rest
|
596
|
+
end
|
597
|
+
|
598
|
+
# Simple substitution to a single element
|
599
|
+
return super(arg1, val) if !arg2 && !arg1.class.method_defined?(:exclude_end?)
|
600
|
+
|
601
|
+
check_bracket_args_type_error(arg1, arg2) # Args are now either (Int, Int) or (Range)
|
602
|
+
|
603
|
+
# raise TypeError, "object to replace must be Array type with an even number of elements." if !val.class.method_defined?(:to_ary) || val.size.odd?
|
604
|
+
|
605
|
+
vals = (val.to_ary rescue [val])
|
606
|
+
if arg2
|
607
|
+
size2delete = size2extract(arg1, arg2, ignore_error: true) # maybe nil (if the index is too small).
|
608
|
+
raise ArgumentError, "odd-even parity of size of array to replace must be identical to that to slice." if size2delete && ((size2delete % 2) != (vals.size % 2))
|
609
|
+
return super
|
610
|
+
end
|
611
|
+
|
612
|
+
begin
|
613
|
+
rang = normalize_index_range(arg1)
|
614
|
+
rescue IndexError => err
|
615
|
+
raise RangeError, err.message
|
616
|
+
end
|
617
|
+
|
618
|
+
raise RangeError if rang.begin < 0 || rang.end < 0
|
619
|
+
|
620
|
+
# The end is smaller than the begin in the positive index. It is the same as insert (default in Ruby), except it returns the replaced Object (which may not be an Array).
|
621
|
+
if rang.end < rang.begin
|
622
|
+
insert(arg1, *vals)
|
623
|
+
return val
|
624
|
+
end
|
625
|
+
|
626
|
+
size2delete = size2extract(rang, skip_renormalize: true)
|
627
|
+
raise ArgumentError, "odd-even parity of size of array to replace must be identical to that to slice." if size2delete && ((size2delete % 2) != (vals.size % 2))
|
628
|
+
ret = super
|
629
|
+
|
630
|
+
# The result may not be in an even number anymore. Correct it.
|
631
|
+
push Boundary.new("") if size.odd?
|
632
|
+
|
633
|
+
# Original method may fill some part of the array with String or even nil.
|
634
|
+
normalize!
|
635
|
+
ret
|
636
|
+
end
|
637
|
+
|
638
|
+
# Array#insert
|
639
|
+
#
|
640
|
+
# The most basic method to add/insert elements to self. Called from {#[]=} and {#push}, for example.
|
641
|
+
#
|
642
|
+
# If ind is greater than size, a number of "", as opposed to nil, are inserted.
|
643
|
+
#
|
644
|
+
# @param ind [Index]
|
645
|
+
# @param rest [Array] This must have an even number of arguments, unless ind is larger than the array size and an odd number.
|
646
|
+
# @option primitive: [String] if true (Def: false), the original {#insert_original_b4_part} is called.
|
647
|
+
# @return [self]
|
648
|
+
def insert(ind, *rest, primitive: false)
|
649
|
+
return insert_original_b4_part(ind, *rest) if primitive
|
650
|
+
ipos = positive_array_index_checked(ind, self)
|
651
|
+
if rest.size.even? && (ipos > size - 1) && ipos.even? # ipos.even? is equivalent to index_para?(ipos), i.e., "is the index for Paragraph?"
|
652
|
+
raise ArgumentError, sprintf("number of arguments (%d) must be odd for index %s.", rest.size, ind)
|
653
|
+
elsif rest.size.odd? && (ipos <= size - 1)
|
654
|
+
raise ArgumentError, sprintf("number of arguments (%d) must be even.", rest.size)
|
655
|
+
end
|
656
|
+
|
657
|
+
if ipos >= size
|
658
|
+
rest = Array.new(ipos - size).map{|i| ""} + rest
|
659
|
+
ipos = size
|
660
|
+
end
|
661
|
+
|
662
|
+
super(ipos, rest)
|
663
|
+
end
|
664
|
+
|
665
|
+
|
666
|
+
# Delete elements and return the deleted content or nil if nothing is deleted.
|
667
|
+
#
|
668
|
+
# The number of elements to be deleted must be even.
|
669
|
+
#
|
670
|
+
# @param arg1 [Integer, Range]
|
671
|
+
# @option arg2 [Integer, NilClass]
|
672
|
+
# @option primitive: [Boolean] if true (Def: false), the original {#insert_original_b4_part} is called.
|
673
|
+
# @return as self or NilClass
|
674
|
+
def slice!(arg1, *rest, primitive: false)
|
675
|
+
return slice_original_b4_part!(arg1, *rest) if primitive
|
676
|
+
|
677
|
+
arg2 = rest[0]
|
678
|
+
|
679
|
+
# Simple substitution to a single element
|
680
|
+
raise ArgumentError, ERR_MSGS[:even_num] if !arg2 && !arg1.class.method_defined?(:exclude_end?)
|
681
|
+
|
682
|
+
check_bracket_args_type_error(arg1, arg2) # Args are now either (Int, Int) or (Range)
|
683
|
+
|
684
|
+
if arg2
|
685
|
+
size2delete = size2extract(arg1, arg2, ignore_error: true) # maybe nil (if the index is too small).
|
686
|
+
# raise ArgumentError, ERR_MSGS[:even_num] if arg2.to_int.odd?
|
687
|
+
raise ArgumentError, ERR_MSGS[:even_num] if size2delete && size2delete.odd?
|
688
|
+
raise ArgumentError, "odd index is not allowed as the starting Range for #{self.class.name}. It must be even." if arg1.odd? # Because the returned value is this class of instance.
|
689
|
+
return super(arg1, *rest)
|
690
|
+
end
|
691
|
+
|
692
|
+
begin
|
693
|
+
rang = normalize_index_range(arg1)
|
694
|
+
rescue IndexError => err
|
695
|
+
raise RangeError, err.message
|
696
|
+
end
|
697
|
+
|
698
|
+
raise RangeError if rang.begin < 0 || rang.end < 0
|
699
|
+
|
700
|
+
return super(arg1, *rest) if (rang.begin > rang.end) # nil or [] is returned
|
701
|
+
|
702
|
+
size2delete = size2extract(rang, skip_renormalize: true)
|
703
|
+
raise ArgumentError, ERR_MSGS[:even_num] if size2delete && size2delete.odd?
|
704
|
+
raise ArgumentError, "odd index is not allowed as the starting Range for #{self.class.name}. It must be even." if rang.begin.odd? # Because the returned value is this class of instance.
|
705
|
+
super(arg1, *rest)
|
706
|
+
end
|
707
|
+
|
708
|
+
|
709
|
+
########## Other methods of Array ##########
|
710
|
+
|
711
|
+
# Array#compact!
|
712
|
+
#
|
713
|
+
# If changed, re-{#normalize!} it.
|
714
|
+
#
|
715
|
+
# @return [self, NilClass]
|
716
|
+
def compact!
|
717
|
+
ret = super
|
718
|
+
ret ? ret.normalize!(recursive: false) : ret
|
719
|
+
end
|
720
|
+
|
721
|
+
# Array#concat
|
722
|
+
#
|
723
|
+
# @see #insert
|
724
|
+
#
|
725
|
+
# @param *rest [Array<Array>]
|
726
|
+
# @return [self]
|
727
|
+
def concat(*rest)
|
728
|
+
insert(size, *(rest.sum([])))
|
729
|
+
end
|
730
|
+
|
731
|
+
# Array#push
|
732
|
+
#
|
733
|
+
# @see #concat
|
734
|
+
#
|
735
|
+
# @param ary [Array]
|
736
|
+
# @return [self]
|
737
|
+
def push(*rest)
|
738
|
+
concat(rest)
|
739
|
+
end
|
740
|
+
|
741
|
+
# {#append} is an alias to {#push}
|
742
|
+
alias :append :push
|
743
|
+
|
744
|
+
# {#slice} is an alias to {#[]}
|
745
|
+
alias :slice :[]
|
746
|
+
|
747
|
+
|
748
|
+
##########
|
749
|
+
# Private instance methods
|
750
|
+
##########
|
751
|
+
|
752
|
+
private
|
753
|
+
|
754
|
+
# Checking whether index-type arguments conform
|
755
|
+
#
|
756
|
+
# After this, it is guaranteed the arguments are either (Integer, Integer) or (Range, nil).
|
757
|
+
#
|
758
|
+
# @param arg1 [Integer, Range] Starting index or Range. Maybe including negative values.
|
759
|
+
# @option arg2 [Integer, NilClass] Size.
|
760
|
+
# @return [NilClass]
|
761
|
+
# @raise [TypeError] if not conforms.
|
762
|
+
def check_bracket_args_type_error(arg1, arg2=nil)
|
763
|
+
if arg2
|
764
|
+
raise TypeError, sprintf("no implicit conversion of #{arg2.class} into Integer") if !arg2.class.method_defined?(:to_int)
|
765
|
+
else
|
766
|
+
raise TypeError if !arg1.class.method_defined?(:exclude_end?)
|
767
|
+
end
|
768
|
+
end
|
769
|
+
private :check_bracket_args_type_error
|
770
|
+
|
771
|
+
|
772
|
+
# Emptifies all the Boundaries immediately before the index and squashes it to the one at it.
|
773
|
+
#
|
774
|
+
# @return [Boundary] all the descendants' last Boundaries merged.
|
775
|
+
def emptify_last_boundaries!
|
776
|
+
return Boundary::Empty.dup if size == 0
|
777
|
+
ret = ""
|
778
|
+
ret << prt.public_send(__method__) if prt.class.method_defined? __method__
|
779
|
+
ret << self[-1]
|
780
|
+
self[-1] = Boundary::Empty.dup
|
781
|
+
ret
|
782
|
+
end
|
783
|
+
private :emptify_last_boundaries!
|
784
|
+
|
785
|
+
|
786
|
+
# Returns a positive Integer index guaranteed to be 1 or greater and smaller than the size.
|
787
|
+
#
|
788
|
+
# @param index [Integer]
|
789
|
+
# @return [Integer, nil] nil if a too large index is specified.
|
790
|
+
def get_valid_ipos_for_boundary(index)
|
791
|
+
i_pos = positive_array_index_checked(index, self)
|
792
|
+
raise ArgumentError, "Index #{index} specified was for Part/Paragraph, which should be for Boundary." if index_para?(i_pos, skip_check: true)
|
793
|
+
(i_pos > size - 1) ? nil : i_pos
|
794
|
+
end
|
795
|
+
private :get_valid_ipos_for_boundary
|
796
|
+
|
797
|
+
|
798
|
+
# Core routine for {#map_boundaries} and similar.
|
799
|
+
#
|
800
|
+
# @option map opts: [Boolean] if true (Default), map is performed. Else just each.
|
801
|
+
# @option with_index: [Boolean] if true (Default: false), yield with also index
|
802
|
+
# @option recursive: [Boolean] if true (Default), map is performed recursively.
|
803
|
+
# @return as self if map: is true, else void
|
804
|
+
def map_boundaries_core(map: true, with_index: false, recursive: true, **kwd, &bl)
|
805
|
+
ind = -1
|
806
|
+
arnew = map{ |ec|
|
807
|
+
ind += 1
|
808
|
+
if recursive && index_para?(ind, skip_check: true) && ec.class.method_defined?(__method__)
|
809
|
+
ec.public_send(__method__, recursive: true, **kwd, &bl)
|
810
|
+
elsif !index_para?(ind, skip_check: true)
|
811
|
+
with_index ? yield(ec, ind) : yield(ec)
|
812
|
+
else
|
813
|
+
ec
|
814
|
+
end
|
815
|
+
}
|
816
|
+
self.class.new arnew, recursive: recursive, **kwd if map
|
817
|
+
end
|
818
|
+
private :map_boundaries_core
|
819
|
+
|
820
|
+
# Core routine for {#map_parts}
|
821
|
+
#
|
822
|
+
# @option map: [Boolean] if true (Default), map is performed. Else just each.
|
823
|
+
# @option with_index: [Boolean] if true (Default: false), yield with also index
|
824
|
+
# @option recursive: [Boolean] if true (Default), map is performed recursively.
|
825
|
+
# @return as self
|
826
|
+
# @see #initialize for the other options (:compact and :compacter)
|
827
|
+
def map_parts_core(map: true, with_index: false, recursive: true, **kwd, &bl)
|
828
|
+
ind = -1
|
829
|
+
new_parts = parts.map{ |ec|
|
830
|
+
ind += 1
|
831
|
+
if recursive && ec.class.method_defined?(__method__)
|
832
|
+
ec.public_send(__method__, recursive: true, **kwd, &bl)
|
833
|
+
else
|
834
|
+
with_index ? yield(ec, ind) : yield(ec)
|
835
|
+
end
|
836
|
+
}
|
837
|
+
self.class.new new_parts, boundaries, recursive: recursive, **kwd if map
|
838
|
+
end
|
839
|
+
private :map_parts_core
|
840
|
+
|
841
|
+
# Core routine for {#normalize!} and {#normalize}
|
842
|
+
#
|
843
|
+
# @param ea [Array, String, NilClass] the element to evaluate
|
844
|
+
# @param i [Integer] Main array index
|
845
|
+
# @option recursive: [Boolean] if true (Default), normalize recursively.
|
846
|
+
# @option ignore_array_boundary: [Boolean] if true (Default), even if a Boundary element (odd-numbered index) is an Array, ignore it.
|
847
|
+
# @return [Part, Paragraph, Boundary]
|
848
|
+
def normalize_core(ea, i, recursive: true, ignore_array_boundary: true)
|
849
|
+
if ea.class.method_defined?(:to_ary)
|
850
|
+
if index_para?(i, skip_check: true) || ignore_array_boundary
|
851
|
+
(/\APlainText::/ =~ ea.class.name && defined?(ea.normalize)) ? (recursive ? ea.normalize : ea) : self.class.new(ea, recursive: recursive)
|
852
|
+
else
|
853
|
+
raise "Index ({#i}) is an Array or its child, but it should be Boundary or String."
|
854
|
+
end
|
855
|
+
elsif ea.class.method_defined?(:to_str)
|
856
|
+
if /\APlainText::/ =~ ea.class.name
|
857
|
+
# Paragraph or Boundary
|
858
|
+
ea.unicode_normalize
|
859
|
+
else
|
860
|
+
if index_para?(i, skip_check: true)
|
861
|
+
Paragraph.new(ea.unicode_normalize || "")
|
862
|
+
else
|
863
|
+
Boundary.new( ea.unicode_normalize || "")
|
864
|
+
end
|
865
|
+
end
|
866
|
+
else
|
867
|
+
raise ArgumentError, "Unrecognised elements for #{self.class}: "+ea.inspect
|
868
|
+
end
|
869
|
+
end
|
870
|
+
private :normalize_core
|
871
|
+
|
872
|
+
|
873
|
+
# Returns (inclusive, i.e., not "...") Range of non-negative indices
|
874
|
+
#
|
875
|
+
# @param rng [Range] It has to be a Range
|
876
|
+
# @return [Range]
|
877
|
+
# @raise [IndexError] if too negative index is specified.
|
878
|
+
def normalize_index_range(rng, **kwd)
|
879
|
+
# NOTE to developers: (0..-1).to_a returns [] (!)
|
880
|
+
arpair = [rng.begin, rng.end].to_a.map{ |i| positive_array_index_checked(i, self, **kwd) }
|
881
|
+
arpair[1] -= 1 if rng.exclude_end?
|
882
|
+
(arpair[0]..arpair[1])
|
883
|
+
end
|
884
|
+
private :normalize_index_range
|
885
|
+
|
886
|
+
|
887
|
+
# Returns the size (the number of elements) to extract
|
888
|
+
#
|
889
|
+
# taking into account the size of self
|
890
|
+
#
|
891
|
+
# 1. if (3, 2) is specified when self.size==4, this returns 1.
|
892
|
+
# 2. if (3..2) is specified, this returns 0.
|
893
|
+
#
|
894
|
+
# Make sure to call {#check_bracket_args_type_error} beforehand.
|
895
|
+
#
|
896
|
+
# @param arg1 [Integer, Range] Starting index or Range. Maybe including negative values.
|
897
|
+
# @option arg2 [Integer, NilClass] Size.
|
898
|
+
# @option ignore_error: [Boolean] if true (Def: false), nil is returned instead of raising IndexError (when a negative index is too small)
|
899
|
+
# @option skip_renormalize: [Boolean] if true (Def: false), the given Range is assumed to be already normalized by {#normalize_index_range}
|
900
|
+
# @return [Integer, NilClass] nil if an Error is raised with ignore_error being true
|
901
|
+
def size2extract(arg1, arg2=nil, ignore_error: false, skip_renormalize: false, **kwd)
|
902
|
+
begin
|
903
|
+
if arg1.class.method_defined? :exclude_end?
|
904
|
+
rng = arg1
|
905
|
+
rng = normalize_index_range(rng, **kwd) if !skip_renormalize
|
906
|
+
else
|
907
|
+
ipos = positive_array_index_checked(arg1, self)
|
908
|
+
rng = (ipos..(ipos+arg2.to_int-1))
|
909
|
+
end
|
910
|
+
return 0 if rng.begin > size-1
|
911
|
+
return 0 if rng.begin > rng.end
|
912
|
+
return [rng.end, size-1].min-rng.begin+1
|
913
|
+
rescue IndexError
|
914
|
+
return nil if ignore_error
|
915
|
+
raise
|
916
|
+
end
|
917
|
+
end
|
918
|
+
private :size2extract
|
919
|
+
|
920
|
+
end # class Part < Array
|
921
|
+
|
922
|
+
end # module PlainText
|
923
|
+
|
924
|
+
|
925
|
+
####################################################
|
926
|
+
# Modifies Array
|
927
|
+
####################################################
|
928
|
+
|
929
|
+
class Array
|
930
|
+
|
931
|
+
# Original equal and plus operators of Array
|
932
|
+
hsmethod = {
|
933
|
+
:equal_original_b4_part? => :== ,
|
934
|
+
# :plus_operator_original_b4_part => :+
|
935
|
+
}
|
936
|
+
|
937
|
+
hsmethod.each_pair do |k, ea_orig|
|
938
|
+
if self.method_defined?(k)
|
939
|
+
# To Developer: If you see this message, switch the DEBUG flag on (-d option) and run it.
|
940
|
+
warn sprintf("WARNING: Method %s#%s has been already defined, which should not be. Contact the code developer. Line %d in %s%s", self.name, k.to_s, __FILE__, __LINE__, ($DEBUG ? "\n"+caller_locations.join("\n").map{|i| " "+i} : ""))
|
941
|
+
else
|
942
|
+
alias_method k, ea_orig
|
943
|
+
end
|
944
|
+
end
|
945
|
+
|
946
|
+
# Equal operator modified to deal with {PlainText::Part}
|
947
|
+
#
|
948
|
+
# @param other [Object]
|
949
|
+
def ==(other)
|
950
|
+
return false if !other.class.method_defined?(:to_ary)
|
951
|
+
%i(parts boundaries).each do |ea_m| # %i(...) defined in Ruby 2.0 and later
|
952
|
+
return equal_original_b4_part?(other) if !other.class.method_defined?(ea_m)
|
953
|
+
return false if !self.class.method_defined?(ea_m) || (self.public_send(ea_m) != other.public_send(ea_m)) # public_send() defined in Ruby 2.0 (1.9?) and later
|
954
|
+
end
|
955
|
+
true
|
956
|
+
end
|
957
|
+
|
958
|
+
end
|
959
|
+
|
960
|
+
####################################################
|
961
|
+
# require (after the module is defined)
|
962
|
+
####################################################
|
963
|
+
|
964
|
+
require 'plain_text/part/paragraph'
|
965
|
+
require 'plain_text/part/boundary'
|
966
|
+
require "plain_text/parse_rule"
|
967
|
+
|
968
|
+
#######
|
969
|
+
# idea
|
970
|
+
# * Potentially, a specification of the main array of always an odd number of elements is possible: [Para, Boundary, B, P, B, P], either or both of the last and first of which may be empty.
|
971
|
+
# * then joining is very straightforward.
|
972
|
+
# * A trouble is, open/close tab structures like HTML/LaTeX-type (+<ul>...</ul>+) are not represented very well.
|
973
|
+
#
|