lib/trac-wiki/parser.rb in trac-wiki-0.2.16 vs lib/trac-wiki/parser.rb in trac-wiki-0.2.20

- old
+ new

@@ -1,8 +1,9 @@ require 'cgi' require 'uri' require 'iconv' +require 'yaml' # :main: TracWiki # The TracWiki parses and translates Trac formatted text into # XHTML. Creole is a lightweight markup syntax similar to what many @@ -36,10 +37,13 @@ # each thread that needs to convert Creole to HTML. # # Inherit this to provide custom handling of links. The overrideable # methods are: make_local_link module TracWiki + class TooLongException < Exception + end + class Parser # Allowed url schemes # Examples: http https ftp ftps attr_accessor :allowed_schemes @@ -83,31 +87,75 @@ 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 + attr_accessor :plugins + @plugins = {} # string begins with macro - MACRO_BEG_REX = /\A\{\{ ( [\$]?\w+ | \#\w* ) /x - MACRO_BEG_INSIDE_REX = /(.*?)(?<!\{)\{\{ ( \$\w+ | \#\w* | \w+ ) /x + 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 + 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 @allowed_schemes = %w(http https ftp ftps) @anames = {} + plugins = options.delete :plugins + @plugins.merge! plugins if ! plugins.nil? @text = text @no_escape = nil @base = '' options.each_pair {|k,v| send("#{k}=", v) } @base += '/' if !@base.empty? && @base[-1] != '/' end + def init_plugins + @plugins = { + '!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) + top = env.arg(1) + tmpl = env.arg(2) + #print "top#{top}\n" + if top =~ /^\d+/ + set = (0..(top.to_i-1)) + else + 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) + else + print "error top(#{top}), set#{set} #{set.class}\n" + env.pp_env + raise "Error in {{!for #{i_name}|#{top}|#{tmpl}}}" + end + end + set.map do |i| + env[i_name] = i.to_s + env.expand(tmpl) + end.join('') + }, + } + + end + # th(macroname) -> template_text - attr_writer :template_handler + attr_accessor :template_handler @was_math = false def was_math?; @was_math; end # Convert CCreole text to HTML and return @@ -136,10 +184,16 @@ @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) @@ -405,25 +459,27 @@ @tree.tag_end(:a) end def parse_inline_tag(str) case - when str =~ /\A\{\{\{(.*?\}*)\}\}\}/ # inline pre (tt) + when raw_html? && str =~ /\A\{\{\{!(.*?\}*)\}\}\}/ # inline {{{! raw html }}} + do_raw_html($1, true) + 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})" + #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.add("$#{$1}$") #@tree.tag(:span, {class:'math'}, $1) @was_math = true when str =~ /\A(\&\w*;)/ # html entity - print "add html ent: #{$1}\n" + #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 @@ -453,107 +509,35 @@ @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 # sideefect: parse result of macro def parse_macro(macro_name, str) + @env = Env.new(self) if @env.nil? begin - mac_out, rest, lines = parse_macro_arg(macro_name, str, {}) - #parse_inline(mac) - #@tree.add(mac) - #print "MACOUT:'#{mac_out}'\n" + mac_out, rest, lines = @env.parse_macro_all(macro_name, str) + #print "mac: '#{mac_out}' rest: '#{rest}'\n" return mac_out + rest, lines - rescue Exception => a - return "TOO_LONG_EXPANSION_OF_MACRO(#{macro_name})QUIT" + rescue TooLongException => e + return "TOO_LONG_EXPANSION_OF_MACRO(#{macro_name})QUIT", 0 end end - # read to args to }} (while balancing {{ and }}) - # ret: (arg, rest, lines) - # mac_out -- string to }} (macros inside expanded) - # rest -- str aftrer }} - # lines -- howmany \n eaten from str (from begining to }}) - def parse_macro_arg(macro_name, str, env) - lines = 0 - arg = '' - # FIXME: MACRO_REX - # prefix }}... {{macro_name - while str =~ MACRO_END_REX - prefix, bracket, sub_macro_name, str = $1, $2, $3, $' - arg << prefix - lines += prefix.count("\n") - if bracket == '}}' - #print "prefix: #{prefix}\n" - env = do_macro_arg_to_env(arg, env[:depth]) - return do_macro(macro_name, env), str, lines - end - # we need to go deeper! - mac_out, str, l = parse_macro_arg(sub_macro_name, str, env) - arg << mac_out - lines += l - end - raise "Error parsing macro near '#{str}' (arg:#{arg}, lines=#{lines})" - end + ################################################################# - # expand macro `macro_name` with `args` - # afer expansion all {{macros}} will be expanded recursively - # r: expanded string - def do_macro(macro_name, env) - return "!{{toc}}" if macro_name == 'toc' - return env[:arg].strip if macro_name == '#echo' - return '' if macro_name == '#' - return do_macro_var(macro_name, env) if macro_name =~ /^\$/ - - - if ! @template_handler.nil? - text = @template_handler.call(macro_name, env) - if !text.nil? - #print "dep:#{env[:depth]}(#{macro_name}, #{text})\n" - if env[:depth] > 32 - return "TOO_DEEP_RECURSION(`#{text}`)\n" - #return "TOO_DEEP_RECURSION" - end - # FIXME: melo by nahlasit jestli to chce expandovat | wiki expadnovat |raw html - #print "temp(#{macro_name}) --> : #{text}\n" - text = do_macro_expand_result(text, env) - #print "af expand:#{text}\n" - - return text - end - end - "UMACRO(#{macro_name}#{env[:arg]})" - end - - def do_macro_var(macro_name, env) - "VAR(#{macro_name})" - end - - def do_macro_arg_to_env(arg, depth) - { arg: arg , depth: (depth||0) + 1 } - end - - # template expand - def do_macro_expand_result(text, env) - ret = '' - while text =~ MACRO_BEG_INSIDE_REX - prefix, macro_name2, text = $1, $2, $' - ret << prefix - mac_out, text, lines = parse_macro_arg(macro_name2, text, env) - ret << mac_out - raise "Too long macro expadion" if ret.size > 1_000_000 - end - return ret + text - end - - def parse_table_row(str) 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 @@ -672,12 +656,12 @@ end_paragraph nowikiblock = make_nowikiblock(text) @tree.tag(:pre, nowikiblock) end - def do_raw_html(text) - end_paragraph + def do_raw_html(text, inline=false) + end_paragraph if !inline @tree.add_raw(text) end def do_hr end_paragraph @@ -739,11 +723,12 @@ until str.empty? case # macro when str =~ MACRO_BEG_REX str, lines = parse_macro($1, $') - #print "MACRO.block(#{$1})\n" + #print "MACRO.block(#{$1})next:#{str}\n" + @line_no += lines next # display math $$ when math? && str =~ /\A\$\$(.*?)\$\$/m do_math($1) # merge @@ -776,18 +761,18 @@ # citation when str =~ /\A(>[>\s]*)(.*?)$(\r?\n)?/ do_citation($1.count('>'), $2) # ordinary line when str =~ /\A(\s*)(\S+.*?)$(\r?\n)?/ - do_ord_line($1.size, $2) + do_ord_line($1.size, $2) 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 + @headings.last[:eline] = @line_no - 1 end def aname_nice(aname, title) if aname.nil? && id_from_heading?