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?