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.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/.gitignore +40 -0
- data/.gitmodules +3 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile +4 -0
- data/LICENSE +188 -0
- data/README.md +193 -0
- data/Rakefile +52 -0
- data/config/README.md +142 -0
- data/config/v1.json +8 -0
- data/config/v2.json +29 -0
- data/config/v3.json +30 -0
- data/lib/assets/tld_lib.yml +1577 -0
- data/lib/twitter-text/autolink.rb +455 -0
- data/lib/twitter-text/configuration.rb +68 -0
- data/lib/twitter-text/deprecation.rb +21 -0
- data/lib/twitter-text/emoji_regex.rb +27 -0
- data/lib/twitter-text/extractor.rb +388 -0
- data/lib/twitter-text/hash_helper.rb +27 -0
- data/lib/twitter-text/hit_highlighter.rb +92 -0
- data/lib/twitter-text/regex.rb +381 -0
- data/lib/twitter-text/rewriter.rb +69 -0
- data/lib/twitter-text/unicode.rb +31 -0
- data/lib/twitter-text/validation.rb +251 -0
- data/lib/twitter-text/weighted_range.rb +24 -0
- data/lib/twitter-text.rb +29 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/autolinking_spec.rb +858 -0
- data/spec/configuration_spec.rb +136 -0
- data/spec/extractor_spec.rb +392 -0
- data/spec/hithighlighter_spec.rb +96 -0
- data/spec/regex_spec.rb +76 -0
- data/spec/rewriter_spec.rb +553 -0
- data/spec/spec_helper.rb +139 -0
- data/spec/test_urls.rb +90 -0
- data/spec/twitter_text_spec.rb +25 -0
- data/spec/unicode_spec.rb +35 -0
- data/spec/validation_spec.rb +87 -0
- data/test/conformance_test.rb +242 -0
- data/twitter-text.gemspec +35 -0
- metadata +228 -0
@@ -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
|
+
'&' => '&',
|
203
|
+
'>' => '>',
|
204
|
+
'<' => '<',
|
205
|
+
'"' => '"',
|
206
|
+
"'" => '''
|
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 here so that the … doesn't bump up against the URL and ruin it.
|
313
|
+
# The 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'> </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'> </span>
|
326
|
+
# …
|
327
|
+
# </span>
|
328
|
+
%(<span class="tco-ellipsis">#{preceding_ellipsis}<span #{invisible_tag_attrs}> </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}> </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
|