twitter-text-kow 1.3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,455 @@
1
+ # Copyright 2018 Twitter, Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+
5
+ # encoding: utf-8
6
+
7
+ require 'set'
8
+ require 'twitter-text/hash_helper'
9
+
10
+ module Twitter
11
+ module TwitterText
12
+ # A module for including Tweet auto-linking in a class. The primary use of this is for helpers/views so they can auto-link
13
+ # usernames, lists, hashtags and URLs.
14
+ module Autolink extend self
15
+ # Default CSS class for auto-linked lists
16
+ DEFAULT_LIST_CLASS = "tweet-url list-slug".freeze
17
+ # Default CSS class for auto-linked usernames
18
+ DEFAULT_USERNAME_CLASS = "tweet-url username".freeze
19
+ # Default CSS class for auto-linked hashtags
20
+ DEFAULT_HASHTAG_CLASS = "tweet-url hashtag".freeze
21
+ # Default CSS class for auto-linked cashtags
22
+ DEFAULT_CASHTAG_CLASS = "tweet-url cashtag".freeze
23
+
24
+ # Default URL base for auto-linked usernames
25
+ DEFAULT_USERNAME_URL_BASE = "https://twitter.com/".freeze
26
+ # Default URL base for auto-linked lists
27
+ DEFAULT_LIST_URL_BASE = "https://twitter.com/".freeze
28
+ # Default URL base for auto-linked hashtags
29
+ DEFAULT_HASHTAG_URL_BASE = "https://twitter.com/search?q=%23".freeze
30
+ # Default URL base for auto-linked cashtags
31
+ DEFAULT_CASHTAG_URL_BASE = "https://twitter.com/search?q=%24".freeze
32
+
33
+ # Default attributes for invisible span tag
34
+ DEFAULT_INVISIBLE_TAG_ATTRS = "style='position:absolute;left:-9999px;'".freeze
35
+
36
+ DEFAULT_OPTIONS = {
37
+ :list_class => DEFAULT_LIST_CLASS,
38
+ :username_class => DEFAULT_USERNAME_CLASS,
39
+ :hashtag_class => DEFAULT_HASHTAG_CLASS,
40
+ :cashtag_class => DEFAULT_CASHTAG_CLASS,
41
+
42
+ :username_url_base => DEFAULT_USERNAME_URL_BASE,
43
+ :list_url_base => DEFAULT_LIST_URL_BASE,
44
+ :hashtag_url_base => DEFAULT_HASHTAG_URL_BASE,
45
+ :cashtag_url_base => DEFAULT_CASHTAG_URL_BASE,
46
+
47
+ :invisible_tag_attrs => DEFAULT_INVISIBLE_TAG_ATTRS
48
+ }.freeze
49
+
50
+ def auto_link_with_json(text, json, options = {})
51
+ # concatenate entities
52
+ entities = json.values().flatten()
53
+
54
+ # map JSON entity to twitter-text entity
55
+ # be careful not to alter arguments received
56
+ entities.map! do |entity|
57
+ entity = HashHelper.symbolize_keys(entity)
58
+ # hashtag
59
+ entity[:hashtag] = entity[:text] if entity[:text]
60
+ entity
61
+ end
62
+
63
+ auto_link_entities(text, entities, options)
64
+ end
65
+
66
+ def auto_link_entities(text, entities, options = {}, &block)
67
+ return text if entities.empty?
68
+
69
+ # NOTE deprecate these attributes not options keys in options hash, then use html_attrs
70
+ options = DEFAULT_OPTIONS.merge(options)
71
+ options[:html_attrs] = extract_html_attrs_from_options!(options)
72
+ options[:html_attrs][:rel] ||= "nofollow" unless options[:suppress_no_follow]
73
+ options[:html_attrs][:target] = "_blank" if options[:target_blank] == true
74
+
75
+ Twitter::TwitterText::Rewriter.rewrite_entities(text.dup, entities) do |entity, chars|
76
+ if entity[:url]
77
+ link_to_url(entity, chars, options, &block)
78
+ elsif entity[:hashtag]
79
+ link_to_hashtag(entity, chars, options, &block)
80
+ elsif entity[:screen_name]
81
+ link_to_screen_name(entity, chars, options, &block)
82
+ elsif entity[:cashtag]
83
+ link_to_cashtag(entity, chars, options, &block)
84
+ elsif entity[:emoji]
85
+ entity[:emoji]
86
+ end
87
+ end
88
+ end
89
+
90
+ # Add <tt><a></a></tt> tags around the usernames, lists, hashtags and URLs in the provided <tt>text</tt>.
91
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash:
92
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
93
+ # and place in the <tt><a></tt> tag.
94
+ #
95
+ # <tt>:url_class</tt>:: class to add to url <tt><a></tt> tags
96
+ # <tt>:list_class</tt>:: class to add to list <tt><a></tt> tags
97
+ # <tt>:username_class</tt>:: class to add to username <tt><a></tt> tags
98
+ # <tt>:hashtag_class</tt>:: class to add to hashtag <tt><a></tt> tags
99
+ # <tt>:cashtag_class</tt>:: class to add to cashtag <tt><a></tt> tags
100
+ # <tt>:username_url_base</tt>:: the value for <tt>href</tt> attribute on username links. The <tt>@username</tt> (minus the <tt>@</tt>) will be appended at the end of this.
101
+ # <tt>:list_url_base</tt>:: the value for <tt>href</tt> attribute on list links. The <tt>@username/list</tt> (minus the <tt>@</tt>) will be appended at the end of this.
102
+ # <tt>:hashtag_url_base</tt>:: the value for <tt>href</tt> attribute on hashtag links. The <tt>#hashtag</tt> (minus the <tt>#</tt>) will be appended at the end of this.
103
+ # <tt>:cashtag_url_base</tt>:: the value for <tt>href</tt> attribute on cashtag links. The <tt>$cashtag</tt> (minus the <tt>$</tt>) will be appended at the end of this.
104
+ # <tt>:invisible_tag_attrs</tt>:: HTML attribute to add to invisible span tags
105
+ # <tt>:username_include_symbol</tt>:: place the <tt>@</tt> symbol within username and list links
106
+ # <tt>:suppress_lists</tt>:: disable auto-linking to lists
107
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
108
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
109
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
110
+ # <tt>:url_target</tt>:: the value for <tt>target</tt> attribute on URL links.
111
+ # <tt>:target_blank</tt>:: adds <tt>target="_blank"</tt> to all auto_linked items username / hashtag / cashtag links / urls
112
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
113
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
114
+ def auto_link(text, options = {}, &block)
115
+ auto_link_entities(text, Extractor.extract_entities_with_indices(text, :extract_url_without_protocol => false), options, &block)
116
+ end
117
+
118
+ # Add <tt><a></a></tt> tags around the usernames and lists in the provided <tt>text</tt>. The
119
+ # <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
120
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
121
+ # and place in the <tt><a></tt> tag.
122
+ #
123
+ # <tt>:list_class</tt>:: class to add to list <tt><a></tt> tags
124
+ # <tt>:username_class</tt>:: class to add to username <tt><a></tt> tags
125
+ # <tt>:username_url_base</tt>:: the value for <tt>href</tt> attribute on username links. The <tt>@username</tt> (minus the <tt>@</tt>) will be appended at the end of this.
126
+ # <tt>:list_url_base</tt>:: the value for <tt>href</tt> attribute on list links. The <tt>@username/list</tt> (minus the <tt>@</tt>) will be appended at the end of this.
127
+ # <tt>:username_include_symbol</tt>:: place the <tt>@</tt> symbol within username and list links
128
+ # <tt>:suppress_lists</tt>:: disable auto-linking to lists
129
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
130
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
131
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
132
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
133
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
134
+ def auto_link_usernames_or_lists(text, options = {}, &block) # :yields: list_or_username
135
+ auto_link_entities(text, Extractor.extract_mentions_or_lists_with_indices(text), options, &block)
136
+ end
137
+
138
+ # Add <tt><a></a></tt> tags around the hashtags in the provided <tt>text</tt>.
139
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
140
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
141
+ # and place in the <tt><a></tt> tag.
142
+ #
143
+ # <tt>:hashtag_class</tt>:: class to add to hashtag <tt><a></tt> tags
144
+ # <tt>:hashtag_url_base</tt>:: the value for <tt>href</tt> attribute. The hashtag text (minus the <tt>#</tt>) will be appended at the end of this.
145
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
146
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
147
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
148
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
149
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
150
+ def auto_link_hashtags(text, options = {}, &block) # :yields: hashtag_text
151
+ auto_link_entities(text, Extractor.extract_hashtags_with_indices(text), options, &block)
152
+ end
153
+
154
+ # Add <tt><a></a></tt> tags around the cashtags in the provided <tt>text</tt>.
155
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
156
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
157
+ # and place in the <tt><a></tt> tag.
158
+ #
159
+ # <tt>:cashtag_class</tt>:: class to add to cashtag <tt><a></tt> tags
160
+ # <tt>:cashtag_url_base</tt>:: the value for <tt>href</tt> attribute. The cashtag text (minus the <tt>$</tt>) will be appended at the end of this.
161
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
162
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
163
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
164
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
165
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
166
+ def auto_link_cashtags(text, options = {}, &block) # :yields: cashtag_text
167
+ auto_link_entities(text, Extractor.extract_cashtags_with_indices(text), options, &block)
168
+ end
169
+
170
+ # Add <tt><a></a></tt> tags around the URLs in the provided <tt>text</tt>.
171
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
172
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
173
+ # and place in the <tt><a></tt> tag.
174
+ #
175
+ # <tt>:url_class</tt>:: class to add to url <tt><a></tt> tags
176
+ # <tt>:invisible_tag_attrs</tt>:: HTML attribute to add to invisible span tags
177
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
178
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
179
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
180
+ # <tt>:url_target</tt>:: the value for <tt>target</tt> attribute on URL links.
181
+ # <tt>:link_attribute_block</tt>:: function to modify the attributes of a link based on the entity. called with |entity, attributes| params, and should modify the attributes hash.
182
+ # <tt>:link_text_block</tt>:: function to modify the text of a link based on the entity. called with |entity, text| params, and should return a modified text.
183
+ def auto_link_urls(text, options = {}, &block)
184
+ auto_link_entities(text, Extractor.extract_urls_with_indices(text, :extract_url_without_protocol => false), options, &block)
185
+ end
186
+
187
+ # These methods are deprecated, will be removed in future.
188
+ extend Deprecation
189
+
190
+ # <b>Deprecated</b>: Please use auto_link_urls instead.
191
+ # Add <tt><a></a></tt> tags around the URLs in the provided <tt>text</tt>.
192
+ # Any elements in the <tt>href_options</tt> hash will be converted to HTML attributes
193
+ # and place in the <tt><a></tt> tag.
194
+ # Unless <tt>href_options</tt> contains <tt>:suppress_no_follow</tt>
195
+ # the <tt>rel="nofollow"</tt> attribute will be added.
196
+ alias :auto_link_urls_custom :auto_link_urls
197
+ deprecate :auto_link_urls_custom, :auto_link_urls
198
+
199
+ private
200
+
201
+ HTML_ENTITIES = {
202
+ '&' => '&amp;',
203
+ '>' => '&gt;',
204
+ '<' => '&lt;',
205
+ '"' => '&quot;',
206
+ "'" => '&#39;'
207
+ }
208
+
209
+ def html_escape(text)
210
+ text && text.to_s.gsub(/[&"'><]/) do |character|
211
+ HTML_ENTITIES[character]
212
+ end
213
+ end
214
+
215
+ # NOTE We will make this private in future.
216
+ public :html_escape
217
+
218
+ # Options which should not be passed as HTML attributes
219
+ OPTIONS_NOT_ATTRIBUTES = Set.new([
220
+ :url_class, :list_class, :username_class, :hashtag_class, :cashtag_class,
221
+ :username_url_base, :list_url_base, :hashtag_url_base, :cashtag_url_base,
222
+ :username_url_block, :list_url_block, :hashtag_url_block, :cashtag_url_block, :link_url_block,
223
+ :username_include_symbol, :suppress_lists, :suppress_no_follow, :url_entities,
224
+ :invisible_tag_attrs, :symbol_tag, :text_with_symbol_tag, :url_target, :target_blank,
225
+ :link_attribute_block, :link_text_block
226
+ ]).freeze
227
+
228
+ def extract_html_attrs_from_options!(options)
229
+ html_attrs = {}
230
+ options.reject! do |key, value|
231
+ unless OPTIONS_NOT_ATTRIBUTES.include?(key)
232
+ html_attrs[key] = value
233
+ true
234
+ end
235
+ end
236
+ html_attrs
237
+ end
238
+
239
+ def url_entities_hash(url_entities)
240
+ (url_entities || {}).inject({}) do |entities, entity|
241
+ # be careful not to alter arguments received
242
+ _entity = HashHelper.symbolize_keys(entity)
243
+ entities[_entity[:url]] = _entity
244
+ entities
245
+ end
246
+ end
247
+
248
+ def link_to_url(entity, chars, options = {})
249
+ url = entity[:url]
250
+
251
+ href = if options[:link_url_block]
252
+ options[:link_url_block].call(url)
253
+ else
254
+ url
255
+ end
256
+
257
+ # NOTE auto link to urls do not use any default values and options
258
+ # like url_class but use suppress_no_follow.
259
+ html_attrs = options[:html_attrs].dup
260
+ html_attrs[:class] = options[:url_class] if options.key?(:url_class)
261
+
262
+ # add target attribute only if :url_target is specified
263
+ html_attrs[:target] = options[:url_target] if options.key?(:url_target)
264
+
265
+ url_entities = url_entities_hash(options[:url_entities])
266
+
267
+ # use entity from urlEntities if available
268
+ url_entity = url_entities[url] || entity
269
+ link_text = if url_entity[:display_url]
270
+ html_attrs[:title] ||= url_entity[:expanded_url]
271
+ link_url_with_entity(url_entity, options)
272
+ else
273
+ html_escape(url)
274
+ end
275
+
276
+ link_to_text(entity, link_text, href, html_attrs, options)
277
+ end
278
+
279
+ def link_url_with_entity(entity, options)
280
+ display_url = entity[:display_url]
281
+ expanded_url = entity[:expanded_url]
282
+ invisible_tag_attrs = options[:invisible_tag_attrs] || DEFAULT_INVISIBLE_TAG_ATTRS
283
+
284
+ # Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
285
+ # should contain the full original URL (expanded_url), not the display URL.
286
+ #
287
+ # Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
288
+ # font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
289
+ # Elements with font-size:0 get copied even though they are not visible.
290
+ # Note that display:none doesn't work here. Elements with display:none don't get copied.
291
+ #
292
+ # Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
293
+ # wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
294
+ # everything with the tco-ellipsis class.
295
+ #
296
+ # Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/username/status/1234/photo/1
297
+ # For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
298
+ # For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine.
299
+ display_url_sans_ellipses = display_url.gsub("…", "")
300
+
301
+ if expanded_url.include?(display_url_sans_ellipses)
302
+ before_display_url, after_display_url = expanded_url.split(display_url_sans_ellipses, 2)
303
+ preceding_ellipsis = /\A…/.match(display_url).to_s
304
+ following_ellipsis = /…\z/.match(display_url).to_s
305
+
306
+ # As an example: The user tweets "hi http://longdomainname.com/foo"
307
+ # This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
308
+ # This will get rendered as:
309
+ # <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
310
+ # …
311
+ # <!-- There's a chance the onCopy event handler might not fire. In case that happens,
312
+ # we include an &nbsp; here so that the … doesn't bump up against the URL and ruin it.
313
+ # The &nbsp; is inside the tco-ellipsis span so that when the onCopy handler *does*
314
+ # fire, it doesn't get copied. Otherwise the copied text would have two spaces in a row,
315
+ # e.g. "hi http://longdomainname.com/foo".
316
+ # <span style='font-size:0'>&nbsp;</span>
317
+ # </span>
318
+ # <span style='font-size:0'> <!-- This stuff should get copied but not displayed -->
319
+ # http://longdomai
320
+ # </span>
321
+ # <span class='js-display-url'> <!-- This stuff should get displayed *and* copied -->
322
+ # nname.com/foo
323
+ # </span>
324
+ # <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
325
+ # <span style='font-size:0'>&nbsp;</span>
326
+ # …
327
+ # </span>
328
+ %(<span class="tco-ellipsis">#{preceding_ellipsis}<span #{invisible_tag_attrs}>&nbsp;</span></span>) <<
329
+ %(<span #{invisible_tag_attrs}>#{html_escape(before_display_url)}</span>) <<
330
+ %(<span class="js-display-url">#{html_escape(display_url_sans_ellipses)}</span>) <<
331
+ %(<span #{invisible_tag_attrs}>#{html_escape(after_display_url)}</span>) <<
332
+ %(<span class="tco-ellipsis"><span #{invisible_tag_attrs}>&nbsp;</span>#{following_ellipsis}</span>)
333
+ else
334
+ html_escape(display_url)
335
+ end
336
+ end
337
+
338
+ def link_to_hashtag(entity, chars, options = {})
339
+ hash = chars[entity[:indices].first]
340
+ hashtag = entity[:hashtag]
341
+ hashtag = yield(hashtag) if block_given?
342
+ hashtag_class = options[:hashtag_class].to_s
343
+
344
+ if hashtag.match Twitter::TwitterText::Regex::REGEXEN[:rtl_chars]
345
+ hashtag_class += ' rtl'
346
+ end
347
+
348
+ href = if options[:hashtag_url_block]
349
+ options[:hashtag_url_block].call(hashtag)
350
+ else
351
+ "#{options[:hashtag_url_base]}#{hashtag}"
352
+ end
353
+
354
+ html_attrs = {
355
+ :class => hashtag_class,
356
+ # FIXME As our conformance test, hash in title should be half-width,
357
+ # this should be bug of conformance data.
358
+ :title => "##{hashtag}"
359
+ }.merge(options[:html_attrs])
360
+
361
+ link_to_text_with_symbol(entity, hash, hashtag, href, html_attrs, options)
362
+ end
363
+
364
+ def link_to_cashtag(entity, chars, options = {})
365
+ dollar = chars[entity[:indices].first]
366
+ cashtag = entity[:cashtag]
367
+ cashtag = yield(cashtag) if block_given?
368
+
369
+ href = if options[:cashtag_url_block]
370
+ options[:cashtag_url_block].call(cashtag)
371
+ else
372
+ "#{options[:cashtag_url_base]}#{cashtag}"
373
+ end
374
+
375
+ html_attrs = {
376
+ :class => "#{options[:cashtag_class]}",
377
+ :title => "$#{cashtag}"
378
+ }.merge(options[:html_attrs])
379
+
380
+ link_to_text_with_symbol(entity, dollar, cashtag, href, html_attrs, options)
381
+ end
382
+
383
+ def link_to_screen_name(entity, chars, options = {})
384
+ name = "#{entity[:screen_name]}#{entity[:list_slug]}"
385
+
386
+ chunk = name.dup
387
+ chunk = yield(chunk) if block_given?
388
+
389
+ at = chars[entity[:indices].first]
390
+
391
+ html_attrs = options[:html_attrs].dup
392
+
393
+ if entity[:list_slug] && !entity[:list_slug].empty? && !options[:suppress_lists]
394
+ href = if options[:list_url_block]
395
+ options[:list_url_block].call(name)
396
+ else
397
+ "#{options[:list_url_base]}#{name}"
398
+ end
399
+ html_attrs[:class] ||= "#{options[:list_class]}"
400
+ else
401
+ href = if options[:username_url_block]
402
+ options[:username_url_block].call(chunk)
403
+ else
404
+ "#{options[:username_url_base]}#{name}"
405
+ end
406
+ html_attrs[:class] ||= "#{options[:username_class]}"
407
+ end
408
+
409
+ link_to_text_with_symbol(entity, at, chunk, href, html_attrs, options)
410
+ end
411
+
412
+ def link_to_text_with_symbol(entity, symbol, text, href, attributes = {}, options = {})
413
+ tagged_symbol = options[:symbol_tag] ? "<#{options[:symbol_tag]}>#{symbol}</#{options[:symbol_tag]}>" : symbol
414
+ text = html_escape(text)
415
+ tagged_text = options[:text_with_symbol_tag] ? "<#{options[:text_with_symbol_tag]}>#{text}</#{options[:text_with_symbol_tag]}>" : text
416
+ if options[:username_include_symbol] || symbol !~ Twitter::TwitterText::Regex::REGEXEN[:at_signs]
417
+ "#{link_to_text(entity, tagged_symbol + tagged_text, href, attributes, options)}"
418
+ else
419
+ "#{tagged_symbol}#{link_to_text(entity, tagged_text, href, attributes, options)}"
420
+ end
421
+ end
422
+
423
+ def link_to_text(entity, text, href, attributes = {}, options = {})
424
+ attributes[:href] = href
425
+ options[:link_attribute_block].call(entity, attributes) if options[:link_attribute_block]
426
+ text = options[:link_text_block].call(entity, text) if options[:link_text_block]
427
+ %(<a#{tag_attrs(attributes)}>#{text}</a>)
428
+ end
429
+
430
+ BOOLEAN_ATTRIBUTES = Set.new([:disabled, :readonly, :multiple, :checked]).freeze
431
+
432
+ def tag_attrs(attributes)
433
+ attributes.keys.sort_by{|k| k.to_s}.inject("") do |attrs, key|
434
+ value = attributes[key]
435
+
436
+ if BOOLEAN_ATTRIBUTES.include?(key)
437
+ value = value ? key : nil
438
+ end
439
+
440
+ unless value.nil?
441
+ value = case value
442
+ when Array
443
+ value.compact.join(" ")
444
+ else
445
+ value
446
+ end
447
+ attrs << %( #{html_escape(key)}="#{html_escape(value)}")
448
+ end
449
+
450
+ attrs
451
+ end
452
+ end
453
+ end
454
+ end
455
+ end
@@ -0,0 +1,68 @@
1
+ # Copyright 2018 Twitter, Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+
5
+ # encoding: UTF-8
6
+
7
+ module Twitter
8
+ module TwitterText
9
+ class Configuration
10
+ require 'json'
11
+
12
+ PARSER_VERSION_CLASSIC = "v1"
13
+ PARSER_VERSION_WEIGHTED = "v2"
14
+ PARSER_VERSION_EMOJI_PARSING = "v3"
15
+
16
+ PARSER_VERSION_DEFAULT = PARSER_VERSION_WEIGHTED
17
+
18
+ class << self
19
+ attr_accessor :default_configuration
20
+ end
21
+
22
+ attr_reader :version, :max_weighted_tweet_length, :scale
23
+ attr_reader :default_weight, :transformed_url_length, :ranges
24
+ attr_reader :emoji_parsing_enabled
25
+
26
+ CONFIG_V1 = File.join(
27
+ File.expand_path('../../../config', __FILE__), # project root
28
+ "#{PARSER_VERSION_CLASSIC}.json"
29
+ )
30
+
31
+ CONFIG_V2 = File.join(
32
+ File.expand_path('../../../config', __FILE__), # project root
33
+ "#{PARSER_VERSION_WEIGHTED}.json"
34
+ )
35
+
36
+ CONFIG_V3 = File.join(
37
+ File.expand_path('../../../config', __FILE__), # project root
38
+ "#{PARSER_VERSION_EMOJI_PARSING}.json"
39
+ )
40
+
41
+ def self.parse_string(string, options = {})
42
+ JSON.parse(string, options.merge(symbolize_names: true))
43
+ end
44
+
45
+ def self.parse_file(filename)
46
+ string = File.open(filename, 'rb') { |f| f.read }
47
+ parse_string(string)
48
+ end
49
+
50
+ def self.configuration_from_file(filename)
51
+ config = parse_file(filename)
52
+ config ? self.new(config) : nil
53
+ end
54
+
55
+ def initialize(config = {})
56
+ @version = config[:version]
57
+ @max_weighted_tweet_length = config[:maxWeightedTweetLength]
58
+ @scale = config[:scale]
59
+ @default_weight = config[:defaultWeight]
60
+ @transformed_url_length = config[:transformedURLLength]
61
+ @emoji_parsing_enabled = config[:emojiParsingEnabled]
62
+ @ranges = config[:ranges].map { |range| Twitter::TwitterText::WeightedRange.new(range) } if config.key?(:ranges) && config[:ranges].is_a?(Array)
63
+ end
64
+
65
+ self.default_configuration = self.configuration_from_file(CONFIG_V3)
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright 2018 Twitter, Inc.
2
+ # Licensed under the Apache License, Version 2.0
3
+ # http://www.apache.org/licenses/LICENSE-2.0
4
+
5
+ module Twitter
6
+ module TwitterText
7
+ module Deprecation
8
+ def deprecate(method, new_method = nil)
9
+ deprecated_method = :"deprecated_#{method}"
10
+ message = "Deprecation: `#{method}` is deprecated."
11
+ message << " Please use `#{new_method}` instead." if new_method
12
+
13
+ alias_method(deprecated_method, method)
14
+ define_method method do |*args, &block|
15
+ warn message unless $TESTING
16
+ send(deprecated_method, *args, &block)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+ # emoji_regex.rb
3
+ #
4
+ # Copyright © 2018 Twitter. All rights reserved.
5
+ #
6
+ # DO NOT MODIFY THIS FILE -- it is generated for twitter-text automatically
7
+
8
+ # encoding: utf-8
9
+
10
+ module Twitter
11
+ module TwitterText
12
+ class Regex
13
+ class Emoji
14
+ REGEXEN = {} # :nodoc:
15
+
16
+ # This regex pattern matches a single emoji
17
+ REGEXEN[:valid_emoji] = %r{
18
+ (?:\u{01f468}\u{01f3fb}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fc}-\u{01f3ff}]|\u{01f468}\u{01f3fc}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fb}\u{01f3fd}-\u{01f3ff}]|\u{01f468}\u{01f3fd}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fb}\u{01f3fc}\u{01f3fe}\u{01f3ff}]|\u{01f468}\u{01f3fe}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fb}-\u{01f3fd}\u{01f3ff}]|\u{01f468}\u{01f3ff}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fb}-\u{01f3fe}]|\u{01f469}\u{01f3fb}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fc}-\u{01f3ff}]|\u{01f469}\u{01f3fb}\u200d\u{01f91d}\u200d\u{01f469}[\u{01f3fc}-\u{01f3ff}]|\u{01f469}\u{01f3fc}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fb}\u{01f3fd}-\u{01f3ff}]|\u{01f469}\u{01f3fc}\u200d\u{01f91d}\u200d\u{01f469}[\u{01f3fb}\u{01f3fd}-\u{01f3ff}]|\u{01f469}\u{01f3fd}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fb}\u{01f3fc}\u{01f3fe}\u{01f3ff}]|\u{01f469}\u{01f3fd}\u200d\u{01f91d}\u200d\u{01f469}[\u{01f3fb}\u{01f3fc}\u{01f3fe}\u{01f3ff}]|\u{01f469}\u{01f3fe}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fb}-\u{01f3fd}\u{01f3ff}]|\u{01f469}\u{01f3fe}\u200d\u{01f91d}\u200d\u{01f469}[\u{01f3fb}-\u{01f3fd}\u{01f3ff}]|\u{01f469}\u{01f3ff}\u200d\u{01f91d}\u200d\u{01f468}[\u{01f3fb}-\u{01f3fe}]|\u{01f469}\u{01f3ff}\u200d\u{01f91d}\u200d\u{01f469}[\u{01f3fb}-\u{01f3fe}]|\u{01f9d1}\u{01f3fb}\u200d\u{01f91d}\u200d\u{01f9d1}[\u{01f3fb}-\u{01f3ff}]|\u{01f9d1}\u{01f3fc}\u200d\u{01f91d}\u200d\u{01f9d1}[\u{01f3fb}-\u{01f3ff}]|\u{01f9d1}\u{01f3fd}\u200d\u{01f91d}\u200d\u{01f9d1}[\u{01f3fb}-\u{01f3ff}]|\u{01f9d1}\u{01f3fe}\u200d\u{01f91d}\u200d\u{01f9d1}[\u{01f3fb}-\u{01f3ff}]|\u{01f9d1}\u{01f3ff}\u200d\u{01f91d}\u200d\u{01f9d1}[\u{01f3fb}-\u{01f3ff}]|\u{01f9d1}\u200d\u{01f91d}\u200d\u{01f9d1}|\u{01f46b}[\u{01f3fb}-\u{01f3ff}]|\u{01f46c}[\u{01f3fb}-\u{01f3ff}]|\u{01f46d}[\u{01f3fb}-\u{01f3ff}]|[\u{01f46b}-\u{01f46d}])|[\u{01f468}\u{01f469}\u{01f9d1}][\u{01f3fb}-\u{01f3ff}]?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|[\u{01f33e}\u{01f373}\u{01f393}\u{01f3a4}\u{01f3a8}\u{01f3eb}\u{01f3ed}\u{01f4bb}\u{01f4bc}\u{01f527}\u{01f52c}\u{01f680}\u{01f692}\u{01f9af}-\u{01f9b3}\u{01f9bc}\u{01f9bd}])|[\u26f9\u{01f3cb}\u{01f3cc}\u{01f574}\u{01f575}](?:[\ufe0f\u{01f3fb}-\u{01f3ff}]\u200d[\u2640\u2642]\ufe0f)|[\u{01f3c3}\u{01f3c4}\u{01f3ca}\u{01f46e}\u{01f471}\u{01f473}\u{01f477}\u{01f481}\u{01f482}\u{01f486}\u{01f487}\u{01f645}-\u{01f647}\u{01f64b}\u{01f64d}\u{01f64e}\u{01f6a3}\u{01f6b4}-\u{01f6b6}\u{01f926}\u{01f935}\u{01f937}-\u{01f939}\u{01f93d}\u{01f93e}\u{01f9b8}\u{01f9b9}\u{01f9cd}-\u{01f9cf}\u{01f9d6}-\u{01f9dd}][\u{01f3fb}-\u{01f3ff}]?\u200d[\u2640\u2642]\ufe0f|(?:\u{01f468}\u200d\u2764\ufe0f\u200d\u{01f48b}\u200d\u{01f468}|\u{01f469}\u200d\u2764\ufe0f\u200d\u{01f48b}\u200d[\u{01f468}\u{01f469}]|\u{01f468}\u200d\u{01f468}\u200d\u{01f466}\u200d\u{01f466}|\u{01f468}\u200d\u{01f468}\u200d\u{01f467}\u200d[\u{01f466}\u{01f467}]|\u{01f468}\u200d\u{01f469}\u200d\u{01f466}\u200d\u{01f466}|\u{01f468}\u200d\u{01f469}\u200d\u{01f467}\u200d[\u{01f466}\u{01f467}]|\u{01f469}\u200d\u{01f469}\u200d\u{01f466}\u200d\u{01f466}|\u{01f469}\u200d\u{01f469}\u200d\u{01f467}\u200d[\u{01f466}\u{01f467}]|\u{01f468}\u200d\u2764\ufe0f\u200d\u{01f468}|\u{01f469}\u200d\u2764\ufe0f\u200d[\u{01f468}\u{01f469}]|\u{01f3f3}\ufe0f\u200d\u26a7\ufe0f|\u{01f468}\u200d\u{01f466}\u200d\u{01f466}|\u{01f468}\u200d\u{01f467}\u200d[\u{01f466}\u{01f467}]|\u{01f468}\u200d\u{01f468}\u200d[\u{01f466}\u{01f467}]|\u{01f468}\u200d\u{01f469}\u200d[\u{01f466}\u{01f467}]|\u{01f469}\u200d\u{01f466}\u200d\u{01f466}|\u{01f469}\u200d\u{01f467}\u200d[\u{01f466}\u{01f467}]|\u{01f469}\u200d\u{01f469}\u200d[\u{01f466}\u{01f467}]|\u{01f3f3}\ufe0f\u200d\u{01f308}|\u{01f3f4}\u200d\u2620\ufe0f|\u{01f46f}\u200d\u2640\ufe0f|\u{01f46f}\u200d\u2642\ufe0f|\u{01f93c}\u200d\u2640\ufe0f|\u{01f93c}\u200d\u2642\ufe0f|\u{01f9de}\u200d\u2640\ufe0f|\u{01f9de}\u200d\u2642\ufe0f|\u{01f9df}\u200d\u2640\ufe0f|\u{01f9df}\u200d\u2642\ufe0f|\u{01f415}\u200d\u{01f9ba}|\u{01f441}\u200d\u{01f5e8}|\u{01f468}\u200d[\u{01f466}\u{01f467}]|\u{01f469}\u200d[\u{01f466}\u{01f467}])|[#*0-9]\ufe0f?\u20e3|(?:[©®\u2122\u265f]\ufe0f)|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299\u{01f004}\u{01f170}\u{01f171}\u{01f17e}\u{01f17f}\u{01f202}\u{01f21a}\u{01f22f}\u{01f237}\u{01f321}\u{01f324}-\u{01f32c}\u{01f336}\u{01f37d}\u{01f396}\u{01f397}\u{01f399}-\u{01f39b}\u{01f39e}\u{01f39f}\u{01f3cd}\u{01f3ce}\u{01f3d4}-\u{01f3df}\u{01f3f3}\u{01f3f5}\u{01f3f7}\u{01f43f}\u{01f441}\u{01f4fd}\u{01f549}\u{01f54a}\u{01f56f}\u{01f570}\u{01f573}\u{01f576}-\u{01f579}\u{01f587}\u{01f58a}-\u{01f58d}\u{01f5a5}\u{01f5a8}\u{01f5b1}\u{01f5b2}\u{01f5bc}\u{01f5c2}-\u{01f5c4}\u{01f5d1}-\u{01f5d3}\u{01f5dc}-\u{01f5de}\u{01f5e1}\u{01f5e3}\u{01f5e8}\u{01f5ef}\u{01f5f3}\u{01f5fa}\u{01f6cb}\u{01f6cd}-\u{01f6cf}\u{01f6e0}-\u{01f6e5}\u{01f6e9}\u{01f6f0}\u{01f6f3}](?:\ufe0f|(?!\ufe0e))|(?:[\u261d\u26f7\u26f9\u270c\u270d\u{01f3cb}\u{01f3cc}\u{01f574}\u{01f575}\u{01f590}](?:\ufe0f|(?!\ufe0e))|[\u270a\u270b\u{01f385}\u{01f3c2}-\u{01f3c4}\u{01f3c7}\u{01f3ca}\u{01f442}\u{01f443}\u{01f446}-\u{01f450}\u{01f466}-\u{01f469}\u{01f46e}\u{01f470}-\u{01f478}\u{01f47c}\u{01f481}-\u{01f483}\u{01f485}-\u{01f487}\u{01f4aa}\u{01f57a}\u{01f595}\u{01f596}\u{01f645}-\u{01f647}\u{01f64b}-\u{01f64f}\u{01f6a3}\u{01f6b4}-\u{01f6b6}\u{01f6c0}\u{01f6cc}\u{01f90f}\u{01f918}-\u{01f91c}\u{01f91e}\u{01f91f}\u{01f926}\u{01f930}-\u{01f939}\u{01f93d}\u{01f93e}\u{01f9b5}\u{01f9b6}\u{01f9b8}\u{01f9b9}\u{01f9bb}\u{01f9cd}-\u{01f9cf}\u{01f9d1}-\u{01f9dd}])[\u{01f3fb}-\u{01f3ff}]?|(?:\u{01f3f4}\u{0e0067}\u{0e0062}\u{0e0065}\u{0e006e}\u{0e0067}\u{0e007f}|\u{01f3f4}\u{0e0067}\u{0e0062}\u{0e0073}\u{0e0063}\u{0e0074}\u{0e007f}|\u{01f3f4}\u{0e0067}\u{0e0062}\u{0e0077}\u{0e006c}\u{0e0073}\u{0e007f}|\u{01f1e6}[\u{01f1e8}-\u{01f1ec}\u{01f1ee}\u{01f1f1}\u{01f1f2}\u{01f1f4}\u{01f1f6}-\u{01f1fa}\u{01f1fc}\u{01f1fd}\u{01f1ff}]|\u{01f1e7}[\u{01f1e6}\u{01f1e7}\u{01f1e9}-\u{01f1ef}\u{01f1f1}-\u{01f1f4}\u{01f1f6}-\u{01f1f9}\u{01f1fb}\u{01f1fc}\u{01f1fe}\u{01f1ff}]|\u{01f1e8}[\u{01f1e6}\u{01f1e8}\u{01f1e9}\u{01f1eb}-\u{01f1ee}\u{01f1f0}-\u{01f1f5}\u{01f1f7}\u{01f1fa}-\u{01f1ff}]|\u{01f1e9}[\u{01f1ea}\u{01f1ec}\u{01f1ef}\u{01f1f0}\u{01f1f2}\u{01f1f4}\u{01f1ff}]|\u{01f1ea}[\u{01f1e6}\u{01f1e8}\u{01f1ea}\u{01f1ec}\u{01f1ed}\u{01f1f7}-\u{01f1fa}]|\u{01f1eb}[\u{01f1ee}-\u{01f1f0}\u{01f1f2}\u{01f1f4}\u{01f1f7}]|\u{01f1ec}[\u{01f1e6}\u{01f1e7}\u{01f1e9}-\u{01f1ee}\u{01f1f1}-\u{01f1f3}\u{01f1f5}-\u{01f1fa}\u{01f1fc}\u{01f1fe}]|\u{01f1ed}[\u{01f1f0}\u{01f1f2}\u{01f1f3}\u{01f1f7}\u{01f1f9}\u{01f1fa}]|\u{01f1ee}[\u{01f1e8}-\u{01f1ea}\u{01f1f1}-\u{01f1f4}\u{01f1f6}-\u{01f1f9}]|\u{01f1ef}[\u{01f1ea}\u{01f1f2}\u{01f1f4}\u{01f1f5}]|\u{01f1f0}[\u{01f1ea}\u{01f1ec}-\u{01f1ee}\u{01f1f2}\u{01f1f3}\u{01f1f5}\u{01f1f7}\u{01f1fc}\u{01f1fe}\u{01f1ff}]|\u{01f1f1}[\u{01f1e6}-\u{01f1e8}\u{01f1ee}\u{01f1f0}\u{01f1f7}-\u{01f1fb}\u{01f1fe}]|\u{01f1f2}[\u{01f1e6}\u{01f1e8}-\u{01f1ed}\u{01f1f0}-\u{01f1ff}]|\u{01f1f3}[\u{01f1e6}\u{01f1e8}\u{01f1ea}-\u{01f1ec}\u{01f1ee}\u{01f1f1}\u{01f1f4}\u{01f1f5}\u{01f1f7}\u{01f1fa}\u{01f1ff}]|\u{01f1f4}\u{01f1f2}|\u{01f1f5}[\u{01f1e6}\u{01f1ea}-\u{01f1ed}\u{01f1f0}-\u{01f1f3}\u{01f1f7}-\u{01f1f9}\u{01f1fc}\u{01f1fe}]|\u{01f1f6}\u{01f1e6}|\u{01f1f7}[\u{01f1ea}\u{01f1f4}\u{01f1f8}\u{01f1fa}\u{01f1fc}]|\u{01f1f8}[\u{01f1e6}-\u{01f1ea}\u{01f1ec}-\u{01f1f4}\u{01f1f7}-\u{01f1f9}\u{01f1fb}\u{01f1fd}-\u{01f1ff}]|\u{01f1f9}[\u{01f1e6}\u{01f1e8}\u{01f1e9}\u{01f1eb}-\u{01f1ed}\u{01f1ef}-\u{01f1f4}\u{01f1f7}\u{01f1f9}\u{01f1fb}\u{01f1fc}\u{01f1ff}]|\u{01f1fa}[\u{01f1e6}\u{01f1ec}\u{01f1f2}\u{01f1f3}\u{01f1f8}\u{01f1fe}\u{01f1ff}]|\u{01f1fb}[\u{01f1e6}\u{01f1e8}\u{01f1ea}\u{01f1ec}\u{01f1ee}\u{01f1f3}\u{01f1fa}]|\u{01f1fc}[\u{01f1eb}\u{01f1f8}]|\u{01f1fd}\u{01f1f0}|\u{01f1fe}[\u{01f1ea}\u{01f1f9}]|\u{01f1ff}[\u{01f1e6}\u{01f1f2}\u{01f1fc}]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a\u{01f0cf}\u{01f18e}\u{01f191}-\u{01f19a}\u{01f1e6}-\u{01f1ff}\u{01f201}\u{01f232}-\u{01f236}\u{01f238}-\u{01f23a}\u{01f250}\u{01f251}\u{01f300}-\u{01f320}\u{01f32d}-\u{01f335}\u{01f337}-\u{01f37c}\u{01f37e}-\u{01f384}\u{01f386}-\u{01f393}\u{01f3a0}-\u{01f3c1}\u{01f3c5}\u{01f3c6}\u{01f3c8}\u{01f3c9}\u{01f3cf}-\u{01f3d3}\u{01f3e0}-\u{01f3f0}\u{01f3f4}\u{01f3f8}-\u{01f43e}\u{01f440}\u{01f444}\u{01f445}\u{01f451}-\u{01f465}\u{01f46a}\u{01f46f}\u{01f479}-\u{01f47b}\u{01f47d}-\u{01f480}\u{01f484}\u{01f488}-\u{01f4a9}\u{01f4ab}-\u{01f4fc}\u{01f4ff}-\u{01f53d}\u{01f54b}-\u{01f54e}\u{01f550}-\u{01f567}\u{01f5a4}\u{01f5fb}-\u{01f644}\u{01f648}-\u{01f64a}\u{01f680}-\u{01f6a2}\u{01f6a4}-\u{01f6b3}\u{01f6b7}-\u{01f6bf}\u{01f6c1}-\u{01f6c5}\u{01f6d0}-\u{01f6d2}\u{01f6d5}\u{01f6eb}\u{01f6ec}\u{01f6f4}-\u{01f6fa}\u{01f7e0}-\u{01f7eb}\u{01f90d}\u{01f90e}\u{01f910}-\u{01f917}\u{01f91d}\u{01f920}-\u{01f925}\u{01f927}-\u{01f92f}\u{01f93a}\u{01f93c}\u{01f93f}-\u{01f945}\u{01f947}-\u{01f971}\u{01f973}-\u{01f976}\u{01f97a}-\u{01f9a2}\u{01f9a5}-\u{01f9aa}\u{01f9ae}-\u{01f9b4}\u{01f9b7}\u{01f9ba}\u{01f9bc}-\u{01f9ca}\u{01f9d0}\u{01f9de}-\u{01f9ff}\u{01fa70}-\u{01fa73}\u{01fa78}-\u{01fa7a}\u{01fa80}-\u{01fa82}\u{01fa90}-\u{01fa95}])|\ufe0f
19
+ }iox
20
+
21
+ def self.[](key)
22
+ REGEXEN[key]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end