require 'cgi'
require 'uri'
class DText
def self.u(string)
CGI.escape(string)
end
def self.h(string)
CGI.escapeHTML(string)
end
def self.parse_inline(str, options = {})
str.gsub!(/&/, "&")
str.gsub!(/, "<")
str.gsub!(/>/, ">")
str.gsub!(/\n/m, "
")
str.gsub!(/\[b\](.+?)\[\/b\]/i, '\1')
str.gsub!(/\[i\](.+?)\[\/i\]/i, '\1')
str.gsub!(/\[s\](.+?)\[\/s\]/i, '\1')
str.gsub!(/\[u\](.+?)\[\/u\]/i, '\1')
str = parse_links(str)
str = parse_aliased_wiki_links(str)
str = parse_wiki_links(str)
str = parse_post_links(str)
str = parse_id_links(str)
str
end
def self.parse_links(str)
str.gsub(/("[^"]+":(https?:\/\/|\/)[^\s\r\n<>]+|https?:\/\/[^\s\r\n<>]+)+/) do |url|
if url =~ /^"([^"]+)":(.+)$/
text = $1
url = $2
else
text = url
end
if url =~ /([;,.!?\)\]<>])$/
url.chop!
ch = $1
else
ch = ""
end
'' + text + '' + ch
end
end
def self.parse_aliased_wiki_links(str)
str.gsub(/\[\[([^\|\]]+)\|([^\]]+)\]\]/m) do
text = CGI.unescapeHTML($2)
title = CGI.unescapeHTML($1).tr(" ", "_").downcase
%{#{h(text)}}
end
end
def self.parse_wiki_links(str)
str.gsub(/\[\[([^\]]+)\]\]/) do
text = CGI.unescapeHTML($1)
title = text.tr(" ", "_").downcase
%{#{h(text)}}
end
end
def self.parse_post_links(str)
str.gsub(/\{\{([^\}]+)\}\}/) do
tags = CGI.unescapeHTML($1)
%{#{h(tags)}}
end
end
def self.parse_id_links(str)
str = str.gsub(/\bpost #(\d+)/i, %{post #\\1})
str = str.gsub(/\bforum #(\d+)/i, %{forum #\\1})
str = str.gsub(/\btopic #(\d+)/i, %{topic #\\1})
str = str.gsub(/\bcomment #(\d+)/i, %{comment #\\1})
str = str.gsub(/\bpool #(\d+)/i, %{pool #\\1})
str = str.gsub(/\buser #(\d+)/i, %{user #\\1})
str = str.gsub(/\bartist #(\d+)/i, %{artist #\\1})
str = str.gsub(/\bissue #(\d+)/i, %{issue #\\1})
str = str.gsub(/\bpixiv #(\d+)(?!\/p\d|\d)/i, %{pixiv #\\1})
str = str.gsub(/\bpixiv #(\d+)\/p(\d+)/i, %{pixiv #\\1/p\\2})
end
def self.parse_list(str, options = {})
html = ""
layout = []
nest = 0
str.split(/\n/).each do |line|
if line =~ /^\s*(\*+) (.+)/
nest = $1.size
content = parse_inline($2)
else
content = parse_inline(line)
end
if nest > layout.size
html += "
#{content}
" end end while layout.any? elist = layout.pop html += "#{elist}>" end html end def self.parse(str, options = {}) return "" if str.blank? # Make sure quote tags are surrounded by newlines unless options[:inline] str.gsub!(/\s*\[quote\]\s*/m, "\n\n[quote]\n\n") str.gsub!(/\s*\[\/quote\]\s*/m, "\n\n[/quote]\n\n") str.gsub!(/\s*\[code\]\s*/m, "\n\n[code]\n\n") str.gsub!(/\s*\[\/code\]\s*/m, "\n\n[/code]\n\n") str.gsub!(/\s*\[spoilers?\](?!\])\s*/m, "\n\n[spoiler]\n\n") str.gsub!(/\s*\[\/spoilers?\]\s*/m, "\n\n[/spoiler]\n\n") str.gsub!(/^(h[1-6]\.\s*.+)$/, "\n\n\\1\n\n") str.gsub!(/\s*\[expand(\=[^\]]*)?\]\s*/m, "\n\n[expand\\1]\n\n") str.gsub!(/\s*\[\/expand\]\s*/m, "\n\n[/expand]\n\n") end str.gsub!(/(?:\r?\n){3,}/, "\n\n") str.strip! blocks = str.split(/(?:\r?\n){2}/) stack = [] flags = {} html = blocks.map do |block| case block when /\A(h[1-6])\.\s*(.+)\Z/ tag = $1 content = $2 if options[:inline] "" end when "[/quote]" if options[:inline] "" elsif stack.last == "blockquote" stack.pop '' else "" end when "[spoiler]" stack << "spoiler" '
'
when /\[\/code\](?!\])/
flags[:code] = false
''
when /\[expand(?:\=([^\]]*))?\](?!\])/
stack << "expandable"
expand_html = ''
end
else
if flags[:code]
CGI.escape_html(block) + "\n\n"
else
'' + parse_inline(block) + '
' end end end stack.reverse.each do |tag| if tag == "blockquote" html << "" elsif tag == "div" html << "" elsif tag == "pre" html << "" elsif tag == "spoiler" html << "" elsif tag == "expandable" html << "" end end sanitize(html.join("")).html_safe end def self.sanitize(text) text.gsub!(/<( |-|\Z)/, "<\\1") Sanitize.clean( text, :elements => %w(code center tn h1 h2 h3 h4 h5 h6 a span div blockquote br p ul li ol em strong small big b i font u s pre), :attributes => { "a" => %w(href title style), "span" => %w(class style), "div" => %w(class style align), "p" => %w(class style align), "font" => %w(color size style) }, :protocols => { "a" => { "href" => ["http", "https", :relative] } } ) end end