# Copyright (c) 2008-2018 Minero Aoki, Kenshi Muto # 2002-2007 Minero Aoki # # 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/textutils' require 'nkf' module ReVIEW class IDGXMLBuilder < Builder include TextUtils include HTMLUtils %i[ttbold hint maru keytop labelref ref].each { |e| Compiler.definline(e) } Compiler.defsingle(:dtp, 1) Compiler.defblock(:insn, 0..1) Compiler.defblock(:planning, 0..1) Compiler.defblock(:best, 0..1) Compiler.defblock(:security, 0..1) Compiler.defblock(:point, 0..1) Compiler.defblock(:shoot, 0..1) Compiler.defblock(:reference, 0) Compiler.defblock(:term, 0) Compiler.defblock(:link, 0..1) Compiler.defblock(:practice, 0) Compiler.defblock(:expert, 0) Compiler.defblock(:rawblock, 0) def pre_paragraph '

' end def post_paragraph '

' end def extname '.xml' end def builder_init end private :builder_init def builder_init_file @warns = [] @errors = [] @section = 0 @subsection = 0 @subsubsection = 0 @subsubsubsection = 0 @sec_counter = SecCounter.new(5, @chapter) @column = 0 @noindent = nil @ol_num = nil @first_line_num = nil @rootelement = 'doc' @tsize = nil @texblockequation = 0 @texinlineequation = 0 print %Q(\n) print %Q(<#{@rootelement} xmlns:aid="http://ns.adobe.com/AdobeInDesign/4.0/">) @secttags = @book.config['structuredxml'] end private :builder_init_file def puts(arg) if @book.config['nolf'].present? print arg else super end end def result s = '' if @secttags s += '' if @subsubsubsection > 0 s += '' if @subsubsection > 0 s += '' if @subsection > 0 s += '' if @section > 0 s += '' if @chapter.number > 0 end @output.string + s + "\n" end def headline(level, label, caption) case level when 1 if @secttags print '' if @subsubsubsection > 0 print '' if @subsubsection > 0 print '' if @subsection > 0 print '' if @section > 0 end print %Q() if @secttags @section = 0 @subsection = 0 @subsubsection = 0 @subsubsubsection = 0 when 2 if @secttags print '' if @subsubsubsection > 0 print '' if @subsubsection > 0 print '' if @subsection > 0 print '' if @section > 0 end @section += 1 print %Q() if @secttags @subsection = 0 @subsubsection = 0 @subsubsubsection = 0 when 3 if @secttags print '' if @subsubsubsection > 0 print '' if @subsubsection > 0 print '' if @subsection > 0 end @subsection += 1 print %Q() if @secttags @subsubsection = 0 @subsubsubsection = 0 when 4 if @secttags print '' if @subsubsubsection > 0 print '' if @subsubsection > 0 end @subsubsection += 1 print %Q() if @secttags @subsubsubsection = 0 when 5 print '' if @secttags && @subsubsubsection > 0 @subsubsubsection += 1 print %Q() if @secttags else raise "caption level too deep or unsupported: #{level}" end prefix, _anchor = headline_prefix(level) label = label.nil? ? '' : %Q( id="#{label}") toccaption = escape(compile_inline(caption.gsub(/@\{.+?\}/, '')).gsub(/<[^>]+>/, '')) puts %Q(#{prefix}#{compile_inline(caption)}) end def ul_begin level = block_given? ? yield : '' level = nil if level == 1 puts "" end def ul_item_begin(lines) print %Q(
  • #{lines.join.chomp}) end def ul_item_end puts '
  • ' end def choice_single_begin puts %Q() end def choice_multi_begin puts %Q() end def choice_single_end puts '' end def choice_multi_end puts '' end def ul_end level = block_given? ? yield : '' level = nil if level == 1 puts "" end def ol_begin puts '
      ' @ol_num ||= 1 end def ol_item(lines, num) puts %Q(
    1. #{lines.join.chomp}
    2. ) @ol_num += 1 end def ol_end puts '
    ' @ol_num = nil end def olnum(num) @ol_num = num.to_i end def dl_begin puts '
    ' end def dt(line) puts "
    #{line}
    " end def dd(lines) puts "
    #{lines.join.chomp}
    " end def dl_end puts '
    ' end def paragraph(lines) if @noindent.nil? if lines[0] =~ /\A(\t+)/ puts %Q(

    #{lines.join.sub(/\A\t+/, '')}

    ) else puts "

    #{lines.join}

    " end else puts %Q(

    #{lines.join}

    ) @noindent = nil end end def read(lines) puts %Q(#{split_paragraph(lines).join}) end alias_method :lead, :read def column_label(id, chapter = @chapter) num = chapter.column(id).number "column-#{num}" end private :column_label def inline_column_chap(chapter, id) if @book.config['chapterlink'] %Q(#{I18n.t('column', compile_inline(chapter.column(id).caption))}) else I18n.t('column', compile_inline(chapter.column(id).caption)) end rescue KeyError error "unknown column: #{id}" end def inline_list(id) "#{super(id)}" end def list_header(id, caption, _lang) puts '' return true unless caption.present? if get_chap.nil? puts %Q(#{I18n.t('list')}#{I18n.t('format_number_without_chapter', [@chapter.list(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}) else puts %Q(#{I18n.t('list')}#{I18n.t('format_number', [get_chap, @chapter.list(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}) end end def codelines_body(lines) no = 1 lines.each do |line| if @book.config['listinfo'] print %Q(' end print detab(line) print "\n" print '' if @book.config['listinfo'] no += 1 end end def list_body(_id, lines, _lang) print '
    '
          codelines_body(lines)
          puts '
    ' end def emlist(lines, caption = nil, _lang = nil) quotedlist lines, 'emlist', caption end def emlistnum(lines, caption = nil, _lang = nil) lines2 = [] first_line_num = line_num lines.each_with_index do |line, i| lines2 << detab(%Q() + (i + first_line_num).to_s.rjust(2) + ': ' + line) end quotedlist lines2, 'emlistnum', caption end def listnum_body(lines, _lang) print '
    '
          no = 1
          first_line_num = line_num
          lines.each_with_index do |line, i|
            if @book.config['listinfo']
              print %Q('
            end
            print detab(%Q() + (i + first_line_num).to_s.rjust(2) + ': ' + line)
            print "\n"
            print '' if @book.config['listinfo']
            no += 1
          end
          puts '
    ' end def cmd(lines, caption = nil) quotedlist lines, 'cmd', caption end def quotedlist(lines, css_class, caption) print %Q() puts "#{compile_inline(caption)}" if caption.present? print '
    '
          no = 1
          lines.each do |line|
            if @book.config['listinfo']
              print %Q('
            end
            print detab(line)
            print "\n"
            print '' if @book.config['listinfo']
            no += 1
          end
          puts '
    ' end private :quotedlist def quote(lines) blocked_lines = split_paragraph(lines) puts "#{blocked_lines.join}" end def inline_table(id) "#{super(id)}" end def inline_img(id) "#{super(id)}" end def inline_eq(id) "#{super(id)}" end def inline_imgref(id) chapter, id = extract_chapter_id(id) if chapter.image(id).caption.blank? inline_img(id) elsif get_chap(chapter).nil? "#{I18n.t('image')}#{I18n.t('format_number_without_chapter', [chapter.image(id).number])}#{I18n.t('image_quote', chapter.image(id).caption)}" else "#{I18n.t('image')}#{I18n.t('format_number', [get_chap(chapter), chapter.image(id).number])}#{I18n.t('image_quote', chapter.image(id).caption)}" end end def handle_metric(str) k, v = str.split('=', 2) %Q(#{k}="#{v.sub(/\A["']/, '').sub(/["']\Z/, '')}") end def result_metric(array) " #{array.join(' ')}" end def image_image(id, caption, metric = nil) metrics = parse_metric('idgxml', metric) puts '' puts %Q() image_header id, caption puts '' end def image_dummy(id, caption, lines) puts '' print %Q(
    )
          lines.each do |line|
            print detab(line)
            print "\n"
          end
          print '
    ' image_header id, caption puts '' warn "image not bound: #{id}" end def image_header(id, caption) return true unless caption.present? if get_chap.nil? puts %Q(#{I18n.t('image')}#{I18n.t('format_number_without_chapter', [@chapter.image(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}) else puts %Q(#{I18n.t('image')}#{I18n.t('format_number', [get_chap, @chapter.image(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}) end end def texequation(lines, id = nil, caption = '') @texblockequation += 1 if id puts '' if get_chap.nil? puts %Q(#{I18n.t('equation')}#{I18n.t('format_number_without_chapter', [@chapter.equation(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}) else puts %Q(#{I18n.t('equation')}#{I18n.t('format_number', [get_chap, @chapter.equation(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}) end end puts %Q() puts '
    '
          puts lines.join("\n")
          puts '
    ' puts '
    ' if id puts '
    ' end end def table(lines, id = nil, caption = nil) tablewidth = @book.config['tableopt'] ? @book.config['tableopt'].split(',')[0].to_f / @book.config['pt_to_mm_unit'].to_f : nil col = 0 puts '' rows = [] sepidx = nil lines.each_with_index do |line, idx| if /\A[\=\-]{12}/ =~ line sepidx ||= idx next end if tablewidth rows.push(line.gsub(/\t\.\t/, "\tDUMMYCELLSPLITTER\t").gsub(/\t\.\.\t/, "\t.\t").gsub(/\t\.\Z/, "\tDUMMYCELLSPLITTER").gsub(/\t\.\.\Z/, "\t.").gsub(/\A\./, '')) else rows.push(line.gsub(/\t\.\t/, "\t\t").gsub(/\t\.\.\t/, "\t.\t").gsub(/\t\.\Z/, "\t").gsub(/\t\.\.\Z/, "\t.").gsub(/\A\./, '')) end col2 = rows[rows.length - 1].split(/\t/).length col = col2 if col2 > col end cellwidth = [] if tablewidth if @tsize.nil? col.times { |n| cellwidth[n] = tablewidth / col } else cellwidth = @tsize.split(/\s*,\s*/) totallength = 0 cellwidth.size.times do |n| cellwidth[n] = cellwidth[n].to_f / @book.config['pt_to_mm_unit'].to_f totallength += cellwidth[n] warn "total length exceeds limit for table: #{id}" if totallength > tablewidth end if cellwidth.size < col cw = (tablewidth - totallength) / (col - cellwidth.size) warn "auto cell sizing exceeds limit for table: #{id}" if cw <= 0 (cellwidth.size..(col - 1)).each { |i| cellwidth[i] = cw } end end end begin table_header id, caption if caption.present? rescue KeyError error "no such table: #{id}" end return if rows.empty? if tablewidth.nil? print '' else print %Q() end if sepidx sepidx.times do |y| if tablewidth.nil? puts %Q(#{rows.shift}) else i = 0 rows.shift.split("\t").each_with_index do |cell, x| print %Q() i += 1 end end end end trputs(tablewidth, rows, cellwidth, sepidx) puts '
    #{cell.sub('DUMMYCELLSPLITTER', '')}
    ' @tsize = nil end def trputs(tablewidth, rows, cellwidth, sepidx) sepidx = 0 if sepidx.nil? if tablewidth rows.each_with_index do |row, y| i = 0 row.split("\t").each_with_index do |cell, x| print %Q(#{cell.sub('DUMMYCELLSPLITTER', '')}) i += 1 end end else lastline = rows.pop rows.each { |row| puts "#{row}" } puts %Q(#{lastline}) if lastline end end def table_header(id, caption) if id.nil? puts %Q(#{compile_inline(caption)}) elsif get_chap puts %Q(#{I18n.t('table')}#{I18n.t('format_number', [get_chap, @chapter.table(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}) else puts %Q(#{I18n.t('table')}#{I18n.t('format_number_without_chapter', [@chapter.table(id).number])}#{I18n.t('caption_prefix_idgxml')}#{compile_inline(caption)}) end end def table_begin(ncols) end def tr(rows) puts %Q(#{rows.join("\t")}) end def th(str) %Q(#{str}) end def td(str) str end def table_end print '' end def emtable(lines, caption = nil) table(lines, nil, caption) end def imgtable(lines, id, caption = nil, metric = nil) if @chapter.image(id).bound? metrics = parse_metric('idgxml', metric) puts '' table_header id, caption if caption.present? puts %Q() puts '
    ' else warn "image not bound: #{id}" if @strict image_dummy id, caption, lines end end def comment(lines, comment = nil) return true unless @book.config['draft'] lines ||= [] lines.unshift comment unless comment.blank? str = lines.join("\n") print "#{escape(str)}" end def inline_comment(str) if @book.config['draft'] %Q(#{escape(str)}) else '' end end def footnote(id, str) # see inline_fn end def inline_fn(id) %Q(#{compile_inline(@chapter.footnote(id).content.strip)}) rescue KeyError error "unknown footnote: #{id}" end def compile_ruby(base, ruby) %Q(#{escape(base.strip)}#{escape(ruby.strip)}) end def compile_kw(word, alt) '' + if alt then escape("#{word}(#{alt.strip})") else escape(word) end + '' + %Q() + if alt alt.split(/\s*,\s*/).collect! { |e| %Q() }.join else '' end end def compile_href(url, label) %Q(#{label.nil? ? escape(url) : escape(label)}) end def inline_sup(str) %Q(#{escape(str)}) end def inline_sub(str) %Q(#{escape(str)}) end def inline_raw(str) super(str).gsub('\\n', "\n") end def inline_hint(str) if @book.config['nolf'] %Q(#{escape(str)}) else %Q(\n#{escape(str)}) end end def inline_maru(str) if str =~ /\A\d+\Z/ sprintf('&#x%x;', 9311 + str.to_i) elsif str =~ /\A[A-Z]\Z/ begin sprintf('&#x%x;', 9398 + str.codepoints.to_a[0] - 65) rescue NoMethodError sprintf('&#x%x;', 9398 + str[0] - 65) end elsif str =~ /\A[a-z]\Z/ begin sprintf('&#x%x;', 9392 + str.codepoints.to_a[0] - 65) rescue NoMethodError sprintf('&#x%x;', 9392 + str[0] - 65) end else error "can't parse maru: #{str}" end end def inline_idx(str) %Q(#{escape(str)}) end def inline_hidx(str) %Q() end def inline_ami(str) %Q(#{escape(str)}) end def inline_i(str) %Q(#{escape(str)}) end def inline_b(str) %Q(#{escape(str)}) end def inline_tt(str) %Q(#{escape(str)}) end def inline_ttb(str) %Q(#{escape(str)}) end alias_method :inline_ttbold, :inline_ttb def inline_tti(str) %Q(#{escape(str)}) end def inline_u(str) %Q(#{escape(str)}) end def inline_icon(id) begin %Q() rescue warn "image not bound: #{id}" '' end end def inline_bou(str) %Q(#{escape(str)}) end def inline_keytop(str) %Q(#{escape(str)}) end def inline_labelref(idref) %Q(「#{I18n.t('label_marker')}#{escape(idref)}」) # FIXME: 節名とタイトルも込みで要出力 end alias_method :inline_ref, :inline_labelref def inline_pageref(idref) %Q(●●) # ページ番号を参照 end def inline_balloon(str) %Q(#{escape(str).gsub(/@maru\[(\d+)\]/) { inline_maru($1) }}) end def inline_uchar(str) %Q(&#x#{str};) end def inline_m(str) @texinlineequation += 1 %Q(
    #{escape(str)}
    ) end def noindent @noindent = true end def blankline puts '

    ' end def pagebreak puts '' end def nonum_begin(level, _label, caption) puts %Q(#{compile_inline(caption)}) end def nonum_end(level) end def notoc_begin(level, _label, caption) puts %Q(#{compile_inline(caption)}) end def notoc_end(level) end def nodisp_begin(level, label, caption) end def nodisp_end(level) end def circle_begin(_level, _label, caption) puts %Q(•#{compile_inline(caption)}) end def circle_end(level) end def common_column_begin(type, caption) @column += 1 a_id = %Q(id="column-#{@column}") print "<#{type}column #{a_id}>" puts %Q(#{compile_inline(caption)}) end def common_column_end(type) puts "" end def column_begin(_level, _label, caption) common_column_begin('', caption) end def column_end(_level) common_column_end('') end def xcolumn_begin(_level, _label, caption) common_column_begin('x', caption) end def xcolumn_end(_level) common_column_end('x') end def world_begin(_level, _label, caption) common_column_begin('world', caption) end def world_end(_level) common_column_end('world') end def hood_begin(_level, _label, caption) common_column_begin('hood', caption) end def hood_end(_level) common_column_end('hood') end def edition_begin(_level, _label, caption) common_column_begin('edition', caption) end def edition_end(_level) common_column_end('edition') end def insideout_begin(_level, _label, caption) common_column_begin('insideout', caption) end def insideout_end(_level) common_column_end('insideout') end def ref_begin(_level, label, _caption) if label puts "" else puts '' end end def ref_end(_level) puts '' end def sup_begin(_level, label, _caption) if label puts "" else puts '' end end def sup_end(_level) puts '' end def flushright(lines) puts split_paragraph(lines).join.gsub('

    ', %Q(

    )) end def centering(lines) puts split_paragraph(lines).join.gsub('

    ', %Q(

    )) end def captionblock(type, lines, caption, specialstyle = nil) print "<#{type}>" style = specialstyle.nil? ? "#{type}-title" : specialstyle puts "#{compile_inline(caption)}" if caption.present? blocked_lines = split_paragraph(lines) puts "#{blocked_lines.join}" end def note(lines, caption = nil) captionblock('note', lines, caption) 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 warning(lines, caption = nil) captionblock('warning', lines, caption) end def term(lines) captionblock('term', lines, nil) end def link(lines, caption = nil) captionblock('link', lines, caption) end def notice(lines, caption = nil) if caption captionblock('notice-t', lines, caption, 'notice-title') else captionblock('notice', lines, nil) end end def point(lines, caption = nil) if caption captionblock('point-t', lines, caption, 'point-title') else captionblock('point', lines, nil) end end def shoot(lines, caption = nil) if caption captionblock('shoot-t', lines, caption, 'shoot-title') else captionblock('shoot', lines, nil) end end def reference(lines) captionblock('reference', lines, nil) end def practice(lines) captionblock('practice', lines, nil) end def expert(lines) captionblock('expert', lines, nil) end def syntaxblock(type, lines, caption) if caption.present? titleopentag = %Q(caption aid:pstyle="#{type}-title") titleclosetag = 'caption' if type == 'insn' titleopentag = %Q(floattitle type="insn") titleclosetag = 'floattitle' end puts %Q(<#{type}><#{titleopentag}>#{compile_inline(caption)}) else puts "<#{type}>" end no = 1 lines.each do |line| if @book.config['listinfo'] print %Q(' end print detab(line) print "\n" print '' if @book.config['listinfo'] no += 1 end puts "" end def insn(lines, caption = nil) syntaxblock('insn', lines, caption) end def box(lines, caption = nil) syntaxblock('box', lines, caption) end def indepimage(_lines, id, caption = nil, metric = nil) metrics = parse_metric('idgxml', metric) puts '' begin puts %Q() rescue warn %Q(image not bound: #{id}) end puts %Q(#{compile_inline(caption)}) if caption.present? puts '' end alias_method :numberlessimage, :indepimage def label(id) # FIXME print "


    ' end def bpo(lines) puts %Q(#{lines.join("\n")}) end def inline_dtp(str) "" end def inline_code(str) %Q(#{escape(str)}) end def inline_br(_str) "\n" end def rawblock(lines) no = 1 lines.each do |l| print l.gsub('<', '<').gsub('>', '>').gsub('"', '"').gsub('&', '&') print "\n" unless lines.length == no no += 1 end end def text(str) str end def inline_chapref(id) if @book.config.check_version('2', exception: false) # backward compatibility chs = ['', '「', '」'] if @book.config['chapref'] chs2 = @book.config['chapref'].split(',') if chs2.size != 3 error '--chapsplitter must have exactly 3 parameters with comma.' else chs = chs2 end end s = "#{chs[0]}#{@book.chapter_index.number(id)}#{chs[1]}#{@book.chapter_index.title(id)}#{chs[2]}" if @book.config['chapterlink'] %Q(#{s}) else s end else title = super if @book.config['chapterlink'] %Q(#{title}) else title end end rescue KeyError error "unknown chapter: #{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}" end def inline_title(id) title = super if @book.config['chapterlink'] %Q(#{title}) else title end rescue KeyError error "unknown chapter: #{id}" end def source_header(caption) puts '' puts %Q(#{compile_inline(caption)}) if caption.present? end def source_body(lines, _lang) puts '
    '
          codelines_body(lines)
          puts '
    ' end def bibpaper(lines, id, caption) bibpaper_header id, caption bibpaper_bibpaper id, caption, lines unless lines.empty? puts '' end def bibpaper_header(id, caption) puts %Q() puts "[#{@chapter.bibpaper(id).number}] #{compile_inline(caption)}" if caption.present? end def bibpaper_bibpaper(_id, _caption, lines) print split_paragraph(lines).join end def inline_bib(id) %Q([#{@chapter.bibpaper(id).number}]) rescue KeyError error "unknown bib: #{id}" end def inline_hd_chap(chap, id) if chap.number n = chap.headline_index.number(id) if @book.config['secnolevel'] >= n.split('.').size return I18n.t('hd_quote', [n, compile_inline(chap.headline(id).caption)]) end end I18n.t('hd_quote_without_number', compile_inline(chap.headline(id).caption)) rescue KeyError error "unknown headline: #{id}" end def inline_recipe(id) # FIXME %Q([XXX]「#{escape(id)}」 p.XX) end def nofunc_text(str) escape(str) end def image_ext 'eps' end end end # module ReVIEW