lib/trac-wiki/parser.rb in trac-wiki-0.2.24 vs lib/trac-wiki/parser.rb in trac-wiki-0.3.10

- old
+ new

@@ -1,14 +1,15 @@ +# encoding: utf-8 require 'cgi' require 'uri' -require 'iconv' require 'yaml' +require 'unicode_utils/compatibility_decomposition' # :main: TracWiki # The TracWiki parses and translates Trac formatted text into -# XHTML. Creole is a lightweight markup syntax similar to what many +# XHTML. TracWiki is a lightweight markup syntax similar to what many # WikiWikiWebs use. Example syntax: # # = Heading 1 = # == Heading 2 == # === Heading 3 === @@ -18,16 +19,11 @@ # ||=Table=||=Heading=|| # || Table || Cells || # [[Image(image.png)]] # [[Image(image.png, options)]] # -# The simplest interface is TracWiki.render. The default handling of -# links allow explicit local links using the [[link]] syntax. External -# links will only be allowed if specified using http(s) and ftp(s) -# schemes. If special link handling is needed, such as inter-wiki or -# hierachical local links, you must inherit Creole::CreoleParser and -# override make_local_link. +# for more see http://trac.edgewall.org/wiki/WikiFormatting # # You can customize the created image markup by overriding # make_image. # Main TracWiki parser class. Call TracWikiParser#parse to parse @@ -46,11 +42,23 @@ # Allowed url schemes # Examples: http https ftp ftps attr_accessor :allowed_schemes + # structure where headings are stroed + # list of hasheses with `level` and `title`, `sline` + # [ { leven: 1, # <h1> + # sline: 3, # line where head starts + # eline: 4, # line before next heading starts + # aname: 'anchor-to-this-heading', + # title: 'heading title' + # }, + # ... + # ] attr_accessor :headings + + # url base for links attr_writer :base # Disable url escaping for local links # Escaping: [[/Test]] --> %2FTest # No escaping: [[/Test]] --> Test @@ -60,18 +68,23 @@ # Disable url escaping for local links # [[whatwerver]] stays [[whatwerver]] attr_writer :no_link def no_link?; @no_link; end + # math syntax extension: + # $e^x$ for inline math + # $$ e^x $$ for display math attr_writer :math def math?; @math; end # allow some <b> <form> <html> # html will be sanitized - attr_writer :raw_html - def raw_html?; @raw_html; end + attr_writer :allow_html + def allow_html?; @allow_html; end + # add '<a class='editheading' href="?edit=N>edit</a>' + # to each heading attr_writer :edit_heading def edit_heading?; @edit_heading; end # understand merge tags (see diff3(1)) # >>>>>>> mine @@ -80,48 +93,98 @@ # <<<<<<< 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 + # every heading had id, generated from heading text attr_writer :id_from_heading def id_from_heading?; @id_from_heading; end + # use macros? defalut yes + attr_writer :macros + def macros?; @macros; end + # when id_from_heading, non ascii char are transliterated to ascii attr_writer :id_translit - attr_accessor :plugins - @plugins = {} + def id_translit?; @id_translit; end + # like template but more powerfull + # do no use. + attr_accessor :macro_commands + @macro_commands = {} + + # template_handler(macroname) -> template_text + # when macros enabled and {{myCoolMacro}} ocured, + # result fo `template_handler('myCoolMacro') inserted + attr_accessor :template_handler + # string begins with macro MACRO_BEG_REX = /\A\{\{ ( \$[\$\.\w]+ | [\#!]\w* |\w+ ) /x MACRO_BEG_INSIDE_REX = /\A(.*?)(?<!\{)\{\{ ( \$[\$\.\w]+ | [\#!]\w* | \w+ ) /xm # find end of marcro or begin of inner macro MACRO_END_REX = /\A(.*?) ( \}\} | \{\{ ( \$[\$\.\w]+ | [\#!]\w* | \w+) )/mx - def id_translit?; @id_translit; end # Create a new Parser instance. - def initialize(text, options = {}) - init_plugins + def initialize(options = nil) + init_macros + @macros = true @allowed_schemes = %w(http https ftp ftps) - @anames = {} - plugins = options.delete :plugins - @plugins.merge! plugins if ! plugins.nil? - @text = text + macro_commands = options.delete :macro_commands + @macro_commands.merge! macro_commands if ! macro_commands.nil? @no_escape = nil @base = '' options.each_pair {|k,v| send("#{k}=", v) } @base += '/' if !@base.empty? && @base[-1] != '/' end - def init_plugins - @plugins = { + def text(text) + @text = text + return self + end + + def was_math?; @was_math; end + + def to_html(text = nil) + text(text) if ! text.nil? + @was_math = false + @anames = {} + @count_lines_level = 0 + @text = text if !text.nil? + @tree = TracWiki::Tree.new + @edit_heading_class = 'editheading' + @headings = [ {level: 0, sline: 1 } ] + @p = false + @stack = [] + @stacki = [] + @was_math = false + @line_no = 1 + parse_block(@text) + @tree.to_html + end + + def make_toc_html + @tree = TracWiki::Tree.new + parse_block(make_toc) + @tree.to_html + end + + def add_macro_command(name, &block) + @macro_commands[name] = block + end + + protected + + def add_line_no(count) + @line_no += count if @count_lines_level == 0 + end + + def init_macros + @macro_commands = { '!ifeq' => proc { |env| env.expand_arg(0) == env.expand_arg(1) ? env.expand_arg(2) : env.expand_arg(3) }, '!ifdef' => proc { |env| env.at(env.expand_arg(0), nil, false).nil? ? env.expand_arg(2) : env.expand_arg(1) }, '!set' => proc { |env| env[env.expand_arg(0)] = env.expand_arg(1); '' }, '!yset' => proc { |env| env[env.expand_arg(0)] = YAML.load(env.arg(1)); '' }, - -# '!html' => proc { |env| "\n{{{!\n#{env.arg(0)}\n}}}\n" }, '!sub' => proc { |env| pat = env.expand_arg(1) pat = Regexp.new(pat[1..-2]) if pat =~ /\A\/.*\/\Z/ env.expand_arg(0).gsub(pat, env.expand_arg(2)) }, '!for' => proc { |env| i_name = env.arg(0) @@ -134,14 +197,15 @@ set = env.at(top, nil, false) if set.is_a?(Hash) set = set.keys.sort elsif set.is_a?(Array) set = (0 .. set.size-1) + elsif set.nil? + set = [] else print "error top(#{top}), set#{set} #{set.class}\n" - env.pp_env - raise "Error in {{!for #{i_name}|#{top}|#{tmpl}}}" + raise "Error in {{!for #{i_name}|#{top}|#{tmpl}}} $#{top}.class=#{set.class}(#{set.to_s})" end end set.map do |i| env[i_name] = i.to_s env.expand(tmpl) @@ -149,56 +213,13 @@ }, } end - # th(macroname) -> template_text - attr_accessor :template_handler - - @was_math = false - def was_math?; @was_math; end - - # Convert CCreole text to HTML and return - # the result. The resulting HTML does not contain <html> and - # <body> tags. - # - # Example: - # - # parser = Parser.new("**Hello //World//**") - # parser.to_html - # #=> "<p><strong>Hello <em>World</em></strong></p>" - def to_html - @tree = TracWiki::Tree.new - @edit_heading_class = 'editheading' - @headings = [ {level: 0, sline: 1 } ] - @p = false - @stack = [] - @stacki = [] - @was_math = false - @line_no = 1 - parse_block(@text) - @tree.to_html - end - - def make_toc_html - @tree = TracWiki::Tree.new - parse_block(make_toc) - @tree.to_html - end - - def add_plugin(name, &block) - @plugins[name] = block - end - - - - protected - # Escape any characters with special meaning in HTML using HTML # entities. (&<>" not ') def escape_html(string) - #CGI::escapeHTML(string) Parser.escapeHTML(string) end def self.escapeHTML(string) string.gsub(/&/, '&amp;').gsub(/\"/, '&quot;').gsub(/>/, '&gt;').gsub(/</, '&lt;') @@ -250,14 +271,14 @@ def end_paragraph end_tag while !@stack.empty? @p = false end - def start_paragraph + def start_paragraph(add_spc = true) if @p #FIXME: multiple space s - @tree.add_spc + @tree.add_spc if add_spc else end_paragraph start_tag('p') @p = true end @@ -292,48 +313,16 @@ return "#{@base}#{escape_url(link)}" if ! anch return "##{escape_url(anch)}" if link == '' "#{@base}#{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. - # - # Must ensure that the result is properly URL-escaped. The caller - # will handle HTML escaping as necessary. Links will not be - # converted to HTML links if the function returns link. - # - # Custom versions of this function in inherited classes can - # implement specific link handling behaviour, such as redirection - # to intermediate pages (for example, for notifing the user that - # he is leaving the site). - def make_direct_link(url) #:doc: - url - end - - # Sanatize and prefix image URLs. When images are encountered in - # Creole text, this function is called to obtain the actual URL of - # the image. The default behaviour is to return the image link - # as-is. No image tags are inserted if the function returns nil. - # - # Custom version of the method can be used to sanatize URLs - # (e.g. remove query-parts), inhibit off-site images, or add a - # base URL, for example: - # - # def make_image_link(url) - # URI.join("http://mywiki.org/images/", url) - # end - def make_image_link(url) #:doc: - url - end - # Create image markup. This # method can be overridden to generate custom # markup, for example to add html additional attributes or # to put divs around the imgs. def make_image(uri, attrs='') - #"<img src=\"#{make_explicit_link(uri)}\"#{make_image_attrs(attrs)}/>" - @tree.tag(:img, make_image_attrs(uri, attrs)) + @tree.tag(:img, make_image_attrs(@base + uri, attrs)) end def make_image_attrs(uri, attrs) a = {src: make_explicit_link(uri)} style = [] @@ -359,16 +348,16 @@ a[:style] = style.join(';') if ! style.empty? return {} if a.empty? return a; end - def make_headline(level, text, aname) + def make_headline(level, title, aname, title_offset) hN = "h#{level}".to_sym @tree.tag_beg(hN, { id: aname } ) - parse_inline(text) + parse_inline(title, title_offset) if edit_heading? edit_heading_link(@headings.size - 1) end @tree.tag_end(hN) end @@ -396,161 +385,151 @@ "#{ind}* [[##{h[:aname]}|#{h[:title]}]]\n" end end.join end - def parse_inline(str) + def parse_inline(str, offset) + raise "offset is nil" if offset.nil? until str.empty? - case str - # raw url - when /\A(!)?((https?|ftps?):\/\/\S+?)(?=([\]\,.?!:;"'\)]+)?(\s|$))/ - str = $' - if $1 - @tree.add($2) - else - if uri = make_direct_link($2) - @tree.tag(:a, {href:uri}, $2) - else - @tree.add($&) - end - end + case + # raw url http://example.com/ + when str =~ /\A(!)?((https?|ftps?):\/\/\S+?)(?=([\]\,.?!:;"'\)]+)?(\s|$))/ + notlink, link = $1, $2 + make_link(link, nil, link, 0, !!notlink) # [[Image(pic.jpg|tag)]] - when /\A\[\[Image\(([^,]*?)(,(.*?))?\)\]\]/ # image - str = $' + when str =~ /\A\[\[Image\(([^,]*?)(,(.*?))?\)\]\]/ # image make_image($1, $3) # [[link]] - # [ link1 | text2 ] - when /\A \[ \s* ([^\[|]*?) \s* (\|\s*(.*?))? \s* \] /mx - str = $' - link, content, whole= $1, $3, $& - make_link(link, content, "[#{whole}]") - # [[ link1 | text2 ]] - when /\A \[\[ \s* ([^|]*?) \s* (\|\s*(.*?))? \s* \]\] /mx - str = $' - link, content, whole= $1, $3, $& - make_link(link, content, whole) - else - str = parse_inline_tag(str) + # [ link2 | text5 ] + when str =~ /\A (\[ \s* ([^\[|]*?) \s*) ((\|\s*)(.*?))? \s* \] /mx + link, content, content_offset, whole = $2, $5, $1.size + ($4||'').size, $& + make_link(link, content, "[#{whole}]",offset + content_offset) + # [[ link2 | text5 ]] + when str =~ /\A (\[\[ \s* ([^|]*?) \s*) ((\|\s*)(.*?))? \s* \]\] /mx + link, content, content_offset, whole= $2, $5, $1.size + ($4||'').size, $& + #print "link: #{content_offset} of:#{offset}, '#{$1}', '#{$4||''}'\n" + make_link(link, content, whole, offset + content_offset) + when allow_html? && str =~ /\A<(\/)?(\w+)(?:([^>]*?))?(\/\s*)?>/ # single inline <html> tag + eot, tag, args, closed = $1, $2, $3, $4 + do_raw_tag(eot, tag, args, closed, $'.size) + when str =~ /\A\{\{\{(.*?\}*)\}\}\}/ # inline {{{ }}} pre (tt) + @tree.tag(:tt, $1) + when macros? && str =~ MACRO_BEG_REX # macro {{ + mac, str, lines, offset = parse_macro($1, $', offset, $&.size) + parse_inline(mac.gsub(/\n/, ' '),0); + #print "MACRO.inline(#{$1}), next:#{str}" + #return str, offset + next + when str =~ /\A`(.*?)`/ # inline pre (tt) + @tree.tag(:tt, $1) + when math? && str =~ /\A\$(.+?)\$/ # inline math (tt) + @tree.tag(:span, {:class => 'math'}, $1) + @was_math = true + when str =~ /\A(\&\w*;)/ # html entity + #print "add html ent: #{$1}\n" + @tree.add_raw($1) + when str =~ /\A([:alpha:]|[:digit:])+/ + @tree.add($&) # word + when str =~ /\A\s+/ + @tree.add_spc + when str =~ /\A'''''/ + toggle_tag 'strongem', $& # bolditallic + when str =~ /\A\*\*/ || str =~ /\A'''/ + toggle_tag 'strong', $& # bold + when str =~ /\A''/ || str =~ /\A\/\// + toggle_tag 'em', $& # italic + when str =~ /\A\\\\/ || str =~ /\A\[\[br\]\]/i + @tree.tag(:br) # newline + when str =~ /\A__/ + toggle_tag 'u', $& # underline + when str =~ /\A~~/ + toggle_tag 'del', $& # delete + when str =~ /\A~/ + @tree.add_raw('&nbsp;') # tilde +# when /\A\+\+/ +# toggle_tag 'ins', $& # insert + when str =~ /\A\^/ + toggle_tag 'sup', $& # ^{} + when str =~ /\A,,/ + toggle_tag 'sub', $& # _{} + when str =~ /\A!(\{\{|[^\s])/ + @tree.add($1) # !neco !{{ + when str =~ /\A./ + @tree.add($&) # ordinal char end - + str = $' + offset += $&.size end + return offset end - def make_link(link, content, whole) - # specail "link" [[BR]]: - if link =~ /^br$/i - @tree.tag(:br) - return - end - uri = make_explicit_link(link) - if not uri - @tree.add(whole) - return - end - - if no_link? - if uri !~ /^(ftp|https?):/ - @tree.add(whole) - return - end - end - - @tree.tag_beg(:a, {href:uri}) - if content - until content.empty? - content = parse_inline_tag(content) - end - else - @tree.add(link) - end - @tree.tag_end(:a) - end - - def parse_inline_tag(str) - case - when raw_html? && str =~ /\A<(\/)?(\w+)(?:([^>]*?))?(\/\s*)?>/ # single inline <html> tag - eot, tag, args, closed = $1, $2, $3, $4 - do_raw_tag(eot, tag, args, closed, $'.size) - when str =~ /\A\{\{\{(.*?\}*)\}\}\}/ # inline {{{ }}} pre (tt) - @tree.tag(:tt, $1) - when str =~ MACRO_BEG_REX # macro {{ - str, lines = parse_macro($1, $') - #print "MACRO.inline(#{$1}), next:#{str}" - return str - when str =~ /\A`(.*?)`/ # inline pre (tt) - @tree.tag(:tt, $1) - when math? && str =~ /\A\$(.+?)\$/ # inline math (tt) - #@tree.add("\\( #{$1} \\)") - @tree.tag(:span, {class:'math'}, $1) - @was_math = true - when str =~ /\A(\&\w*;)/ # html entity - #print "add html ent: #{$1}\n" - @tree.add_raw($1) - when str =~ /\A([:alpha:]|[:digit:])+/ - @tree.add($&) # word - when str =~ /\A\s+/ - @tree.add_spc - when str =~ /\A'''''/ - toggle_tag 'strongem', $& # bolditallic - when str =~ /\A\*\*/ || str =~ /\A'''/ - toggle_tag 'strong', $& # bold - when str =~ /\A''/ || str =~ /\A\/\// - toggle_tag 'em', $& # italic - when str =~ /\A\\\\/ || str =~ /\A\[\[br\]\]/i - @tree.tag(:br) # newline - when str =~ /\A__/ - toggle_tag 'u', $& # underline - when str =~ /\A~~/ - toggle_tag 'del', $& # delete - when str =~ /\A~/ - @tree.add_raw('&nbsp;') # tilde -# when /\A\+\+/ -# toggle_tag 'ins', $& # insert - when str =~ /\A\^/ - toggle_tag 'sup', $& # ^{} - when str =~ /\A,,/ - toggle_tag 'sub', $& # _{} - when str =~ /\A!(\{\{|[^\s])/ - @tree.add($1) # !neco !{{ - when str =~ /./ - @tree.add($&) # ordinal char - end - return $' - end - ################################################################# # macro {{ }} # convetntion {{!cmd}} {{template}} {{$var}} {{# comment}} {{!}} (pipe) - # r: expanded macro + rest of str, count lines taken from str + # r: expanded macro , rest of str, count lines taken from str # sideefect: parse result of macro - def parse_macro(macro_name, str) + def parse_macro(macro_name, str, offset, macro_name_size) + raise "offset is nil" if offset.nil? + raise "offset is nil" if macro_name_size.nil? @env = Env.new(self) if @env.nil? + @env.atput('offset', offset) + @env.atput('lineno', @line_no) begin - mac_out, rest, lines = @env.parse_macro_all(macro_name, str) + mac_out, rest, lines, rest_offset = @env.parse_macro_all(macro_name, str, macro_name_size) + raise "lines is nil" if lines.nil? #print "mac: '#{mac_out}' rest: '#{rest}'\n" - return mac_out + rest, lines + #print "mac: ro #{rest_offset}, of#{offset}, lines: #{lines} ms: #{macro_name_size} strlen#{str.size}, str'#{str}' rest:'#{rest}'\n" + rest_offset += offset + macro_name_size if lines == 0 + #print "ro#{rest_offset}\n" + return mac_out, rest, lines, rest_offset rescue TooLongException => e - return "TOO_LONG_EXPANSION_OF_MACRO(#{macro_name})QUIT", 0 + return '', "TOO_LONG_EXPANSION_OF_MACRO(#{macro_name})QUIT", 0, 0 + rescue Exception => e + #@tree.tag(:span, {:title => "#{e}\", :class=>'parse-error'}, "!!!") + @tree.tag(:span, {:title => "#{e}\n#{e.backtrace}", :class=>'parse-error'}, "!!!") + print "tace#{e.backtrace.to_s}\n" + return '', '', 0, 0 end end + ################################################################# + def make_link(link, content, whole, offset, not_make_link = false ) + # was '!' before url? + return @tree.add(whole) if not_make_link - ################################################################# + # specail "link" [[BR]]: + return @tree.tag(:br) if link =~ /^br$/i + uri = make_explicit_link(link) + return @tree.add(whole) if not uri + return @tree.add(whole) if no_link? && uri !~ /^(ftp|https?):/ + @tree.tag_beg(:a, {href:uri}) + if content + parse_inline(content, offset) + else + @tree.add(link) + end + @tree.tag_end(:a) + end + + ################################################################# + def parse_table_row(str) + offset = 0; start_tag('tr') if !@stack.include?('tr') str.sub!(/\r/, '') colspan = 1 print_tr = true last_tail = '' last_txt = '' str.scan(/(=?)(\s*)(.*?)\1?($ | \|\|\\\s*$ | \|\| )/x) do tdth = $1.empty? ? 'td' : 'th' - le, txt, tail = $2.size, $3, $4 + tdth_size = $1.size + le, txt, tail, cell_size = $2.size, $3, $4, $&.size # do not end row, continue on next line print_tr = false if tail =~ /^\|\|\\/ if txt.empty? && le == 0 @@ -569,23 +548,25 @@ colspan = colspan > 1 ? colspan : nil; start_tag(tdth, { style:style, colspan: colspan}); colspan = 1 - parse_inline(txt.strip) if txt + parse_inline(txt.strip, offset + tdth_size + le + 2) if txt end_tag while @stack.last != 'tr' + offset += cell_size end if print_tr end_tag end + return offset end def make_nowikiblock(input) input.gsub(/^ (?=\}\}\})/, '') end - def parse_li_line(spc_size, bullet, text) + def parse_li_line(spc_size, bullet) while !@stacki.empty? && @stacki.last > spc_size end_tag end @@ -621,11 +602,10 @@ start_tag(ulol, {type: type, start: start}, spc_size) end start_tag('li') - parse_inline(text) end def blockquote_level_to(level) cur_level = @stack.count('blockquote') @@ -696,16 +676,16 @@ def do_hr end_paragraph @tree.tag(:hr) end - def do_heading(level, title, aname) + def do_heading(level, title, aname, title_offset) aname= aname_nice(aname, title) @headings.last[:eline] = @line_no - 1 @headings.push({ :title => title, :sline => @line_no, :aname => aname, :level => level, }) end_paragraph - make_headline(level, title, aname) + make_headline(level, title, aname, title_offset) end def do_table_row(text) if !@stack.include?('table') end_paragraph start_tag('table') @@ -718,47 +698,52 @@ @tree.add(term) end_tag start_tag('dd') end - def do_citation(level, quote) + def do_citation(level) start_paragraph if !@stack.include? 'p' blockquote_level_to(level) - parse_inline(quote.strip) end - def do_ord_line(spc_size, text) - text.rstrip! + def do_ord_line(spc_size) if @stack.include?('li') || @stack.include?('dl') # dl, li continuation - parse_inline(' ') - parse_inline(text) + parse_inline(' ', 0) elsif spc_size > 0 # quote continuation start_paragraph if !@stack.include? 'p' blockquote_level_to(1) - parse_inline(text) else # real ordinary line start_paragraph - parse_inline(text) end end - def parse_block(str) + def parse_block(str, want_end_paragraph = true) #print "BLOCK.str(#{str})\n" until str.empty? case # macro - when str =~ MACRO_BEG_REX - str, lines = parse_macro($1, $') - #print "MACRO.block(#{$1})next:#{str}\n" - @line_no += lines + when macros? && str =~ MACRO_BEG_REX + mac, str, lines, offset = parse_macro($1, $', 0, $&.size) + raise 'lines is nil' if lines.nil? + raise 'offset is nil' if offset.nil? + #print "MACRO.lines(#{$1})lines:#{lines}, str:'#{str}'\n" + add_line_no(lines) + @count_lines_level +=1 + parse_block(mac, false) + @count_lines_level -=1 + if mac.size > 0 && str =~ /^(.*)(\r?\n)?/ + line, str = $1 , $' + add_line_no($&.count("\n")) + parse_inline(line, offset) + end next # display math $$ when math? && str =~ /\A\$\$(.*?)\$\$/m do_math($1) # merge @@ -770,54 +755,63 @@ # horizontal rule when str =~ /\A\s*-{4,}\s*$/ do_hr() # heading == Wiki Ruless == # heading == Wiki Ruless == #tag - when str =~ /\A[[:blank:]]*(={1,6})\s*(.*?)\s*=*\s*(#(\S*))?\s*$(\r?\n)?/ - do_heading($1.size, $2, $4) + when str =~ /\A([[:blank:]]*(={1,6})\s*)(.*?)\s*=*\s*(#(\S*))?\s*$(\r?\n)?/ + do_heading($2.size, $3, $5, $1.size) # table row || when str =~ /\A[ \t]*\|\|(.*)$(\r?\n)?/ do_table_row($1) # empty line when str =~ /\A\s*$(\r?\n)?/ end_paragraph when str =~ /\A([:\w\s]+)::(\s+|\r?\n)/ do_term($1) # li - when str =~ /\A(\s*)([*-]|[aAIi\d]\.)\s+(.*?)$(\r?\n)?/ - parse_li_line($1.size, $2, $3) + when str =~ /\A((\s*)([*-]|[aAIi\d]\.)\s+)(.*?)$(\r?\n)?/ + parse_li_line($2.size, $3) + parse_inline($4, $1.size) # citation when str =~ /\A(>[>\s]*)(.*?)$(\r?\n)?/ - do_citation($1.count('>'), $2) + do_citation($1.count('>')) + parse_inline($2, $1.size) # ordinary line when str =~ /\A(\s*)(\S+.*?)$(\r?\n)?/ - do_ord_line($1.size, $2) + text = $2 + do_ord_line($1.size) + parse_inline(text.rstrip, $1.size) else # case str raise "Parse error at #{str[0,30].inspect}" end - @line_no += ($`+$&).count("\n") + add_line_no(($`).count("\n")+($&).count("\n")) str = $' end - end_paragraph + end_paragraph if want_end_paragraph @headings.last[:eline] = @line_no - 1 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 + aname = _translit(aname) if id_translit? 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 + def _translit(text) + # iconv is obsolete, but translit funcionality was not replaced + # see http://stackoverflow.com/questions/20224915/iconv-will-be-deprecated-in-the-future-transliterate + # return Iconv.iconv('ascii//translit', 'utf-8', text).join + + # http://unicode-utils.rubyforge.org/UnicodeUtils.html#method-c-compatibility_decomposition + return UnicodeUtils.compatibility_decomposition(text).chars.grep(/\p{^Mn}/).join('') end end end