Пламен обнови решението на 21.11.2011 18:14 (преди около 13 години)
+# -*- coding: utf-8 -*-
+
+module ContentParser
+ REGEXPS = {
+ a: /\[([^\]]+)\]\(([^\)]+)\)/,
+ em: /_(?<text>.*?)_/,
+ strong: /\*\*(?<text>.*?)\*\*/,
+ a_split: /(\[[^\]]+\]\([^\)]+\))/,
+ em_strong: /_.*?_|\*\*.*?\*\*/,
+ }
+
+ ENTITIES = {
+ '&' => '&',
+ '<' => '<',
+ '>' => '>',
+ '"' => '"',
+ }
+
+ def self.sanitize(text)
+ text.gsub(/(&|<|>|")/) { |entity| ENTITIES[entity] }
+ end
+
+ class EmStrongParser
+ def initialize(raw_text)
+ @head, @text, @tail = raw_text.partition(REGEXPS[:em_strong])
+ @tail = EmStrongParser.new(@tail).render if @tail != ""
+ end
+
+ def render
+ @head + wrap + @tail
+ end
+
+ def wrap
+ result = @text.gsub(REGEXPS[:em], '<em>\k<text></em>')
+
+ result.gsub(REGEXPS[:strong], '<strong>\k<text></strong>')
+ end
+ end
+
+ class LinkParser
+ def initialize(raw_text)
+ @text, @href = REGEXPS[:a].match(raw_text).captures
+ end
+
+ def render
+ "<a href=\"#@href\">#{ EmStrongParser.new(@text).render }</a>"
+ end
+ end
+
+ def self.parse(raw_text)
+ strings = sanitize(raw_text).split(REGEXPS[:a_split])
+
+ parsers = strings.map do |string|
+ case string
+ when REGEXPS[:a] then LinkParser.new(string)
+ else EmStrongParser.new(string)
+ end
+ end
+
+ parsers.map { |parser| parser.render }.join
+ end
+end
+
+module StructureParser
+ class ParagraphParser
+ def initialize(chunk_data)
+ chunks = chunk_data.chunk { |line| line.strip != '' }
+ @paragraphs = chunks.map { |status, array| array }
+ end
+
+ def render
+ joined_paragraphs = @paragraphs.map do |paragraph|
+ (paragraph.first != "\n") ? paragraph.join : ""
+ end
+
+ joined_paragraphs.map { |paragraph| wrap(paragraph) }.join("\n")
+ end
+
+ def wrap(p)
+ (p.strip != "") ? "<p>#{ ContentParser.parse(p.strip) }</p>" : ""
+ end
+ end
+
+ class HeaderParser
+ def initialize(chunk_data, h_number)
+ @h_number = h_number
+ @header = chunk_data.first.sub(Regexp.new("^\s*#{ '#' * h_number }\s+"), '')
+ end
+
+ def render
+ "<h#@h_number>#{ ContentParser.parse(@header.strip) }</h#@h_number>"
+ end
+ end
+
+ class H1Parser < HeaderParser
+ def initialize(chunk_data)
+ super(chunk_data, 1)
+ end
+ end
+
+ class H2Parser < HeaderParser
+ def initialize(chunk_data)
+ super(chunk_data, 2)
+ end
+ end
+
+ class H3Parser < HeaderParser
+ def initialize(chunk_data)
+ super(chunk_data, 3)
+ end
+ end
+
+ class H4Parser < HeaderParser
+ def initialize(chunk_data)
+ super(chunk_data, 4)
+ end
+ end
+
+ class CodeParser
+ def initialize(chunk_data)
+ @code = chunk_data.map { |line| line.sub(/^ /, '') }.join
+ end
+
+ def render
+ "<pre><code>#{ ContentParser.sanitize(@code.chomp("\n")) }</code></pre>"
+ end
+ end
+
+ class BlockQuoteParser
+ def initialize(chunk_data)
+ quote_prefix = chunk_data.first[/^>\s+/]
+ @quote = chunk_data.map { |line| line.sub(quote_prefix, '') }.join
+ end
+
+ def render
+ "<blockquote>#{ StructureParser.parse(@quote) }</blockquote>"
+ end
+ end
+
+ class ListParser
+ def initialize(chunk_data, label, rgxp)
+ @items = chunk_data.map { |item| item.sub(rgxp, '') }
+ @label = label
+ end
+
+ def render
+ items = @items.map { |item| wrap(item.strip) }.join("\n")
+
+ "<#@label>\n#{ items }\n</#@label>"
+ end
+
+ def wrap(li)
+ (li != '') ? " <li>#{ ContentParser.parse(li) }</li>" : ""
+ end
+ end
+
+ class UListParser < ListParser
+ def initialize(chunk_data)
+ super(chunk_data, 'ul', /^\s*\*\s+/)
+ end
+ end
+
+ class OListParser < ListParser
+ def initialize(chunk_data)
+ super(chunk_data, 'ol', /^\s*(\d+)\.\s+/)
+ end
+ end
+
+ REGEXPS = {
+ bquote: /^>\s+.*$/,
+ code: /^ .*$/,
+ ulist: /^\s*\*\s+.*$/,
+ olist: /^\s*(\d)+\.\s+.*$/,
+ h1: /^\s*#\s+\S.*$/,
+ h2: /^\s*##\s+\S.*$/,
+ h3: /^\s*###\s+\S.*$/,
+ h4: /^\s*####\s+\S.*$/,
+ }
+
+ PARSERS = {
+ p: ParagraphParser,
+ bquote: BlockQuoteParser,
+ code: CodeParser,
+ ulist: UListParser,
+ olist: OListParser,
+ h1: H1Parser,
+ h2: H2Parser,
+ h3: H3Parser,
+ h4: H4Parser,
+ }
+
+ def self.parse(raw_text)
+ lines = raw_text.lines.chunk do |line|
+ REGEXPS.keys.find { |code| line =~ REGEXPS[code] } or :p
+ end
+
+ parsed = lines.map { |code, chunk| PARSERS[code].new(chunk) }
+
+ parsed.map { |p| p.render }.join("\n").strip
+ end
+end
+
+class Formatter
+ attr_reader :to_html, :inspect
+ alias :to_s :to_html
+
+ def initialize(raw_text)
+ @inspect = raw_text
+ @to_html = StructureParser.parse raw_text
+ end
+end