textilize 0.0.2
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.
- data/LICENSE +23 -0
- data/README.md +44 -0
- data/lib/redcloth.rb +1137 -0
- data/lib/textilize.rb +5 -0
- data/lib/textilize/helper.rb +66 -0
- data/lib/textilize/version.rb +4 -0
- metadata +68 -0
data/LICENSE
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
Copyright (c) 2012 Mysen Huang
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
|
data/README.md
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
Textilize
|
2
|
+
=========
|
3
|
+
|
4
|
+
As of version 3, Rails doesn't have the `textilize` and `textilize_without_paragraph` helper methods.
|
5
|
+
Textilize is a gem brings back the missing method for Rails 3, and *it includes the library RedCloth 3.0.4* (newest is at 4.X), which doesn't need to be compiled on server.
|
6
|
+
|
7
|
+
The reason we need this gem is that we use :textilize at a few places in our apps, and we don't need super fast RedCloth, we just need a working version that don't force us to install C extensions on various production servers.
|
8
|
+
This could be useful for bundle-package usage, where you can rely on the bundle/cache gems.
|
9
|
+
|
10
|
+
BTW, Textilize is not always the top choice, there are some useful gems that are doing it great, but they will requires C extension due to higher version of RedCloth dependency:
|
11
|
+
|
12
|
+
https://github.com/dtrasbo/formatize/
|
13
|
+
|
14
|
+
https://github.com/rohit/prarupa
|
15
|
+
|
16
|
+
|
17
|
+
Installation
|
18
|
+
------------
|
19
|
+
|
20
|
+
Put `gem 'textilize'` into your Gemfile and do a `bundle install`, and that's
|
21
|
+
it.
|
22
|
+
|
23
|
+
Usage
|
24
|
+
-----
|
25
|
+
|
26
|
+
# textilize("I _love_ ROR(Ruby on Rails)!")
|
27
|
+
# # => "<p>I <em>love</em> <acronym title="Ruby on Rails">ROR</acronym>!</p>"
|
28
|
+
|
29
|
+
# textilize_without_paragraph("I _love_ ROR(Ruby on Rails)!")
|
30
|
+
# # => "I <em>love</em> <acronym title="Ruby on Rails">ROR</acronym>!"
|
31
|
+
|
32
|
+
|
33
|
+
Test
|
34
|
+
------------
|
35
|
+
|
36
|
+
We will add some tests back later.
|
37
|
+
|
38
|
+
Copyright & Licensing
|
39
|
+
---------------------
|
40
|
+
|
41
|
+
Copyright (c) 2012 Kudelabs
|
42
|
+
|
43
|
+
Released under the MIT License. See LICENSE for details.
|
44
|
+
|
data/lib/redcloth.rb
ADDED
@@ -0,0 +1,1137 @@
|
|
1
|
+
#this version of RedCloth doenn't need to be compile as C extension, and I love it.
|
2
|
+
#Newer version ~> 4.X needs compiling C.
|
3
|
+
# vim:ts=4:sw=4:
|
4
|
+
# = RedCloth - Textile and Markdown Hybrid for Ruby
|
5
|
+
#
|
6
|
+
# Homepage:: http://whytheluckystiff.net/ruby/redcloth/
|
7
|
+
# Author:: why the lucky stiff (http://whytheluckystiff.net/)
|
8
|
+
# Copyright:: (cc) 2004 why the lucky stiff (and his puppet organizations.)
|
9
|
+
# License:: BSD
|
10
|
+
#
|
11
|
+
# (see http://hobix.com/textile/ for a Textile Reference.)
|
12
|
+
#
|
13
|
+
# Based on (and also inspired by) both:
|
14
|
+
#
|
15
|
+
# PyTextile: http://diveintomark.org/projects/textile/textile.py.txt
|
16
|
+
# Textism for PHP: http://www.textism.com/tools/textile/
|
17
|
+
#
|
18
|
+
#
|
19
|
+
|
20
|
+
# = RedCloth
|
21
|
+
#
|
22
|
+
# RedCloth is a Ruby library for converting Textile and/or Markdown
|
23
|
+
# into HTML. You can use either format, intermingled or separately.
|
24
|
+
# You can also extend RedCloth to honor your own custom text stylings.
|
25
|
+
#
|
26
|
+
# RedCloth users are encouraged to use Textile if they are generating
|
27
|
+
# HTML and to use Markdown if others will be viewing the plain text.
|
28
|
+
#
|
29
|
+
# == What is Textile?
|
30
|
+
#
|
31
|
+
# Textile is a simple formatting style for text
|
32
|
+
# documents, loosely based on some HTML conventions.
|
33
|
+
#
|
34
|
+
# == Sample Textile Text
|
35
|
+
#
|
36
|
+
# h2. This is a title
|
37
|
+
#
|
38
|
+
# h3. This is a subhead
|
39
|
+
#
|
40
|
+
# This is a bit of paragraph.
|
41
|
+
#
|
42
|
+
# bq. This is a blockquote.
|
43
|
+
#
|
44
|
+
# = Writing Textile
|
45
|
+
#
|
46
|
+
# A Textile document consists of paragraphs. Paragraphs
|
47
|
+
# can be specially formatted by adding a small instruction
|
48
|
+
# to the beginning of the paragraph.
|
49
|
+
#
|
50
|
+
# h[n]. Header of size [n].
|
51
|
+
# bq. Blockquote.
|
52
|
+
# # Numeric list.
|
53
|
+
# * Bulleted list.
|
54
|
+
#
|
55
|
+
# == Quick Phrase Modifiers
|
56
|
+
#
|
57
|
+
# Quick phrase modifiers are also included, to allow formatting
|
58
|
+
# of small portions of text within a paragraph.
|
59
|
+
#
|
60
|
+
# \_emphasis\_
|
61
|
+
# \_\_italicized\_\_
|
62
|
+
# \*strong\*
|
63
|
+
# \*\*bold\*\*
|
64
|
+
# ??citation??
|
65
|
+
# -deleted text-
|
66
|
+
# +inserted text+
|
67
|
+
# ^superscript^
|
68
|
+
# ~subscript~
|
69
|
+
# @code@
|
70
|
+
# %(classname)span%
|
71
|
+
#
|
72
|
+
# ==notextile== (leave text alone)
|
73
|
+
#
|
74
|
+
# == Links
|
75
|
+
#
|
76
|
+
# To make a hypertext link, put the link text in "quotation
|
77
|
+
# marks" followed immediately by a colon and the URL of the link.
|
78
|
+
#
|
79
|
+
# Optional: text in (parentheses) following the link text,
|
80
|
+
# but before the closing quotation mark, will become a Title
|
81
|
+
# attribute for the link, visible as a tool tip when a cursor is above it.
|
82
|
+
#
|
83
|
+
# Example:
|
84
|
+
#
|
85
|
+
# "This is a link (This is a title) ":http://www.textism.com
|
86
|
+
#
|
87
|
+
# Will become:
|
88
|
+
#
|
89
|
+
# <a href="http://www.textism.com" title="This is a title">This is a link</a>
|
90
|
+
#
|
91
|
+
# == Images
|
92
|
+
#
|
93
|
+
# To insert an image, put the URL for the image inside exclamation marks.
|
94
|
+
#
|
95
|
+
# Optional: text that immediately follows the URL in (parentheses) will
|
96
|
+
# be used as the Alt text for the image. Images on the web should always
|
97
|
+
# have descriptive Alt text for the benefit of readers using non-graphical
|
98
|
+
# browsers.
|
99
|
+
#
|
100
|
+
# Optional: place a colon followed by a URL immediately after the
|
101
|
+
# closing ! to make the image into a link.
|
102
|
+
#
|
103
|
+
# Example:
|
104
|
+
#
|
105
|
+
# !http://www.textism.com/common/textist.gif(Textist)!
|
106
|
+
#
|
107
|
+
# Will become:
|
108
|
+
#
|
109
|
+
# <img src="http://www.textism.com/common/textist.gif" alt="Textist" />
|
110
|
+
#
|
111
|
+
# With a link:
|
112
|
+
#
|
113
|
+
# !/common/textist.gif(Textist)!:http://textism.com
|
114
|
+
#
|
115
|
+
# Will become:
|
116
|
+
#
|
117
|
+
# <a href="http://textism.com"><img src="/common/textist.gif" alt="Textist" /></a>
|
118
|
+
#
|
119
|
+
# == Defining Acronyms
|
120
|
+
#
|
121
|
+
# HTML allows authors to define acronyms via the tag. The definition appears as a
|
122
|
+
# tool tip when a cursor hovers over the acronym. A crucial aid to clear writing,
|
123
|
+
# this should be used at least once for each acronym in documents where they appear.
|
124
|
+
#
|
125
|
+
# To quickly define an acronym in Textile, place the full text in (parentheses)
|
126
|
+
# immediately following the acronym.
|
127
|
+
#
|
128
|
+
# Example:
|
129
|
+
#
|
130
|
+
# ACLU(American Civil Liberties Union)
|
131
|
+
#
|
132
|
+
# Will become:
|
133
|
+
#
|
134
|
+
# <acronym title="American Civil Liberties Union">ACLU</acronym>
|
135
|
+
#
|
136
|
+
# == Adding Tables
|
137
|
+
#
|
138
|
+
# In Textile, simple tables can be added by seperating each column by
|
139
|
+
# a pipe.
|
140
|
+
#
|
141
|
+
# |a|simple|table|row|
|
142
|
+
# |And|Another|table|row|
|
143
|
+
#
|
144
|
+
# Attributes are defined by style definitions in parentheses.
|
145
|
+
#
|
146
|
+
# table(border:1px solid black).
|
147
|
+
# (background:#ddd;color:red). |{}| | | |
|
148
|
+
#
|
149
|
+
# == Using RedCloth
|
150
|
+
#
|
151
|
+
# RedCloth is simply an extension of the String class, which can handle
|
152
|
+
# Textile formatting. Use it like a String and output HTML with its
|
153
|
+
# RedCloth#to_html method.
|
154
|
+
#
|
155
|
+
# doc = RedCloth.new "
|
156
|
+
#
|
157
|
+
# h2. Test document
|
158
|
+
#
|
159
|
+
# Just a simple test."
|
160
|
+
#
|
161
|
+
# puts doc.to_html
|
162
|
+
#
|
163
|
+
# By default, RedCloth uses both Textile and Markdown formatting, with
|
164
|
+
# Textile formatting taking precedence. If you want to turn off Markdown
|
165
|
+
# formatting, to boost speed and limit the processor:
|
166
|
+
#
|
167
|
+
# class RedCloth::Textile.new( str )
|
168
|
+
|
169
|
+
class RedCloth < String
|
170
|
+
|
171
|
+
VERSION = '3.0.4'
|
172
|
+
DEFAULT_RULES = [:textile, :markdown]
|
173
|
+
|
174
|
+
#
|
175
|
+
# Two accessor for setting security restrictions.
|
176
|
+
#
|
177
|
+
# This is a nice thing if you're using RedCloth for
|
178
|
+
# formatting in public places (e.g. Wikis) where you
|
179
|
+
# don't want users to abuse HTML for bad things.
|
180
|
+
#
|
181
|
+
# If +:filter_html+ is set, HTML which wasn't
|
182
|
+
# created by the Textile processor will be escaped.
|
183
|
+
#
|
184
|
+
# If +:filter_styles+ is set, it will also disable
|
185
|
+
# the style markup specifier. ('{color: red}')
|
186
|
+
#
|
187
|
+
attr_accessor :filter_html, :filter_styles
|
188
|
+
|
189
|
+
#
|
190
|
+
# Accessor for toggling hard breaks.
|
191
|
+
#
|
192
|
+
# If +:hard_breaks+ is set, single newlines will
|
193
|
+
# be converted to HTML break tags. This is the
|
194
|
+
# default behavior for traditional RedCloth.
|
195
|
+
#
|
196
|
+
attr_accessor :hard_breaks
|
197
|
+
|
198
|
+
# Accessor for toggling lite mode.
|
199
|
+
#
|
200
|
+
# In lite mode, block-level rules are ignored. This means
|
201
|
+
# that tables, paragraphs, lists, and such aren't available.
|
202
|
+
# Only the inline markup for bold, italics, entities and so on.
|
203
|
+
#
|
204
|
+
# r = RedCloth.new( "And then? She *fell*!", [:lite_mode] )
|
205
|
+
# r.to_html
|
206
|
+
# #=> "And then? She <strong>fell</strong>!"
|
207
|
+
#
|
208
|
+
attr_accessor :lite_mode
|
209
|
+
|
210
|
+
#
|
211
|
+
# Accessor for toggling span caps.
|
212
|
+
#
|
213
|
+
# Textile places `span' tags around capitalized
|
214
|
+
# words by default, but this wreaks havoc on Wikis.
|
215
|
+
# If +:no_span_caps+ is set, this will be
|
216
|
+
# suppressed.
|
217
|
+
#
|
218
|
+
attr_accessor :no_span_caps
|
219
|
+
|
220
|
+
#
|
221
|
+
# Establishes the markup predence. Available rules include:
|
222
|
+
#
|
223
|
+
# == Textile Rules
|
224
|
+
#
|
225
|
+
# The following textile rules can be set individually. Or add the complete
|
226
|
+
# set of rules with the single :textile rule, which supplies the rule set in
|
227
|
+
# the following precedence:
|
228
|
+
#
|
229
|
+
# refs_textile:: Textile references (i.e. [hobix]http://hobix.com/)
|
230
|
+
# block_textile_table:: Textile table block structures
|
231
|
+
# block_textile_lists:: Textile list structures
|
232
|
+
# block_textile_prefix:: Textile blocks with prefixes (i.e. bq., h2., etc.)
|
233
|
+
# inline_textile_image:: Textile inline images
|
234
|
+
# inline_textile_link:: Textile inline links
|
235
|
+
# inline_textile_span:: Textile inline spans
|
236
|
+
# glyphs_textile:: Textile entities (such as em-dashes and smart quotes)
|
237
|
+
#
|
238
|
+
# == Markdown
|
239
|
+
#
|
240
|
+
# refs_markdown:: Markdown references (for example: [hobix]: http://hobix.com/)
|
241
|
+
# block_markdown_setext:: Markdown setext headers
|
242
|
+
# block_markdown_atx:: Markdown atx headers
|
243
|
+
# block_markdown_rule:: Markdown horizontal rules
|
244
|
+
# block_markdown_bq:: Markdown blockquotes
|
245
|
+
# block_markdown_lists:: Markdown lists
|
246
|
+
# inline_markdown_link:: Markdown links
|
247
|
+
attr_accessor :rules
|
248
|
+
|
249
|
+
# Returns a new RedCloth object, based on _string_ and
|
250
|
+
# enforcing all the included _restrictions_.
|
251
|
+
#
|
252
|
+
# r = RedCloth.new( "h1. A <b>bold</b> man", [:filter_html] )
|
253
|
+
# r.to_html
|
254
|
+
# #=>"<h1>A <b>bold</b> man</h1>"
|
255
|
+
#
|
256
|
+
def initialize( string, restrictions = [] )
|
257
|
+
restrictions.each { |r| method( "#{ r }=" ).call( true ) }
|
258
|
+
super( string )
|
259
|
+
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# Generates HTML from the Textile contents.
|
263
|
+
#
|
264
|
+
# r = RedCloth.new( "And then? She *fell*!" )
|
265
|
+
# r.to_html( true )
|
266
|
+
# #=>"And then? She <strong>fell</strong>!"
|
267
|
+
#
|
268
|
+
def to_html( *rules )
|
269
|
+
rules = DEFAULT_RULES if rules.empty?
|
270
|
+
# make our working copy
|
271
|
+
text = self.dup
|
272
|
+
|
273
|
+
@urlrefs = {}
|
274
|
+
@shelf = []
|
275
|
+
textile_rules = [:refs_textile, :block_textile_table, :block_textile_lists,
|
276
|
+
:block_textile_prefix, :inline_textile_image, :inline_textile_link,
|
277
|
+
:inline_textile_code, :inline_textile_span, :glyphs_textile]
|
278
|
+
markdown_rules = [:refs_markdown, :block_markdown_setext, :block_markdown_atx, :block_markdown_rule,
|
279
|
+
:block_markdown_bq, :block_markdown_lists,
|
280
|
+
:inline_markdown_reflink, :inline_markdown_link]
|
281
|
+
@rules = rules.collect do |rule|
|
282
|
+
case rule
|
283
|
+
when :markdown
|
284
|
+
markdown_rules
|
285
|
+
when :textile
|
286
|
+
textile_rules
|
287
|
+
else
|
288
|
+
rule
|
289
|
+
end
|
290
|
+
end.flatten
|
291
|
+
|
292
|
+
# standard clean up
|
293
|
+
incoming_entities text
|
294
|
+
clean_white_space text
|
295
|
+
|
296
|
+
# start processor
|
297
|
+
@pre_list = []
|
298
|
+
rip_offtags text
|
299
|
+
no_textile text
|
300
|
+
hard_break text
|
301
|
+
unless @lite_mode
|
302
|
+
refs text
|
303
|
+
blocks text
|
304
|
+
end
|
305
|
+
inline text
|
306
|
+
smooth_offtags text
|
307
|
+
|
308
|
+
retrieve text
|
309
|
+
|
310
|
+
text.gsub!( /<\/?notextile>/, '' )
|
311
|
+
text.gsub!( /x%x%/, '&' )
|
312
|
+
clean_html text if filter_html
|
313
|
+
text.strip!
|
314
|
+
text
|
315
|
+
|
316
|
+
end
|
317
|
+
|
318
|
+
#######
|
319
|
+
private
|
320
|
+
#######
|
321
|
+
#
|
322
|
+
# Mapping of 8-bit ASCII codes to HTML numerical entity equivalents.
|
323
|
+
# (from PyTextile)
|
324
|
+
#
|
325
|
+
TEXTILE_TAGS =
|
326
|
+
|
327
|
+
[[128, 8364], [129, 0], [130, 8218], [131, 402], [132, 8222], [133, 8230],
|
328
|
+
[134, 8224], [135, 8225], [136, 710], [137, 8240], [138, 352], [139, 8249],
|
329
|
+
[140, 338], [141, 0], [142, 0], [143, 0], [144, 0], [145, 8216], [146, 8217],
|
330
|
+
[147, 8220], [148, 8221], [149, 8226], [150, 8211], [151, 8212], [152, 732],
|
331
|
+
[153, 8482], [154, 353], [155, 8250], [156, 339], [157, 0], [158, 0], [159, 376]].
|
332
|
+
|
333
|
+
collect! do |a, b|
|
334
|
+
[a.chr, ( b.zero? and "" or "&#{ b };" )]
|
335
|
+
end
|
336
|
+
|
337
|
+
#
|
338
|
+
# Regular expressions to convert to HTML.
|
339
|
+
#
|
340
|
+
A_HLGN = /(?:(?:<>|<|>|\=|[()]+)+)/
|
341
|
+
A_VLGN = /[\-^~]/
|
342
|
+
C_CLAS = '(?:\([^)]+\))'
|
343
|
+
C_LNGE = '(?:\[[^\]]+\])'
|
344
|
+
C_STYL = '(?:\{[^}]+\})'
|
345
|
+
S_CSPN = '(?:\\\\\d+)'
|
346
|
+
S_RSPN = '(?:/\d+)'
|
347
|
+
A = "(?:#{A_HLGN}?#{A_VLGN}?|#{A_VLGN}?#{A_HLGN}?)"
|
348
|
+
S = "(?:#{S_CSPN}?#{S_RSPN}|#{S_RSPN}?#{S_CSPN}?)"
|
349
|
+
C = "(?:#{C_CLAS}?#{C_STYL}?#{C_LNGE}?|#{C_STYL}?#{C_LNGE}?#{C_CLAS}?|#{C_LNGE}?#{C_STYL}?#{C_CLAS}?)"
|
350
|
+
# PUNCT = Regexp::quote( '!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~' )
|
351
|
+
PUNCT = Regexp::quote( '!"#$%&\'*+,-./:;=?@\\^_`|~' )
|
352
|
+
PUNCT_NOQ = Regexp::quote( '!"#$&\',./:;=?@\\`|' )
|
353
|
+
PUNCT_Q = Regexp::quote( '*-_+^~%' )
|
354
|
+
HYPERLINK = '(\S+?)([^\w\s/;=\?]*?)(?=\s|<|$)'
|
355
|
+
|
356
|
+
# Text markup tags, don't conflict with block tags
|
357
|
+
SIMPLE_HTML_TAGS = [
|
358
|
+
'tt', 'b', 'i', 'big', 'small', 'em', 'strong', 'dfn', 'code',
|
359
|
+
'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'a', 'img', 'br',
|
360
|
+
'br', 'map', 'q', 'sub', 'sup', 'span', 'bdo'
|
361
|
+
]
|
362
|
+
|
363
|
+
QTAGS = [
|
364
|
+
['**', 'b'],
|
365
|
+
['*', 'strong'],
|
366
|
+
['??', 'cite', :limit],
|
367
|
+
['-', 'del', :limit],
|
368
|
+
['__', 'i'],
|
369
|
+
['_', 'em', :limit],
|
370
|
+
['%', 'span', :limit],
|
371
|
+
['+', 'ins', :limit],
|
372
|
+
['^', 'sup'],
|
373
|
+
['~', 'sub']
|
374
|
+
]
|
375
|
+
QTAGS.collect! do |rc, ht, rtype|
|
376
|
+
rcq = Regexp::quote rc
|
377
|
+
re =
|
378
|
+
case rtype
|
379
|
+
when :limit
|
380
|
+
/(\W)
|
381
|
+
(#{rcq})
|
382
|
+
(#{C})
|
383
|
+
(?::(\S+?))?
|
384
|
+
(\S.*?\S|\S)
|
385
|
+
#{rcq}
|
386
|
+
(?=\W)/x
|
387
|
+
else
|
388
|
+
/(#{rcq})
|
389
|
+
(#{C})
|
390
|
+
(?::(\S+))?
|
391
|
+
(\S.*?\S|\S)
|
392
|
+
#{rcq}/xm
|
393
|
+
end
|
394
|
+
[rc, ht, re, rtype]
|
395
|
+
end
|
396
|
+
|
397
|
+
# Elements to handle
|
398
|
+
GLYPHS = [
|
399
|
+
# [ /([^\s\[{(>])?\'([dmst]\b|ll\b|ve\b|\s|:|$)/, '\1’\2' ], # single closing
|
400
|
+
[ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)\'/, '\1’' ], # single closing
|
401
|
+
[ /\'(?=[#{PUNCT_Q}]*(s\b|[\s#{PUNCT_NOQ}]))/, '’' ], # single closing
|
402
|
+
[ /\'/, '‘' ], # single opening
|
403
|
+
[ /</, '<' ], # less-than
|
404
|
+
[ />/, '>' ], # greater-than
|
405
|
+
# [ /([^\s\[{(])?"(\s|:|$)/, '\1”\2' ], # double closing
|
406
|
+
[ /([^\s\[{(>#{PUNCT_Q}][#{PUNCT_Q}]*)"/, '\1”' ], # double closing
|
407
|
+
[ /"(?=[#{PUNCT_Q}]*[\s#{PUNCT_NOQ}])/, '”' ], # double closing
|
408
|
+
[ /"/, '“' ], # double opening
|
409
|
+
[ /\b( )?\.{3}/, '\1…' ], # ellipsis
|
410
|
+
[ /\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])/, '<acronym title="\2">\1</acronym>' ], # 3+ uppercase acronym
|
411
|
+
[ /(^|[^"][>\s])([A-Z][A-Z0-9 ]+[A-Z0-9])([^<A-Za-z0-9]|$)/, '\1<span class="caps">\2</span>\3', :no_span_caps ], # 3+ uppercase caps
|
412
|
+
[ /(\.\s)?\s?--\s?/, '\1—' ], # em dash
|
413
|
+
[ /\s->\s/, ' → ' ], # right arrow
|
414
|
+
[ /\s-\s/, ' – ' ], # en dash
|
415
|
+
[ /(\d+) ?x ?(\d+)/, '\1×\2' ], # dimension sign
|
416
|
+
[ /\b ?[(\[]TM[\])]/i, '™' ], # trademark
|
417
|
+
[ /\b ?[(\[]R[\])]/i, '®' ], # registered
|
418
|
+
[ /\b ?[(\[]C[\])]/i, '©' ] # copyright
|
419
|
+
]
|
420
|
+
|
421
|
+
H_ALGN_VALS = {
|
422
|
+
'<' => 'left',
|
423
|
+
'=' => 'center',
|
424
|
+
'>' => 'right',
|
425
|
+
'<>' => 'justify'
|
426
|
+
}
|
427
|
+
|
428
|
+
V_ALGN_VALS = {
|
429
|
+
'^' => 'top',
|
430
|
+
'-' => 'middle',
|
431
|
+
'~' => 'bottom'
|
432
|
+
}
|
433
|
+
|
434
|
+
#
|
435
|
+
# Flexible HTML escaping
|
436
|
+
#
|
437
|
+
def htmlesc( str, mode )
|
438
|
+
str.gsub!( '&', '&' )
|
439
|
+
str.gsub!( '"', '"' ) if mode != :NoQuotes
|
440
|
+
str.gsub!( "'", ''' ) if mode == :Quotes
|
441
|
+
str.gsub!( '<', '<')
|
442
|
+
str.gsub!( '>', '>')
|
443
|
+
end
|
444
|
+
|
445
|
+
# Search and replace for Textile glyphs (quotes, dashes, other symbols)
|
446
|
+
def pgl( text )
|
447
|
+
GLYPHS.each do |re, resub, tog|
|
448
|
+
next if tog and method( tog ).call
|
449
|
+
text.gsub! re, resub
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# Parses Textile attribute lists and builds an HTML attribute string
|
454
|
+
def pba( text_in, element = "" )
|
455
|
+
|
456
|
+
return '' unless text_in
|
457
|
+
|
458
|
+
style = []
|
459
|
+
text = text_in.dup
|
460
|
+
if element == 'td'
|
461
|
+
colspan = $1 if text =~ /\\(\d+)/
|
462
|
+
rowspan = $1 if text =~ /\/(\d+)/
|
463
|
+
style << "vertical-align:#{ v_align( $& ) };" if text =~ A_VLGN
|
464
|
+
end
|
465
|
+
|
466
|
+
style << "#{ $1 };" if not filter_styles and
|
467
|
+
text.sub!( /\{([^}]*)\}/, '' )
|
468
|
+
|
469
|
+
lang = $1 if
|
470
|
+
text.sub!( /\[([^)]+?)\]/, '' )
|
471
|
+
|
472
|
+
cls = $1 if
|
473
|
+
text.sub!( /\(([^()]+?)\)/, '' )
|
474
|
+
|
475
|
+
style << "padding-left:#{ $1.length }em;" if
|
476
|
+
text.sub!( /([(]+)/, '' )
|
477
|
+
|
478
|
+
style << "padding-right:#{ $1.length }em;" if text.sub!( /([)]+)/, '' )
|
479
|
+
|
480
|
+
style << "text-align:#{ h_align( $& ) };" if text =~ A_HLGN
|
481
|
+
|
482
|
+
cls, id = $1, $2 if cls =~ /^(.*?)#(.*)$/
|
483
|
+
|
484
|
+
atts = ''
|
485
|
+
atts << " style=\"#{ style.join }\"" unless style.empty?
|
486
|
+
atts << " class=\"#{ cls }\"" unless cls.to_s.empty?
|
487
|
+
atts << " lang=\"#{ lang }\"" if lang
|
488
|
+
atts << " id=\"#{ id }\"" if id
|
489
|
+
atts << " colspan=\"#{ colspan }\"" if colspan
|
490
|
+
atts << " rowspan=\"#{ rowspan }\"" if rowspan
|
491
|
+
|
492
|
+
atts
|
493
|
+
end
|
494
|
+
|
495
|
+
TABLE_RE = /^(?:table(_?#{S}#{A}#{C})\. ?\n)?^(#{A}#{C}\.? ?\|.*?\|)(\n\n|\Z)/m
|
496
|
+
|
497
|
+
# Parses a Textile table block, building HTML from the result.
|
498
|
+
def block_textile_table( text )
|
499
|
+
text.gsub!( TABLE_RE ) do |matches|
|
500
|
+
|
501
|
+
tatts, fullrow = $~[1..2]
|
502
|
+
tatts = pba( tatts, 'table' )
|
503
|
+
tatts = shelve( tatts ) if tatts
|
504
|
+
rows = []
|
505
|
+
|
506
|
+
fullrow.
|
507
|
+
split( /\|$/m ).
|
508
|
+
delete_if { |x| x.empty? }.
|
509
|
+
each do |row|
|
510
|
+
|
511
|
+
ratts, row = pba( $1, 'tr' ), $2 if row =~ /^(#{A}#{C}\. )(.*)/m
|
512
|
+
|
513
|
+
cells = []
|
514
|
+
row.split( '|' ).each do |cell|
|
515
|
+
ctyp = 'd'
|
516
|
+
ctyp = 'h' if cell =~ /^_/
|
517
|
+
|
518
|
+
catts = ''
|
519
|
+
catts, cell = pba( $1, 'td' ), $2 if cell =~ /^(_?#{S}#{A}#{C}\. ?)(.*)/
|
520
|
+
|
521
|
+
unless cell.strip.empty?
|
522
|
+
catts = shelve( catts ) if catts
|
523
|
+
cells << "\t\t\t<t#{ ctyp }#{ catts }>#{ cell }</t#{ ctyp }>"
|
524
|
+
end
|
525
|
+
end
|
526
|
+
ratts = shelve( ratts ) if ratts
|
527
|
+
rows << "\t\t<tr#{ ratts }>\n#{ cells.join( "\n" ) }\n\t\t</tr>"
|
528
|
+
end
|
529
|
+
"\t<table#{ tatts }>\n#{ rows.join( "\n" ) }\n\t</table>\n\n"
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
LISTS_RE = /^([#*]+?#{C} .*?)$(?![^#*])/m
|
534
|
+
LISTS_CONTENT_RE = /^([#*]+)(#{A}#{C}) (.*)$/m
|
535
|
+
|
536
|
+
# Parses Textile lists and generates HTML
|
537
|
+
def block_textile_lists( text )
|
538
|
+
text.gsub!( LISTS_RE ) do |match|
|
539
|
+
lines = match.split( /\n/ )
|
540
|
+
last_line = -1
|
541
|
+
depth = []
|
542
|
+
lines.each_with_index do |line, line_id|
|
543
|
+
if line =~ LISTS_CONTENT_RE
|
544
|
+
tl,atts,content = $~[1..3]
|
545
|
+
if depth.last
|
546
|
+
if depth.last.length > tl.length
|
547
|
+
(depth.length - 1).downto(0) do |i|
|
548
|
+
break if depth[i].length == tl.length
|
549
|
+
lines[line_id - 1] << "</li>\n\t</#{ lT( depth[i] ) }l>\n\t"
|
550
|
+
depth.pop
|
551
|
+
end
|
552
|
+
end
|
553
|
+
if depth.last and depth.last.length == tl.length
|
554
|
+
lines[line_id - 1] << '</li>'
|
555
|
+
end
|
556
|
+
end
|
557
|
+
unless depth.last == tl
|
558
|
+
depth << tl
|
559
|
+
atts = pba( atts )
|
560
|
+
atts = shelve( atts ) if atts
|
561
|
+
lines[line_id] = "\t<#{ lT(tl) }l#{ atts }>\n\t<li>#{ content }"
|
562
|
+
else
|
563
|
+
lines[line_id] = "\t\t<li>#{ content }"
|
564
|
+
end
|
565
|
+
last_line = line_id
|
566
|
+
|
567
|
+
else
|
568
|
+
last_line = line_id
|
569
|
+
end
|
570
|
+
if line_id - last_line > 1 or line_id == lines.length - 1
|
571
|
+
depth.delete_if do |v|
|
572
|
+
lines[last_line] << "</li>\n\t</#{ lT( v ) }l>"
|
573
|
+
end
|
574
|
+
end
|
575
|
+
end
|
576
|
+
lines.join( "\n" )
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
CODE_RE = /(\W)
|
581
|
+
@
|
582
|
+
(?:\|(\w+?)\|)?
|
583
|
+
(.+?)
|
584
|
+
@
|
585
|
+
(?=\W)/x
|
586
|
+
|
587
|
+
def inline_textile_code( text )
|
588
|
+
text.gsub!( CODE_RE ) do |m|
|
589
|
+
before,lang,code,after = $~[1..4]
|
590
|
+
lang = " lang=\"#{ lang }\"" if lang
|
591
|
+
rip_offtags( "#{ before }<code#{ lang }>#{ code }</code>#{ after }" )
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
def lT( text )
|
596
|
+
text =~ /\#$/ ? 'o' : 'u'
|
597
|
+
end
|
598
|
+
|
599
|
+
# def hard_break( text )
|
600
|
+
# text.gsub!( /(.)\n(?!\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
|
601
|
+
# end
|
602
|
+
# Patch for RedCloth. Fixed in RedCloth r128 but _why hasn't released it yet.
|
603
|
+
# <a href="http://code.whytheluckystiff.net/redcloth/changeset/128">http://code.whytheluckystiff.net/redcloth/changeset/128</a>
|
604
|
+
def hard_break( text )
|
605
|
+
text.gsub!( /(.)\n(?!\n|\Z| *([#*=]+(\s|$)|[{|]))/, "\\1<br />" ) if hard_breaks
|
606
|
+
end
|
607
|
+
|
608
|
+
BLOCKS_GROUP_RE = /\n{2,}(?! )/m
|
609
|
+
|
610
|
+
def blocks( text, deep_code = false )
|
611
|
+
text.replace( text.split( BLOCKS_GROUP_RE ).collect do |blk|
|
612
|
+
plain = blk !~ /\A[#*> ]/
|
613
|
+
|
614
|
+
# skip blocks that are complex HTML
|
615
|
+
if blk =~ /^<\/?(\w+).*>/ and not SIMPLE_HTML_TAGS.include? $1
|
616
|
+
blk
|
617
|
+
else
|
618
|
+
# search for indentation levels
|
619
|
+
blk.strip!
|
620
|
+
if blk.empty?
|
621
|
+
blk
|
622
|
+
else
|
623
|
+
code_blk = nil
|
624
|
+
blk.gsub!( /((?:\n(?:\n^ +[^\n]*)+)+)/m ) do |iblk|
|
625
|
+
flush_left iblk
|
626
|
+
blocks iblk, plain
|
627
|
+
iblk.gsub( /^(\S)/, "\t\\1" )
|
628
|
+
if plain
|
629
|
+
code_blk = iblk; ""
|
630
|
+
else
|
631
|
+
iblk
|
632
|
+
end
|
633
|
+
end
|
634
|
+
|
635
|
+
block_applied = 0
|
636
|
+
@rules.each do |rule_name|
|
637
|
+
block_applied += 1 if ( rule_name.to_s.match /^block_/ and method( rule_name ).call( blk ) )
|
638
|
+
end
|
639
|
+
if block_applied.zero?
|
640
|
+
if deep_code
|
641
|
+
blk = "\t<pre><code>#{ blk }</code></pre>"
|
642
|
+
else
|
643
|
+
blk = "\t<p>#{ blk }</p>"
|
644
|
+
end
|
645
|
+
end
|
646
|
+
# hard_break blk
|
647
|
+
blk + "\n#{ code_blk }"
|
648
|
+
end
|
649
|
+
end
|
650
|
+
|
651
|
+
end.join( "\n\n" ) )
|
652
|
+
end
|
653
|
+
|
654
|
+
def textile_bq( tag, atts, cite, content )
|
655
|
+
cite, cite_title = check_refs( cite )
|
656
|
+
cite = " cite=\"#{ cite }\"" if cite
|
657
|
+
atts = shelve( atts ) if atts
|
658
|
+
"\t<blockquote#{ cite }>\n\t\t<p#{ atts }>#{ content }</p>\n\t</blockquote>"
|
659
|
+
end
|
660
|
+
|
661
|
+
def textile_p( tag, atts, cite, content )
|
662
|
+
atts = shelve( atts ) if atts
|
663
|
+
"\t<#{ tag }#{ atts }>#{ content }</#{ tag }>"
|
664
|
+
end
|
665
|
+
|
666
|
+
alias textile_h1 textile_p
|
667
|
+
alias textile_h2 textile_p
|
668
|
+
alias textile_h3 textile_p
|
669
|
+
alias textile_h4 textile_p
|
670
|
+
alias textile_h5 textile_p
|
671
|
+
alias textile_h6 textile_p
|
672
|
+
|
673
|
+
def textile_fn_( tag, num, atts, cite, content )
|
674
|
+
atts << " id=\"fn#{ num }\""
|
675
|
+
content = "<sup>#{ num }</sup> #{ content }"
|
676
|
+
atts = shelve( atts ) if atts
|
677
|
+
"\t<p#{ atts }>#{ content }</p>"
|
678
|
+
end
|
679
|
+
|
680
|
+
BLOCK_RE = /^(([a-z]+)(\d*))(#{A}#{C})\.(?::(\S+))? (.*)$/m
|
681
|
+
|
682
|
+
def block_textile_prefix( text )
|
683
|
+
if text =~ BLOCK_RE
|
684
|
+
tag,tagpre,num,atts,cite,content = $~[1..6]
|
685
|
+
atts = pba( atts )
|
686
|
+
|
687
|
+
# pass to prefix handler
|
688
|
+
if respond_to? "textile_#{ tag }", true
|
689
|
+
text.gsub!( $&, method( "textile_#{ tag }" ).call( tag, atts, cite, content ) )
|
690
|
+
elsif respond_to? "textile_#{ tagpre }_", true
|
691
|
+
text.gsub!( $&, method( "textile_#{ tagpre }_" ).call( tagpre, num, atts, cite, content ) )
|
692
|
+
end
|
693
|
+
end
|
694
|
+
end
|
695
|
+
|
696
|
+
SETEXT_RE = /\A(.+?)\n([=-])[=-]* *$/m
|
697
|
+
def block_markdown_setext( text )
|
698
|
+
if text =~ SETEXT_RE
|
699
|
+
tag = if $2 == "="; "h1"; else; "h2"; end
|
700
|
+
blk, cont = "<#{ tag }>#{ $1 }</#{ tag }>", $'
|
701
|
+
blocks cont
|
702
|
+
text.replace( blk + cont )
|
703
|
+
end
|
704
|
+
end
|
705
|
+
|
706
|
+
ATX_RE = /\A(\#{1,6}) # $1 = string of #'s
|
707
|
+
[ ]*
|
708
|
+
(.+?) # $2 = Header text
|
709
|
+
[ ]*
|
710
|
+
\#* # optional closing #'s (not counted)
|
711
|
+
$/x
|
712
|
+
def block_markdown_atx( text )
|
713
|
+
if text =~ ATX_RE
|
714
|
+
tag = "h#{ $1.length }"
|
715
|
+
blk, cont = "<#{ tag }>#{ $2 }</#{ tag }>\n\n", $'
|
716
|
+
blocks cont
|
717
|
+
text.replace( blk + cont )
|
718
|
+
end
|
719
|
+
end
|
720
|
+
|
721
|
+
MARKDOWN_BQ_RE = /\A(^ *> ?.+$(.+\n)*\n*)+/m
|
722
|
+
|
723
|
+
def block_markdown_bq( text )
|
724
|
+
text.gsub!( MARKDOWN_BQ_RE ) do |blk|
|
725
|
+
blk.gsub!( /^ *> ?/, '' )
|
726
|
+
flush_left blk
|
727
|
+
blocks blk
|
728
|
+
blk.gsub!( /^(\S)/, "\t\\1" )
|
729
|
+
"<blockquote>\n#{ blk }\n</blockquote>\n\n"
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
MARKDOWN_RULE_RE = /^(#{
|
734
|
+
['*', '-', '_'].collect { |ch| '( ?' + Regexp::quote( ch ) + ' ?){3,}' }.join( '|' )
|
735
|
+
})$/
|
736
|
+
|
737
|
+
def block_markdown_rule( text )
|
738
|
+
text.gsub!( MARKDOWN_RULE_RE ) do |blk|
|
739
|
+
"<hr />"
|
740
|
+
end
|
741
|
+
end
|
742
|
+
|
743
|
+
# XXX TODO XXX
|
744
|
+
def block_markdown_lists( text )
|
745
|
+
end
|
746
|
+
|
747
|
+
def inline_textile_span( text )
|
748
|
+
QTAGS.each do |qtag_rc, ht, qtag_re, rtype|
|
749
|
+
text.gsub!( qtag_re ) do |m|
|
750
|
+
|
751
|
+
case rtype
|
752
|
+
when :limit
|
753
|
+
sta,qtag,atts,cite,content = $~[1..5]
|
754
|
+
else
|
755
|
+
qtag,atts,cite,content = $~[1..4]
|
756
|
+
sta = ''
|
757
|
+
end
|
758
|
+
atts = pba( atts )
|
759
|
+
atts << " cite=\"#{ cite }\"" if cite
|
760
|
+
atts = shelve( atts ) if atts
|
761
|
+
|
762
|
+
"#{ sta }<#{ ht }#{ atts }>#{ content }</#{ ht }>"
|
763
|
+
|
764
|
+
end
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
LINK_RE = /
|
769
|
+
([\s\[{(]|[#{PUNCT}])? # $pre
|
770
|
+
" # start
|
771
|
+
(#{C}) # $atts
|
772
|
+
([^"]+?) # $text
|
773
|
+
\s?
|
774
|
+
(?:\(([^)]+?)\)(?="))? # $title
|
775
|
+
":
|
776
|
+
(\S+?) # $url
|
777
|
+
(\/)? # $slash
|
778
|
+
([^\w\/;]*?) # $post
|
779
|
+
(?=<|\s|$)
|
780
|
+
/x
|
781
|
+
|
782
|
+
def inline_textile_link( text )
|
783
|
+
text.gsub!( LINK_RE ) do |m|
|
784
|
+
pre,atts,text,title,url,slash,post = $~[1..7]
|
785
|
+
|
786
|
+
url, url_title = check_refs( url )
|
787
|
+
title ||= url_title
|
788
|
+
|
789
|
+
atts = pba( atts )
|
790
|
+
atts = " href=\"#{ url }#{ slash }\"#{ atts }"
|
791
|
+
atts << " title=\"#{ title }\"" if title
|
792
|
+
atts = shelve( atts ) if atts
|
793
|
+
|
794
|
+
"#{ pre }<a#{ atts }>#{ text }</a>#{ post }"
|
795
|
+
end
|
796
|
+
end
|
797
|
+
|
798
|
+
MARKDOWN_REFLINK_RE = /
|
799
|
+
\[([^\[\]]+)\] # $text
|
800
|
+
[ ]? # opt. space
|
801
|
+
(?:\n[ ]*)? # one optional newline followed by spaces
|
802
|
+
\[(.*?)\] # $id
|
803
|
+
/x
|
804
|
+
|
805
|
+
def inline_markdown_reflink( text )
|
806
|
+
text.gsub!( MARKDOWN_REFLINK_RE ) do |m|
|
807
|
+
text, id = $~[1..2]
|
808
|
+
|
809
|
+
if id.empty?
|
810
|
+
url, title = check_refs( text )
|
811
|
+
else
|
812
|
+
url, title = check_refs( id )
|
813
|
+
end
|
814
|
+
|
815
|
+
atts = " href=\"#{ url }\""
|
816
|
+
atts << " title=\"#{ title }\"" if title
|
817
|
+
atts = shelve( atts )
|
818
|
+
|
819
|
+
"<a#{ atts }>#{ text }</a>"
|
820
|
+
end
|
821
|
+
end
|
822
|
+
|
823
|
+
MARKDOWN_LINK_RE = /
|
824
|
+
\[([^\[\]]+)\] # $text
|
825
|
+
\( # open paren
|
826
|
+
[ \t]* # opt space
|
827
|
+
<?(.+?)>? # $href
|
828
|
+
[ \t]* # opt space
|
829
|
+
(?: # whole title
|
830
|
+
(['"]) # $quote
|
831
|
+
(.*?) # $title
|
832
|
+
\3 # matching quote
|
833
|
+
)? # title is optional
|
834
|
+
\)
|
835
|
+
/x
|
836
|
+
|
837
|
+
def inline_markdown_link( text )
|
838
|
+
text.gsub!( MARKDOWN_LINK_RE ) do |m|
|
839
|
+
text, url, quote, title = $~[1..4]
|
840
|
+
|
841
|
+
atts = " href=\"#{ url }\""
|
842
|
+
atts << " title=\"#{ title }\"" if title
|
843
|
+
atts = shelve( atts )
|
844
|
+
|
845
|
+
"<a#{ atts }>#{ text }</a>"
|
846
|
+
end
|
847
|
+
end
|
848
|
+
|
849
|
+
TEXTILE_REFS_RE = /(^ *)\[([^\n]+?)\](#{HYPERLINK})(?=\s|$)/
|
850
|
+
MARKDOWN_REFS_RE = /(^ *)\[([^\n]+?)\]:\s+<?(#{HYPERLINK})>?(?:\s+"((?:[^"]|\\")+)")?(?=\s|$)/m
|
851
|
+
|
852
|
+
def refs( text )
|
853
|
+
@rules.each do |rule_name|
|
854
|
+
method( rule_name ).call( text ) if rule_name.to_s.match /^refs_/
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
def refs_textile( text )
|
859
|
+
text.gsub!( TEXTILE_REFS_RE ) do |m|
|
860
|
+
flag, url = $~[2..3]
|
861
|
+
@urlrefs[flag.downcase] = [url, nil]
|
862
|
+
nil
|
863
|
+
end
|
864
|
+
end
|
865
|
+
|
866
|
+
def refs_markdown( text )
|
867
|
+
text.gsub!( MARKDOWN_REFS_RE ) do |m|
|
868
|
+
flag, url = $~[2..3]
|
869
|
+
title = $~[6]
|
870
|
+
@urlrefs[flag.downcase] = [url, title]
|
871
|
+
nil
|
872
|
+
end
|
873
|
+
end
|
874
|
+
|
875
|
+
def check_refs( text )
|
876
|
+
ret = @urlrefs[text.downcase] if text
|
877
|
+
ret || [text, nil]
|
878
|
+
end
|
879
|
+
|
880
|
+
IMAGE_RE = /
|
881
|
+
(<p>|.|^) # start of line?
|
882
|
+
\! # opening
|
883
|
+
(\<|\=|\>)? # optional alignment atts
|
884
|
+
(#{C}) # optional style,class atts
|
885
|
+
(?:\. )? # optional dot-space
|
886
|
+
([^\s(!]+?) # presume this is the src
|
887
|
+
\s? # optional space
|
888
|
+
(?:\(((?:[^\(\)]|\([^\)]+\))+?)\))? # optional title
|
889
|
+
\! # closing
|
890
|
+
(?::#{ HYPERLINK })? # optional href
|
891
|
+
/x
|
892
|
+
|
893
|
+
def inline_textile_image( text )
|
894
|
+
text.gsub!( IMAGE_RE ) do |m|
|
895
|
+
stln,algn,atts,url,title,href,href_a1,href_a2 = $~[1..8]
|
896
|
+
atts = pba( atts )
|
897
|
+
atts = " src=\"#{ url }\"#{ atts }"
|
898
|
+
atts << " title=\"#{ title }\"" if title
|
899
|
+
atts << " alt=\"#{ title }\""
|
900
|
+
# size = @getimagesize($url);
|
901
|
+
# if($size) $atts.= " $size[3]";
|
902
|
+
|
903
|
+
href, alt_title = check_refs( href ) if href
|
904
|
+
url, url_title = check_refs( url )
|
905
|
+
|
906
|
+
out = ''
|
907
|
+
out << "<a#{ shelve( " href=\"#{ href }\"" ) }>" if href
|
908
|
+
out << "<img#{ shelve( atts ) } />"
|
909
|
+
out << "</a>#{ href_a1 }#{ href_a2 }" if href
|
910
|
+
|
911
|
+
if algn
|
912
|
+
algn = h_align( algn )
|
913
|
+
if stln == "<p>"
|
914
|
+
out = "<p style=\"float:#{ algn }\">#{ out }"
|
915
|
+
else
|
916
|
+
out = "#{ stln }<div style=\"float:#{ algn }\">#{ out }</div>"
|
917
|
+
end
|
918
|
+
else
|
919
|
+
out = stln + out
|
920
|
+
end
|
921
|
+
|
922
|
+
out
|
923
|
+
end
|
924
|
+
end
|
925
|
+
|
926
|
+
def shelve( val )
|
927
|
+
@shelf << val
|
928
|
+
" :redsh##{ @shelf.length }:"
|
929
|
+
end
|
930
|
+
|
931
|
+
def retrieve( text )
|
932
|
+
@shelf.each_with_index do |r, i|
|
933
|
+
text.gsub!( " :redsh##{ i + 1 }:", r )
|
934
|
+
end
|
935
|
+
end
|
936
|
+
|
937
|
+
def incoming_entities( text )
|
938
|
+
## turn any incoming ampersands into a dummy character for now.
|
939
|
+
## This uses a negative lookahead for alphanumerics followed by a semicolon,
|
940
|
+
## implying an incoming html entity, to be skipped
|
941
|
+
|
942
|
+
text.gsub!( /&(?![#a-z0-9]+;)/i, "x%x%" )
|
943
|
+
end
|
944
|
+
|
945
|
+
def no_textile( text )
|
946
|
+
text.gsub!( /(^|\s)==([^=]+.*?)==(\s|$)?/,
|
947
|
+
'\1<notextile>\2</notextile>\3' )
|
948
|
+
text.gsub!( /^ *==([^=]+.*?)==/m,
|
949
|
+
'\1<notextile>\2</notextile>\3' )
|
950
|
+
end
|
951
|
+
|
952
|
+
def clean_white_space( text )
|
953
|
+
# normalize line breaks
|
954
|
+
text.gsub!( /\r\n/, "\n" )
|
955
|
+
text.gsub!( /\r/, "\n" )
|
956
|
+
text.gsub!( /\t/, ' ' )
|
957
|
+
text.gsub!( /^ +$/, '' )
|
958
|
+
text.gsub!( /\n{3,}/, "\n\n" )
|
959
|
+
text.gsub!( /"$/, "\" " )
|
960
|
+
|
961
|
+
# if entire document is indented, flush
|
962
|
+
# to the left side
|
963
|
+
flush_left text
|
964
|
+
end
|
965
|
+
|
966
|
+
def flush_left( text )
|
967
|
+
indt = 0
|
968
|
+
if text =~ /^ /
|
969
|
+
while text !~ /^ {#{indt}}\S/
|
970
|
+
indt += 1
|
971
|
+
end unless text.empty?
|
972
|
+
if indt.nonzero?
|
973
|
+
text.gsub!( /^ {#{indt}}/, '' )
|
974
|
+
end
|
975
|
+
end
|
976
|
+
end
|
977
|
+
|
978
|
+
def footnote_ref( text )
|
979
|
+
text.gsub!( /\b\[([0-9]+?)\](\s)?/,
|
980
|
+
'<sup><a href="#fn\1">\1</a></sup>\2' )
|
981
|
+
end
|
982
|
+
|
983
|
+
OFFTAGS = /(code|pre|kbd|notextile)/
|
984
|
+
OFFTAG_MATCH = /(?:(<\/#{ OFFTAGS }>)|(<#{ OFFTAGS }[^>]*>))(.*?)(?=<\/?#{ OFFTAGS }|\Z)/mi
|
985
|
+
OFFTAG_OPEN = /<#{ OFFTAGS }/
|
986
|
+
OFFTAG_CLOSE = /<\/?#{ OFFTAGS }/
|
987
|
+
HASTAG_MATCH = /(<\/?\w[^\n]*?>)/m
|
988
|
+
ALLTAG_MATCH = /(<\/?\w[^\n]*?>)|.*?(?=<\/?\w[^\n]*?>|$)/m
|
989
|
+
|
990
|
+
def glyphs_textile( text, level = 0 )
|
991
|
+
if text !~ HASTAG_MATCH
|
992
|
+
pgl text
|
993
|
+
footnote_ref text
|
994
|
+
else
|
995
|
+
codepre = 0
|
996
|
+
text.gsub!( ALLTAG_MATCH ) do |line|
|
997
|
+
## matches are off if we're between <code>, <pre> etc.
|
998
|
+
if $1
|
999
|
+
if line =~ OFFTAG_OPEN
|
1000
|
+
codepre += 1
|
1001
|
+
elsif line =~ OFFTAG_CLOSE
|
1002
|
+
codepre -= 1
|
1003
|
+
codepre = 0 if codepre < 0
|
1004
|
+
end
|
1005
|
+
elsif codepre.zero?
|
1006
|
+
glyphs_textile( line, level + 1 )
|
1007
|
+
else
|
1008
|
+
htmlesc( line, :NoQuotes )
|
1009
|
+
end
|
1010
|
+
# p [level, codepre, line]
|
1011
|
+
|
1012
|
+
line
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
def rip_offtags( text )
|
1018
|
+
if text =~ /<.*>/
|
1019
|
+
## strip and encode <pre> content
|
1020
|
+
codepre, used_offtags = 0, {}
|
1021
|
+
text.gsub!( OFFTAG_MATCH ) do |line|
|
1022
|
+
if $3
|
1023
|
+
offtag, aftertag = $4, $5
|
1024
|
+
codepre += 1
|
1025
|
+
used_offtags[offtag] = true
|
1026
|
+
if codepre - used_offtags.length > 0
|
1027
|
+
htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
|
1028
|
+
@pre_list.last << line
|
1029
|
+
line = ""
|
1030
|
+
else
|
1031
|
+
htmlesc( aftertag, :NoQuotes ) if aftertag and not used_offtags['notextile']
|
1032
|
+
line = "<redpre##{ @pre_list.length }>"
|
1033
|
+
@pre_list << "#{ $3 }#{ aftertag }"
|
1034
|
+
end
|
1035
|
+
elsif $1 and codepre > 0
|
1036
|
+
if codepre - used_offtags.length > 0
|
1037
|
+
htmlesc( line, :NoQuotes ) unless used_offtags['notextile']
|
1038
|
+
@pre_list.last << line
|
1039
|
+
line = ""
|
1040
|
+
end
|
1041
|
+
codepre -= 1 unless codepre.zero?
|
1042
|
+
used_offtags = {} if codepre.zero?
|
1043
|
+
end
|
1044
|
+
line
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
text
|
1048
|
+
end
|
1049
|
+
|
1050
|
+
def smooth_offtags( text )
|
1051
|
+
unless @pre_list.empty?
|
1052
|
+
## replace <pre> content
|
1053
|
+
text.gsub!( /<redpre#(\d+)>/ ) { @pre_list[$1.to_i] }
|
1054
|
+
end
|
1055
|
+
end
|
1056
|
+
|
1057
|
+
def inline( text )
|
1058
|
+
[/^inline_/, /^glyphs_/].each do |meth_re|
|
1059
|
+
@rules.each do |rule_name|
|
1060
|
+
method( rule_name ).call( text ) if rule_name.to_s.match( meth_re )
|
1061
|
+
end
|
1062
|
+
end
|
1063
|
+
end
|
1064
|
+
|
1065
|
+
def h_align( text )
|
1066
|
+
H_ALGN_VALS[text]
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
def v_align( text )
|
1070
|
+
V_ALGN_VALS[text]
|
1071
|
+
end
|
1072
|
+
|
1073
|
+
def textile_popup_help( name, windowW, windowH )
|
1074
|
+
' <a target="_blank" href="http://hobix.com/textile/#' + helpvar + '" onclick="window.open(this.href, \'popupwindow\', \'width=' + windowW + ',height=' + windowH + ',scrollbars,resizable\'); return false;">' + name + '</a><br />'
|
1075
|
+
end
|
1076
|
+
|
1077
|
+
# HTML cleansing stuff
|
1078
|
+
BASIC_TAGS = {
|
1079
|
+
'a' => ['href', 'title'],
|
1080
|
+
'img' => ['src', 'alt', 'title'],
|
1081
|
+
'br' => [],
|
1082
|
+
'i' => nil,
|
1083
|
+
'u' => nil,
|
1084
|
+
'b' => nil,
|
1085
|
+
'pre' => nil,
|
1086
|
+
'kbd' => nil,
|
1087
|
+
'code' => ['lang'],
|
1088
|
+
'cite' => nil,
|
1089
|
+
'strong' => nil,
|
1090
|
+
'em' => nil,
|
1091
|
+
'ins' => nil,
|
1092
|
+
'sup' => nil,
|
1093
|
+
'sub' => nil,
|
1094
|
+
'del' => nil,
|
1095
|
+
'table' => nil,
|
1096
|
+
'tr' => nil,
|
1097
|
+
'td' => ['colspan', 'rowspan'],
|
1098
|
+
'th' => nil,
|
1099
|
+
'ol' => nil,
|
1100
|
+
'ul' => nil,
|
1101
|
+
'li' => nil,
|
1102
|
+
'p' => nil,
|
1103
|
+
'h1' => nil,
|
1104
|
+
'h2' => nil,
|
1105
|
+
'h3' => nil,
|
1106
|
+
'h4' => nil,
|
1107
|
+
'h5' => nil,
|
1108
|
+
'h6' => nil,
|
1109
|
+
'blockquote' => ['cite']
|
1110
|
+
}
|
1111
|
+
|
1112
|
+
def clean_html( text, tags = BASIC_TAGS )
|
1113
|
+
text.gsub!( /<!\[CDATA\[/, '' )
|
1114
|
+
text.gsub!( /<(\/*)(\w+)([^>]*)>/ ) do
|
1115
|
+
raw = $~
|
1116
|
+
tag = raw[2].downcase
|
1117
|
+
if tags.has_key? tag
|
1118
|
+
pcs = [tag]
|
1119
|
+
tags[tag].each do |prop|
|
1120
|
+
['"', "'", ''].each do |q|
|
1121
|
+
q2 = ( q != '' ? q : '\s' )
|
1122
|
+
if raw[3] =~ /#{prop}\s*=\s*#{q}([^#{q2}]+)#{q}/i
|
1123
|
+
attrv = $1
|
1124
|
+
next if prop == 'src' and attrv =~ %r{^(?!http)\w+:}
|
1125
|
+
pcs << "#{prop}=\"#{$1.gsub('"', '\\"')}\""
|
1126
|
+
break
|
1127
|
+
end
|
1128
|
+
end
|
1129
|
+
end if tags[tag]
|
1130
|
+
"<#{raw[1]}#{pcs.join " "}>"
|
1131
|
+
else
|
1132
|
+
" "
|
1133
|
+
end
|
1134
|
+
end
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
|
data/lib/textilize.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
#mainly extract from Rails2.
|
2
|
+
module Textilize
|
3
|
+
module Helper
|
4
|
+
require 'RedCloth'
|
5
|
+
# Returns the text with all the Textile[http://www.textism.com/tools/textile] codes turned into HTML tags.
|
6
|
+
#
|
7
|
+
# You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
|
8
|
+
# <i>This method is only available if RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
|
9
|
+
# is available</i>.
|
10
|
+
#
|
11
|
+
# ==== Examples
|
12
|
+
# textilize("*This is Textile!* Rejoice!")
|
13
|
+
# # => "<p><strong>This is Textile!</strong> Rejoice!</p>"
|
14
|
+
#
|
15
|
+
# textilize("I _love_ ROR(Ruby on Rails)!")
|
16
|
+
# # => "<p>I <em>love</em> <acronym title="Ruby on Rails">ROR</acronym>!</p>"
|
17
|
+
#
|
18
|
+
# textilize("h2. Textile makes markup -easy- simple!")
|
19
|
+
# # => "<h2>Textile makes markup <del>easy</del> simple!</h2>"
|
20
|
+
#
|
21
|
+
# textilize("Visit the Rails website "here":http://www.rubyonrails.org/.)
|
22
|
+
# # => "<p>Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>.</p>"
|
23
|
+
#
|
24
|
+
# textilize("This is worded <strong>strongly</strong>")
|
25
|
+
# # => "<p>This is worded <strong>strongly</strong></p>"
|
26
|
+
#
|
27
|
+
# textilize("This is worded <strong>strongly</strong>", :filter_html)
|
28
|
+
# # => "<p>This is worded <strong>strongly</strong></p>"
|
29
|
+
def textilize(text, *options)
|
30
|
+
text = sanitize(text) unless text.html_safe?
|
31
|
+
text.blank? ? "" : RedCloth.new(text, options).to_html.html_safe
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the text with all the Textile codes turned into HTML tags,
|
35
|
+
# but without the bounding <p> tag that RedCloth adds.
|
36
|
+
#
|
37
|
+
# You can learn more about Textile's syntax at its website[http://www.textism.com/tools/textile].
|
38
|
+
# <i>This method is requires RedCloth[http://whytheluckystiff.net/ruby/redcloth/]
|
39
|
+
# to be available</i>.
|
40
|
+
#
|
41
|
+
# ==== Examples
|
42
|
+
# textilize_without_paragraph("*This is Textile!* Rejoice!")
|
43
|
+
# # => "<strong>This is Textile!</strong> Rejoice!"
|
44
|
+
#
|
45
|
+
# textilize_without_paragraph("I _love_ ROR(Ruby on Rails)!")
|
46
|
+
# # => "I <em>love</em> <acronym title="Ruby on Rails">ROR</acronym>!"
|
47
|
+
#
|
48
|
+
# textilize_without_paragraph("h2. Textile makes markup -easy- simple!")
|
49
|
+
# # => "<h2>Textile makes markup <del>easy</del> simple!</h2>"
|
50
|
+
#
|
51
|
+
# textilize_without_paragraph("Visit the Rails website "here":http://www.rubyonrails.org/.)
|
52
|
+
# # => "Visit the Rails website <a href="http://www.rubyonrails.org/">here</a>."
|
53
|
+
def textilize_without_paragraph(text, *options)
|
54
|
+
textiled = textilize(text, *options)
|
55
|
+
if textiled[0..2] == "<p>"
|
56
|
+
textiled = textiled[3..-1]
|
57
|
+
end
|
58
|
+
if textiled[-4..-1] == "</p>"
|
59
|
+
textiled = textiled[0..-5]
|
60
|
+
end
|
61
|
+
textiled.html_safe
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: textilize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mysen Huang
|
9
|
+
- Adeh DeSandies
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-07-05 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rake
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ! '>='
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '0'
|
31
|
+
description:
|
32
|
+
email: mysen@kudelabs.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- lib/redcloth.rb
|
38
|
+
- lib/textilize/helper.rb
|
39
|
+
- lib/textilize/version.rb
|
40
|
+
- lib/textilize.rb
|
41
|
+
- LICENSE
|
42
|
+
- README.md
|
43
|
+
homepage: http://github.com/kudelabs/textilize
|
44
|
+
licenses: []
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 1.8.24
|
64
|
+
signing_key:
|
65
|
+
specification_version: 3
|
66
|
+
summary: Gem containing the textilize and textilize_without_paragraph methods removed
|
67
|
+
from Rails without C Extensions.
|
68
|
+
test_files: []
|