# encoding: utf-8 # # Copyright (c) 2002-2007 Minero Aoki # 2008-2012 Minero Aoki, Kenshi Muto, Masayoshi Takahashi, # KADO Masanori # # This program is free software. # You can distribute or modify this program under the terms of # the GNU LGPL, Lesser General Public License version 2.1. # require 'review/builder' require 'review/htmlutils' require 'review/htmllayout' require 'review/textutils' module ReVIEW class HTMLBuilder < Builder include TextUtils include HTMLUtils [:ref].each {|e| Compiler.definline(e) } Compiler.defblock(:memo, 0..1) Compiler.defblock(:tip, 0..1) Compiler.defblock(:info, 0..1) Compiler.defblock(:planning, 0..1) Compiler.defblock(:best, 0..1) Compiler.defblock(:important, 0..1) Compiler.defblock(:security, 0..1) Compiler.defblock(:caution, 0..1) Compiler.defblock(:notice, 0..1) Compiler.defblock(:point, 0..1) Compiler.defblock(:shoot, 0..1) def pre_paragraph '

' end def post_paragraph '

' end def extname ".#{ReVIEW.book.param["htmlext"]}" end def builder_init(no_error = false) @no_error = no_error @section = 0 @subsection = 0 @subsubsection = 0 @subsubsubsection = 0 @column = 0 @noindent = nil @ol_num = nil end private :builder_init def builder_init_file @warns = [] @errors = [] end private :builder_init_file def result layout_file = File.join(@book.basedir, "layouts", "layout.erb") if File.exists?(layout_file) messages() + HTMLLayout.new(@output.string, @chapter.title, layout_file).result else # default XHTML header/footer header = < EOT if ReVIEW.book.param["htmlversion"] == 5 header += < EOT else header += < EOT end unless ReVIEW.book.param["stylesheet"].nil? ReVIEW.book.param["stylesheet"].each do |style| header += < EOT end end header += < #{convert_outencoding(strip_html(compile_inline(@chapter.title)), ReVIEW.book.param["outencoding"])} EOT footer = < EOT header + messages() + convert_outencoding(@output.string, ReVIEW.book.param["outencoding"]) + footer end end def warn(msg) if @no_error @warns.push [@location.filename, @location.lineno, msg] puts "----WARNING: #{escape_html(msg)}----" else $stderr.puts "#{@location}: warning: #{msg}" end end def error(msg) if @no_error @errors.push [@location.filename, @location.lineno, msg] puts "----ERROR: #{escape_html(msg)}----" else $stderr.puts "#{@location}: error: #{msg}" end end def messages error_messages() + warning_messages() end def error_messages return '' if @errors.empty? "

Syntax Errors

\n" + "
    \n" + @errors.map {|file, line, msg| "
  • #{escape_html(file)}:#{line}: #{escape_html(msg.to_s)}
  • \n" }.join('') + "
\n" end def warning_messages return '' if @warns.empty? "

Warnings

\n" + "
    \n" + @warns.map {|file, line, msg| "
  • #{escape_html(file)}:#{line}: #{escape_html(msg)}
  • \n" }.join('') + "
\n" end def headline_prefix(level) anchor = "" case level when 1 @section = 0 @subsection = 0 @subsubsection = 0 @subsubsubsection = 0 anchor = "#{@chapter.number}" if ReVIEW.book.param["secnolevel"] >= 1 if @chapter.number.blank? prefix = "" else placeholder = if @chapter.is_a? ReVIEW::Book::Part 'part' elsif @chapter.on_POSTDEF? 'appendix' else 'chapter' end prefix = "#{I18n.t(placeholder, @chapter.number)}#{I18n.t("chapter_postfix")}" end end when 2 @section += 1 @subsection = 0 @subsubsection = 0 @subsubsubsection = 0 anchor = "#{@chapter.number}-#{@section}" if ReVIEW.book.param["secnolevel"] >= 2 if @chapter.number.blank? or @chapter.on_POSTDEF? prefix = "" else prefix = "#{@chapter.number}.#{@section}#{I18n.t("chapter_postfix")}" end end when 3 @subsection += 1 @subsubsection = 0 @subsubsubsection = 0 anchor = "#{@chapter.number}-#{@section}-#{@subsection}" if ReVIEW.book.param["secnolevel"] >= 3 if @chapter.number.blank? or @chapter.on_POSTDEF? prefix = "" else prefix = "#{@chapter.number}.#{@section}.#{@subsection}#{I18n.t("chapter_postfix")}" end end when 4 @subsubsection += 1 @subsubsubsection = 0 anchor = "#{@chapter.number}-#{@section}-#{@subsection}-#{@subsubsection}" if ReVIEW.book.param["secnolevel"] >= 4 if @chapter.number.blank? or @chapter.on_POSTDEF? prefix = "" else prefix = "#{@chapter.number}.#{@section}.#{@subsection}.#{@subsubsection}#{I18n.t("chapter_postfix")}" end end when 5 @subsubsubsection += 1 anchor = "#{@chapter.number}-#{@section}-#{@subsection}-#{@subsubsection}-#{@subsubsubsection}" if ReVIEW.book.param["secnolevel"] >= 5 if @chapter.number.blank? or @chapter.on_POSTDEF? prefix = "" else prefix = "#{@chapter.number}.#{@section}.#{@subsection}.#{@subsubsection}.#{@subsubsubsection}#{I18n.t("chapter_postfix")}" end end end [prefix, anchor] end private :headline_prefix def headline(level, label, caption) prefix, anchor = headline_prefix(level) puts '' if level > 1 a_id = "" unless anchor.nil? a_id = %Q[] end if caption.empty? puts a_id unless label.nil? else if label.nil? puts %Q[#{a_id}#{prefix}#{compile_inline(caption)}] else puts %Q[#{a_id}#{prefix}#{compile_inline(caption)}] end end end def nonum_begin(level, label, caption) puts '' if level > 1 unless caption.empty? if label.nil? puts %Q[#{compile_inline(caption)}] else puts %Q[#{compile_inline(caption)}] end end end def nonum_end(level) end def column_begin(level, label, caption) puts %Q[
] @column += 1 puts '' if level > 1 a_id = %Q[] if caption.empty? puts a_id unless label.nil? else if label.nil? puts %Q[#{a_id}#{compile_inline(caption)}] else puts %Q[#{a_id}#{compile_inline(caption)}] end end # headline(level, label, caption) end def column_end(level) puts '
' end def xcolumn_begin(level, label, caption) puts %Q[
] headline(level, label, caption) end def xcolumn_end(level) puts '
' end def ref_begin(level, label, caption) print %Q[
] headline(level, label, caption) end def ref_end(level) puts '
' end def sup_begin(level, label, caption) print %Q[
] headline(level, label, caption) end def sup_end(level) puts '
' end def tsize(str) # null end def captionblock(type, lines, caption) puts %Q[
] unless caption.nil? puts %Q[

#{compile_inline(caption)}

] end if ReVIEW.book.param["deprecated-blocklines"].nil? blocked_lines = split_paragraph(lines) puts blocked_lines.join("\n") else lines.each {|l| puts "

#{l}

" } end puts '
' end def memo(lines, caption = nil) captionblock("memo", lines, caption) end def tip(lines, caption = nil) captionblock("tip", lines, caption) end def info(lines, caption = nil) captionblock("info", lines, caption) end def planning(lines, caption = nil) captionblock("planning", lines, caption) end def best(lines, caption = nil) captionblock("best", lines, caption) end def important(lines, caption = nil) captionblock("important", lines, caption) end def security(lines, caption = nil) captionblock("security", lines, caption) end def caution(lines, caption = nil) captionblock("caution", lines, caption) end def notice(lines, caption = nil) captionblock("notice", lines, caption) end def point(lines, caption = nil) captionblock("point", lines, caption) end def shoot(lines, caption = nil) captionblock("shoot", lines, caption) end def box(lines, caption = nil) puts %Q[
] puts %Q[

#{compile_inline(caption)}

] unless caption.nil? print %Q[
]
      lines.each {|line| puts detab(line) }
      puts '
' puts '
' end def note(lines, caption = nil) captionblock("note", lines, caption) end def ul_begin puts '
    ' end def ul_item(lines) puts "
  • #{lines.join}
  • " end def ul_item_begin(lines) print "
  • #{lines.join}" end def ul_item_end puts "
  • " end def ul_end puts '
' end def ol_begin if @ol_num puts "
    " ## it's OK in HTML5, but not OK in XHTML1.1 @ol_num = nil else puts '
      ' end end def ol_item(lines, num) puts "
    1. #{lines.join}
    2. " end def ol_end puts '
    ' end def dl_begin puts '
    ' end def dt(line) puts "
    #{line}
    " end def dd(lines) puts "
    #{lines.join}
    " end def dl_end puts '
    ' end def paragraph(lines) if @noindent.nil? puts "

    #{lines.join}

    " else puts %Q[

    #{lines.join}

    ] @noindent = nil end end def parasep() puts '
    ' end def read(lines) if ReVIEW.book.param["deprecated-blocklines"].nil? blocked_lines = split_paragraph(lines) puts %Q[
    \n#{blocked_lines.join("\n")}\n
    ] else puts %Q[

    \n#{lines.join("\n")}\n

    ] end end alias :lead read def list(lines, id, caption) puts %Q[
    ] begin list_header id, caption rescue KeyError error "no such list: #{id}" end list_body id, lines puts '
    ' end def list_header(id, caption) if get_chap.nil? puts %Q[

    #{I18n.t("list")}#{I18n.t("format_number_header_without_chapter", [@chapter.list(id).number])}#{I18n.t("caption_prefix")}#{compile_inline(caption)}

    ] else puts %Q[

    #{I18n.t("list")}#{I18n.t("format_number_header", [get_chap, @chapter.list(id).number])}#{I18n.t("caption_prefix")}#{compile_inline(caption)}

    ] end end def list_body(id, lines) id ||= '' print %Q[
    ]
          body = lines.inject(''){|i, j| i + detab(j) + "\n"}
          lexer = File.extname(id).gsub(/\./, '')
          puts highlight(:body => body, :lexer => lexer, :format => 'html')
          puts '
    ' end def source(lines, caption = nil) puts %Q[
    ] source_header caption source_body caption, lines puts '
    ' end def source_header(caption) if caption.present? puts %Q[

    #{compile_inline(caption)}

    ] end end def source_body(id, lines) id ||= '' print %Q[
    ]
          body = lines.inject(''){|i, j| i + detab(j) + "\n"}
          lexer = File.extname(id).gsub(/\./, '')
          puts highlight(:body => body, :lexer => lexer, :format => 'html')
          puts '
    ' end def listnum(lines, id, caption) puts %Q[
    ] begin list_header id, caption rescue KeyError error "no such list: #{id}" end listnum_body lines puts '
    ' end def listnum_body(lines) print %Q[
    ]
          lines.each_with_index do |line, i|
            puts detab((i+1).to_s.rjust(2) + ": " + line)
          end
          puts '
    ' end def emlist(lines, caption = nil) puts %Q[
    ] puts %Q(

    #{caption}

    ) unless caption.nil? print %Q[
    ]
          lines.each do |line|
            puts detab(line)
          end
          puts '
    ' puts '
    ' end def emlistnum(lines) puts %Q[
    ] print %Q[
    ]
          lines.each_with_index do |line, i|
            puts detab((i+1).to_s.rjust(2) + ": " + line)
          end
          puts '
    ' puts '
    ' end def cmd(lines, caption = nil) puts %Q[
    ] puts %Q(

    #{caption}

    ) unless caption.nil? print %Q[
    ]
          lines.each do |line|
            puts detab(line)
          end
          puts '
    ' puts '
    ' end def quotedlist(lines, css_class) print %Q[
    ]
          lines.each do |line|
            puts detab(line)
          end
          puts '
    ' end private :quotedlist def quote(lines) if ReVIEW.book.param["deprecated-blocklines"].nil? blocked_lines = split_paragraph(lines) puts "
    #{blocked_lines.join("\n")}
    " else puts "
    #{lines.join("\n")}
    " end end def doorquote(lines, ref) if ReVIEW.book.param["deprecated-blocklines"].nil? blocked_lines = split_paragraph(lines) puts %Q[
    ] puts "#{blocked_lines.join("\n")}" puts %Q[

    #{ref}より

    ] puts %Q[
    ] else puts <<-QUOTE
    #{lines.join("\n")}
    
    #{ref}より
    QUOTE end end def talk(lines) puts %Q[
    ] if ReVIEW.book.param["deprecated-blocklines"].nil? blocked_lines = split_paragraph(lines) puts "#{blocked_lines.join("\n")}" else print '
    '
            puts "#{lines.join("\n")}"
            puts '
    ' end puts '
    ' end def texequation(lines) puts %Q[
    ] if ReVIEW.book.param["mathml"] p = MathML::LaTeX::Parser.new puts p.parse(unescape_html(lines.join("\n")), true) else print '
    '
            puts "#{lines.join("\n")}"
            puts '
    ' end puts '
    ' end def handle_metric(str) if str =~ /\Ascale=([\d.]+)\Z/ return "width=\"#{($1.to_f * 100).round}%\"" else k, v = str.split('=', 2) return %Q|#{k}=\"#{v.sub(/\A["']/, '').sub(/["']\Z/, '')}\"| end end def result_metric(array) " #{array.join(' ')}" end def image_image(id, caption, metric) metrics = parse_metric("html", metric) puts %Q[
    ] puts %Q[#{escape_html(compile_inline(caption))}] image_header id, caption puts %Q[
    ] end def image_dummy(id, caption, lines) puts %Q[
    ] puts %Q[
    ]
          lines.each do |line|
            puts detab(line)
          end
          puts %Q[
    ] image_header id, caption puts %Q[
    ] end def image_header(id, caption) puts %Q[

    ] if get_chap.nil? puts %Q[#{I18n.t("image")}#{I18n.t("format_number_header_without_chapter", [@chapter.image(id).number])}#{I18n.t("caption_prefix")}#{compile_inline(caption)}] else puts %Q[#{I18n.t("image")}#{I18n.t("format_number_header", [get_chap, @chapter.image(id).number])}#{I18n.t("caption_prefix")}#{compile_inline(caption)}] end puts %Q[

    ] end def table(lines, id = nil, caption = nil) rows = [] sepidx = nil lines.each_with_index do |line, idx| if /\A[\=\-]{12}/ =~ line # just ignore #error "too many table separator" if sepidx sepidx ||= idx next end rows.push line.strip.split(/\t+/).map {|s| s.sub(/\A\./, '') } end rows = adjust_n_cols(rows) puts %Q[
    ] begin table_header id, caption unless caption.nil? rescue KeyError => err error "no such table: #{id}" end table_begin rows.first.size return if rows.empty? if sepidx sepidx.times do tr rows.shift.map {|s| th(s) } end rows.each do |cols| tr cols.map {|s| td(s) } end else rows.each do |cols| h, *cs = *cols tr [th(h)] + cs.map {|s| td(s) } end end table_end puts %Q[
    ] end def table_header(id, caption) if get_chap.nil? puts %Q[

    #{I18n.t("table")}#{I18n.t("format_number_header_without_chapter", [@chapter.table(id).number])}#{I18n.t("caption_prefix")}#{compile_inline(caption)}

    ] else puts %Q[

    #{I18n.t("table")}#{I18n.t("format_number_header", [get_chap, @chapter.table(id).number])}#{I18n.t("caption_prefix")}#{compile_inline(caption)}

    ] end end def table_begin(ncols) puts '' end def tr(rows) puts "#{rows.join}" end def th(str) "" end def td(str) "" end def table_end puts '
    #{str}#{str}
    ' end def comment(lines, comment = nil) lines ||= [] lines.unshift comment unless comment.blank? if ReVIEW.book.param["draft"] str = lines.map{|line| escape_html(line) }.join("
    ") puts %Q(
    #{str}
    ) else str = lines.join("\n") puts %Q() end end def footnote(id, str) puts %Q(

    [*#{@chapter.footnote(id).number}] #{compile_inline(str)}

    ) end def indepimage(id, caption="", metric=nil) metrics = parse_metric("html", metric) caption = "" if caption.nil? puts %Q[
    ] begin puts %Q[#{escape_html(compile_inline(caption))}] rescue puts %Q[
    missing image: #{id}
    ] end unless caption.empty? puts %Q[

    ] puts %Q[#{I18n.t("numberless_image")}#{I18n.t("caption_prefix")}#{compile_inline(caption)}] puts %Q[

    ] end puts %Q[
    ] end alias :numberlessimage indepimage def hr puts "
    " end def label(id) puts %Q() end def linebreak puts "
    " end def pagebreak puts %Q(
    ) end def bpo(lines) puts "" lines.each do |line| puts detab(line) end puts "" end def noindent @noindent = true end def inline_labelref(idref) %Q[「●● #{escape_html(idref)}」] end alias inline_ref inline_labelref def inline_chapref(id) if ReVIEW.book.param["chapterlink"] %Q(#{@chapter.env.chapter_index.display_string(id)}) else @chapter.env.chapter_index.display_string(id) end rescue KeyError error "unknown chapter: #{id}" nofunc_text("[UnknownChapter:#{id}]") end def inline_chap(id) if ReVIEW.book.param["chapterlink"] %Q(#{@chapter.env.chapter_index.number(id)}) else @chapter.env.chapter_index.number(id) end rescue KeyError error "unknown chapter: #{id}" nofunc_text("[UnknownChapter:#{id}]") end def inline_title(id) if ReVIEW.book.param["chapterlink"] %Q(#{compile_inline(@chapter.env.chapter_index.title(id))}) else @chapter.env.chapter_index.title(id) end rescue KeyError error "unknown chapter: #{id}" nofunc_text("[UnknownChapter:#{id}]") end def inline_fn(id) %Q(*#{@chapter.footnote(id).number}) end def compile_ruby(base, ruby) if ReVIEW.book.param["htmlversion"] == 5 %Q[#{escape_html(base)}#{I18n.t("ruby_prefix")}#{ruby}#{I18n.t("ruby_postfix")}] else %Q[#{escape_html(base)}#{I18n.t("ruby_prefix")}#{ruby}#{I18n.t("ruby_postfix")}] end end def compile_kw(word, alt) %Q[] + if alt then escape_html(word + " (#{alt.strip})") else escape_html(word) end + "" end def inline_i(str) %Q(#{escape_html(str)}) end def inline_b(str) %Q(#{escape_html(str)}) end def inline_ami(str) %Q(#{escape_html(str)}) end def inline_bou(str) %Q(#{escape_html(str)}) end def inline_tti(str) if ReVIEW.book.param["htmlversion"] == 5 %Q(#{escape_html(str)}) else %Q(#{escape_html(str)}) end end def inline_ttb(str) if ReVIEW.book.param["htmlversion"] == 5 %Q(#{escape_html(str)}) else %Q(#{escape_html(str)}) end end def inline_dtp(str) "" end def inline_code(str) if ReVIEW.book.param["htmlversion"] == 5 %Q(#{escape_html(str)}) else %Q(#{escape_html(str)}) end end def inline_idx(str) %Q(#{escape_html(str)}) end def inline_hidx(str) %Q() end def inline_br(str) %Q(
    ) end def inline_m(str) if ReVIEW.book.param["mathml"] p = MathML::LaTeX::Parser.new %Q[#{p.parse(str, nil)}] else %Q[#{escape_html(str)}] end end def text(str) str end def bibpaper(lines, id, caption) puts %Q[
    ] bibpaper_header id, caption unless lines.empty? bibpaper_bibpaper id, caption, lines end puts "
    " end def bibpaper_header(id, caption) print %Q() print "[#{@chapter.bibpaper(id).number}]" print %Q() puts " #{compile_inline(caption)}" end def bibpaper_bibpaper(id, caption, lines) print split_paragraph(lines).join("") end def inline_bib(id) %Q([#{@chapter.bibpaper(id).number}]) end def inline_hd_chap(chap, id) n = chap.headline_index.number(id) if chap.number and ReVIEW.book.param["secnolevel"] >= n.split('.').size str = "「#{n} #{compile_inline(chap.headline(id).caption)}」" else str = "「#{compile_inline(chap.headline(id).caption)}」" end if ReVIEW.book.param["chapterlink"] anchor = "h"+n.gsub(/\./, "-") %Q(#{str}) else str end end def inline_list(id) chapter, id = extract_chapter_id(id) if get_chap(chapter).nil? "#{I18n.t("list")}#{I18n.t("format_number_without_header", [chapter.list(id).number])}" else "#{I18n.t("list")}#{I18n.t("format_number", [get_chap(chapter), chapter.list(id).number])}" end rescue KeyError error "unknown list: #{id}" nofunc_text("[UnknownList:#{id}]") end def inline_table(id) chapter, id = extract_chapter_id(id) if get_chap(chapter).nil? "#{I18n.t("table")}#{I18n.t("format_number_without_chapter", [chapter.table(id).number])}" else "#{I18n.t("table")}#{I18n.t("format_number", [get_chap(chapter), chapter.table(id).number])}" end rescue KeyError error "unknown table: #{id}" nofunc_text("[UnknownTable:#{id}]") end def inline_img(id) chapter, id = extract_chapter_id(id) if get_chap(chapter).nil? "#{I18n.t("image")}#{I18n.t("format_number_without_chapter", [chapter.image(id).number])}" else "#{I18n.t("image")}#{I18n.t("format_number", [get_chap(chapter), chapter.image(id).number])}" end rescue KeyError error "unknown image: #{id}" nofunc_text("[UnknownImage:#{id}]") end def inline_asis(str, tag) %Q(<#{tag}>#{escape_html(str)}) end def inline_abbr(str) inline_asis(str, "abbr") end def inline_acronym(str) inline_asis(str, "acronym") end def inline_cite(str) inline_asis(str, "cite") end def inline_dfn(str) inline_asis(str, "dfn") end def inline_em(str) inline_asis(str, "em") end def inline_kbd(str) inline_asis(str, "kbd") end def inline_samp(str) inline_asis(str, "samp") end def inline_strong(str) inline_asis(str, "strong") end def inline_var(str) inline_asis(str, "var") end def inline_big(str) inline_asis(str, "big") end def inline_small(str) inline_asis(str, "small") end def inline_sub(str) inline_asis(str, "sub") end def inline_sup(str) inline_asis(str, "sup") end def inline_tt(str) if ReVIEW.book.param["htmlversion"] == 5 %Q(#{escape_html(str)}) else %Q(#{escape_html(str)}) end end def inline_del(str) inline_asis(str, "del") end def inline_ins(str) inline_asis(str, "ins") end def inline_u(str) %Q(#{escape_html(str)}) end def inline_recipe(str) %Q(「#{escape_html(str)}」) end def inline_icon(id) begin %Q[[#{id}]] rescue %Q[
    missing image: #{id}
    ] end end def inline_uchar(str) %Q(&#x#{str};) end def inline_comment(str) if ReVIEW.book.param["draft"] %Q(#{escape_html(str)}) else %Q() end end def inline_raw(str) super(str) end def nofunc_text(str) escape_html(str) end def compile_href(url, label) %Q(#{label.nil? ? escape_html(url) : escape_html(label)}) end def flushright(lines) if ReVIEW.book.param["deprecated-blocklines"].nil? puts split_paragraph(lines).join("\n").gsub("

    ", "

    ") else puts %Q[

    ] print %Q[
    ]
            lines.each {|line| puts detab(line) }
            puts '
    ' puts '
    ' end end def centering(lines) puts split_paragraph(lines).join("\n").gsub("

    ", "

    ") end def image_ext "png" end def olnum(num) @ol_num = num.to_i end end end # module ReVIEW