lib/trac-wiki/parser.rb in trac-wiki-0.1.9 vs lib/trac-wiki/parser.rb in trac-wiki-0.1.12

- old
+ new

@@ -1,7 +1,8 @@ require 'cgi' require 'uri' +require 'iconv' # :main: TracWiki # The TracWiki parses and translates Trac formatted text into # XHTML. Creole is a lightweight markup syntax similar to what many @@ -41,11 +42,13 @@ # Allowed url schemes # Examples: http https ftp ftps attr_accessor :allowed_schemes + attr_accessor :headings + # Disable url escaping for local links # Escaping: [[/Test]] --> %2FTest # No escaping: [[/Test]] --> Test attr_writer :no_escape def no_escape?; @no_escape; end @@ -56,16 +59,34 @@ def no_link?; @no_link; end attr_writer :math def math?; @math; end + attr_writer :edit_heading + def edit_heading?; @edit_heading; end + + # understand merge tags (see diff3(1)) + # >>>>>>> mine + # ||||||| orig + # ======= + # <<<<<<< yours + # convert to <div class="merge merge-mine">mine</div> attr_writer :merge def merge?; @merge; end + # every heading will had id, generated from heading text + attr_writer :id_from_heading + def id_from_heading?; @id_from_heading; end + + # when id_from_heading, non ascii char are transliterated to ascii + attr_writer :id_translit + def id_translit?; @id_translit; end + # Create a new Parser instance. def initialize(text, options = {}) @allowed_schemes = %w(http https ftp ftps) + @anames = {} @text = text @no_escape = nil options.each_pair {|k,v| send("#{k}=", v) } end @@ -81,26 +102,39 @@ # parser = Parser.new("**Hello //World//**") # parser.to_html # #=> "<p><strong>Hello <em>World</em></strong></p>" def to_html @out = '' + @edit_heading_class = 'editheading' + @headings = [ {level: 0, sline: 1 } ] @p = false @stack = [] @stacki = [] @was_math = false + @line_no = 1 parse_block(@text) @out end + def make_toc_html + @out = '' + parse_block(make_toc) + end + protected # Escape any characters with special meaning in HTML using HTML - # entities. + # entities. (&<>" not ') def escape_html(string) - CGI::escapeHTML(string) + #CGI::escapeHTML(string) + Parser.escapeHTML(string) end + def self.escapeHTML(string) + string.gsub(/&/n, '&amp;').gsub(/\"/n, '&quot;').gsub(/>/n, '&gt;').gsub(/</n, '&lt;') + end + # Escape any characters with special meaning in URLs using URL # encoding. def escape_url(string) CGI::escape(string) end @@ -174,11 +208,14 @@ # Example custom behaviour: # # make_local_link("LocalLink") #=> "/LocalLink" # make_local_link("Wikipedia:Bread") #=> "http://en.wikipedia.org/wiki/Bread" def make_local_link(link) #:doc: - no_escape? ? link : escape_url(link) + return link if no_escape? + link, anch = link.split(/#/, 2) + return escape_url(link) if ! anch + "#{escape_url(link)}##{escape_url(anch)}" end # Sanatize a direct url (e.g. http://wikipedia.org/). The default # behaviour returns the original link as-is. # @@ -244,26 +281,49 @@ return '' if a.empty? return ' ' + a.map{|k,v| "#{k}=\"#{v}\"" }.sort.join(' ') end def make_headline(level, text, aname) - ret = "<h#{level}>" << escape_html(text) << "</h#{level}>" + ret = "<h#{level}" if aname - ret = "<a name=\"#{ escape_html(aname) }\"/>" + ret + ret += " id=\"#{ escape_html(aname) }\"" end + ret += ">" + escape_html(text) + + if edit_heading? + ret += edit_heading_link(@headings.size - 1) + end + + ret += "</h#{level}>" ret end + def edit_heading_link(section) + "<a class='#{@edit_heading_class}' href=\"?edit=#{section}\">edit</a>" + end + def make_explicit_link(link) begin uri = URI.parse(link) return uri.to_s if uri.scheme && @allowed_schemes.include?(uri.scheme) rescue URI::InvalidURIError end make_local_link(link) end + + def make_toc + @headings.map do |h| + if h[:level] < 1 + '' + else + ind = " " * (h[:level] - 1) + "#{ind}* [[##{h[:aname]}|#{h[:title]}]]\n" + end + end.join + end + def parse_inline(str) until str.empty? case str # raw url when /\A(!)?((https?|ftps?):\/\/\S+?)(?=([\]\,.?!:;"'\)]+)?(\s|$))/ @@ -510,14 +570,16 @@ end_paragraph @out << '<hr/>' # heading == Wiki Ruless == # heading == Wiki Ruless == #tag - when str =~ /\A\s*(={1,6})\s*(.*?)\s*=*\s*(#(\S*))?\s*$(\r?\n)?/ + when str =~ /\A[[:blank:]]*(={1,6})\s*(.*?)\s*=*\s*(#(\S*))?\s*$(\r?\n)?/ level = $1.size title= $2 - aname= $4 + aname= aname_nice($4, title) + @headings.last[:eline] = @line_no - 1 + @headings.push({ :title => title, :sline => @line_no, :aname => aname, :level => level, }) end_paragraph @out << make_headline(level, title, aname) # table row when str =~ /\A[ \t]*\|\|(.*)$(\r?\n)?/ @@ -574,12 +636,36 @@ parse_inline(text) end else # case str raise "Parse error at #{str[0,30].inspect}" end + @line_no += ($`+$&).count("\n") str = $' end end_paragraph + @headings.last[:eline] = @line_no - 1 @out end + + def aname_nice(aname, title) + + if aname.nil? && id_from_heading? + aname = title.gsub /\s+/, '_' + if id_translit? + aname = Iconv.iconv('ascii//translit', 'utf-8', aname).join + end + end + return nil if aname.nil? + aname_ori = aname + count = 2 + while @anames[aname] + aname = aname_ori + ".#{count}" + count+=1 + end + @anames[aname] = true + aname + end + + + end end