twitter-text-kow 1.3.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|