# -*- 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 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 image_image(id, caption, metric) src = @chapter.image(id).path.sub(%r{\A\./}, '') alt = escape_html(compile_inline(caption)) metrics = parse_metric('html', metric) metrics = " class=\"img\"" unless metrics.present? puts "
" puts "\"#{alt}\"#{metrics}" image_header(id, caption) puts "
" end ## コードブロック(//program, //terminal) def program(lines, id=nil, caption=nil, optionstr=nil) _codeblock('program', 'code', lines, id, caption, optionstr) end def terminal(lines, id=nil, caption=nil, optionstr=nil) _codeblock('terminal', 'cmd-code', lines, id, caption, optionstr) end protected def _codeblock(blockname, classname, lines, id, caption, optionstr) ## ブロックコマンドのオプション引数はCompilerクラスでパースすべき。 ## しかしCompilerクラスがそのような設計になってないので、 ## 仕方ないのでBuilderクラスでパースする。 opts = _parse_codeblock_optionstr(optionstr, blockname) CODEBLOCK_OPTIONS.each {|k, v| opts[k] = v unless opts.key?(k) } # if opts['eolmark'] lines = lines.map {|line| "#{detab(line)}" } else lines = lines.map {|line| detab(line) } end # indent_w = opts['indentwidth'] if indent_w && indent_w > 0 indent_str = " " * (indent_w - 1) + ' ' lines = lines.map {|line| line.sub(/\A( +)/) { m, n = ($1.length - 1).divmod indent_w " " << indent_str * m << " " * n } } end # puts "
" if id.present? puts "
" unless id.present? # if id.present? || caption.present? str = _build_caption_str(id, caption) print "#{str}\n" classattr = "list" 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']
        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'] >= 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 public ## コードリスト(//list, //emlist, //listnum, //emlistnum, //cmd, //source) def list(lines, id=nil, caption=nil, lang=nil) _codeblock("list", "caption-code", lines, id, caption, _codeblock_optstr(lang, false)) end def listnum(lines, id=nil, caption=nil, lang=nil) _codeblock("listnum", "code", lines, id, caption, _codeblock_optstr(lang, true)) end def emlist(lines, caption=nil, lang=nil) _codeblock("emlist", "emlist-code", lines, nil, caption, _codeblock_optstr(lang, false)) end def emlistnum(lines, caption=nil, lang=nil) _codeblock("emlistnum", "emlistnum-code", lines, nil, caption, _codeblock_optstr(lang, true)) end def source(lines, caption=nil, lang=nil) _codeblock("source", "source-code", lines, nil, caption, _codeblock_optstr(lang, false)) end def cmd(lines, caption=nil, lang=nil) lang ||= "shell-session" _codeblock("cmd", "cmd-code", 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 on_minicolumn(type, caption, &b) puts "
    " puts "

    #{compile_inline(caption)}

    " if caption.present? yield puts '
    ' end protected :on_minicolumn def on_sideimage_block(imagefile, imagewidth, option_str=nil, &b) imagefile, imagewidth, opts = validate_sideimage_args(imagefile, imagewidth, option_str) filepath = find_image_filepath(imagefile) 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 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 abstract(lines) puts '
    ' puts lines puts '
    ' end ## 章タイトルを独立したページに def makechaptitlepage(option=nil) puts '' # HTMLでは特に何もしない end ## 縦方向のスペースがなければ改ページ def needvspace(builder_name, height) if builder_name == 'html' || builder_name == 'epub' puts "
    " end 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) caption, label = label, nil if caption.nil? if label puts "
    " else puts "
    " end puts "
    #{compile_inline(caption)}
    " if caption.present? yield puts "
    " 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 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_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 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'] return _inline_hyperlink(url, escape(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 end class TEXTBuilder ## 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