twitter-text-kow 1.3.1.0

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.
@@ -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