PageRenderTime 68ms CodeModel.GetById 38ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/rpagedown/converter.rb

https://github.com/ArieShout/rpagedown
Ruby | 740 lines | 687 code | 53 blank | 0 comment | 20 complexity | a21c1b1a710140d31fc9d3d7b50095ac MD5 | raw file
  1. require 'rpagedown/hook_collection'
  2. module RPageDown
  3. class Converter
  4. attr_reader :hooks
  5. def initialize
  6. @pluginHooks = @hooks = HookCollection.new
  7. @pluginHooks.add_noop(:plain_link_text)
  8. @pluginHooks.add_noop(:pre_conversion)
  9. @pluginHooks.add_noop(:post_conversion)
  10. @g_urls = nil
  11. @g_titles = nil
  12. @g_html_blocks = nil
  13. @g_list_level = -1
  14. @_list_item_markers = {
  15. :ol => "\\d+[.]",
  16. :ul => "[*+-]"
  17. }
  18. @_problem_url_chars = /(?:["'*()\[\]:]|~D)/
  19. end
  20. # gsub! on $1~9 will not affect the original variable
  21. # you can first assign $1~9 to a variable and then do that on the new variable
  22. # however $1~9 will remain unchanged
  23. # it is designed to be changed only when group capture changes
  24. def make_html(text)
  25. if @g_urls
  26. raise "Recursive call to converter.makeHtml"
  27. end
  28. @g_urls = Hash.new
  29. @g_titles = Hash.new
  30. @g_html_blocks = []
  31. @g_list_level = 0
  32. text = "#{@pluginHooks.pre_conversion(text)}"
  33. text.gsub!(/~/, '~T')
  34. text.gsub!(/\$/, '~D')
  35. text.gsub!(/\r\n/, "\n")
  36. text.gsub!(/\r/, "\n")
  37. text = "\n\n#{text}\n\n"
  38. text = _detab(text)
  39. text.gsub!(/^[ \t]+$/, '')
  40. text = _hash_html_blocks(text)
  41. text = _strip_link_definitions(text)
  42. text = _run_block_gamut(text)
  43. text = _unescape_special_chars(text)
  44. text.gsub!(/~D/, '$')
  45. text.gsub!(/~T/, '~')
  46. text = @pluginHooks.post_conversion(text)
  47. @g_html_blocks = @g_titles = @g_urls = nil
  48. text
  49. end
  50. private
  51. def _strip_link_definitions(text)
  52. regexp = %r{
  53. ^[ ]{0,3}\[(.+)\]: # id = $1 attacklab: g_tab_width - 1
  54. [ \t]*
  55. \n? # maybe *one* newline
  56. [ \t]*
  57. <?(\S+?)>? # url = $2
  58. (?=\s|$) # lookahead for whitespace instead of the lookbehind removed below
  59. [ \t]*
  60. \n? # maybe one newline
  61. [ \t]*
  62. ( # (potential) title = $3
  63. (\n*) # any lines skipped = $4 attacklab: lookbehind removed
  64. [ \t]+
  65. ["(]
  66. (.+?) # title = $5
  67. [")]
  68. [ \t]*
  69. )? # title is optional
  70. (?:\n+|$)
  71. }x
  72. text.gsub regexp do |match|
  73. m1 = $1.downcase
  74. @g_urls[m1] = _encode_amps_and_angles($2)
  75. if $4 and not $4.empty?
  76. next $3
  77. elsif $5 and not $5.empty?
  78. @g_titles[m1] = $5.gsub(/"/, "&quot;")
  79. end
  80. ""
  81. end
  82. end
  83. def _hash_html_blocks(text)
  84. block_tags_a = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math|ins|del"
  85. block_tags_b = "p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|script|noscript|form|fieldset|iframe|math"
  86. text.gsub!(%r{
  87. ( # save in $1
  88. ^ # start of line (with /m)
  89. <(#{block_tags_a}) # start tag = $2
  90. \b # word break
  91. # attacklab: hack around khtml/pcre bug...
  92. [^\r]*?\n # any number of lines, minimally matching
  93. </\2> # the matching end tag
  94. [ \t]* # trailing spaces/tabs
  95. (?=\n+) # followed by a newline
  96. ) # attacklab: there are sentinel newlines at end of document
  97. }x) {|match| hash_element(match, $1)}
  98. text.gsub!(%r{
  99. ( # save in $1
  100. ^ # start of line (with /m)
  101. <(#{block_tags_b}) # start tag = $2
  102. \b # word break
  103. # attacklab: hack around khtml/pcre bug...
  104. [^\r]*? # any number of lines, minimally matching
  105. .*</\2> # the matching end tag
  106. [ \t]* # trailing spaces/tabs
  107. (?=\n+) # followed by a newline
  108. ) # attacklab: there are sentinel newlines at end of document
  109. }x) {|match| hash_element(match, $1) }
  110. text.gsub!(%r{
  111. \n # Starting after a blank line
  112. [ ]{0,3}
  113. ( # save in $1
  114. (<(hr) # start tag = $2
  115. \b # word break
  116. ([^<>])*?
  117. \/?>) # the matching end tag
  118. [ \t]*
  119. (?=\n{2,}) # followed by a blank line
  120. )
  121. }x) {|match| hash_element(match, $1) }
  122. text.gsub!(%r{
  123. \n\n # Starting after a blank line
  124. [ ]{0,3} # attacklab: g_tab_width - 1
  125. ( # save in $1
  126. <!
  127. (--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--) # see http://www.w3.org/TR/html-markup/syntax.html#comments and http://meta.stackoverflow.com/q/95256
  128. >
  129. [ \t]*
  130. (?=\n{2,}) # followed by a blank line
  131. )
  132. }x) {|match| hash_element(match, $1) }
  133. text.gsub!(%r{
  134. (?:
  135. \n\n # Starting after a blank line
  136. )
  137. ( # save in $1
  138. [ ]{0,3} # attacklab: g_tab_width - 1
  139. (?:
  140. <([?%]) # $2
  141. [^\r]*?
  142. \2>
  143. )
  144. [ \t]*
  145. (?=\n{2,}) # followed by a blank line
  146. )
  147. }x) {|match| hash_element(match, $1) }
  148. text
  149. end
  150. def hash_element(match, m1)
  151. block_text = m1
  152. block_text.gsub! /\A\n+/, ''
  153. block_text.gsub! /\n+\z/, ''
  154. "\n\n~K#{@g_html_blocks.push(block_text).size - 1}K\n\n"
  155. end
  156. def _run_block_gamut(text, do_not_unhash = nil)
  157. text = _do_headers(text)
  158. replacement = "<hr />\n"
  159. text.gsub!(/^[ ]{0,2}([ ]?\*[ ]?){3,}[ \t]*$/, replacement)
  160. text.gsub!(/^[ ]{0,2}([ ]?-[ ]?){3,}[ \t]*$/, replacement)
  161. text.gsub!(/^[ ]{0,2}([ ]?_[ ]?){3,}[ \t]*$/, replacement)
  162. text = _do_lists(text)
  163. text = _do_code_blocks(text)
  164. text = _do_block_quotes(text)
  165. text = _hash_html_blocks(text)
  166. text = _form_paragraphs(text, do_not_unhash)
  167. text
  168. end
  169. def _run_span_gamut(text)
  170. text = _do_code_spans(text)
  171. text = _escape_special_chars_within_tag_attributes(text)
  172. text = _encode_backslash_escapes(text)
  173. text = _do_images(text)
  174. text = _do_anchors(text)
  175. text = _do_auto_links(text)
  176. text.gsub!(/~P/, '://')
  177. text = _encode_amps_and_angles(text)
  178. text = _do_italics_and_bold(text)
  179. text.gsub(/ +\n/, " <br />\n")
  180. end
  181. def _escape_special_chars_within_tag_attributes(text)
  182. regexp = /(<[a-z\/!$]("[^"]*"|'[^']*'|[^'">])*>|<!(--(?:|(?:[^>-]|-[^>])(?:[^-]|-[^-])*)--)>)/i
  183. text.gsub! regexp do |match|
  184. tag = match.gsub(/(.)<\/?code>(?=.)/, "\\1`")
  185. tag = escape_characters(tag, if match[0] == '!' then "\\`*_/" else "\\`*_" end)
  186. tag
  187. end
  188. text
  189. end
  190. def _do_anchors(text)
  191. text.gsub!(%r{
  192. ( # wrap whole match in $1
  193. \[
  194. (
  195. (?:
  196. \[[^\]]*\] # allow brackets nested one level
  197. |
  198. [^\[] # or anything else
  199. )*
  200. )
  201. \]
  202. [ ]? # one optional space
  203. (?:\n[ ]*)? # one optional newline followed by spaces
  204. \[
  205. (.*?) # id = $3
  206. \]
  207. )
  208. ()()()() # pad remaining backreferences
  209. }x) {|match| write_anchor_tag(match, $1, $2, $3, $4, $5, $6, $7)}
  210. text.gsub!(%r{
  211. ( # wrap whole match in $1
  212. \[
  213. (
  214. (?:
  215. \[[^\]]*\] # allow brackets nested one level
  216. |
  217. [^\[\]] # or anything else
  218. )*
  219. )
  220. \]
  221. \( # literal paren
  222. [ \t]*
  223. () # no id, so leave $3 empty
  224. <?( # href = $4
  225. (?:
  226. \([^)]*\) # allow one level of (correctly nested) parens (think MSDN)
  227. |
  228. [^()\s]
  229. )*?
  230. )>?
  231. [ \t]*
  232. ( # $5
  233. (['"]) # quote char = $6
  234. (.*?) # Title = $7
  235. \6 # matching quote
  236. [ \t]* # ignore any spaces/tabs between closing quote and )
  237. )? # title is optional
  238. \)
  239. )
  240. }x) {|match| write_anchor_tag(match, $1, $2, $3, $4, $5, $6, $7)}
  241. text.gsub!(%r{
  242. ( # wrap whole match in $1
  243. \[
  244. ([^\[\]]+) # link text = $2; can't contain '[' or ']'
  245. \]
  246. )
  247. ()()()()() # pad rest of backreferences
  248. }x) {|match| write_anchor_tag(match, $1, $2, $3, $4, $5, $6, $7)}
  249. text
  250. end
  251. def write_anchor_tag(match, m1, m2, m3, m4, m5, m6, m7)
  252. m7 = '' if not m7
  253. whole_match = m1
  254. link_text = m2.gsub(/:\/\//, "~P")
  255. link_id = m3.downcase
  256. url = m4
  257. title = m7
  258. if url == ''
  259. if link_id == ''
  260. link_id = link_text.downcase.gsub(/ ?\n/, " ")
  261. end
  262. url = "##{link_id}"
  263. if @g_urls.has_key? link_id
  264. url = @g_urls[link_id]
  265. if @g_titles.has_key? link_id
  266. title = @g_titles[link_id]
  267. end
  268. else
  269. if whole_match =~ /\(\s*\)\z/
  270. url = ''
  271. else
  272. return whole_match
  273. end
  274. end
  275. end
  276. url = encode_problem_url_chars(url)
  277. url = escape_characters(url, "*_")
  278. result = %Q{<a href="#{url}"}
  279. if not title.empty?
  280. title = attribute_encode(title)
  281. title = escape_characters(title, "*_")
  282. result << %Q{ title="#{title}"}
  283. end
  284. result << %Q{>#{link_text}</a>}
  285. result
  286. end
  287. def _do_images(text)
  288. text.gsub!(%r{
  289. ( # wrap whole match in $1
  290. !\[
  291. (.*?) # alt text = $2
  292. \]
  293. [ ]? # one optional space
  294. (?:\n[ ]*)? # one optional newline followed by spaces
  295. \[
  296. (.*?) # id = $3
  297. \]
  298. )
  299. ()()()() # pad rest of backreferences
  300. }x) {|match| write_image_tag(match, $1, $2, $3, $4, $5, $6, $7)}
  301. text.gsub!(%r{
  302. ( # wrap whole match in $1
  303. !\[
  304. (.*?) # alt text = $2
  305. \]
  306. \s? # One optional whitespace character
  307. \( # literal paren
  308. [ \t]*
  309. () # no id, so leave $3 empty
  310. <?(\S+?)>? # src url = $4
  311. [ \t]*
  312. ( # $5
  313. (['"]) # quote char = $6
  314. (.*?) # title = $7
  315. \6 # matching quote
  316. [ \t]*
  317. )? # title is optional
  318. \)
  319. )
  320. }x) {|match| write_image_tag(match, $1, $2, $3, $4, $5, $6, $7)}
  321. text
  322. end
  323. def attribute_encode(text)
  324. text.gsub(/>/, '&gt;').gsub(/</, '&lt;').gsub(/"/, '&quot;')
  325. end
  326. def write_image_tag(match, m1, m2, m3, m4, m5, m6, m7)
  327. whole_match = m1
  328. alt_text = m2
  329. link_id = m3.downcase
  330. url = m4
  331. title = m7
  332. title = '' if not title
  333. if url == ''
  334. if link_id == ''
  335. link_id = alt_text.downcase.gsub(/ ?\n/, " ")
  336. end
  337. url = '#' + link_id
  338. if @g_urls.has_key? link_id
  339. url = @g_urls[link_id]
  340. if @g_titles.has_key? link_id
  341. title = @g_titles[link_id]
  342. end
  343. else
  344. return whole_match
  345. end
  346. end
  347. alt_text = escape_characters(attribute_encode(alt_text), "*_[]()")
  348. url = escape_characters(url, "*_")
  349. result = %Q{<img src="#{url}" alt="#{alt_text}"}
  350. title = attribute_encode(title)
  351. title = escape_characters(title, "*_")
  352. result << %Q{ title="#{title}"}
  353. result << " />"
  354. result
  355. end
  356. def _do_headers(text)
  357. text.gsub! /^(.+)[ \t]*\n=+[ \t]*\n+/ do |match|
  358. %Q{<h1>#{_run_span_gamut($1)}</h1>\n\n}
  359. end
  360. text.gsub! /^(.+)[ \t]*\n-+[ \t]*\n+/ do |match|
  361. %Q{<h2>#{_run_span_gamut($1)}</h2>\n\n}
  362. end
  363. text.gsub! %r{
  364. ^(\#{1,6}) # $1 = string of #'s
  365. [ \t]*
  366. (.+?) # $2 = Header text
  367. [ \t]*
  368. \#* # optional closing #'s (not counted)
  369. \n+
  370. }x do |match|
  371. h_level = $1.length;
  372. %Q{<h#{h_level}>#{_run_span_gamut($2)}</h#{h_level}>\n\n}
  373. end
  374. text
  375. end
  376. def _do_lists(text)
  377. text << "~0"
  378. whole_list = %r{
  379. ( # $1 = whole list
  380. ( # $2
  381. [ ]{0,3} # attacklab: g_tab_width - 1
  382. ([*+-]|\d+[.]) # $3 = first list item marker
  383. [ \t]+
  384. )
  385. [^\r]+?
  386. ( # $4
  387. ~0 # sentinel for workaround; should be $
  388. |
  389. \n{2,}
  390. (?=\S)
  391. (?! # Negative lookahead for another list item marker
  392. [ \t]*
  393. (?:[*+-]|\d+[.])[ \t]+
  394. )
  395. )
  396. )
  397. }x
  398. if @g_list_level and @g_list_level > 0
  399. text.gsub! whole_list do |match|
  400. list = $1
  401. list_type = if $2 =~ /[*+-]/ then "ul" else "ol" end
  402. result = _process_list_items(list, list_type)
  403. result.sub!(/\s+\z/, "")
  404. %Q{<#{list_type}>#{result}</#{list_type}>\n}
  405. end
  406. else
  407. whole_list = /(\n\n|\A\n?)(([ ]{0,3}([*+-]|\d+[.])[ \t]+)[^\r]+?(~0|\n{2,}(?=\S)(?![ \t]*(?:[*+-]|\d+[.])[ \t]+)))/
  408. text.gsub! whole_list do |match|
  409. runup = $1
  410. list = $2
  411. list_type = if $3 =~ /[*+-]/ then "ul" else "ol" end
  412. result = _process_list_items(list, list_type)
  413. %Q{#{runup}<#{list_type}>\n#{result}</#{list_type}>\n}
  414. end
  415. end
  416. text.sub /~0/, ''
  417. end
  418. def _process_list_items(list_str, list_type)
  419. @g_list_level += 1
  420. list_str.sub! /\n{2,}\z/, "\n"
  421. list_str << "~0"
  422. marker = "#{@_list_item_markers[list_type.to_sym]}"
  423. re = %r{(^[ \t]*)(#{marker})[ \t]+([^\r]+?(\n+))(?=(~0|\1(#{marker})[ \t]+))}
  424. last_item_had_a_double_newline = false
  425. list_str.gsub! re do |whole_match|
  426. item = $3
  427. leading_space = $1
  428. ends_with_double_newline = item =~ /\n\n\z/
  429. contains_double_newline = ends_with_double_newline or item =~ /\n{2,}/
  430. if contains_double_newline or last_item_had_a_double_newline
  431. item = _run_block_gamut(_outdent(item), true)
  432. else
  433. item = _do_lists(_outdent(item))
  434. item.sub! /\n\z/, ''
  435. item = _run_span_gamut(item)
  436. end
  437. last_item_had_a_double_newline = ends_with_double_newline
  438. %Q{<li>#{item}</li>\n}
  439. end
  440. list_str.gsub! /~0/, ''
  441. @g_list_level -= 1
  442. list_str
  443. end
  444. def _do_code_blocks(text)
  445. text << "~0"
  446. text.gsub! %r{
  447. (?:\n\n|\A)
  448. ( # $1 = the code block -- one or more lines, starting with a space/tab
  449. (?:
  450. (?:[ ]{4}|\t) # Lines must start with a tab or a tab-width of spaces - attacklab: g_tab_width
  451. .*\n+
  452. )+
  453. )
  454. (\n*[ ]{0,3}[^ \t\n]|(?=~0)) # attacklab: g_tab_width
  455. }x do |whole_match|
  456. codeblock = $1
  457. nextchar = $2
  458. codeblock = _encode_code(_outdent(codeblock))
  459. codeblock = _detab(codeblock)
  460. codeblock.gsub!(/\A\n+/, '')
  461. codeblock.gsub!(/\n+\z/, '')
  462. codeblock = %Q{<pre><code>#{codeblock}\n</code></pre>}
  463. %Q{\n\n#{codeblock}\n\n#{nextchar}}
  464. end
  465. text.sub(/~0/, '')
  466. end
  467. def hash_block(text)
  468. text.gsub!(/(\A\n+|\n+\z)/, '')
  469. %Q{\n\n~K#{@g_html_blocks.push(text).size - 1}K\n\n}
  470. end
  471. def _do_code_spans(text)
  472. text.gsub! %r{
  473. (^|[^\\]) # Character before opening ` can't be a backslash
  474. (`+) # $2 = Opening run of `
  475. ( # $3 = The code block
  476. [^\r]*?
  477. [^`] # attacklab: work around lack of lookbehind
  478. )
  479. \2 # Matching closer
  480. (?!`)
  481. }x do |whole_match|
  482. m1 = $1
  483. c = $3
  484. c.gsub! /\A([ \t]*)/, ''
  485. c.gsub! /[ \t]*\z/, ''
  486. c = _encode_code(c)
  487. c.gsub! /:\/\//, '~P'
  488. %Q{#{m1}<code>#{c}</code>}
  489. end
  490. text
  491. end
  492. def _encode_code(text)
  493. text.gsub! '&', '&amp;'
  494. text.gsub! '<', '&lt;'
  495. text.gsub! '>', '&gt;'
  496. text = escape_characters(text, "\*_{}[]\\", false)
  497. text
  498. end
  499. def _do_italics_and_bold(text)
  500. text.gsub! /([\W_]|^)(\*\*\*|___)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/, "\\1<strong><em>\\3</em></strong>\\4"
  501. text.gsub! /([\W_]|^)(\*\*|__)(?=\S)([^\r]*?\S[\*_]*)\2([\W_]|$)/, "\\1<strong>\\3</strong>\\4"
  502. text.gsub! /([\W_]|^)(\*|_)(?=\S)([^\r\*_]*?\S)\2([\W_]|$)/, "\\1<em>\\3</em>\\4"
  503. text
  504. end
  505. def _do_block_quotes(text)
  506. text.gsub! %r{
  507. ( # Wrap whole match in $1
  508. (
  509. ^[ \t]*>[ \t]? # '>' at the start of a line
  510. .+\n # rest of the first line
  511. (.+\n)* # subsequent consecutive lines
  512. \n* # blanks
  513. )+
  514. )
  515. }x do |match|
  516. bq = $1
  517. bq.gsub! /^[ \t]*>[ \t]?/, "~0"
  518. bq.gsub! /~0/, ""
  519. bq.gsub! /^[ \t]+$/, ""
  520. bq = _run_block_gamut(bq)
  521. bq.gsub! /(\A|\n)/, "\\1 "
  522. bq.gsub! /(\s*<pre>[^\r]+?<\/pre>)/ do |match|
  523. pre = $1
  524. pre.gsub! /^ /, "~0"
  525. pre.gsub! /^~0/, ''
  526. pre
  527. end
  528. hash_block(%Q{<blockquote>\n#{bq}\n</blockquote>})
  529. end
  530. text
  531. end
  532. def _form_paragraphs(text, do_not_unhash)
  533. text.gsub! /\A\n+/, ''
  534. text.gsub! /\n+\z/, ''
  535. grafs = text.split(/\n{2,}/)
  536. grafs_out = []
  537. marker_re = /~K(\d+)K/
  538. grafs.each do |str|
  539. if str =~ marker_re
  540. grafs_out.push(str)
  541. elsif str =~ /\S/
  542. str = _run_span_gamut(str)
  543. str.gsub! /\A([ \t]*)/, '<p>'
  544. str << "</p>"
  545. grafs_out.push(str)
  546. end
  547. end
  548. if not do_not_unhash
  549. 0.upto(grafs_out.length - 1) do |i|
  550. found_any = true
  551. while found_any
  552. found_any = false
  553. grafs_out[i].gsub! /~K(\d+)K/ do |match|
  554. found_any = true
  555. @g_html_blocks[$1.to_i]
  556. end
  557. end
  558. end
  559. end
  560. grafs_out.join("\n\n")
  561. end
  562. def _encode_amps_and_angles(text)
  563. text.gsub! /&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/, "&amp;"
  564. text.gsub /<(?![a-z\/?!]|~D)/i, '&lt;'
  565. end
  566. def _encode_backslash_escapes(text)
  567. text.gsub!(/\\(\\)/) {|match| escape_characters_callback(match, $1)}
  568. text.gsub(/\\([`*_{}\[\]()>#+-.!])/) {|match| escape_characters_callback(match, $1)}
  569. end
  570. def _do_auto_links(text)
  571. text.gsub! /(^|\s)(https?|ftp)(:\/\/[-A-Z0-9+&@#\/%?=~_|\[\]\(\)!:,\.;]*[-A-Z0-9+&@#\/%=~_|\[\]])($|\W)/i, "\\1<\\2\\3>\\4"
  572. text.gsub /<((https?|ftp):[^'">\s]+)>/i do |match|
  573. %Q{<a href="#{$1}">#{@pluginHooks.plain_link_text($1)}</a>}
  574. end
  575. end
  576. def _unescape_special_chars(text)
  577. text.gsub /~E(\d+)E/ do |match|
  578. charCodeToReplace = $1.to_i
  579. [charCodeToReplace].pack "U*"
  580. end
  581. end
  582. def _outdent(text)
  583. text.gsub! /^(\t|[ ]{1,4})/, "~0"
  584. text.gsub! /^~0/, ''
  585. text
  586. end
  587. def _detab(text)
  588. return text if not text =~ /\t/
  589. spaces = [" ", " ", " ", " "]
  590. skew = 0
  591. text.gsub /[\n\t]/ do |match|
  592. offset = $~.begin(0)
  593. if match == "\n"
  594. skew = offset + 1
  595. next match
  596. end
  597. v = (offset - skew) % 4
  598. skew = offset + 1
  599. spaces[v]
  600. end
  601. end
  602. def encode_problem_url_chars(url)
  603. return "" if not url or url.empty?
  604. len = url.length
  605. url.gsub @_problem_url_chars do |match|
  606. next "%24" if match == "~D"
  607. offset = $~.begin(0)
  608. if match == ":"
  609. if offset == len - 1 || url[offset + 1] =~ /[0-9\/]/
  610. next ":"
  611. end
  612. end
  613. "%#{match[0].ord}"
  614. end
  615. end
  616. def escape_characters(text, chars_to_escape, after_backslash = nil)
  617. regex_string = %Q{([#{chars_to_escape.gsub(/([\[\]\\])/, "\\\\\\1")}])}
  618. regex_string = "\\\\" + regex_string if after_backslash
  619. text.gsub(/#{regex_string}/) {|match| escape_characters_callback(match, $1)}
  620. end
  621. def escape_characters_callback(match, m1)
  622. char_code_to_escape = m1[0].ord
  623. "~E#{char_code_to_escape}E"
  624. end
  625. end
  626. end