# encoding: utf-8 # # Copyright (c) 2002-2007 Minero Aoki # 2008-2014 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 extname ".#{@book.config["htmlext"]}" end def builder_init(no_error = false) @no_error = no_error @noindent = nil @ol_num = nil end private :builder_init def builder_init_file @warns = [] @errors = [] @chapter.book.image_types = %w( .png .jpg .jpeg .gif .svg ) @column = 0 @sec_counter = SecCounter.new(5, @chapter) end private :builder_init_file def result layout_file = File.join(@book.basedir, "layouts", "layout.html.erb") unless File.exist?(layout_file) # backward compatibility layout_file = File.join(@book.basedir, "layouts", "layout.erb") end if File.exist?(layout_file) if ENV["REVIEW_SAFE_MODE"].to_i & 4 > 0 warn "user's layout is prohibited in safe mode. ignored." else title = strip_html(@chapter.title) toc = "" toc_level = 0 @chapter.headline_index.items.each do |i| caption = "
  • #{strip_html(i.caption)}
  • \n" if toc_level == i.number.size # do nothing elsif toc_level < i.number.size toc += "\n" * (toc_level - i.number.size) toc_level = i.number.size toc += "" * toc_level return messages() + HTMLLayout.new( {'body' => @output.string, 'title' => title, 'toc' => toc, 'builder' => self, 'next' => @chapter.next_chapter, 'prev' => @chapter.prev_chapter}, layout_file).result end end # default XHTML header/footer @error_messages = error_messages @warning_messages = warning_messages @title = strip_html(@chapter.title) @body = @output.string @language = @book.config['language'] @stylesheets = @book.config["stylesheet"] if @book.htmlversion == 5 htmlfilename = "layout-html5.html.erb" else htmlfilename = "layout-xhtml1.html.erb" end tmplfile = File.expand_path('./html/'+htmlfilename, ReVIEW::Template::TEMPLATE_DIR) tmpl = ReVIEW::Template.load(tmplfile) tmpl.result(binding) end def xmlns_ops_prefix if @book.config["epubversion"].to_i == 3 "epub" else "ops" 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" end def warning_messages return '' if @warns.empty? "

    Warnings

    \n" + "\n" end def headline(level, label, caption) buf = "" prefix, anchor = headline_prefix(level) unless prefix.nil? prefix = %Q[#{prefix}] end a_id = "" unless anchor.nil? a_id = %Q[] end if caption.empty? buf << a_id+"\n" unless label.nil? else if label.nil? buf << %Q[#{a_id}#{prefix}#{caption}\n] else buf << %Q[#{a_id}#{prefix}#{caption}\n] end end buf end def nonum_begin(level, label, caption) buf = "" buf << "\n" if level > 1 unless caption.empty? if label.nil? buf << %Q[#{caption}\n] else buf << %Q[#{caption}\n] end end buf end def nonum_end(level) end def column_begin(level, label, caption) buf = %Q[
    \n] @column += 1 buf << "\n" if level > 1 a_id = %Q[] if caption.empty? buf << a_id + "\n" unless label.nil? else if label.nil? buf << %Q[#{a_id}#{caption}\n] else buf << %Q[#{a_id}#{caption}\n] end end buf end def column_end(level) "
    \n" end def xcolumn_begin(level, label, caption) buf << %Q[
    \n] buf << headline(level, label, caption) buf end def xcolumn_end(level) "
    \n" end def ref_begin(level, label, caption) buf << %Q[
    \n] buf << headline(level, label, caption) buf end def ref_end(level) "
    \n" end def sup_begin(level, label, caption) buf << %Q[
    \n] buf << headline(level, label, caption) buf end def sup_end(level) "
    \n" end def tsize(str) # null end def captionblock(type, lines, caption) buf = %Q[
    \n] unless caption.nil? buf << %Q[

    #{caption}

    \n] end if @book.config["deprecated-blocklines"].nil? buf << lines.join("") else error "deprecated-blocklines is obsoleted." end buf << "
    \n" buf 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) buf = "" buf << %Q[
    \n] buf << %Q[

    #{caption}

    \n] unless caption.nil? buf << %Q[
    ]
          lines.each {|line| buf << detab(line) << "\n" }
          buf << "
    \n" buf << "
    \n" buf end def note(lines, caption = nil) captionblock("note", lines, caption) end def ul_begin "\n" end def ol_begin if @ol_num num = @ol_num @ol_num = nil "
      \n" ## it's OK in HTML5, but not OK in XHTML1.1 else "
        \n" end end def ol_item(lines, num) "
      1. #{lines.map(&:to_s).join}
      2. \n" end def ol_end "
      \n" end def dl_begin "
      \n" end def dt(line) "
      #{line}
      \n" end def dd(lines) "
      #{lines.join}
      \n" end def dl_end "
      \n" end def paragraph(lines) if @noindent.nil? "

      #{lines.join}

      \n" else @noindent = nil %Q[

      #{lines.join}

      \n] end end def parasep "
      \n" end def read(lines) if @book.config["deprecated-blocklines"].nil? %Q[
      \n#{lines.join("")}\n
      \n] else error "deprecated-blocklines is obsoleted." end end alias_method :lead, :read def list(lines, id, caption, lang = nil) buf = %Q[
      \n] begin buf << list_header(id, caption, lang) rescue KeyError error "no such list: #{id}" end buf << list_body(id, lines, lang) buf << "
      \n" buf end def list_header(id, caption, lang) if get_chap.nil? %Q[

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

      \n] else %Q[

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

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

      #{caption}

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

      #{caption}

      \n) end buf << %Q[
      ]
            body = lines.inject(''){|i, j| i + detab(j) + "\n"}
            lexer = lang
            buf << highlight(:body => body, :lexer => lexer, :format => 'html')
            buf << "
      \n" buf << "
      \n" buf end def emlistnum(lines, caption = nil, lang = nil) buf = %Q[
      \n] if caption.present? buf << %Q(

      #{caption}

      \n) end if highlight? body = lines.inject(''){|i, j| i + detab(j) + "\n"} lexer = lang buf << highlight(:body => body, :lexer => lexer, :format => 'html', :options => {:linenos => 'inline', :nowrap => false}) else buf << '
      '
              lines.each_with_index do |line, i|
                buf << detab((i+1).to_s.rjust(2) + ": " + line) << "\n"
              end
              buf << '
      ' << "\n" end buf << '
      ' << "\n" buf end def cmd(lines, caption = nil) buf = %Q[
      \n] if caption.present? buf << %Q(

      #{caption}

      \n) end buf << %Q[
      ]
            body = lines.inject(''){|i, j| i + detab(j) + "\n"}
            lexer = 'shell-session'
            buf << highlight(:body => body, :lexer => lexer, :format => 'html')
            buf << "
      \n" buf << "
      \n" buf end def quotedlist(lines, css_class) buf = %Q[
      \n]
            lines.each do |line|
              buf << detab(line) << "\n"
            end
            buf << "
      \n" end private :quotedlist def quote(lines) if @book.config["deprecated-blocklines"].nil? "
      #{lines.join("")}
      \n" else error "deprecated-blocklines is obsoleted." end end def doorquote(lines, ref) buf = "" if @book.config["deprecated-blocklines"].nil? buf << %Q[
      \n] buf << "#{lines.join("")}\n" buf << %Q[

      #{ref}より

      \n] buf << %Q[
      \n] else error "deprecated-blocklines is obsoleted." end buf end def talk(lines) buf = "" buf << %Q[
      \n] if @book.config["deprecated-blocklines"].nil? buf << "#{lines.join("\n")}\n" else error "deprecated-blocklines is obsoleted." end buf << "
      \n" buf end def texequation(lines) buf << %Q[
      \n] if @book.config["mathml"] require 'math_ml' require 'math_ml/symbol/character_reference' p = MathML::LaTeX::Parser.new(:symbol=>MathML::Symbol::CharacterReference) buf << p.parse(unescape_html(lines.join("\n")), true) << "\n" else buf << '
      '
              buf << lines.join("\n") << "\n"
              buf << "
      \n" end buf << "
      \n" buf 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(/\A["']/, '').sub(/"\Z/, '').sub(/["']\Z/, '')}\"| end end def result_metric(array) " #{array.join(' ')}" end def image_image(id, caption, metric) metrics = parse_metric("html", metric) buf = %Q[
      \n] buf << %Q[#{escape_html(caption)}\n] buf << image_header(id, caption) buf << %Q[
      \n] buf end def image_dummy(id, caption, lines) buf = %Q[
      \n] buf << %Q[
      \n]
            lines.each do |line|
              buf << detab(line) << "\n"
            end
            buf << %Q[
      \n] buf << image_header(id, caption) buf << %Q[
      \n] warn "no such image: #{id}" buf end def image_header(id, caption) buf = %Q[

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

      \n] buf 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) if id buf = %Q[
      \n] else buf = %Q[
      \n] end begin buf << table_header(id, caption) unless caption.nil? rescue KeyError error "no such table: #{id}" end buf << table_begin(rows.first.size) return if rows.empty? if sepidx sepidx.times do buf << tr(rows.shift.map {|s| th(s) }) end rows.each do |cols| buf << tr(cols.map {|s| td(s) }) end else rows.each do |cols| h, *cs = *cols buf << tr([th(h)] + cs.map {|s| td(s) }) end end buf << table_end buf << %Q[
      \n] buf end def table_header(id, caption) if get_chap.nil? %Q[

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

      \n] else %Q[

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

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

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

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

      \n] buf << %Q[#{I18n.t("numberless_image")}#{I18n.t("caption_prefix")}#{caption}\n] buf << %Q[

      \n] end buf << %Q[
      \n] buf end alias_method :numberlessimage, :indepimage def hr "
      \n" end def label(id) %Q(\n) end def linebreak "
      \n" end def pagebreak %Q(
      \n) end def bpo(lines) buf = "\n" lines.each do |line| buf << detab(line) + "\n" end buf << "\n" buf end def noindent @noindent = true end def inline_labelref(idref) %Q[「#{I18n.t("label_marker")}#{(idref)}」] end alias_method :inline_ref, :inline_labelref def inline_chapref(id) title = super if @book.config["chapterlink"] %Q(#{title}) else title end rescue KeyError error "unknown chapter: #{id}" nofunc_text("[UnknownChapter:#{id}]") end def inline_chap(id) if @book.config["chapterlink"] %Q(#{@book.chapter_index.number(id)}) else @book.chapter_index.number(id) end rescue KeyError error "unknown chapter: #{id}" nofunc_text("[UnknownChapter:#{id}]") end def inline_title(id) title = super if @book.config["chapterlink"] %Q(#{title}) else title end rescue KeyError error "unknown chapter: #{id}" nofunc_text("[UnknownChapter:#{id}]") end def inline_fn(id) if @book.config["epubversion"].to_i == 3 %Q(*#{@chapter.footnote(id).number}) else %Q(*#{@chapter.footnote(id).number}) end end def compile_ruby(base, ruby) if @book.htmlversion == 5 %Q[#{base}#{I18n.t("ruby_prefix")}#{ruby}#{I18n.t("ruby_postfix")}] else %Q[#{(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(#{str}) end def inline_b(str) %Q(#{str}) end def inline_ami(str) %Q(#{(str)}) end def inline_bou(str) %Q(#{(str)}) end def inline_tti(str) if @book.htmlversion == 5 %Q(#{(str)}) else %Q(#{(str)}) end end def inline_ttb(str) if @book.htmlversion == 5 %Q(#{(str)}) else %Q(#{(str)}) end end def inline_dtp(str) "" end def inline_code(str) if @book.htmlversion == 5 %Q(#{(str)}) else %Q(#{(str)}) end end def inline_idx(str) %Q(#{(str)}) end def inline_hidx(str) %Q() end def inline_br(str) %Q(
      ) end def inline_m(str) if @book.config["mathml"] require 'math_ml' require 'math_ml/symbol/character_reference' parser = MathML::LaTeX::Parser.new( :symbol => MathML::Symbol::CharacterReference) %Q[#{parser.parse(str, nil)}] else %Q[#{(str)}] end end def text(str) str end def bibpaper(lines, id, caption) buf = %Q[
      \n] buf << bibpaper_header(id, caption) unless lines.empty? buf << bibpaper_bibpaper(id, caption, lines) end buf << "
      " << "\n" buf end def bibpaper_header(id, caption) buf = %Q() buf << "[#{@chapter.bibpaper(id).number}]" buf << %Q() buf << " #{(caption)}" << "\n" end def bibpaper_bibpaper(id, caption, lines) 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 @book.config["secnolevel"] >= n.split('.').size str = I18n.t("chapter_quote", "#{n} #{chap.headline(id).caption}") else str = I18n.t("chapter_quote", chap.headline(id).caption) end if @book.config["chapterlink"] anchor = "h"+n.gsub(/\./, "-") %Q(#{str}) else str end end def column_label(id) num = @chapter.column(id).number "column-#{num}" end private :column_label def inline_column(id) if @book.config["chapterlink"] %Q(#{I18n.t("column", (@chapter.column(id).caption))}) else I18n.t("column", (@chapter.column(id).caption)) end rescue KeyError error "unknown column: #{id}" nofunc_text("[UnknownColumn:#{id}]") 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) str = nil if get_chap(chapter).nil? str = "#{I18n.t("table")}#{I18n.t("format_number_without_chapter", [chapter.table(id).number])}" else str = "#{I18n.t("table")}#{I18n.t("format_number", [get_chap(chapter), chapter.table(id).number])}" end if @book.config["chapterlink"] %Q(#{str}) else str end rescue KeyError error "unknown table: #{id}" nofunc_text("[UnknownTable:#{id}]") end def inline_img(id) chapter, id = extract_chapter_id(id) str = nil if get_chap(chapter).nil? str = "#{I18n.t("image")}#{I18n.t("format_number_without_chapter", [chapter.image(id).number])}" else str = "#{I18n.t("image")}#{I18n.t("format_number", [get_chap(chapter), chapter.image(id).number])}" end if @book.config["chapterlink"] %Q(#{str}) else str end rescue KeyError error "unknown image: #{id}" nofunc_text("[UnknownImage:#{id}]") end def inline_asis(str, tag) %Q(<#{tag}>#{(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 @book.htmlversion == 5 %Q(#{(str)}) else %Q(#{(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(#{(str)}) end def inline_recipe(str) %Q(「#{(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 @book.config["draft"] %Q(#{(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? ? (url) : (label)}) end def flushright(lines) result = "" if @book.config["deprecated-blocklines"].nil? result << lines.join("").gsub("

      ", "

      ") else error "deprecated-blocklines is obsoleted." end result end def centering(lines) lines.join("").gsub("

      ", "

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