twitter-text-relative 1.6.2.pre.3

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,22 @@
1
+ major, minor, patch = RUBY_VERSION.split('.')
2
+
3
+ $RUBY_1_9 = if major.to_i == 1 && minor.to_i < 9
4
+ # Ruby 1.8 KCODE check. Not needed on 1.9 and later.
5
+ raise("twitter-text requires the $KCODE variable be set to 'UTF8' or 'u'") unless $KCODE[0].chr =~ /u/i
6
+ false
7
+ else
8
+ true
9
+ end
10
+
11
+ %w(
12
+ deprecation
13
+ regex
14
+ rewriter
15
+ autolink
16
+ extractor
17
+ unicode
18
+ validation
19
+ hit_highlighter
20
+ ).each do |name|
21
+ require "twitter-text-relative/#{name}"
22
+ end
@@ -0,0 +1,443 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'set'
4
+ require 'twitter-text-relative/hash_helper'
5
+
6
+ module Twitter
7
+ # A module for including Tweet auto-linking in a class. The primary use of this is for helpers/views so they can auto-link
8
+ # usernames, lists, hashtags and URLs.
9
+ module Autolink extend self
10
+ # Default CSS class for auto-linked lists
11
+ DEFAULT_LIST_CLASS = "tweet-url list-slug"
12
+ # Default CSS class for auto-linked usernames
13
+ DEFAULT_USERNAME_CLASS = "tweet-url username"
14
+ # Default CSS class for auto-linked hashtags
15
+ DEFAULT_HASHTAG_CLASS = "tweet-url hashtag"
16
+ # Default CSS class for auto-linked cashtags
17
+ DEFAULT_CASHTAG_CLASS = "tweet-url cashtag"
18
+
19
+ # Default URL base for auto-linked usernames
20
+ DEFAULT_USERNAME_URL_BASE = "/"
21
+ # Default URL base for auto-linked lists
22
+ DEFAULT_LIST_URL_BASE = "/"
23
+ # Default URL base for auto-linked hashtags
24
+ DEFAULT_HASHTAG_URL_BASE = "/#!/search?q=%23"
25
+ # Default URL base for auto-linked cashtags
26
+ DEFAULT_CASHTAG_URL_BASE = "/#!/search?q=%24"
27
+
28
+ # Default attributes for invisible span tag
29
+ DEFAULT_INVISIBLE_TAG_ATTRS = "style='position:absolute;left:-9999px;'"
30
+
31
+ DEFAULT_OPTIONS = {
32
+ :list_class => DEFAULT_LIST_CLASS,
33
+ :username_class => DEFAULT_USERNAME_CLASS,
34
+ :hashtag_class => DEFAULT_HASHTAG_CLASS,
35
+ :cashtag_class => DEFAULT_CASHTAG_CLASS,
36
+
37
+ :username_url_base => DEFAULT_USERNAME_URL_BASE,
38
+ :list_url_base => DEFAULT_LIST_URL_BASE,
39
+ :hashtag_url_base => DEFAULT_HASHTAG_URL_BASE,
40
+ :cashtag_url_base => DEFAULT_CASHTAG_URL_BASE,
41
+
42
+ :invisible_tag_attrs => DEFAULT_INVISIBLE_TAG_ATTRS
43
+ }.freeze
44
+
45
+ def auto_link_with_json(text, json, options = {})
46
+ # concatenate entities
47
+ entities = json.values().flatten()
48
+
49
+ # map JSON entity to twitter-text entity
50
+ entities.each do |entity|
51
+ HashHelper.symbolize_keys!(entity)
52
+ # hashtag
53
+ entity[:hashtag] = entity[:text] if entity[:text]
54
+ end
55
+
56
+ auto_link_entities(text, entities, options)
57
+ end
58
+
59
+ def auto_link_entities(text, entities, options = {}, &block)
60
+ return text if entities.empty?
61
+
62
+ # NOTE deprecate these attributes not options keys in options hash, then use html_attrs
63
+ options = DEFAULT_OPTIONS.merge(options)
64
+ options[:html_attrs] = extract_html_attrs_from_options!(options)
65
+ options[:html_attrs][:rel] ||= "nofollow" unless options[:suppress_no_follow]
66
+
67
+ Twitter::Rewriter.rewrite_entities(text, entities) do |entity, chars|
68
+ if entity[:url]
69
+ link_to_url(entity, chars, options, &block)
70
+ elsif entity[:hashtag]
71
+ link_to_hashtag(entity, chars, options, &block)
72
+ elsif entity[:screen_name]
73
+ link_to_screen_name(entity, chars, options, &block)
74
+ elsif entity[:cashtag]
75
+ link_to_cashtag(entity, chars, options, &block)
76
+ end
77
+ end
78
+ end
79
+
80
+ # Add <tt><a></a></tt> tags around the usernames, lists, hashtags and URLs in the provided <tt>text</tt>.
81
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash:
82
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
83
+ # and place in the <tt><a></tt> tag.
84
+ #
85
+ # <tt>:url_class</tt>:: class to add to url <tt><a></tt> tags
86
+ # <tt>:list_class</tt>:: class to add to list <tt><a></tt> tags
87
+ # <tt>:username_class</tt>:: class to add to username <tt><a></tt> tags
88
+ # <tt>:hashtag_class</tt>:: class to add to hashtag <tt><a></tt> tags
89
+ # <tt>:cashtag_class</tt>:: class to add to cashtag <tt><a></tt> tags
90
+ # <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.
91
+ # <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.
92
+ # <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.
93
+ # <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.
94
+ # <tt>:invisible_tag_attrs</tt>:: HTML attribute to add to invisible span tags
95
+ # <tt>:username_include_symbol</tt>:: place the <tt>@</tt> symbol within username and list links
96
+ # <tt>:suppress_lists</tt>:: disable auto-linking to lists
97
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
98
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
99
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
100
+ # <tt>:url_target</tt>:: the value for <tt>target</tt> attribute on URL links.
101
+ # <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.
102
+ # <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.
103
+ def auto_link(text, options = {}, &block)
104
+ auto_link_entities(text, Extractor.extract_entities_with_indices(text, :extract_url_without_protocol => false), options, &block)
105
+ end
106
+
107
+ # Add <tt><a></a></tt> tags around the usernames and lists in the provided <tt>text</tt>. The
108
+ # <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
109
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
110
+ # and place in the <tt><a></tt> tag.
111
+ #
112
+ # <tt>:list_class</tt>:: class to add to list <tt><a></tt> tags
113
+ # <tt>:username_class</tt>:: class to add to username <tt><a></tt> tags
114
+ # <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.
115
+ # <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.
116
+ # <tt>:username_include_symbol</tt>:: place the <tt>@</tt> symbol within username and list links
117
+ # <tt>:suppress_lists</tt>:: disable auto-linking to lists
118
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
119
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
120
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
121
+ # <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.
122
+ # <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.
123
+ def auto_link_usernames_or_lists(text, options = {}, &block) # :yields: list_or_username
124
+ auto_link_entities(text, Extractor.extract_mentions_or_lists_with_indices(text), options, &block)
125
+ end
126
+
127
+ # Add <tt><a></a></tt> tags around the hashtags in the provided <tt>text</tt>.
128
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
129
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
130
+ # and place in the <tt><a></tt> tag.
131
+ #
132
+ # <tt>:hashtag_class</tt>:: class to add to hashtag <tt><a></tt> tags
133
+ # <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.
134
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
135
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
136
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
137
+ # <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.
138
+ # <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.
139
+ def auto_link_hashtags(text, options = {}, &block) # :yields: hashtag_text
140
+ auto_link_entities(text, Extractor.extract_hashtags_with_indices(text), options, &block)
141
+ end
142
+
143
+ # Add <tt><a></a></tt> tags around the cashtags in the provided <tt>text</tt>.
144
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
145
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
146
+ # and place in the <tt><a></tt> tag.
147
+ #
148
+ # <tt>:cashtag_class</tt>:: class to add to cashtag <tt><a></tt> tags
149
+ # <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.
150
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
151
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
152
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
153
+ # <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.
154
+ # <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.
155
+ def auto_link_cashtags(text, options = {}, &block) # :yields: cashtag_text
156
+ auto_link_entities(text, Extractor.extract_cashtags_with_indices(text), options, &block)
157
+ end
158
+
159
+ # Add <tt><a></a></tt> tags around the URLs in the provided <tt>text</tt>.
160
+ # The <tt><a></tt> tags can be controlled with the following entries in the <tt>options</tt> hash.
161
+ # Also any elements in the <tt>options</tt> hash will be converted to HTML attributes
162
+ # and place in the <tt><a></tt> tag.
163
+ #
164
+ # <tt>:url_class</tt>:: class to add to url <tt><a></tt> tags
165
+ # <tt>:invisible_tag_attrs</tt>:: HTML attribute to add to invisible span tags
166
+ # <tt>:suppress_no_follow</tt>:: do not add <tt>rel="nofollow"</tt> to auto-linked items
167
+ # <tt>:symbol_tag</tt>:: tag to apply around symbol (@, #, $) in username / hashtag / cashtag links
168
+ # <tt>:text_with_symbol_tag</tt>:: tag to apply around text part in username / hashtag / cashtag links
169
+ # <tt>:url_target</tt>:: the value for <tt>target</tt> attribute on URL links.
170
+ # <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.
171
+ # <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.
172
+ def auto_link_urls(text, options = {}, &block)
173
+ auto_link_entities(text, Extractor.extract_urls_with_indices(text, :extract_url_without_protocol => false), options, &block)
174
+ end
175
+
176
+ # These methods are deprecated, will be removed in future.
177
+ extend Deprecation
178
+
179
+ # <b>Deprecated</b>: Please use auto_link_urls instead.
180
+ # Add <tt><a></a></tt> tags around the URLs in the provided <tt>text</tt>.
181
+ # Any elements in the <tt>href_options</tt> hash will be converted to HTML attributes
182
+ # and place in the <tt><a></tt> tag.
183
+ # Unless <tt>href_options</tt> contains <tt>:suppress_no_follow</tt>
184
+ # the <tt>rel="nofollow"</tt> attribute will be added.
185
+ alias :auto_link_urls_custom :auto_link_urls
186
+ deprecate :auto_link_urls_custom, :auto_link_urls
187
+
188
+ private
189
+
190
+ HTML_ENTITIES = {
191
+ '&' => '&amp;',
192
+ '>' => '&gt;',
193
+ '<' => '&lt;',
194
+ '"' => '&quot;',
195
+ "'" => '&#39;'
196
+ }
197
+
198
+ def html_escape(text)
199
+ text && text.to_s.gsub(/[&"'><]/) do |character|
200
+ HTML_ENTITIES[character]
201
+ end
202
+ end
203
+
204
+ # NOTE We will make this private in future.
205
+ public :html_escape
206
+
207
+ # Options which should not be passed as HTML attributes
208
+ OPTIONS_NOT_ATTRIBUTES = Set.new([
209
+ :url_class, :list_class, :username_class, :hashtag_class, :cashtag_class,
210
+ :username_url_base, :list_url_base, :hashtag_url_base, :cashtag_url_base,
211
+ :username_url_block, :list_url_block, :hashtag_url_block, :cashtag_url_block, :link_url_block,
212
+ :username_include_symbol, :suppress_lists, :suppress_no_follow, :url_entities,
213
+ :invisible_tag_attrs, :symbol_tag, :text_with_symbol_tag, :url_target,
214
+ :link_attribute_block, :link_text_block
215
+ ]).freeze
216
+
217
+ def extract_html_attrs_from_options!(options)
218
+ html_attrs = {}
219
+ options.reject! do |key, value|
220
+ unless OPTIONS_NOT_ATTRIBUTES.include?(key)
221
+ html_attrs[key] = value
222
+ true
223
+ end
224
+ end
225
+ html_attrs
226
+ end
227
+
228
+ def url_entities_hash(url_entities)
229
+ (url_entities || {}).inject({}) do |entities, entity|
230
+ HashHelper.symbolize_keys!(entity)
231
+ entities[entity[:url]] = entity
232
+ entities
233
+ end
234
+ end
235
+
236
+ def link_to_url(entity, chars, options = {})
237
+ url = entity[:url]
238
+
239
+ href = if options[:link_url_block]
240
+ options[:link_url_block].call(url)
241
+ else
242
+ url
243
+ end
244
+
245
+ # NOTE auto link to urls do not use any default values and options
246
+ # like url_class but use suppress_no_follow.
247
+ html_attrs = options[:html_attrs].dup
248
+ html_attrs[:class] = options[:url_class] if options.key?(:url_class)
249
+
250
+ # add target attribute only if :url_target is specified
251
+ html_attrs[:target] = options[:url_target] if options.key?(:url_target)
252
+
253
+ url_entities = url_entities_hash(options[:url_entities])
254
+
255
+ # use entity from urlEntities if available
256
+ url_entity = url_entities[url] || entity
257
+ link_text = if url_entity[:display_url]
258
+ html_attrs[:title] ||= url_entity[:expanded_url]
259
+ link_url_with_entity(url_entity, options)
260
+ else
261
+ html_escape(url)
262
+ end
263
+
264
+ link_to_text(entity, link_text, href, html_attrs, options)
265
+ end
266
+
267
+ def link_url_with_entity(entity, options)
268
+ display_url = entity[:display_url]
269
+ expanded_url = entity[:expanded_url]
270
+ invisible_tag_attrs = options[:invisible_tag_attrs] || DEFAULT_INVISIBLE_TAG_ATTRS
271
+
272
+ # Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
273
+ # should contain the full original URL (expanded_url), not the display URL.
274
+ #
275
+ # Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
276
+ # font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
277
+ # Elements with font-size:0 get copied even though they are not visible.
278
+ # Note that display:none doesn't work here. Elements with display:none don't get copied.
279
+ #
280
+ # Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
281
+ # wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
282
+ # everything with the tco-ellipsis class.
283
+ #
284
+ # Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/#!/username/status/1234/photo/1
285
+ # For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
286
+ # For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine.
287
+ display_url_sans_ellipses = display_url.gsub("…", "")
288
+
289
+ if expanded_url.include?(display_url_sans_ellipses)
290
+ before_display_url, after_display_url = expanded_url.split(display_url_sans_ellipses, 2)
291
+ preceding_ellipsis = /\A…/.match(display_url).to_s
292
+ following_ellipsis = /…\z/.match(display_url).to_s
293
+
294
+ # As an example: The user tweets "hi http://longdomainname.com/foo"
295
+ # This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
296
+ # This will get rendered as:
297
+ # <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
298
+ # …
299
+ # <!-- There's a chance the onCopy event handler might not fire. In case that happens,
300
+ # we include an &nbsp; here so that the … doesn't bump up against the URL and ruin it.
301
+ # The &nbsp; is inside the tco-ellipsis span so that when the onCopy handler *does*
302
+ # fire, it doesn't get copied. Otherwise the copied text would have two spaces in a row,
303
+ # e.g. "hi http://longdomainname.com/foo".
304
+ # <span style='font-size:0'>&nbsp;</span>
305
+ # </span>
306
+ # <span style='font-size:0'> <!-- This stuff should get copied but not displayed -->
307
+ # http://longdomai
308
+ # </span>
309
+ # <span class='js-display-url'> <!-- This stuff should get displayed *and* copied -->
310
+ # nname.com/foo
311
+ # </span>
312
+ # <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
313
+ # <span style='font-size:0'>&nbsp;</span>
314
+ # …
315
+ # </span>
316
+ %(<span class="tco-ellipsis">#{preceding_ellipsis}<span #{invisible_tag_attrs}>&nbsp;</span></span>) <<
317
+ %(<span #{invisible_tag_attrs}>#{html_escape(before_display_url)}</span>) <<
318
+ %(<span class="js-display-url">#{html_escape(display_url_sans_ellipses)}</span>) <<
319
+ %(<span #{invisible_tag_attrs}>#{html_escape(after_display_url)}</span>) <<
320
+ %(<span class="tco-ellipsis"><span #{invisible_tag_attrs}>&nbsp;</span>#{following_ellipsis}</span>)
321
+ else
322
+ html_escape(display_url)
323
+ end
324
+ end
325
+
326
+ def link_to_hashtag(entity, chars, options = {})
327
+ hash = chars[entity[:indices].first]
328
+ hashtag = entity[:hashtag]
329
+ hashtag = yield(hashtag) if block_given?
330
+ hashtag_class = options[:hashtag_class]
331
+
332
+ if hashtag.match Twitter::Regex::REGEXEN[:rtl_chars]
333
+ hashtag_class += ' rtl'
334
+ end
335
+
336
+ href = if options[:hashtag_url_block]
337
+ options[:hashtag_url_block].call(hashtag)
338
+ else
339
+ "#{options[:hashtag_url_base]}#{hashtag}"
340
+ end
341
+
342
+ html_attrs = {
343
+ :class => hashtag_class,
344
+ # FIXME As our conformance test, hash in title should be half-width,
345
+ # this should be bug of conformance data.
346
+ :title => "##{hashtag}"
347
+ }.merge(options[:html_attrs])
348
+
349
+ link_to_text_with_symbol(entity, hash, hashtag, href, html_attrs, options)
350
+ end
351
+
352
+ def link_to_cashtag(entity, chars, options = {})
353
+ dollar = chars[entity[:indices].first]
354
+ cashtag = entity[:cashtag]
355
+ cashtag = yield(cashtag) if block_given?
356
+
357
+ href = if options[:cashtag_url_block]
358
+ options[:cashtag_url_block].call(cashtag)
359
+ else
360
+ "#{options[:cashtag_url_base]}#{cashtag}"
361
+ end
362
+
363
+ html_attrs = {
364
+ :class => "#{options[:cashtag_class]}",
365
+ :title => "$#{cashtag}"
366
+ }.merge(options[:html_attrs])
367
+
368
+ link_to_text_with_symbol(entity, dollar, cashtag, href, html_attrs, options)
369
+ end
370
+
371
+ def link_to_screen_name(entity, chars, options = {})
372
+ name = "#{entity[:screen_name]}#{entity[:list_slug]}"
373
+ chunk = name
374
+ chunk = yield(name) if block_given?
375
+ name.downcase!
376
+
377
+ at = chars[entity[:indices].first]
378
+
379
+ html_attrs = options[:html_attrs].dup
380
+
381
+ if entity[:list_slug] && !entity[:list_slug].empty? && !options[:suppress_lists]
382
+ href = if options[:list_url_block]
383
+ options[:list_url_block].call(name)
384
+ else
385
+ "#{options[:list_url_base]}#{name}"
386
+ end
387
+ html_attrs[:class] ||= "#{options[:list_class]}"
388
+ else
389
+ href = if options[:username_url_block]
390
+ options[:username_url_block].call(chunk)
391
+ else
392
+ "#{options[:username_url_base]}#{name}"
393
+ end
394
+ html_attrs[:class] ||= "#{options[:username_class]}"
395
+ end
396
+
397
+ link_to_text_with_symbol(entity, at, chunk, href, html_attrs, options)
398
+ end
399
+
400
+ def link_to_text_with_symbol(entity, symbol, text, href, attributes = {}, options = {})
401
+ tagged_symbol = options[:symbol_tag] ? "<#{options[:symbol_tag]}>#{symbol}</#{options[:symbol_tag]}>" : symbol
402
+ text = html_escape(text)
403
+ tagged_text = options[:text_with_symbol_tag] ? "<#{options[:text_with_symbol_tag]}>#{text}</#{options[:text_with_symbol_tag]}>" : text
404
+ if options[:username_include_symbol] || symbol !~ Twitter::Regex::REGEXEN[:at_signs]
405
+ "#{link_to_text(entity, tagged_symbol + tagged_text, href, attributes, options)}"
406
+ else
407
+ "#{tagged_symbol}#{link_to_text(entity, tagged_text, href, attributes, options)}"
408
+ end
409
+ end
410
+
411
+ def link_to_text(entity, text, href, attributes = {}, options = {})
412
+ attributes[:href] = href
413
+ options[:link_attribute_block].call(entity, attributes) if options[:link_attribute_block]
414
+ text = options[:link_text_block].call(entity, text) if options[:link_text_block]
415
+ %(<a#{tag_attrs(attributes)}>#{text}</a>)
416
+ end
417
+
418
+ BOOLEAN_ATTRIBUTES = Set.new([:disabled, :readonly, :multiple, :checked]).freeze
419
+
420
+ def tag_attrs(attributes)
421
+ attributes.keys.sort_by{|k| k.to_s}.inject("") do |attrs, key|
422
+ value = attributes[key]
423
+
424
+ if BOOLEAN_ATTRIBUTES.include?(key)
425
+ value = value ? key : nil
426
+ end
427
+
428
+ unless value.nil?
429
+ value = case value
430
+ when Array
431
+ value.compact.join(" ")
432
+ else
433
+ value
434
+ end
435
+ attrs << %( #{html_escape(key)}="#{html_escape(value)}")
436
+ end
437
+
438
+ attrs
439
+ end
440
+ end
441
+
442
+ end
443
+ end