# -*- coding: utf-8 -*- ### ### ReVIEW::HTMLBuilderクラスを拡張する ### require 'review/htmlbuilder' module ReVIEW defined?(HTMLBuilder) or raise "internal error: HTMLBuilder not found." class HTMLBuilder attr_accessor :starter_config def target_name "html" end def layoutfile ## 'rake web' のときに使うレイアウトファイルを 'layout.html5.erb' へ変更 if @book.config.maker == 'webmaker' htmldir = 'web/html' #localfilename = 'layout-web.html.erb' localfilename = 'layout.html5.erb' else htmldir = 'html' localfilename = 'layout.html.erb' end ## 以下はリファクタリングした結果 basename = @book.htmlversion == 5 ? 'layout-html5.html.erb' : 'layout-xhtml1.html.erb' htmlfilename = File.join(htmldir, basename) # layout_file = File.join(@book.basedir, 'layouts', localfilename) if ! File.exist?(layout_file) ! File.exist?(File.join(@book.basedir, 'layouts', 'layout.erb')) or raise ReVIEW::ConfigError, 'layout.erb is obsoleted. Please use layout.html.erb.' layout_file = nil elsif ENV['REVIEW_SAFE_MODE'].to_i & 4 > 0 warn "user's layout is prohibited in safe mode. ignored." layout_file = nil end layout_file ||= File.expand_path(htmlfilename, ReVIEW::Template::TEMPLATE_DIR) return layout_file end def result # default XHTML header/footer @title = strip_html(compile_inline(@chapter.title)) @body = @output.string @language = @book.config['language'] @stylesheets = @book.config['stylesheet'] @next = @chapter.next_chapter @prev = @chapter.prev_chapter @next_title = @next ? compile_inline(@next.title) : '' @prev_title = @prev ? compile_inline(@prev.title) : '' if @book.config.maker == 'webmaker' #@toc = ReVIEW::WEBTOCPrinter.book_to_string(@book) #- @toc = ReVIEW::WEBTOCPrinter.book_to_html(@book, @chapter) #+ end ReVIEW::Template.load(layoutfile()).result(binding()) end def headline(level, label, caption) prefix, anchor = headline_prefix(level) prefix = "#{prefix} " if prefix puts "" if level > 1 a_id = "" a_id = "" if anchor # if caption.empty? puts a_id if label else br = "" attr = label ? " id=\"#{normalize_id(label)}\"" : "" conf = @starter_config case level when 1 ; attr << " class=\"#{conf['chapter_decoration']} #{conf['chapter_align']} #{conf['chapter_oneline'] ? 'oneline' : 'twolines'}\"" if prefix attr << " class=\"none #{conf['chapter_align']}\"" unless prefix br = "
" unless conf['chapter_oneline'] when 2 ; attr << " class=\"#{conf['section_decoration']}\"" when 3 ; attr << " class=\"#{conf['subsection_decoration']}\"" end puts "#{a_id}#{prefix}#{br}#{compile_inline(caption)}" end end def result_metric(array) attrs = {} array.each do |item| k = item.keys[0] v = item[k] if k == 'border' && v == 'on' k = 'class'; v = 'border-on' end (attrs[k] ||= []) << v end attrs.map {|k, arr| " #{k}=\"#{arr.join(' ')}\"" }.join() end ## 画像 def _render_image(id, image_filepath, caption, opts) src = image_filepath.sub(/\A\.\//, '') alt = escape_html(compile_inline(caption)) # classes = ["img"] styles = [] # if opts[:scale] case (scale = opts[:scale]) when /\A\d+\z/, /\A\d\.\d*\z/, /\A\.\d+\z/ per = (scale.to_f * 100).round() when /\A\d+(\.\d+)?%\z/ per = scale.sub(/%\z/, '').to_f.round() else error "scale=#{scale}: invalid scale value." end found = PERCENT_THRESHOLDS.find {|x| x == per } if found classes << 'width-%03dper' % per else styles << "max-width:#{per}%" end end # classes << opts[:class] if opts[:class] classes << "border" if opts[:border] classes << "draft" if opts[:draft] styles << opts[:style] if opts[:style] styles << "width:#{opts[:width]}" if opts[:width] # puts "
" print "\"#{alt}\""" image_header(id, caption) puts "
" end PERCENT_THRESHOLDS = [ 10, 20, 25, 30, 33, 40, 50, 60, 67, 70, 75, 80, 90, 100, ] protected ## コードブロック(//program, //terminal, //output) CLASSNAMES = { "program" => "code", "terminal" => "cmd-code", "list" => "caption-code", "listnum" => "code", "emlist" => "emlist-code", "emlistnum" => "emlistnum-code", "source" => "source-code", "cmd" => "cmd-code", "output" => "output", } def _codeblock_eolmark() "" end def _codeblock_indentmark() ' ' end def _render_codeblock(blockname, lines, id, caption_str, opts) caption = caption_str # if opts['eolmark'] eolmark = _codeblock_eolmark() # ex: '{\startereolmark}' else eolmark = nil end # eol = eolmark ? "#{eolmark}\n" : "\n" lines = lines.map {|line| line = _parse_inline(line) {|text| escape(text) } } # indent_width = opts['indent'] if indent_width && indent_width > 0 lines = _add_indent_mark(lines, indent_width) end # classname = CLASSNAMES[blockname] || "code" classname += " #{opts['classname']}" if opts['classname'] puts "
" if id.present? puts "
" unless id.present? # classattr = nil if id.present? || caption.present? str = _build_caption_str(id, caption) print "#{str}\n" classattr = "list" end if blockname == "output" classattr = blockname else classattr ||= "emlist" end # lang = opts['lang'] lang = File.extname(id || "").gsub(".", "") if lang.blank? classattr << " language-#{lang}" unless lang.blank? classattr << " highlight" if highlight? print "
"
      #
      gen = opts['lineno'] ? LineNumberGenerator.new(opts['lineno']).each : nil
      if gen
        width = opts['linenowidth'] || -1
        if width < 0
          format = "%s"
        elsif width == 0
          last_lineno = gen.each.take(lines.length).compact.last
          width = last_lineno.to_s.length
          format = "%#{width}s"
        else
          format = "%#{width}s"
        end
      end
      buf = []
      start_tag = (opts['linenowidth'] || -1) >= 0 ? "" : ""
      lines.each_with_index do |line, i|
        buf << start_tag << (format % gen.next) << ": " if gen
        buf << line #<< "\n"
      end
      puts highlight(body: buf.join(), lexer: lang,
                     format: "html", linenum: !!gen,
                     #options: {linenostart: start}
                     )
      #
      print "
\n" print "
\n" end def _add_indent_mark(lines, indent_width) space = " " rexp = /\A( +)/ # width = indent_width mark = _codeblock_indentmark() # ex: '{\starterindentmark}' indent = space * (width - 1) + mark nchar = space.length return lines.map {|line| line.sub(rexp) { m, n = ($1.length / nchar - 1).divmod width "#{space}#{indent * m}#{space * n}" } } end public ## コードリスト(//list, //emlist, //listnum, //emlistnum, //cmd, //source) def list(lines, id=nil, caption=nil, lang=nil) _codeblock("list", lines, id, caption, _codeblock_optstr(lang, false)) end def listnum(lines, id=nil, caption=nil, lang=nil) _codeblock("listnum", lines, id, caption, _codeblock_optstr(lang, true)) end def emlist(lines, caption=nil, lang=nil) _codeblock("emlist", lines, nil, caption, _codeblock_optstr(lang, false)) end def emlistnum(lines, caption=nil, lang=nil) _codeblock("emlistnum", lines, nil, caption, _codeblock_optstr(lang, true)) end def source(lines, caption=nil, lang=nil) _codeblock("source", lines, nil, caption, _codeblock_optstr(lang, false)) end def cmd(lines, caption=nil, lang=nil) lang ||= "shell-session" _codeblock("cmd", lines, nil, caption, _codeblock_optstr(lang, false)) end def _codeblock_optstr(lang, lineno_flag) arr = [] arr << lang if lang if lineno_flag first_line_num = line_num() arr << "lineno=#{first_line_num}" arr << "linenowidth=0" end return arr.join(",") end private :_codeblock_optstr protected ## @{} def _build_secref(chap, num, title, parent_title) s = "" ## 親セクションのタイトルがあれば使う if parent_title s << "「%s」内の" % parent_title # TODO: I18n化 end ## 対象セクションへのリンクを作成する if @book.config['chapterlink'] filename = "#{chap.id}#{extname()}" dom_id = 'h' + num.gsub('.', '-') s << "「#{title}」" else s << "「#{title}」" end return s end public ## 順序つきリスト def ol_begin(start_num=nil) @_ol_types ||= [] # stack case start_num when nil type = "1"; start = 1 when /\A(\d+)\.\z/ type = "1"; start = $1.to_i when /\A([A-Z])\.\z/ type = "A"; start = $1.ord - 'A'.ord + 1 when /\A([a-z])\.\z/ type = "a"; start = $1.ord - 'a'.ord + 1 else type = nil; start = nil end if type puts "
    " else puts "
      " end @_ol_types.push(type) end def ol_end() ol = !! @_ol_types.pop() if ol puts "
    " else puts "
" end end def ol_item_begin(lines, num) ol = !! @_ol_types[-1] if ol print "
  • #{lines.join}" else n = escape_html(num) print "
  • #{n} #{lines.join}" end end def ol_item_end() puts "
  • " end def dl_dd_begin() puts "
    " end def dl_dd_end() puts "
    " end ## 画像横に文章 def _render_sideimage(filepath, imagewidth, opts, &b) side = (opts['side'] || 'L') == 'L' ? 'left' : 'right' imgclass = opts['border'] ? "image-bordered" : nil normalize = proc {|s| s =~ /^\A(\d+(\.\d+))%\z/ ? "#{$1.to_f/100.0}\\textwidth" : s } imgwidth = normalize.call(imagewidth) boxwidth = normalize.call(opts['boxwidth']) || imgwidth sepwidth = normalize.call(opts['sep'] || "0pt") # puts "
    \n" puts "
    \n" puts " \n" puts "
    \n" puts "
    \n" puts "
    \n" yield puts "
    \n" puts "
    \n" puts "
    \n" end ## 入れ子可能なブロック命令 def on_minicolumn(type, caption=nil, &b) with_context(:minicolumn) do puts "
    " puts "

    #{compile_inline(caption)}

    " if caption.present? yield puts '
    ' end end protected :on_minicolumn #### ブロック命令 def footnote(id, str) id_val = "fn-#{normalize_id(id)}" href = "#fnb-#{normalize_id(id)}" text = compile_inline(str) chap_num = @chapter.footnote(id).number if @book.config['epubversion'].to_i == 3 attr = " epub:type=\"footnote\"" mark = "[*#{chap_num}]" else attr = "" mark = "[*#{chap_num}]" end # if truncate_if_endwith?("
    \n") else puts "
    " end print "
    " print "

    " print "#{mark} " print text print "

    " puts "
    " puts "
    \n" end alias __original_texequation texequation def texequation(lines, label=nil, caption=nil) if label.present? chap = get_chap() if chap.nil? key = "format_number_header_without_chapter" args = [@chapter.equation(label).number] else key = "format_number_header" args = [chap, @chapter.equation(label).number] end s1 = I18n.t("equation") s2 = I18n.t(key, args) s3 = I18n.t("caption_prefix") s4 = compile_inline(caption) puts "#{s1}#{s2}#{s3}#{s4}" has_caption_line = true elsif caption.present? s3 = I18n.t("caption_prefix") s4 = compile_inline(caption) puts "#{s3}#{s4}" has_caption_line = true else has_caption_line = false end # __original_texequation(lines) end ## 章 (Chapter) の概要 def on_abstract_block() puts '
    ' yield puts '
    ' end ## 章 (Chapter) の著者 def on_chapterauthor_block(name) puts "
    #{escape(name)}
    " end ## 会話リスト def _render_talklist(opts, &b) c = opts[:classname] s = c ? " #{c}" : nil puts "
    " puts "
      " yield puts "
    " puts "
    " end ## 会話項目 def _render_talk(image_filepath=nil, name=nil, &b) puts "
  • " print " " if image_filepath.present? print "" else s = compile_inline(name || '') print "#{s}" end puts "" print "
    " yield if @output.string.end_with?("\n") @output.seek(-1, IO::SEEK_CUR) # 改行文字を取り除く #@output.string.chomp! # これだとヌル文字が入ってしまう end puts "
    " puts "
  • " end ## キーと説明文のリスト def _render_desclist(opts, &b) bkup = @_desclist_opts @_desclist_opts = opts classnames = ['desc-list'] classnames << 'compact' if opts[:compact] classnames << 'item-bold' if opts[:bold] classnames << opts[:classname] if opts[:classname] puts "
    " yield puts "
    " @_desclist_opts = bkup end ## キーと説明文 def _render_desc(key, &b) opts = @_desclist_opts s = compile_inline(key) s = "#{s}" if opts[:bold] puts "
    #{s}
    " puts "
    " yield puts "
    " end ## 縦方向の空きを入れる def _render_vspace(size) puts "
    " end def _render_addvspace(size) puts "
    " end ## 章タイトルを独立したページに def makechaptitlepage(option=nil) puts '' # HTMLでは特に何もしない end ## 縦方向のスペースがなければ改ページ def _render_needvspace(height) puts "
    " end ## 段(Paragraph)の終わりにスペースを入れる def paragraphend() puts '' # HTMLでは特に何もしない end ## 小段(Subparagraph)の終わりにスペースを入れる(あれば) def subparagraphend() puts '' # HTMLでは特に何もしない end ## 引用(複数段落に対応) def blockquote(lines) puts '
    ' puts lines puts '
    ' end ## 引用(//quote{ ... //}) ## (入れ子対応なので、中に箇条書きや別のブロックを入れられる) def on_quote_block() puts '
    ' yield puts '
    ' end def quote(lines) on_quote_block() do puts lines end end ## 引用 (====[quote] ... ====[/quote]) ## (ブロック構文ではないので、中に別のブロックや箇条書きを入れられる) def quote_begin(level, label, caption) puts '
    ' end def quote_end(level) puts '
    ' end ## ノート(//note{ ... //}) ## (入れ子対応なので、中に箇条書きや別のブロックを入れられる) def on_note_block(label=nil, caption=nil) with_context(:minicolumn) do caption, label = label, nil if caption.nil? if label puts "
    " else puts "
    " end puts "
    #{compile_inline(caption)}
    " if caption.present? yield puts "
    " end end def note(lines, label=nil, caption=nil) on_quote_block(label, caption) do puts lines end end ## ノート (====[note] ... ====[/note]) ## (ブロック構文ではないので、中に別のブロックや箇条書きを入れられる) def note_begin(level, label, caption) s = compile_inline(caption || "") puts "
    " puts "
    #{s}
    " if s.present? end def note_end(level) puts "
    " end ## テーブル def table(lines, id=nil, caption=nil, option=nil) super end def table_header(id, caption, options) if id.present? begin num = @chapter.table(id).number rescue KeyError error "no such table: #{id}" end s1 = I18n.t('table') s2 = get_chap() \ ? I18n.t('format_number_header', [get_chap(), num]) \ : I18n.t('format_number_header_without_chapter', [num]) s3 = I18n.t('caption_prefix') puts "

    #{s1}#{s2}#{s3}#{compile_inline(caption||'')}

    " elsif caption.present? puts "

    #{compile_inline(caption||'')}

    " end end def table_begin(_ncols, fontsize: nil) if fontsize puts "" else puts "
    " end end protected def _table_before(id, caption, opts) class_ = opts[:csv] ? "table table-nohline" : "table" if id.present? puts "
    " else puts "
    " end end def _table_after(id, caption, opts) puts "
    " end def _table_bottom(hline: false) end def _table_tr(cells, hline: false) if hline "
    #{cells.join}" else "#{cells.join}" end end public ## //imgtable def imgtable(lines, id, caption=nil, option=nil) super end protected def _render_imgtable(id, caption, opts) puts "
    " table_header(id, caption, opts) puts "
    " yield puts "
    " puts "
    " end def _render_imgtable_caption(caption) end def _render_imgtable_label(id) end public #### インライン命令 def inline_fn(id) if @book.config['epubversion'].to_i == 3 type = " epub:type=\"noteref\"" else type = "" end return "*#{@chapter.footnote(id).number}" rescue KeyError error "unknown footnote: #{id}" end ## 改段落(箇条書き内では空行を入れられないため) def inline_par(arg) case arg when 'i' "

    " else "

    " end end ## ファイル名 def inline_file(str) on_inline_file { escape(str) } end def on_inline_file "#{yield}" end ## ユーザ入力 def inline_userinput(str) on_inline_input { escape(str) } end def on_inline_userinput "#{yield}" end ## 引数をそのまま表示 (No Operation) def inline_nop(str) escape_html(str || "") end alias inline_letitgo inline_nop ## 目立たせない(@{} の反対) def inline_weak(str) on_inline_weak { escape(str) } end def on_inline_weak "#{yield}" end ## 文字を小さくする def inline_small(str) ; on_inline_small { escape(str) } ; end def inline_xsmall(str) ; on_inline_xsmall { escape(str) } ; end def inline_xxsmall(str) ; on_inline_xxsmall { escape(str) } ; end def on_inline_small ; "#{yield}" ; end def on_inline_xsmall ; "#{yield}" ; end def on_inline_xxsmall ; "#{yield}"; end ## 文字を大きくする def inline_large(str) ; on_inline_large { escape(str) } ; end def inline_xlarge(str) ; on_inline_xlarge { escape(str) } ; end def inline_xxlarge(str) ; on_inline_xxlarge { escape(str) } ; end def on_inline_large ; "#{yield}" ; end def on_inline_xlarge ; "#{yield}" ; end def on_inline_xxlarge ; "#{yield}"; end ## 文字を大きくした@{} def inline_xstrong(str) ; on_inline_xstrong { escape(str) }; end def inline_xxstrong(str); on_inline_xxstrong { escape(str) }; end def on_inline_xstrong ; "#{yield}"; end def on_inline_xxstrong; "#{yield}"; end ## コードブロック中で折り返し箇所を手動で指定する def inline_foldhere(arg) '
    ' end ## ターミナルでのカーソル(背景が白、文字が黒) def inline_cursor(str) "#{escape_html(str)}" end ## nestable inline commands def on_inline_i() ; "#{yield}" ; end def on_inline_b() ; "#{yield}" ; end #def on_inline_code() ; "#{yield}" ; end def on_inline_tt() ; "#{yield}" ; end def on_inline_del() ; "#{yield}" ; end def on_inline_sub() ; "#{yield}" ; end def on_inline_sup() ; "#{yield}" ; end def on_inline_em() ; "#{yield}" ; end def on_inline_strong(); "#{yield}" ; end def on_inline_u() ; "#{yield}" ; end def on_inline_ami() ; "#{yield}"; end def on_inline_balloon(); "← #{yield}"; end def on_inline_code() return "#{yield}" end ## 「“」と「”」で囲む def on_inline_qq() "“#{yield}”" end def build_inline_href(url, escaped_label) # compile_href()をベースに改造 flag_link = @book.config['externallink'] return _inline_hyperlink(url, escaped_label, flag_link) end def inline_hlink(str) url, label = str.split(/, /, 2) flag_link = @book.config['externallink'] label_ = label.present? ? escape(label) : nil return _inline_hyperlink(url, label_, flag_link) end def _inline_hyperlink(url, escaped_label, flag_link) if flag_link label = escaped_label || escape_html(url) "#{label}" elsif escaped_label I18n.t('external_link', [escaped_label, escape_html(url)]) else escape_html(url) end end private :_inline_hyperlink def build_inline_ruby(escaped_word, escaped_yomi) # compile_ruby()をベースに改造 pre = I18n.t('ruby_prefix'); post = I18n.t('ruby_postfix') if @book.htmlversion == 5 "#{escaped_word}#{pre}#{escaped_yomi}#{post}" else "#{escaped_word}#{pre}#{escaped_yomi}#{post}" end end protected ## ノートを参照する def build_noteref(chapter, label, caption) href = chapter ? "#{chapter.id}#{extname()}##{label}" : "##{label}" %Q`ノート「#{escape(caption)}」` end ## 数式を参照する def build_eq(chapter, label, number) s = "#{I18n.t('equation')}#{chapter.number}.#{number}" "#{escape(s)}" end ## 索引に載せる語句 (@{}, @{}) public def inline_idx(str) s1, s2 = _compile_term(str) %Q`#{s1}` end def inline_hidx(str) _, s2 = _compile_term(str) %Q`` end def inline_term(str) s1, s2 = _compile_term(str) %Q`#{s1}` end def on_inline_termnoidx(str) %Q`#{yield}` end def _compile_term(str) arr = [] placeholder = "---" display_str, see = parse_term(str, placeholder) do |term, term_e, yomi| if yomi arr << escape(yomi) else arr << term_e end end title_attr = arr.join() title_attr += " → see: #{escape(see)}" if see return display_str, title_attr end private :_compile_term end class TEXTBuilder def target_name "text" end ## nestable inline commands def on_inline_i() ; "#{yield}" ; end def on_inline_b() ; "#{yield}" ; end def on_inline_code() ; "#{yield}" ; end def on_inline_tt() ; "#{yield}" ; end def on_inline_del() ; "#{yield}" ; end def on_inline_sub() ; "#{yield}" ; end def on_inline_sup() ; "#{yield}" ; end def on_inline_em() ; "#{yield}" ; end def on_inline_strong(); "#{yield}" ; end def on_inline_u() ; "#{yield}" ; end def on_inline_ami() ; "#{yield}"; end def build_inline_href(url, escaped_label) # compile_href()をベースに改造 if @book.config['externallink'] label = escaped_label || escape_html(url) "#{label}" elsif escaped_label I18n.t('external_link', [escaped_label, escape_html(url)]) else escape_html(url) end end def build_inline_ruby(escaped_word, escaped_yomi) # compile_ruby()をベースに改造 pre = I18n.t('ruby_prefix'); post = I18n.t('ruby_postfix') if @book.htmlversion == 5 "#{escaped_word}#{pre}#{escaped_yomi}#{post}" else "#{escaped_word}#{pre}#{escaped_yomi}#{post}" end end ## その他 def inline_nop(str) str || '' end alias inline_letitgo inline_nop end end