svnno****@sourc*****
svnno****@sourc*****
2009年 8月 1日 (土) 00:56:08 JST
Revision: 947 http://sourceforge.jp/projects/hiki/svn/view?view=rev&revision=947 Author: fdiary Date: 2009-08-01 00:56:07 +0900 (Sat, 01 Aug 2009) Log Message: ----------- use recent hikidoc.rb (rev.110). Modified Paths: -------------- hiki/trunk/ChangeLog hiki/trunk/style/default/hikidoc.rb Modified: hiki/trunk/ChangeLog =================================================================== --- hiki/trunk/ChangeLog 2009-07-28 09:39:07 UTC (rev 946) +++ hiki/trunk/ChangeLog 2009-07-31 15:56:07 UTC (rev 947) @@ -1,3 +1,7 @@ +2009-07-31 Kazuhiko <kazuh****@fdiar*****> + + * style/default/hikidoc.rb: use recent hikidoc.rb (rev.110). + 2009-07-28 okkez <okkez****@gmail*****> * misc/plugin/history.rb (Hiki::History#history): uncomment. (see rev.942) Modified: hiki/trunk/style/default/hikidoc.rb =================================================================== --- hiki/trunk/style/default/hikidoc.rb 2009-07-28 09:39:07 UTC (rev 946) +++ hiki/trunk/style/default/hikidoc.rb 2009-07-31 15:56:07 UTC (rev 947) @@ -1,4 +1,6 @@ +# -*- coding: utf-8; -*- # Copyright (c) 2005, Kazuhiko <kazuh****@fdiar*****> +# Copyright (c) 2007 Minero Aoki # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -26,446 +28,876 @@ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -require 'uri' -class HikiDoc < String - Revision = %q$Rev: 38 $ +require "stringio" +require "strscan" +require "uri" +begin + require "syntax/convertors/html" +rescue LoadError +end - def initialize( content = '', options = {} ) +class HikiDoc + VERSION = "0.0.4" # FIXME + + class Error < StandardError + end + + class UnexpectedError < Error + end + + def HikiDoc.to_html(src, options = {}) + new(HTMLOutput.new(">"), options).compile(src) + end + + def HikiDoc.to_xhtml(src, options = {}) + new(HTMLOutput.new(" />"), options).compile(src) + end + + def initialize(output, options = {}) + @output = output + @options = default_options.merge(options) + @header_re = nil @level = options[:level] || 1 - @empty_element_suffix = options[:empty_element_suffix] || ' />' - super( content ) + @plugin_syntax = options[:plugin_syntax] || method(:valid_plugin_syntax?) end + def compile(src) + @output.reset + escape_plugin_blocks(src) {|escaped| + compile_blocks escaped + @output.finish + } + end + + # for backward compatibility def to_html - @stack = [] - @plugin_stack = [] - text = self.gsub( /\r\n?/, "\n" ) - text.sub!( /\n*\z/, "\n\n" ) - # escape '&', '<' and '>' - text = escape_html( text ) - # escape some symbols - text = escape_meta_char( text ) - # parse blocks - text = block_parser( text ) - # remove needless new lines - text.gsub!( /\n{2,}/, "\n" ) - # restore some html parts - text = restore_block( text ) - text = restore_plugin_block( text ) - # unescape some symbols - text = unescape_meta_char( text ) - # terminate with a single new line - text.sub!( /\n*\z/, "\n" ) - text + $stderr.puts("warning: HikiDoc#to_html is deprecated. Please use HikiDoc.to_html or HikiDoc.to_xhtml instead.") + self.class.to_html(@output, @options) end private - ###################################################################### - # block parser - ###################################################################### + def default_options + { + :allow_bracket_inline_image => true, + :use_wiki_name => true, + :use_not_wiki_name => true, + } + end - def block_parser( text ) - ret = text - ret = parse_plugin( ret ) - ret = parse_pre( ret ) - ret = parse_comment( ret ) - ret = parse_header( ret ) - ret = parse_hrules( ret ) - ret = parse_list( ret ) - ret = parse_definition( ret ) - ret = parse_blockquote( ret ) - ret = parse_table( ret ) - ret = parse_paragraph( ret ) - ret.lstrip + # + # Plugin + # + + def valid_plugin_syntax?(code) + /['"]/ !~ code.gsub(/'(?:[^\\']+|\\.)*'|"(?:[^\\"]+|\\.)*"/m, "") end - ###################################################################### - # plugin + def escape_plugin_blocks(text) + s = StringScanner.new(text) + buf = "" + @plugin_blocks = [] + while chunk = s.scan_until(/\{\{/) + tail = chunk[-2, 2] + chunk[-2, 2] = "" + buf << chunk + # plugin + if block = extract_plugin_block(s) + @plugin_blocks.push block + buf << "\0#{@plugin_blocks.size - 1}\0" + else + buf << "{{" + end + end + buf << s.rest + yield(buf) + end - PLUGIN_OPEN = '{{' - PLUGIN_CLOSE = '}}' - PLUGIN_SPLIT_RE = /(#{Regexp.union( PLUGIN_OPEN, PLUGIN_CLOSE )})/ + def restore_plugin_block(str) + str.gsub(/\0(\d+)\0/) { + "{{" + plugin_block($1.to_i) + "}}" + } + end - def parse_plugin( text ) - ret = '' - plugin = false - plugin_str = '' - text.split( PLUGIN_SPLIT_RE ).each do |str| - case str - when PLUGIN_OPEN - plugin = true - plugin_str += str - when PLUGIN_CLOSE - if plugin - plugin_str += str - unless /['"]/ =~ plugin_str.gsub( /(['"]).*?\1/m, '' ) - plugin = false - ret << store_plugin_block( unescape_meta_char( plugin_str, true ) ) - plugin_str = '' - end - else - ret << str - end + def evaluate_plugin_block(str, buf = nil) + buf ||=****@outpu***** + str.split(/(\0\d+\0)/).each do |s| + if s[0, 1] == "\0" and s[-1, 1] == "\0" + buf << @output.inline_plugin(plugin_block(s[1..-2].to_i)) else - if plugin - plugin_str << str - else - ret << str - end + buf << @output.text(s) end end - ret << plugin_str if plugin - ret + buf end - ###################################################################### - # pre + def plugin_block(id) + @plugin_blocks[id] or raise UnexpectedError, "must not happen: #{id.inspect}" + end - MULTI_PRE_OPEN_RE = /<<</ - MULTI_PRE_CLOSE_RE = />>>/ - PRE_RE = /^[ \t]/ + def extract_plugin_block(s) + pos = s.pos + buf = "" + while chunk = s.scan_until(/\}\}/) + buf << chunk + buf.chomp!("}}") + if @plugin_syntax.call(buf) + return buf + end + buf << "}}" + end + s.pos = pos + nil + end - def parse_pre( text ) - ret = text - ret.gsub!( /^#{MULTI_PRE_OPEN_RE}$(.*?)^#{MULTI_PRE_CLOSE_RE}$/m ) do |str| - "\n" + store_block( "<pre>%s</pre>" % restore_pre( $1 ) ) + "\n\n" + # + # Block Level + # + + def compile_blocks(src) + f = LineInput.new(StringIO.new(src)) + while line = f.peek + case line + when COMMENT_RE + f.gets + when HEADER_RE + compile_header f.gets + when HRULE_RE + f.gets + compile_hrule + when LIST_RE + compile_list f + when DLIST_RE + compile_dlist f + when TABLE_RE + compile_table f + when BLOCKQUOTE_RE + compile_blockquote f + when INDENTED_PRE_RE + compile_indented_pre f + when BLOCK_PRE_OPEN_RE + compile_block_pre f + else + if /^$/ =~ line + f.gets + next + end + compile_paragraph f + end end - ret.gsub!( /(?:#{PRE_RE}.*\n?)+/ ) do |str| - str.chomp! - str.gsub!( PRE_RE, '' ) - "\n" + store_block( "<pre>\n%s\n</pre>" % restore_pre( str ) ) + "\n\n" + end + + COMMENT_RE = %r<\A//> + + def skip_comments(f) + f.while_match(COMMENT_RE) do |line| end - ret end - def restore_pre( text ) - ret = unescape_meta_char( text, true ) - ret = restore_plugin_block( ret, true ) + HEADER_RE = /\A!+/ + + def compile_header(line) + @header_re ||= /\A!{1,#{7 - @level}}/ + level = @level + (line.slice!(@header_re).size - 1) + title = strip(line) + @output.headline level, compile_inline(title) end - ###################################################################### - # header + HRULE_RE = /\A----$/ - HEADER_RE = /!/ + def compile_hrule + @output.hrule + end - def parse_header( text ) - text.gsub( /^(#{HEADER_RE}{1,#{7- @ level}})\s*(.*)\n?/ ) do |str| - level, title = $1.size + @level - 1, $2 - "\n<h#{level}>%s</h#{level}>\n\n" % - inline_parser(title) + ULIST = "*" + OLIST = "#" + LIST_RE = /\A#{Regexp.union(ULIST, OLIST)}+/ + + def compile_list(f) + typestack = [] + level = 0 + @output.list_begin + f.while_match(LIST_RE) do |line| + list_type = (line[0,1] == ULIST ? "ul" : "ol") + new_level = line.slice(LIST_RE).size + item = strip(line.sub(LIST_RE, "")) + if new_level > level + (new_level - level).times do + typestack.push list_type + @output.list_open list_type + @output.listitem_open + end + @output.listitem compile_inline(item) + elsif new_level < level + (level - new_level).times do + @output.listitem_close + @output.list_close typestack.pop + end + @output.listitem_close + @output.listitem_open + @output.listitem compile_inline(item) + elsif list_type == typestack.last + @output.listitem_close + @output.listitem_open + @output.listitem compile_inline(item) + else + @output.listitem_close + @output.list_close typestack.pop + @output.list_open list_type + @output.listitem_open + @output.listitem compile_inline(item) + typestack.push list_type + end + level = new_level + skip_comments f end + level.times do + @output.listitem_close + @output.list_close typestack.pop + end + @output.list_end end - ###################################################################### - # hrules + DLIST_RE = /\A:/ - HRULES_RE = /^----$/ + def compile_dlist(f) + @output.dlist_open + f.while_match(DLIST_RE) do |line| + dt, dd = split_dlitem(line.sub(DLIST_RE, "")) + @output.dlist_item compile_inline(dt), compile_inline(dd) + skip_comments f + end + @output.dlist_close + end - def parse_hrules( text ) - text.gsub( HRULES_RE ) do |str| - "\n<hr#{@empty_element_suffix}\n" + def split_dlitem(line) + re = /\A((?:#{BRACKET_LINK_RE}|.)*?):/o + if m = re.match(line) + return m[1], m.post_match + else + return line, "" end end - ###################################################################### - # list + TABLE_RE = /\A\|\|/ - LIST_UL = '*' - LIST_OL = '#' - LIST_MARK_RE = Regexp.union( LIST_UL, LIST_OL ) - LIST_RE = /^((#{LIST_MARK_RE})\2*)\s*(.*)\n?/ - LISTS_RE = /(?:#{LIST_RE})+/ - - def parse_list( text ) - text.gsub( LISTS_RE ) do |str| - cur_str = "\n" - list_type_array = [] - level = 0 - str.each do |line| - if LIST_RE =~ line - list_type = ( $2 == LIST_UL ? 'ul' : 'ol' ) - new_level, item = $1.size, $3 - if new_level > level - (new_level - level).times do - list_type_array << list_type - cur_str << "<#{list_type}>\n<li>" - end - cur_str << "%s" % inline_parser( item ) - elsif new_level < level - (level - new_level).times do - cur_str << "</li>\n</#{list_type_array.pop}>" - end - cur_str << "</li>\n<li>%s" % inline_parser( item ) - elsif list_type == list_type_array.last - cur_str << "</li>\n<li>%s" % inline_parser( item ) - else - cur_str << "</li>\n</%s>\n" % list_type_array.pop - cur_str << "<%s>\n" % list_type - cur_str << "<li>%s" % inline_parser( item ) - list_type_array << list_type - end - level = new_level - end + def compile_table(f) + lines = [] + f.while_match(TABLE_RE) do |line| + lines.push line + skip_comments f + end + @output.table_open + lines.each do |line| + @output.table_record_open + split_columns(line.sub(TABLE_RE, "")).each do |col| + mid = col.sub!(/\A!/, "") ? "table_head" : "table_data" + span = col.slice!(/\A[\^>]*/) + rs = span_count(span, "^") + cs = span_count(span, ">") + @output.__send__(mid, compile_inline(col), rs, cs) end - level.times do - cur_str << "</li>\n</#{list_type_array.pop}>" - end - cur_str << "\n\n" - cur_str + @output.table_record_close end + @output.table_close end - ###################################################################### - # definition + def split_columns(str) + cols = str.split(/\|\|/) + cols.pop if cols.last.chomp.empty? + cols + end - DEFINITION_RE = /^:(.*?)?:(.*)\n?/ - DEFINITIONS_RE = /(#{DEFINITION_RE})+/ + def span_count(str, ch) + c = str.count(ch) + c == 0 ? nil : c + 1 + end - def parse_definition( text ) - parsed_text = text.gsub( DEFINITION_RE ) do |str| - inline_parser( str ) + BLOCKQUOTE_RE = /\A""[ \t]?/ + + def compile_blockquote(f) + @output.blockquote_open + lines = [] + f.while_match(BLOCKQUOTE_RE) do |line| + lines.push line.sub(BLOCKQUOTE_RE, "") + skip_comments f end - parsed_text.gsub( DEFINITIONS_RE ) do |str| - ret = "\n<dl>\n" - str.chomp! - str.scan( DEFINITION_RE ) do |t, d| - if t.empty? - ret << "<dd>%s</dd>\n" % d - elsif d.empty? - ret << "<dt>%s</dt>\n" % t - else - ret << "<dt>%s</dt><dd>%s</dd>\n" % [ t, d ] - end + compile_blocks lines.join("") + @output.blockquote_close + end + + INDENTED_PRE_RE = /\A[ \t]/ + + def compile_indented_pre(f) + lines = f.span(INDENTED_PRE_RE)\ + .map {|line| rstrip(line.sub(INDENTED_PRE_RE, "")) } + text = restore_plugin_block(lines.join("\n")) + @output.preformatted(@output.text(text)) + end + + BLOCK_PRE_OPEN_RE = /\A<<<\s*(\w+)?/ + BLOCK_PRE_CLOSE_RE = /\A>>>/ + + def compile_block_pre(f) + m = BLOCK_PRE_OPEN_RE.match(f.gets) or raise UnexpectedError, "must not happen" + str = restore_plugin_block(f.break(BLOCK_PRE_CLOSE_RE).join.chomp) + f.gets + @output.block_preformatted(str, m[1]) + end + + BLANK = /\A$/ + PARAGRAPH_END_RE = Regexp.union(BLANK, + HEADER_RE, HRULE_RE, LIST_RE, DLIST_RE, + BLOCKQUOTE_RE, TABLE_RE, + INDENTED_PRE_RE, BLOCK_PRE_OPEN_RE) + + def compile_paragraph(f) + lines = f.break(PARAGRAPH_END_RE)\ + .reject {|line| COMMENT_RE =~ line } + if lines.size == 1 and /\A\0(\d+)\0\z/ =~ strip(lines[0]) + @output.block_plugin plugin_block($1.to_i) + else + line_buffer =****@outpu*****(:paragraph) + lines.each_with_index do |line, i| + buffer =****@outpu***** + line_buffer << buffer + compile_inline(lstrip(line).chomp, buffer) end - ret << "</dl>\n\n" - ret + @output.paragraph(line_buffer) end end - ###################################################################### - # blockquote + # + # Inline Level + # - BLOCKQUOTE_RE = /^""[ \t]?/ - BLOCKQUOTES_RE = /(#{BLOCKQUOTE_RE}.*\n?)+/ + BRACKET_LINK_RE = /\[\[.+?\]\]/ + URI_RE = /(?:https?|ftp|file|mailto):[A-Za-z0-9;\/?:@&=+$,\-_.!~*\'()#%]+/ + WIKI_NAME_RE = /\b(?:[A-Z]+[a-z\d]+){2,}\b/ - def parse_blockquote( text ) - text.gsub( BLOCKQUOTES_RE ) do |str| - str.chomp! - str.gsub!( BLOCKQUOTE_RE, '' ) - "\n<blockquote>\n%s\n</blockquote>\n\n" % block_parser(str) + def inline_syntax_re + if @options[:use_wiki_name] + if @options[:use_not_wiki_name] + / (#{BRACKET_LINK_RE}) + | (#{URI_RE}) + | (#{MODIFIER_RE}) + | (\^?#{WIKI_NAME_RE}) + /xo + else + / (#{BRACKET_LINK_RE}) + | (#{URI_RE}) + | (#{MODIFIER_RE}) + | (#{WIKI_NAME_RE}) + /xo + end + else + / (#{BRACKET_LINK_RE}) + | (#{URI_RE}) + | (#{MODIFIER_RE}) + /xo end end - ###################################################################### - # table + def compile_inline(str, buf = nil) + buf ||=****@outpu***** + re = inline_syntax_re + pending_str = nil + while m = re.match(str) + str = m.post_match - TABLE_SPLIT_RE = /\|\|/ - TABLE_RE = /^#{TABLE_SPLIT_RE}.+\n?/ - TABLES_RE = /(#{TABLE_RE})+/ + link, uri, mod, wiki_name = m[1, 4] + if wiki_name and wiki_name[0, 1] == "^" + pending_str = m.pre_match + wiki_name[1..-1] + next + end - def parse_table( text ) - parsed_text = text.gsub( TABLE_RE ) do |str| - inline_parser( str ) + pre_str = "#{pending_str}#{m.pre_match}" + pending_str = nil + evaluate_plugin_block(pre_str, buf) + compile_inline_markup(buf, link, uri, mod, wiki_name) end - parsed_text.gsub( TABLES_RE ) do |str| - ret = %Q|\n<table border="1">\n| - str.each do |line| - ret << "<tr>" - line.chomp.sub( /#{TABLE_SPLIT_RE}$/, '').split( TABLE_SPLIT_RE, -1 )[1..-1].each do |i| - tag = i.sub!( /^!/, '' ) ? 'th' : 'td' - attr = '' - if i.sub!( /^((?:\^|>)+)/, '' ) - rs = $1.count( '^' ) + 1 - cs = $1.scan( />/ ).size + 1 - attr << ' rowspan="%d"' % rs if rs > 1 - attr << ' colspan="%d"' % cs if cs > 1 - end - ret << "<#{tag}#{attr}>#{inline_parser( i )}</#{tag}>" - end - ret << "</tr>\n" + evaluate_plugin_block(pending_str || str, buf) + buf + end + + def compile_inline_markup(buf, link, uri, mod, wiki_name) + case + when link + buf << compile_bracket_link(link[2...-2]) + when uri + buf << compile_uri_autolink(uri) + when mod + buf << compile_modifier(mod) + when wiki_name + buf << @output.wiki_name(wiki_name) + else + raise UnexpectedError, "must not happen" + end + end + + def compile_bracket_link(link) + if m = /\A(?>[^|\\]+|\\.)*\|/.match(link) + title = m[0].chop + uri = m.post_match + fixed_uri = fix_uri(uri) + if can_image_link?(uri) + @output.image_hyperlink(fixed_uri, title) + else + @output.hyperlink(fixed_uri, compile_modifier(title)) end - ret << "</table>\n\n" - ret + else + fixed_link = fix_uri(link) + if can_image_link?(link) + @output.image_hyperlink(fixed_link) + else + @output.hyperlink(fixed_link, @output.text(link)) + end end end - ###################################################################### - # comment + def can_image_link?(uri) + image?(uri) and @options[:allow_bracket_inline_image] + end - COMMENT_RE = %r|^//.*\n?| + def compile_uri_autolink(uri) + if image?(uri) + @output.image_hyperlink(fix_uri(uri)) + else + @output.hyperlink(fix_uri(uri), @output.text(uri)) + end + end - def parse_comment( text ) - text.gsub( COMMENT_RE, '' ) + def fix_uri(uri) + if /\A(?:https?|ftp|file):(?!\/\/)/ =~ uri + uri.sub(/\A\w+:/, "") + else + uri + end end - ###################################################################### - # paragraph + IMAGE_EXTS = %w(.jpg .jpeg .gif .png) - PARAGRAPH_BOUNDARY_RE = /\n{2,}/ - NON_PARAGRAPH_RE = /^<[^!]/ + def image?(uri) + IMAGE_EXTS.include?(File.extname(uri).downcase) + end - def parse_paragraph( text ) - text.split( PARAGRAPH_BOUNDARY_RE ).collect { |str| - str.chomp! - if str.empty? - '' - elsif NON_PARAGRAPH_RE =~ str - str + STRONG = "'''" + EM = "''" + DEL = "==" + + STRONG_RE = /'''.+?'''/ + EM_RE = /''.+?''/ + DEL_RE = /==.+?==/ + + MODIFIER_RE = Regexp.union(STRONG_RE, EM_RE, DEL_RE) + + MODTAG = { + STRONG => "strong", + EM => "em", + DEL => "del" + } + + def compile_modifier(str) + buf =****@outpu***** + while m = / (#{MODIFIER_RE}) + /xo.match(str) + evaluate_plugin_block(m.pre_match, buf) + case + when chunk = m[1] + mod, s = split_mod(chunk) + mid = MODTAG[mod] + buf << @output.__send__(mid, compile_inline(s)) else - "<p>%s</p>" % inline_parser( str ) + raise UnexpectedError, "must not happen" end - }.join( "\n\n" ) + str = m.post_match + end + evaluate_plugin_block(str, buf) + buf end - ###################################################################### - # inline parser - ###################################################################### + def split_mod(str) + case str + when /\A'''/ + return str[0, 3], str[3...-3] + when /\A''/ + return str[0, 2], str[2...-2] + when /\A==/ + return str[0, 2], str[2...-2] + else + raise UnexpectedError, "must not happen: #{str.inspect}" + end + end - def inline_parser( text ) - text = parse_link( text ) - text = parse_modifier( text ) + def strip(str) + rstrip(lstrip(str)) end - ###################################################################### - # link and image + def rstrip(str) + str.sub(/[ \t\r\n\v\f]+\z/, "") + end - IMAGE_RE = /\.(jpg|jpeg|gif|png)\z/i - BRACKET_LINK_RE = /\[\[(.+?)\]\]/ - NAMED_LINK_RE = /(.+?)\|(.+)/ - URI_RE = /(?:(?:https?|ftp|file):|mailto:)[A-Za-z0-9;\/?:@&=+$,\-_.!~*\'()#%]+/ + def lstrip(str) + str.sub(/\A[ \t\r\n\v\f]+/, "") + end - def parse_link( text ) - ret = text - ret.gsub!( BRACKET_LINK_RE ) do |str| - link = $1 - if NAMED_LINK_RE =~ link - uri, title = $2, $1 - title = parse_modifier( title ) + + class HTMLOutput + def initialize(suffix = " />") + @suffix = suffix + @f = nil + end + + def reset + @f = StringIO.new + end + + def finish + @f.string + end + + def container(_for=nil) + case _for + when :paragraph + [] else - uri = title = link + "" end - uri.sub!( /^(?:https?|ftp|file)+:/, '' ) if %r|://| !~ uri && /^mailto:/ !~ uri - store_block( %Q|<a href="#{escape_quote( uri )}">#{title}</a>| ) end - ret.gsub!( URI_RE ) do |uri| - uri.sub!( /^\w+:/, '' ) if %r|://| !~ uri && /^mailto:/ !~ uri - if IMAGE_RE =~ uri - store_block( %Q|<img src="#{uri}" alt="#{File.basename( uri )}"#{@empty_element_suffix}| ) + + # + # Procedures + # + + def headline(level, title) + @f.puts "<h#{level}>#{title}</h#{level}>" + end + + def hrule + @f.puts "<hr#{@suffix}" + end + + def list_begin + end + + def list_end + @f.puts + end + + def list_open(type) + @f.puts "<#{type}>" + end + + def list_close(type) + @f.print "</#{type}>" + end + + def listitem_open + @f.print "<li>" + end + + def listitem_close + @f.puts "</li>" + end + + def listitem(item) + @f.print item + end + + def dlist_open + @f.puts "<dl>" + end + + def dlist_close + @f.puts "</dl>" + end + + def dlist_item(dt, dd) + case + when dd.empty? + @f.puts "<dt>#{dt}</dt>" + when dt.empty? + @f.puts "<dd>#{dd}</dd>" else - store_block( %Q|<a href="#{uri}">#{uri}</a>| ) + @f.puts "<dt>#{dt}</dt>" + @f.puts "<dd>#{dd}</dd>" end end - ret - end - ###################################################################### - # modifier( strong, em, re ) + def table_open + @f.puts %Q(<table border="1">) + end - STRONG = "'''" - EM = "''" - DEL = '==' - MODIFIER_RE = /(#{STRONG}|#{EM}|#{DEL})(.+?)(?:\1)/ + def table_close + @f.puts "</table>" + end - def parse_modifier( text ) - text.gsub( MODIFIER_RE ) do |str| - case $1 - when STRONG - store_block( "<strong>#{parse_modifier($2)}</strong>" ) - when EM - store_block( "<em>#{parse_modifier($2)}</em>" ) - when DEL - store_block( "<del>#{parse_modifier($2)}</del>" ) + def table_record_open + @f.print "<tr>" + end + + def table_record_close + @f.puts "</tr>" + end + + def table_head(item, rs, cs) + @f.print "<th#{tdattr(rs, cs)}>#{item}</th>" + end + + def table_data(item, rs, cs) + @f.print "<td#{tdattr(rs, cs)}>#{item}</td>" + end + + def tdattr(rs, cs) + buf = "" + buf << %Q( rowspan="#{rs}") if rs + buf << %Q( colspan="#{cs}") if cs + buf + end + private :tdattr + + def blockquote_open + @f.print "<blockquote>" + end + + def blockquote_close + @f.puts "</blockquote>" + end + + def block_preformatted(str, info) + syntax = info ? info.downcase : nil + if syntax + begin + convertor = Syntax::Convertors::HTML.for_syntax(syntax) + @f.puts convertor.convert(str) + return + rescue NameError, RuntimeError + end end + preformatted(text(str)) end - end - ###################################################################### - # utility methods - ###################################################################### + def preformatted(str) + @f.print "<pre>" + @f.print str + @f.puts "</pre>" + end - def escape_html( text ) - text.gsub( /&/, '&' ). - gsub( /</, '<' ). - gsub( />/, '>' ) - end + def paragraph(lines) + @f.puts "<p>#{lines.join("\n")}</p>" + end - def escape_quote( text ) - text.gsub( /"/, '"' ) - end + def block_plugin(str) + @f.puts %Q(<div class="plugin">{{#{escape_html(str)}}}</div>) + end - def store_block( text ) - key = "<#{@stack.size}>" - @stack << text - key - end + # + # Functions + # - BLOCK_RE = /<(\d+)>/ + def hyperlink(uri, title) + %Q(<a href="#{escape_html_param(uri)}">#{title}</a>) + end - def restore_block( text ) - return text if****@stack*****? - ret = text.dup - while ret.gsub!( BLOCK_RE ) { |str| - ( @stack[$1.to_i] || '' ).rstrip - } + def wiki_name(name) + hyperlink(name, text(name)) end - ret - end - def store_plugin_block( text ) - key = "<!#{@plugin_stack.size}>" - @plugin_stack << text - key + def image_hyperlink(uri, alt = nil) + alt ||= uri.split(/\//).last + alt = escape_html(alt) + %Q(<img src="#{escape_html_param(uri)}" alt="#{alt}"#{@suffix}) + end + + def strong(item) + "<strong>#{item}</strong>" + end + + def em(item) + "<em>#{item}</em>" + end + + def del(item) + "<del>#{item}</del>" + end + + def text(str) + escape_html(str) + end + + def inline_plugin(src) + %Q(<span class="plugin">{{#{src}}}</span>) + end + + # + # Utilities + # + + def escape_html_param(str) + escape_quote(escape_html(str)) + end + + def escape_html(text) + text.gsub(/&/, "&").gsub(/</, "<").gsub(/>/, ">") + end + + def unescape_html(text) + text.gsub(/>/, ">").gsub(/</, "<").gsub(/&/, "&") + end + + def escape_quote(text) + text.gsub(/"/, """) + end end - PLUGIN_BLOCK_RE = /<!(\d+)>/ - INLINE_PLUGIN_RE = %r|<p><!(\d+)></p>| - INLINE_PLUGIN_OPEN = '<span class="plugin">' - INLINE_PLUGIN_CLOSE = '</span>' - BLOCK_PLUGIN_OPEN = '<div class="plugin">' - BLOCK_PLUGIN_CLOSE = '</div>' - def restore_plugin_block( text, original = false ) - return text if @plugin_stack.empty? - if original - text.gsub!( PLUGIN_BLOCK_RE ) do |str| - @plugin_stack[$1.to_i] + class LineInput + def initialize(f) + @input = f + @buf = [] + @lineno = 0 + @eof_p = false + end + + def inspect + "\#<#{self.class} file=#{@input.inspect} line=#{lineno()}>" + end + + def eof? + @eof_p + end + + def lineno + @lineno + end + + def gets + unles****@buf*****? + @lineno += 1 + retur****@buf***** end - else - # block plugin - text.gsub!( INLINE_PLUGIN_RE ) do |str| - "#{BLOCK_PLUGIN_OPEN}#{@plugin_stack[$1.to_i]}#{BLOCK_PLUGIN_CLOSE}" + return nil if @eof_p # to avoid ARGF blocking. + line =****@input***** + line = line.sub(/\r\n/, "\n") if line + @eof_p = line.nil? + @lineno += 1 + line + end + + def ungets(line) + return unless line + @lineno -= 1 + @buf.push line + line + end + + def peek + line = gets() + ungets line if line + line + end + + def next? + peek() ? true : false + end + + def skip_blank_lines + n = 0 + while line = gets() + unless line.strip.empty? + ungets line + return n + end + n += 1 end - text.gsub!( PLUGIN_BLOCK_RE ) do |str| - "#{INLINE_PLUGIN_OPEN}#{@plugin_stack[$1.to_i]}#{INLINE_PLUGIN_CLOSE}" + n + end + + def gets_if(re) + line = gets() + if not line or not (re =~ line) + ungets line + return nil end + line end - text - end - META_CHAR_RE = /\\\{|\\\}|\\:|\\'|\\"|\\\|/ + def gets_unless(re) + line = gets() + if not line or re =~ line + ungets line + return nil + end + line + end - def escape_meta_char( text ) - text.gsub( META_CHAR_RE ) do |s| - '&#x%x;' % s[1] + def each + while line = gets() + yield line + end end - end - ESCAPED_META_CHAR_RE = /(?:&\#x([0-9a-f]{2});)/i + def while_match(re) + while line = gets() + unless re =~ line + ungets line + return + end + yield line + end + nil + end - def unescape_meta_char( text, original = false ) - text.gsub( ESCAPED_META_CHAR_RE ) do - if original - '\\' + [$1].pack( 'H2' ) - else - [$1].pack( 'H2' ) + def getlines_while(re) + buf = [] + while_match(re) do |line| + buf.push line end + buf end + + alias span getlines_while # from Haskell + + def until_match(re) + while line = gets() + if re =~ line + ungets line + return + end + yield line + end + nil + end + + def getlines_until(re) + buf = [] + until_match(re) do |line| + buf.push line + end + buf + end + + alias break getlines_until # from Haskell + + def until_terminator(re) + while line = gets() + return if re =~ line # discard terminal line + yield line + end + nil + end + + def getblock(term_re) + buf = [] + until_terminator(term_re) do |line| + buf.push line + end + buf + end end end if __FILE__ == $0 - puts HikiDoc.new( ARGF.read ).to_html + puts HikiDoc.to_html(ARGF.read(nil)) end