Ангел обнови решението на 23.11.2011 18:55 (преди около 13 години)
+module Parser
+ def self.parse(text)
+ parser_classes = [SpecialChars, Paragraph, Header, Code, List, BlockQuote]
+ parser_classes.each do |parser_class|
+ text = (parser_class.new text).to_html
+ end
+ text.strip
+ end
+
+ #inner content parsing is not used in code blocks, only special chars replacing
+ def self.inner_parse(content)
+ [ItalicBold, Links].each do |inner_parser_class|
+ content = (inner_parser_class.new content).to_html
+ end
+ content
+ end
+
+ class Header
+ REGEX = /^[ \t]{,3}(\#{1,4})(?:[ \t]+)(\S.+)$/u
+
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ @text.gsub(REGEX) do
+ tag = "h#{$1.size}"
+ "<%s>%s</%s>" % [tag, Parser.inner_parse($2.strip), tag]
+ end
+ end
+
+ def self.identifization_pattern
+ '[ \t]{,3}\#{1,4}[ \t]+\S'
+ end
+ end
+
+ class Links
+ REGEX = /\[(?<text>[^\]\[\n]+)\]\((?<href>[^\)\n]+)\)/u
+
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ @text.gsub REGEX, '<a href="\k<href>">\k<text></a>'
+ end
+ end
+
+ class Code
+ REGEX = /^(([ \t]{4}.*\n?)+)/u
+
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ @text.gsub(REGEX) do |match|
+ "<pre><code>" + format(match).chomp + "</code></pre>\n"
+ end
+ end
+
+ def format(code_block)
+ ''.tap { |output| code_block.lines { |line| output << line[4..-1] } }
+ end
+
+ def self.identifization_pattern
+ '[ \t]{4,}'
+ end
+ end
+
+ class List
+ REGEX = /^((([ \t]{,3}\*|\d+\.)[ \t]+(.+)\n?)+)/u
+ TAGS = {"*" => 'ul'}
+
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ @text.gsub(REGEX) do |match|
+ tag = TAGS.fetch($3, "ol")
+ "<%s>\n%s</%s>\n" % [tag, format(match), tag]
+ end
+ end
+
+ def format(list_items)
+ formatted = ""
+ list_items.lines do |item|
+ item = Parser.inner_parse(item)
+ formatted << item.sub(/(([ \t]{,3}\*|\d+\.)[ \t]+)/, " <li>").chomp + "</li>\n"
+ end
+ formatted
+ end
+
+ def self.identifization_pattern
+ '(?:[ \t]{,3}\*|\d+\.)[ \t]+'
+ end
+ end
+
+ class ItalicBold
+ REGEX = /(\*{2}|_)([^\/\n]+?)\1/u
+ TAGS = {'**' => 'strong', '_' => 'em'}
+
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ output_text = @text
+ while output_text =~ REGEX
+ output_text = output_text.sub REGEX, '<%s>\2</%s>' % ([TAGS[$1]] * 2)
+ end
+ output_text
+ end
+ end
+
+ class SpecialChars
+ REGEX = /((?!^)>|<|&|")/
+ ESCAPES = {'&' => '&', '<' => '<', '>' => '>', '"' => '"'}
+
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ @text.gsub(REGEX) { ESCAPES[$1] }
+ end
+ end
+
+ class BlockQuote
+ REGEX = /^(([ \t]{,3}>[ \t]+)(.*?)(\n|$))+/u
+
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ tag = 'blockquote'
+ @text.gsub(REGEX) do |match|
+ content = Paragraph.new(format(match).chomp).to_html.chomp
+ "<%s>%s</%s>\n" % [tag, content, tag]
+ end
+ end
+
+ def format(quote_block)
+ formatted = ""
+ quote_block.lines do |line|
+ formatted << line.sub(/[ \t]{,3}>[ \t]+/, "").rstrip + "\n"
+ end
+ formatted
+ end
+
+ def self.identifization_pattern
+ '[ \t]{,3}>[ \t]+'
+ end
+ end
+
+ class Paragraph
+ HEADER = Header.identifization_pattern
+ CODE = Code.identifization_pattern
+ BLOCKQUOTE = BlockQuote.identifization_pattern
+ LIST = List.identifization_pattern
+
+ REGEX = /(^(?!#{[HEADER, CODE, BLOCKQUOTE, LIST].join('|')})(.+)(\n|$))+/u
+
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ @text.gsub(REGEX) { |match| "<p>" + Parser.inner_parse(match.strip) + "</p>\n" }
+ end
+ end
+end
+
+class Formatter
+ def initialize(text)
+ @text = text
+ end
+
+ def to_html
+ Parser.parse(@text)
+ end
+
+ def to_s
+ to_html
+ end
+
+ def inspect
+ @text
+ end
+end