\n)
end
s << %Q(#{type}>\n)
diff --git a/lib/epubmaker/epubv2.rb b/lib/epubmaker/epubv2.rb
index d7a998ac5..d5d09db5c 100644
--- a/lib/epubmaker/epubv2.rb
+++ b/lib/epubmaker/epubv2.rb
@@ -9,14 +9,13 @@
#
require 'epubmaker/epubcommon'
-require 'cgi'
require 'epubmaker/zip_exporter'
module EPUBMaker
# EPUBv2 is EPUB version 2 producer.
class EPUBv2 < EPUBCommon
# Construct object with parameter hash +config+ and message resource hash +res+.
- def initialize(producer)
+ def initialize(producer) # rubocop:disable Lint/UselessMethodDefinition
super
end
@@ -37,9 +36,9 @@ def opf_metainfo
%w[title language date type format source description relation coverage subject rights].each do |item|
next unless @producer.config[item]
if @producer.config[item].is_a?(Array)
- s << @producer.config.names_of(item).map { |i| %Q( #{CGI.escapeHTML(i)}\n) }.join
+ s << @producer.config.names_of(item).map { |i| %Q( #{h(i)}\n) }.join
else
- s << %Q( #{CGI.escapeHTML(@producer.config.name_of(item).to_s)}\n)
+ s << %Q( #{h(@producer.config.name_of(item).to_s)}\n)
end
end
@@ -54,7 +53,7 @@ def opf_metainfo
%w[aut a-adp a-ann a-arr a-art a-asn a-aqt a-aft a-aui a-ant a-bkp a-clb a-cmm a-dsr a-edt a-ill a-lyr a-mdc a-mus a-nrt a-oth a-pht a-prt a-red a-rev a-spn a-ths a-trc a-trl].each do |role|
next unless @producer.config[role]
@producer.config.names_of(role).each do |v|
- s << %Q( #{CGI.escapeHTML(v)}\n)
+ s << %Q( #{h(v)}\n)
end
end
@@ -62,7 +61,7 @@ def opf_metainfo
%w[adp ann arr art asn aqt aft aui ant bkp clb cmm dsr edt ill lyr mdc mus nrt oth pht prt red rev spn ths trc trl].each do |role|
next unless @producer.config[role]
@producer.config.names_of(role).each do |v|
- s << %Q( #{CGI.escapeHTML(v)}\n)
+ s << %Q( #{h(v)}\n)
if role == 'prt'
s << %Q( #{v}\n)
end
diff --git a/lib/epubmaker/epubv3.rb b/lib/epubmaker/epubv3.rb
index c6115741d..b744e9c8a 100644
--- a/lib/epubmaker/epubv3.rb
+++ b/lib/epubmaker/epubv3.rb
@@ -40,6 +40,7 @@ def opf
ReVIEW::Template.load(tmplfile).result(binding)
end
+ # rubocop:disable Metrics/PerceivedComplexity
def opf_metainfo
s = ''
%w[title language date type format source description relation coverage subject rights].each do |item|
@@ -47,23 +48,23 @@ def opf_metainfo
if @producer.config[item].is_a?(Array)
@producer.config[item].each_with_index do |v, i|
if v.is_a?(Hash)
- s << %Q( #{CGI.escapeHTML(v['name'])}\n)
+ s << %Q( #{h(v['name'])}\n)
v.each_pair do |name, val|
next if name == 'name'
- s << %Q( #{CGI.escapeHTML(val)}\n)
+ s << %Q( #{h(val)}\n)
end
else
- s << %Q( #{CGI.escapeHTML(v.to_s)}\n)
+ s << %Q( #{h(v.to_s)}\n)
end
end
elsif @producer.config[item].is_a?(Hash)
- s << %Q( #{CGI.escapeHTML(@producer.config[item]['name'])}\n)
+ s << %Q( #{h(@producer.config[item]['name'])}\n)
@producer.config[item].each_pair do |name, val|
next if name == 'name'
- s << %Q( #{CGI.escapeHTML(val)}\n)
+ s << %Q( #{h(val)}\n)
end
else
- s << %Q( #{CGI.escapeHTML(@producer.config[item].to_s)}\n)
+ s << %Q( #{h(@producer.config[item].to_s)}\n)
end
end
@@ -81,14 +82,14 @@ def opf_metainfo
next unless @producer.config[role]
@producer.config[role].each_with_index do |v, i|
if v.is_a?(Hash)
- s << %Q( #{CGI.escapeHTML(v['name'])}\n)
+ s << %Q( #{h(v['name'])}\n)
s << %Q( #{role.sub('a-', '')}\n)
v.each_pair do |name, val|
next if name == 'name'
- s << %Q( #{CGI.escapeHTML(val)}\n)
+ s << %Q( #{h(val)}\n)
end
else
- s << %Q( #{CGI.escapeHTML(v)}\n)
+ s << %Q( #{h(v)}\n)
s << %Q( #{role.sub('a-', '')}\n)
end
end
@@ -99,27 +100,27 @@ def opf_metainfo
next unless @producer.config[role]
@producer.config[role].each_with_index do |v, i|
if v.is_a?(Hash)
- s << %Q( #{CGI.escapeHTML(v['name'])}\n)
+ s << %Q( #{h(v['name'])}\n)
s << %Q( #{role}\n)
v.each_pair do |name, val|
next if name == 'name'
- s << %Q( #{CGI.escapeHTML(val)}\n)
+ s << %Q( #{h(val)}\n)
end
else
- s << %Q( #{CGI.escapeHTML(v)}\n)
+ s << %Q( #{h(v)}\n)
s << %Q( #{role}\n)
end
if %w[prt pbl].include?(role)
if v.is_a?(Hash)
- s << %Q( #{CGI.escapeHTML(v['name'])}\n)
+ s << %Q( #{h(v['name'])}\n)
s << %Q( #{role}\n)
v.each_pair do |name, val|
next if name == 'name'
- s << %Q( #{CGI.escapeHTML(val)}\n)
+ s << %Q( #{h(val)}\n)
end
else
- s << %Q( #{CGI.escapeHTML(v)}\n)
+ s << %Q( #{h(v)}\n)
s << %Q( prt\n)
end
end
@@ -129,12 +130,13 @@ def opf_metainfo
## add custom element
if @producer.config['opf_meta'].present?
@producer.config['opf_meta'].each do |k, v|
- s << %Q( #{CGI.escapeHTML(v)}\n)
+ s << %Q( #{h(v)}\n)
end
end
s
end
+ # rubocop:enable Metrics/PerceivedComplexity
def opf_manifest
s = ''
@@ -206,11 +208,11 @@ def ncx(indentarray)
@body = <
-
#{CGI.escapeHTML(@producer.res.v('toctitle'))}
+
#{h(@producer.res.v('toctitle'))}
#{ncx_main}
EOT
- @title = CGI.escapeHTML(@producer.res.v('toctitle'))
+ @title = h(@producer.res.v('toctitle'))
@language = @producer.config['language']
@stylesheets = @producer.config['stylesheet']
tmplfile = File.expand_path('./html/layout-html5.html.erb', ReVIEW::Template::TEMPLATE_DIR)
diff --git a/lib/review/book.rb b/lib/review/book.rb
index 5e1218987..3e3df80d8 100644
--- a/lib/review/book.rb
+++ b/lib/review/book.rb
@@ -18,8 +18,8 @@
module ReVIEW
module Book
- def self.load(dir)
- Base.load(dir)
+ def self.load(_dir)
+ raise NotImplementedError, 'ReVIEW::Book.load is obsoleted. Please use ReVIEW::Book::Base.new.'
end
end
end
diff --git a/lib/review/book/base.rb b/lib/review/book/base.rb
index d8437c2dd..3d96ff0fd 100644
--- a/lib/review/book/base.rb
+++ b/lib/review/book/base.rb
@@ -9,26 +9,34 @@
#
require 'review/configure'
require 'review/catalog'
+require 'review/book/bib'
module ReVIEW
module Book
class Base
attr_accessor :config
attr_writer :parts
- attr_writer :catalog
+ attr_accessor :catalog
attr_reader :basedir
+ attr_accessor :bibpaper_index
- def self.load(dir = '.')
- new(dir)
+ def self.load(basedir = '.', config: nil)
+ new(basedir, config: config)
end
- def initialize(basedir = '.')
+ def initialize(basedir = '.', config: nil)
@basedir = basedir
@logger = ReVIEW.logger
@parts = nil
@chapter_index = nil
- @config = ReVIEW::Configure.values
+ @config = config || ReVIEW::Configure.values
@catalog = nil
+ @bibpaper_index = nil
+ catalog_path = filename_join(@basedir, @config['catalogfile'])
+ if catalog_path && File.file?(catalog_path)
+ parse_catalog_file(catalog_path)
+ end
+
@warn_old_files = {} # XXX for checking CHAPS, PREDEF, POSTDEF
@basedir_seen = {}
update_rubyenv
@@ -44,6 +52,14 @@ def update_rubyenv
end
end
+ def execute_indexer
+ return unless @catalog
+
+ parts.each do |part|
+ part.chapters.each(&:execute_indexer)
+ end
+ end
+
def bib_file
config['bib_file']
end
@@ -94,6 +110,30 @@ def htmlversion
end
end
+ def create_chapter_index
+ chapter_index = ChapterIndex.new
+ each_chapter do |chap|
+ chapter_index.add_item(Index::Item.new(chap.id, chap.number, chap))
+ end
+ parts.each do |prt|
+ if prt.id.present?
+ chapter_index.add_item(Index::Item.new(prt.id, prt.number, prt))
+ end
+ end
+ chapter_index
+ end
+
+ def generate_indexes
+ if bib_exist?
+ bib = ReVIEW::Book::Bib.new(file_content: bib_content, book: self)
+ bib.generate_indexes(use_bib: true)
+ @bibpaper_index = bib.bibpaper_index
+ end
+ self.each_chapter(&:generate_indexes)
+ self.parts.map(&:generate_indexes)
+ @chapter_index = create_chapter_index
+ end
+
def parts
@parts ||= read_parts
end
@@ -101,7 +141,7 @@ def parts
def parts_in_file
# TODO: should be `parts.find_all{|part| part.present? and part.file?}` ?
parts.find_all do |part|
- part if part.present? and part.file?
+ part if part.present? && part.file?
end
end
@@ -136,15 +176,7 @@ def each_chapter_r(&block)
def chapter_index
return @chapter_index if @chapter_index
- @chapter_index = ChapterIndex.new
- each_chapter do |chap|
- @chapter_index.add_item(Index::Item.new(chap.id, chap.number, chap))
- end
- parts.each do |prt|
- if prt.id.present?
- @chapter_index.add_item(Index::Item.new(prt.id, prt.number, prt))
- end
- end
+ @chapter_index = create_chapter_index
@chapter_index
end
@@ -183,17 +215,15 @@ def load_config(filename)
@config.merge!(new_conf)
end
- def catalog
- return @catalog if @catalog.present?
-
- catalogfile_path = filename_join(@basedir, config['catalogfile'])
- if File.file?(catalogfile_path)
- @catalog = File.open(catalogfile_path, 'rt:BOM|utf-8') { |f| Catalog.new(f) }
+ def parse_catalog_file(path)
+ unless File.file?(path)
+ raise FileNotFound, "catalog.yml is not found #{path}"
end
- if @catalog
+
+ File.open(path, 'rt:BOM|utf-8') do |f|
+ @catalog = Catalog.new(f)
@catalog.validate!(@config, basedir)
end
- @catalog
end
def read_chaps
@@ -252,6 +282,10 @@ def bib_exist?
File.exist?(File.join(contentdir, bib_file))
end
+ def bib_content
+ File.read(File.join(contentdir, bib_file))
+ end
+
def prefaces
if catalog
return Part.mkpart_from_namelist(self, catalog.predef)
@@ -333,8 +367,8 @@ def parse_chapters
end
end
- chap = read_chaps.map(&:strip).join("\n").split(/\n{2,}/).
- map do |part_chunk|
+ # rubocop:disable Style/RedundantAssignment
+ chap = read_chaps.map(&:strip).join("\n").split(/\n{2,}/).map do |part_chunk|
chaps = part_chunk.split.map { |chapid| Chapter.new(self, num += 1, chapid, File.join(contentdir, chapid)) }
if part_exist? && read_part.size > part
Part.new(self, part += 1, chaps, read_part[part - 1])
@@ -342,6 +376,8 @@ def parse_chapters
Part.new(self, nil, chaps)
end
end
+ # rubocop:enable Style/RedundantAssignment
+
chap
end
diff --git a/lib/review/book/bib.rb b/lib/review/book/bib.rb
new file mode 100644
index 000000000..fb1f66307
--- /dev/null
+++ b/lib/review/book/bib.rb
@@ -0,0 +1,21 @@
+#
+# Copyright (c) 2009-2020 Minero Aoki, Kenshi Muto
+#
+# 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.
+# For details of the GNU LGPL, see the file "COPYING".
+#
+require 'review/book/book_unit'
+require 'review/lineinput'
+require 'review/preprocessor'
+
+module ReVIEW
+ module Book
+ class Bib < BookUnit
+ def number
+ nil
+ end
+ end
+ end
+end
diff --git a/lib/review/book/book_unit.rb b/lib/review/book/book_unit.rb
new file mode 100644
index 000000000..944658a36
--- /dev/null
+++ b/lib/review/book/book_unit.rb
@@ -0,0 +1,155 @@
+# Copyright (c) 2009-2017 Minero Aoki, Kenshi Muto
+# 2002-2008 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.
+# For details of the GNU LGPL, see the file "COPYING".
+#
+require 'review/textutils'
+require 'review/index_builder'
+
+module ReVIEW
+ module Book
+ class BookUnit
+ include TextUtils
+ attr_reader :book
+ attr_reader :path
+ attr_reader :lines
+ attr_accessor :content
+
+ attr_reader :list_index, :table_index, :equation_index, :footnote_index,
+ :numberless_image_index, :image_index, :icon_index, :indepimage_index,
+ :headline_index, :column_index
+
+ def initialize(file_content: nil, book: nil)
+ if book
+ @book = book
+ end
+ if file_content
+ @content = file_content
+ end
+ if @content
+ @lines = @content.lines
+ end
+ end
+
+ def execute_indexer(force: false)
+ if @index_builder && !force
+ return @index_builder
+ end
+
+ @index_builder = ReVIEW::IndexBuilder.new
+ compiler = ReVIEW::Compiler.new(@index_builder)
+ compiler.compile(self)
+ @index_builder
+ end
+
+ def generate_indexes(use_bib: false)
+ return unless content
+
+ @lines = content.lines
+
+ @indexes = execute_indexer
+
+ @list_index = @indexes.list_index
+ @table_index = @indexes.table_index
+ @equation_index = @indexes.equation_index
+ @footnote_index = @indexes.footnote_index
+ @headline_index = @indexes.headline_index
+ @column_index = @indexes.column_index
+ if use_bib
+ @book.bibpaper_index = @indexes.bibpaper_index
+ end
+ end
+
+ def dirname
+ @path && File.dirname(@path)
+ end
+
+ def basename
+ @path && File.basename(@path)
+ end
+
+ def name
+ @name && File.basename(@name, '.*')
+ end
+
+ alias_method :id, :name
+
+ def title
+ return @title if @title
+
+ @title = ''
+ return @title unless content
+ content.each_line do |line|
+ if line =~ /\A=+/
+ @title = line.sub(/\A=+(\[.+?\])?(\{.+?\})?/, '').strip
+ break
+ end
+ end
+ @title
+ end
+
+ def size
+ content.size
+ end
+
+ def volume
+ @volume ||= Volume.count_file(path)
+ end
+
+ def list(id)
+ list_index[id]
+ end
+
+ def table(id)
+ table_index[id]
+ end
+
+ def equation(id)
+ equation_index[id]
+ end
+
+ def footnote(id)
+ footnote_index[id]
+ end
+
+ def image(id)
+ return image_index[id] if image_index.key?(id)
+ return icon_index[id] if icon_index.key?(id)
+ return numberless_image_index[id] if numberless_image_index.key?(id)
+ indepimage_index[id]
+ end
+
+ def bibpaper(id)
+ bibpaper_index[id]
+ end
+
+ def bibpaper_index
+ raise FileNotFound, "no such bib file: #{@book.bib_file}" unless @book.bib_exist?
+ @book.bibpaper_index
+ end
+
+ def headline(caption)
+ headline_index[caption]
+ end
+
+ def column(id)
+ column_index[id]
+ end
+
+ def next_chapter
+ book.next_chapter(self)
+ end
+
+ def prev_chapter
+ book.prev_chapter(self)
+ end
+
+ def image_bound?(item_id)
+ image(item_id).path
+ end
+ end
+ end
+end
diff --git a/lib/review/book/chapter.rb b/lib/review/book/chapter.rb
index 014d752b4..ec52c596d 100644
--- a/lib/review/book/chapter.rb
+++ b/lib/review/book/chapter.rb
@@ -7,15 +7,13 @@
# the GNU LGPL, Lesser General Public License version 2.1.
# For details of the GNU LGPL, see the file "COPYING".
#
-require 'review/book/compilable'
+require 'review/book/book_unit'
require 'review/lineinput'
require 'review/preprocessor'
module ReVIEW
module Book
- class Chapter
- include Compilable
-
+ class Chapter < BookUnit
attr_reader :number, :book
def self.mkchap(book, name, number = nil)
@@ -53,35 +51,41 @@ def initialize(book, number, name, path, io = nil)
@content = File.read(@path, mode: 'rt:BOM|utf-8')
@number = nil if %w[nonum nodisp notoc].include?(find_first_header_option)
end
- @list_index = nil
- @table_index = nil
- @equation_index = nil
- @footnote_index = nil
- @image_index = nil
- @icon_index = nil
- @numberless_image_index = nil
- @indepimage_index = nil
- @headline_index = nil
- @column_index = nil
- @volume = nil
+
+ super()
+ end
+
+ def generate_indexes
+ super
+
+ return unless content
+
+ @numberless_image_index = @indexes.numberless_image_index
+ @image_index = @indexes.image_index
+ @icon_index = @indexes.icon_index
+ @indepimage_index = @indexes.indepimage_index
end
def find_first_header_option
f = LineInput.new(StringIO.new(@content))
- while f.next?
- case f.peek
- when /\A=+[\[\s\{]/
- m = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/.match(f.gets)
- return m[2] # tag
- when %r{/\A//[a-z]+/}
- line = f.gets
- if line.rstrip[-1, 1] == '{'
- f.until_match(%r{\A//\}})
+ begin
+ while f.next?
+ case f.peek
+ when /\A=+[\[\s{]/
+ m = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/.match(f.gets)
+ return m[2] # tag
+ when %r{/\A//[a-z]+/}
+ line = f.gets
+ if line.rstrip[-1, 1] == '{'
+ f.until_match(%r{\A//\}})
+ end
end
+ f.gets
end
- f.gets
+ nil
+ rescue ArgumentError => e
+ raise ReVIEW::CompileError, "#{@name}: #{e}"
end
- nil
end
def inspect
diff --git a/lib/review/book/compilable.rb b/lib/review/book/compilable.rb
deleted file mode 100644
index 16b4ed5b8..000000000
--- a/lib/review/book/compilable.rb
+++ /dev/null
@@ -1,174 +0,0 @@
-# Copyright (c) 2009-2017 Minero Aoki, Kenshi Muto
-# 2002-2008 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.
-# For details of the GNU LGPL, see the file "COPYING".
-#
-require 'review/textutils'
-
-module ReVIEW
- module Book
- module Compilable
- include TextUtils
- attr_reader :book
- attr_reader :path
- attr_accessor :content
-
- def dirname
- return nil unless @path
- File.dirname(@path)
- end
-
- def basename
- return nil unless @path
- File.basename(@path)
- end
-
- def name
- return nil unless @name
- File.basename(@name, '.*')
- end
-
- alias_method :id, :name
-
- def title
- return @title if @title
-
- @title = ''
- return @title unless content
- content.each_line do |line|
- if line =~ /\A=+/
- @title = line.sub(/\A=+(\[.+?\])?(\{.+?\})?/, '').strip
- break
- end
- end
- @title
- end
-
- def size
- content.size
- end
-
- def volume
- @volume ||= Volume.count_file(path)
- end
-
- def lines
- # FIXME: we cannot duplicate Enumerator on ruby 1.9 HEAD
- (@lines ||= content.lines.to_a).dup
- end
-
- def list(id)
- list_index[id]
- end
-
- def list_index
- @list_index ||= ListIndex.parse(lines)
- @list_index
- end
-
- def table(id)
- table_index[id]
- end
-
- def table_index
- @table_index ||= TableIndex.parse(lines)
- @table_index
- end
-
- def equation(id)
- equation_index[id]
- end
-
- def equation_index
- @equation_index ||= EquationIndex.parse(lines)
- @equation_index
- end
-
- def footnote(id)
- footnote_index[id]
- end
-
- def footnote_index
- @footnote_index ||= FootnoteIndex.parse(lines)
- @footnote_index
- end
-
- def image(id)
- return image_index[id] if image_index.key?(id)
- return icon_index[id] if icon_index.key?(id)
- return numberless_image_index[id] if numberless_image_index.key?(id)
- indepimage_index[id]
- end
-
- def numberless_image_index
- @numberless_image_index ||=
- NumberlessImageIndex.parse(lines, id,
- @book.imagedir,
- @book.image_types, @book.config['builder'])
- end
-
- def image_index
- @image_index ||= ImageIndex.parse(lines, id,
- @book.imagedir,
- @book.image_types, @book.config['builder'])
- @image_index
- end
-
- def icon_index
- @icon_index ||= IconIndex.parse(lines, id,
- @book.imagedir,
- @book.image_types, @book.config['builder'])
- @icon_index
- end
-
- def indepimage_index
- @indepimage_index ||=
- IndepImageIndex.parse(lines, id,
- @book.imagedir,
- @book.image_types, @book.config['builder'])
- end
-
- def bibpaper(id)
- bibpaper_index[id]
- end
-
- def bibpaper_index
- raise FileNotFound, "no such bib file: #{@book.bib_file}" unless @book.bib_exist?
- @bibpaper_index ||= BibpaperIndex.parse(@book.read_bib.lines.to_a)
- @bibpaper_index
- end
-
- def headline(caption)
- headline_index[caption]
- end
-
- def headline_index
- @headline_index ||= HeadlineIndex.parse(lines, self)
- end
-
- def column(id)
- column_index[id]
- end
-
- def column_index
- @column_index ||= ColumnIndex.parse(lines)
- end
-
- def next_chapter
- book.next_chapter(self)
- end
-
- def prev_chapter
- book.prev_chapter(self)
- end
-
- def image_bound?(item_id)
- item = self.image(item_id)
- item.path
- end
- end
- end
-end
diff --git a/lib/review/book/index.rb b/lib/review/book/index.rb
index 7ba3c50f1..37c66671e 100644
--- a/lib/review/book/index.rb
+++ b/lib/review/book/index.rb
@@ -17,21 +17,6 @@
module ReVIEW
module Book
class Index
- def self.parse(src, *args)
- index = self.new(*args)
- seq = 1
- src.grep(%r{\A//#{item_type}}) do |line|
- if id = line.slice(/\[(.*?)\]/, 1)
- index.add_item(ReVIEW::Book::Index::Item.new(id, seq))
- seq += 1
- if id.empty?
- ReVIEW.logger.warn "warning: no ID of #{item_type} in #{line}"
- end
- end
- end
- index
- end
-
include Enumerable
def item_type
@@ -44,9 +29,13 @@ def initialize
@image_finder = nil
end
+ def size
+ @index.size
+ end
+
def add_item(item)
- if @index[item.id]
- @logger.warn "warning: duplicate ID: #{item.id} (#{item})"
+ if @index[item.id] && self.class != ReVIEW::Book::IconIndex
+ @logger.warn "warning: duplicate ID: #{item.id} (#{item.inspect})"
end
@index[item.id] = item
if item.class != ReVIEW::Book::Chapter
@@ -57,13 +46,13 @@ def add_item(item)
def [](id)
@index.fetch(id)
rescue
- if @index.keys.map { |i| i.split('|').last }.flatten. # unfold all ids
- each_with_object(Hash.new(0)) { |i, h| h[i] += 1 }. # number of occurrences
+ index_keys = @index.keys.map { |i| i.split('|').last }.flatten # unfold all ids
+ if index_keys.each_with_object(Hash.new(0)) { |i, h| h[i] += 1 }. # number of occurrences
select { |k, v| k == id && v > 1 }.present? # detect duplicated
raise KeyError, "key '#{id}' is ambiguous for #{self.class}"
end
- @index.values.each do |item|
+ @index.each_value do |item|
if item.id.split('|').include?(id)
return item
end
@@ -134,55 +123,24 @@ def self.item_type
end
class FootnoteIndex < Index
- def self.parse(src)
- index = self.new
- seq = 1
- src.grep(%r{\A//footnote}) do |line|
- if m = /\[(.*?)\]\[(.*)\]/.match(line)
- m1 = m[1].gsub(/\\(\])/) { $1 }
- m2 = m[2].gsub(/\\(\])/) { $1 }
- index.add_item(Item.new(m1, seq, m2))
- end
- seq += 1
- end
- index
- end
end
class ImageIndex < Index
- def self.parse(src, *args)
- index = self.new(*args)
- seq = 1
- src.grep(%r{\A//#{item_type}}) do |line|
- # ex. ["//image", "id", "", "caption"]
- elements = line.split(/\[(.*?)\]/)
- if elements[1].present?
- if line.start_with?('//imgtable')
- index.add_item(ReVIEW::Book::Index::Item.new(elements[1], 0, elements[3]))
- else ## %r<\A//(image|graph)>
- index.add_item(ReVIEW::Book::Index::Item.new(elements[1], seq, elements[3]))
- seq += 1
- end
- if elements[1] == ''
- ReVIEW.logger.warn "warning: no ID of #{item_type} in #{line}"
- end
- end
- end
- index
- end
-
def self.item_type
'(image|graph|imgtable)'
end
attr_reader :image_finder
- def initialize(chapid, basedir, types, builder)
+ def initialize(chapter)
super()
- @chapid = chapid
- @basedir = basedir
- @types = types
- @logger = ReVIEW.logger
+ @chapter = chapter
+ book = @chapter.book
+
+ chapid = chapter.id
+ basedir = book.imagedir
+ builder = book.config['builder']
+ types = book.image_types
@image_finder = ReVIEW::Book::ImageFinder.new(basedir, chapid, builder, types)
end
@@ -193,43 +151,9 @@ def find_path(id)
end
class IconIndex < ImageIndex
- def initialize(chapid, basedir, types, builder)
- @index = {}
- @chapid = chapid
- @basedir = basedir
- @types = types
- @logger = ReVIEW.logger
-
- @image_finder = ImageFinder.new(basedir, chapid, builder, types)
- end
-
- def self.parse(src, *args)
- index = self.new(*args)
- seq = 1
- src.grep(/@/) do |line|
- line.gsub(/@\{(.+?)\}/) do
- index.add_item(ReVIEW::Book::Index::Item.new($1, seq))
- seq += 1
- end
- end
- index
- end
end
class BibpaperIndex < Index
- def self.parse(src)
- index = self.new
- seq = 1
- src.grep(%r{\A//bibpaper}) do |line|
- if m = /\[(.*?)\]\[(.*)\]/.match(line)
- m1 = m[1].gsub(/\\(.)/) { $1 }
- m2 = m[2].gsub(/\\(.)/) { $1 }
- index.add_item(Item.new(m1, seq, m2))
- end
- seq += 1
- end
- index
- end
end
class NumberlessImageIndex < ImageIndex
@@ -255,77 +179,9 @@ def number(_id)
class HeadlineIndex < Index
HEADLINE_PATTERN = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/
- def self.parse(src, chap)
- headline_index = self.new(chap)
- indexs = []
- headlines = []
- inside_column = false
- inside_block = nil
- column_level = -1
- src.each do |line|
- if line =~ %r{\A//[a-z]+.*\{\Z}
- inside_block = true
- next
- elsif line.start_with?('//}')
- inside_block = nil
- next
- elsif inside_block
- next
- end
-
- m = HEADLINE_PATTERN.match(line)
- if m.nil? || m[1].size > 10 # Ignore too deep index
- next
- end
-
- index = m[1].size - 2
-
- # column
- if m[2] == 'column'
- inside_column = true
- column_level = index
- next
- elsif m[2] == '/column'
- inside_column = false
- next
- end
- if indexs.blank? || index <= column_level
- inside_column = false
- end
- next if inside_column
- next if m[4].strip.empty? # no title
-
- next unless index >= 0
- if indexs.size > (index + 1)
- unless %w[nonum notoc nodisp].include?(m[2])
- indexs = indexs.take(index + 1)
- end
- headlines = headlines.take(index + 1)
- end
- if indexs[index].nil?
- (0..index).each do |i|
- indexs[i] ||= 0
- end
- end
-
- if %w[nonum notoc nodisp].include?(m[2])
- headlines[index] = m[3].present? ? m[3].strip : m[4].strip
- item_id = headlines.join('|')
- headline_index.add_item(Item.new(item_id, nil, m[4].strip))
- else
- indexs[index] += 1
- headlines[index] = m[3].present? ? m[3].strip : m[4].strip
- item_id = headlines.join('|')
- headline_index.add_item(Item.new(item_id, indexs.dup, m[4].strip))
- end
- end
- headline_index
- end
-
- def initialize(chap)
- @chap = chap
- @index = {}
- @logger = ReVIEW.logger
+ def initialize(chapter)
+ super()
+ @chapter = chapter
end
def number(id)
@@ -333,34 +189,16 @@ def number(id)
# when notoc
return ''
end
- n = @chap.number
+ n = @chapter.number
# XXX: remove magic number (move to lib/review/book/chapter.rb)
- if @chap.on_appendix? && @chap.number > 0 && @chap.number < 28
- n = @chap.format_number(false)
+ if @chapter.on_appendix? && @chapter.number > 0 && @chapter.number < 28
+ n = @chapter.format_number(false)
end
([n] + self[id].number).join('.')
end
end
class ColumnIndex < Index
- COLUMN_PATTERN = /\A(=+)\[column\](?:\{(.+?)\})?(.*)/
-
- def self.parse(src, *_args)
- index = self.new
- seq = 1
- src.each do |line|
- m = COLUMN_PATTERN.match(line)
- next unless m
- _level = m[1] ## not use it yet
- id = m[2]
- caption = m[3].strip
- id = caption if id.nil? || id.empty?
-
- index.add_item(ReVIEW::Book::Index::Item.new(id, seq, caption))
- seq += 1
- end
- index
- end
end
end
end
diff --git a/lib/review/book/index/item.rb b/lib/review/book/index/item.rb
index f2edece56..d9458e68c 100644
--- a/lib/review/book/index/item.rb
+++ b/lib/review/book/index/item.rb
@@ -32,7 +32,16 @@ def initialize(id, number, caption = nil)
alias_method :content, :caption
def path
- @path ||= @index.find_path(id)
+ if @path
+ return @path
+ end
+
+ if @id =~ /\s/
+ raise ReVIEW::SyntaxError, "invalid ID character for path: `#{@id}`"
+ end
+ @path = @index.find_path(@id)
+
+ @path
end
end
end
diff --git a/lib/review/book/part.rb b/lib/review/book/part.rb
index e540452cd..6ea4a7879 100644
--- a/lib/review/book/part.rb
+++ b/lib/review/book/part.rb
@@ -1,4 +1,4 @@
-# Copyright (c) 2009-2019 Minero Aoki, Kenshi Muto
+# Copyright (c) 2009-2020 Minero Aoki, Kenshi Muto, Masayoshi Takahashi
# 2002-2008 Minero Aoki
#
# This program is free software.
@@ -6,13 +6,11 @@
# the GNU LGPL, Lesser General Public License version 2.1.
# For details of the GNU LGPL, see the file "COPYING".
#
-require 'review/book/compilable'
+require 'review/book/book_unit'
module ReVIEW
module Book
- class Part
- include Compilable
-
+ class Part < BookUnit
def self.mkpart_from_namelistfile(book, path)
chaps = []
File.read(path, mode: 'rt:BOM|utf-8').split.each_with_index do |name, number|
@@ -30,7 +28,7 @@ def self.mkpart_from_namelist(book, names)
end
def self.mkpart(chaps)
- chaps.empty? ? nil : Part.new(self, nil, chaps)
+ chaps.empty? ? nil : Part.new(chaps[0].book, nil, chaps)
end
# if Part is dummy, `number` is nil.
@@ -38,15 +36,16 @@ def self.mkpart(chaps)
def initialize(book, number, chapters, name = '', io = nil)
@book = book
@number = number
- @chapters = chapters
@name = name
+ @chapters = chapters
@path = name
- @content = ''
if io
@content = io.read
elsif @path.present? && File.exist?(File.join(@book.config['contentdir'], @path))
@content = File.read(File.join(@book.config['contentdir'], @path), mode: 'rt:BOM|utf-8')
- @name = File.basename(@name, '.re')
+ @name = File.basename(name, '.re')
+ else
+ @content = ''
end
if file?
@title = nil
@@ -54,6 +53,19 @@ def initialize(book, number, chapters, name = '', io = nil)
@title = name
end
@volume = nil
+
+ super()
+ end
+
+ def generate_indexes
+ super
+
+ return unless content
+
+ @numberless_image_index = @indexes.numberless_image_index
+ @image_index = @indexes.image_index
+ @icon_index = @indexes.icon_index
+ @indepimage_index = @indexes.indepimage_index
end
attr_reader :number
diff --git a/lib/review/book/volume.rb b/lib/review/book/volume.rb
index d5a02a8f0..8a6d03040 100644
--- a/lib/review/book/volume.rb
+++ b/lib/review/book/volume.rb
@@ -22,7 +22,7 @@ def self.count_file(path)
end
def self.sum(vols)
- vols.inject(new) { |sum, i| sum + i }
+ vols.inject(new) { |sum, i| sum + i } # rubocop:disable Performance/Sum
end
def self.dummy
diff --git a/lib/review/builder.rb b/lib/review/builder.rb
index d9888006b..4b4e36c5f 100644
--- a/lib/review/builder.rb
+++ b/lib/review/builder.rb
@@ -11,7 +11,6 @@
require 'review/compiler'
require 'review/sec_counter'
require 'stringio'
-require 'cgi'
require 'fileutils'
require 'tempfile'
require 'csv'
@@ -20,7 +19,7 @@ module ReVIEW
class Builder
include TextUtils
- CAPTION_TITLES = %w[note memo tip info warning important caution notice].freeze
+ CAPTION_TITLES = Compiler.minicolumn_names
def pre_paragraph
nil
@@ -32,20 +31,15 @@ def post_paragraph
attr_accessor :doc_status, :previous_list_type
- def initialize(strict = false, *args)
+ def initialize(strict = false, *_args)
@strict = strict
@output = nil
@logger = ReVIEW.logger
@doc_status = {}
@dictionary = {}
@previous_list_type = nil
- builder_init(*args)
end
- def builder_init(*args)
- end
- private :builder_init
-
def bind(compiler, chapter, location)
@compiler = compiler
@chapter = chapter
@@ -54,6 +48,10 @@ def bind(compiler, chapter, location)
if @chapter.present?
@book = @chapter.book
end
+ @chapter.generate_indexes
+ if @book
+ @book.generate_indexes
+ end
@tabwidth = nil
@tsize = nil
if @book && @book.config
@@ -85,6 +83,7 @@ def bind(compiler, chapter, location)
def builder_init_file
@sec_counter = SecCounter.new(5, @chapter)
+ @doc_status = {}
end
private :builder_init_file
@@ -154,25 +153,28 @@ def line_num
def list(lines, id, caption, lang = nil)
begin
- list_header(id, caption, lang)
+ list_header(id, caption, lang) if caption_top?('list')
+ list_body(id, lines, lang)
+ list_header(id, caption, lang) unless caption_top?('list')
rescue KeyError
error "no such list: #{id}"
end
- list_body(id, lines, lang)
end
def listnum(lines, id, caption, lang = nil)
begin
- list_header(id, caption, lang)
+ list_header(id, caption, lang) if caption_top?('list')
+ listnum_body(lines, lang)
+ list_header(id, caption, lang) unless caption_top?('list')
rescue KeyError
error "no such list: #{id}"
end
- listnum_body(lines, lang)
end
def source(lines, caption = nil, lang = nil)
- source_header(caption)
+ source_header(caption) if caption_top?('list')
source_body(lines, lang)
+ source_header(caption) unless caption_top?('list')
end
def image(lines, id, caption, metric = nil)
@@ -187,15 +189,18 @@ def image(lines, id, caption, metric = nil)
def table(lines, id = nil, caption = nil)
sepidx, rows = parse_table_rows(lines)
begin
- if caption.present?
+ if caption_top?('table') && caption.present?
+ table_header(id, caption)
+ end
+ table_begin(rows.first.size)
+ table_rows(sepidx, rows)
+ table_end
+ if !caption_top?('table') && caption.present?
table_header(id, caption)
end
rescue KeyError
error "no such table: #{id}"
end
- table_begin(rows.first.size)
- table_rows(sepidx, rows)
- table_end
end
def table_row_separator_regexp
@@ -217,7 +222,7 @@ def parse_table_rows(lines)
sepidx = nil
rows = []
lines.each_with_index do |line, idx|
- if /\A[\=\-]{12}/ =~ line || /\A[\=\{\-\}]{12}/ =~ line
+ if /\A[=\-]{12}/ =~ line || /\A[={\-}]{12}/ =~ line
sepidx ||= idx
next
end
@@ -246,7 +251,7 @@ def table_rows(sepidx, rows)
def adjust_n_cols(rows)
rows.each do |cols|
- while cols.last and cols.last.strip.empty?
+ while cols.last && cols.last.strip.empty?
cols.pop
end
end
@@ -550,9 +555,36 @@ def captionblock(_type, _lines, _caption, _specialstyle = nil)
CAPTION_TITLES.each do |name|
class_eval %Q(
def #{name}(lines, caption = nil)
+ check_nested_minicolumn
captionblock("#{name}", lines, caption)
end
- )
+
+ def #{name}_begin(caption = nil)
+ check_nested_minicolumn
+ @doc_status[:minicolumn] = '#{name}'
+ if caption
+ puts compile_inline(caption)
+ end
+ end
+
+ def #{name}_end
+ @doc_status[:minicolumn] = nil
+ end
+ ), __FILE__, __LINE__ - 17
+ end
+
+ def check_nested_minicolumn
+ if @doc_status[:minicolumn]
+ error "#{@location}: nested mini-column is not allowed"
+ end
+ end
+
+ def in_minicolumn?
+ @doc_status[:minicolumn]
+ end
+
+ def minicolumn_block_name?(name)
+ CAPTION_TITLES.include?(name)
end
def graph(lines, id, command, caption = '')
@@ -709,5 +741,12 @@ def endchild(_comment = nil)
puts "\x01→/#{@children.pop}←\x01"
end
end
+
+ def caption_top?(type)
+ unless %w[top bottom].include?(@book.config['caption_position'][type])
+ warn("invalid caption_position/#{type} parameter. 'top' is assumed")
+ end
+ @book.config['caption_position'][type] != 'bottom'
+ end
end
end # module ReVIEW
diff --git a/lib/review/catalog.rb b/lib/review/catalog.rb
index 3f94d816b..cde56a0b8 100644
--- a/lib/review/catalog.rb
+++ b/lib/review/catalog.rb
@@ -41,7 +41,7 @@ def parts
def replace_part(old_name, new_name)
@yaml['CHAPS'].map! do |e|
- if e.is_a?(Hash) and (e.keys.first == old_name)
+ if e.is_a?(Hash) && (e.keys.first == old_name)
e = { new_name => e.values.first }
end
e
@@ -89,7 +89,7 @@ def validate!(config, basedir)
filenames.concat(postdef)
end
filenames.each do |filename|
- refile = File.join(basedir, config['contentdir'], filename)
+ refile = File.expand_path(File.join(config['contentdir'], filename), basedir)
unless File.exist?(refile)
raise FileNotFound, "file not found in catalog.yml: #{refile}"
end
diff --git a/lib/review/compiler.rb b/lib/review/compiler.rb
index fcf9b9e6f..65f71995d 100644
--- a/lib/review/compiler.rb
+++ b/lib/review/compiler.rb
@@ -14,8 +14,8 @@
module ReVIEW
class Compiler
- def initialize(strategy)
- @strategy = strategy
+ def initialize(builder)
+ @builder = builder
## commands which do not parse block lines in compiler
@non_parsed_commands = %i[embed texequation graph]
@@ -24,10 +24,16 @@ def initialize(strategy)
@command_name_stack = []
end
- attr_reader :strategy, :previous_list_type
+ attr_reader :previous_list_type
+ attr_reader :builder
+
+ def strategy
+ error 'Compiler#strategy is obsoleted. Use Compiler#builder.'
+ @builder
+ end
def non_escaped_commands
- if @strategy.highlight?
+ if @builder.highlight?
%i[list emlist listnum emlistnum cmd]
else
[]
@@ -37,7 +43,7 @@ def non_escaped_commands
def compile(chap)
@chapter = chap
do_compile
- @strategy.result
+ @builder.result
end
class SyntaxElement
@@ -68,12 +74,16 @@ def min_argc
end
end
+ def minicolumn?
+ @type == :minicolumn
+ end
+
def block_required?
- @type == :block
+ @type == :block or @type == :minicolumn
end
def block_allowed?
- @type == :block or @type == :optional
+ @type == :block or @type == :optional or @type == :minicolumn
end
end
@@ -83,6 +93,10 @@ def self.defblock(name, argc, optional = false, &block)
defsyntax(name, (optional ? :optional : :block), argc, &block)
end
+ def self.defminicolumn(name, argc, _optional = false, &block)
+ defsyntax(name, :minicolumn, argc, &block)
+ end
+
def self.defsingle(name, argc, &block)
defsyntax(name, :line, argc, &block)
end
@@ -95,6 +109,16 @@ def self.definline(name)
INLINE[name] = InlineSyntaxElement.new(name)
end
+ def self.minicolumn_names
+ buf = []
+ SYNTAX.each do |name, syntax|
+ if syntax.minicolumn?
+ buf << name.to_s
+ end
+ end
+ buf
+ end
+
def syntax_defined?(name)
SYNTAX.key?(name.to_sym)
end
@@ -143,18 +167,19 @@ def inline_defined?(name)
defblock :bpo, 0
defblock :flushright, 0
defblock :centering, 0
- defblock :note, 0..1
- defblock :memo, 0..1
- defblock :info, 0..1
- defblock :important, 0..1
- defblock :caution, 0..1
- defblock :notice, 0..1
- defblock :warning, 0..1
- defblock :tip, 0..1
defblock :box, 0..1
defblock :comment, 0..1, true
defblock :embed, 0..1
+ defminicolumn :note, 0..1
+ defminicolumn :memo, 0..1
+ defminicolumn :tip, 0..1
+ defminicolumn :info, 0..1
+ defminicolumn :warning, 0..1
+ defminicolumn :important, 0..1
+ defminicolumn :caution, 0..1
+ defminicolumn :notice, 0..1
+
defsingle :footnote, 2
defsingle :noindent, 0
defsingle :blankline, 0
@@ -233,45 +258,62 @@ def inline_defined?(name)
def do_compile
f = LineInput.new(StringIO.new(@chapter.content))
- @strategy.bind(self, @chapter, Location.new(@chapter.basename, f))
+ @builder.bind(self, @chapter, Location.new(@chapter.basename, f))
+
+ ## in minicolumn, such as note/info/alert...
+ @minicolumn_name = nil
tagged_section_init
while f.next?
case f.peek
when /\A\#@/
f.gets # Nothing to do
- when /\A=+[\[\s\{]/
+ when /\A=+[\[\s{]/
compile_headline(f.gets)
- @strategy.previous_list_type = nil
+ @builder.previous_list_type = nil
when /\A\s+\*/
compile_ulist(f)
- @strategy.previous_list_type = 'ul'
+ @builder.previous_list_type = 'ul'
when /\A\s+\d+\./
compile_olist(f)
- @strategy.previous_list_type = 'ol'
+ @builder.previous_list_type = 'ol'
when /\A\s+:\s/
compile_dlist(f)
- @strategy.previous_list_type = 'dl'
+ @builder.previous_list_type = 'dl'
when /\A\s*:\s/
warn 'Definition list starting with `:` is deprecated. It should start with ` : `.'
compile_dlist(f)
- @strategy.previous_list_type = 'dl'
+ @builder.previous_list_type = 'dl'
when %r{\A//\}}
- f.gets
- error 'block end seen but not opened'
+ if in_minicolumn?
+ _line = f.gets
+ compile_minicolumn_end
+ else
+ f.gets
+ error 'block end seen but not opened'
+ end
when %r{\A//[a-z]+}
- # @command_name_stack.push(name) ## <- move into read_command() to use name
- name, args, lines = read_command(f)
- syntax = syntax_descriptor(name)
- unless syntax
- error "unknown command: //#{name}"
- compile_unknown_command(args, lines)
+ line = f.peek
+ matched = line =~ %r|\A//([a-z]+)(:?\[.*\])?{\s*$|
+ if matched && minicolumn_block_name?($1)
+ line = f.gets
+ name = $1
+ args = parse_args(line.sub(%r{\A//[a-z]+}, '').rstrip.chomp('{'), name)
+ compile_minicolumn_begin(name, *args)
+ else
+ # @command_name_stack.push(name) ## <- move into read_command() to use name
+ name, args, lines = read_command(f)
+ syntax = syntax_descriptor(name)
+ unless syntax
+ error "unknown command: //#{name}"
+ compile_unknown_command(args, lines)
+ @command_name_stack.pop
+ next
+ end
+ compile_command(syntax, args, lines)
@command_name_stack.pop
- next
end
- compile_command(syntax, args, lines)
- @command_name_stack.pop
- @strategy.previous_list_type = nil
+ @builder.previous_list_type = nil
when %r{\A//}
line = f.gets
warn "`//' seen but is not valid command: #{line.strip.inspect}"
@@ -279,19 +321,46 @@ def do_compile
warn 'skipping block...'
read_block(f, false)
end
- @strategy.previous_list_type = nil
+ @builder.previous_list_type = nil
else
if f.peek.strip.empty?
f.gets
next
end
compile_paragraph(f)
- @strategy.previous_list_type = nil
+ @builder.previous_list_type = nil
end
end
close_all_tagged_section
end
+ def compile_minicolumn_begin(name, caption = nil)
+ mid = "#{name}_begin"
+ unless @builder.respond_to?(mid)
+ error "strategy does not support minicolumn: #{name}"
+ end
+
+ if @minicolumn_name
+ error "minicolumn cannot be nested: #{name}"
+ return
+ end
+ @minicolumn_name = name
+
+ @builder.__send__(mid, caption)
+ end
+
+ def compile_minicolumn_end
+ unless @minicolumn_name
+ error "minicolumn is not used: #{name}"
+ return
+ end
+ name = @minicolumn_name
+
+ mid = "#{name}_end"
+ @builder.__send__(mid)
+ @minicolumn_name = nil
+ end
+
def compile_headline(line)
@headline_indexs ||= [@chapter.number.to_i - 1]
m = /\A(=+)(?:\[(.+?)\])?(?:\{(.+?)\})?(.*)/.match(line)
@@ -327,18 +396,18 @@ def compile_headline(line)
end
@headline_indexs[index] += 1
close_current_tagged_section(level)
- @strategy.headline(level, label, caption)
+ @builder.headline(level, label, caption)
end
end
def close_current_tagged_section(level)
- while @tagged_section.last and @tagged_section.last[1] >= level
+ while @tagged_section.last && (@tagged_section.last[1] >= level)
close_tagged_section(* @tagged_section.pop)
end
end
def headline(level, label, caption)
- @strategy.headline(level, label, caption)
+ @builder.headline(level, label, caption)
end
def tagged_section_init
@@ -347,21 +416,21 @@ def tagged_section_init
def open_tagged_section(tag, level, label, caption)
mid = "#{tag}_begin"
- unless @strategy.respond_to?(mid)
- error "strategy does not support tagged section: #{tag}"
+ unless @builder.respond_to?(mid)
+ error "builder does not support tagged section: #{tag}"
headline(level, label, caption)
return
end
@tagged_section.push([tag, level])
- @strategy.__send__(mid, level, label, caption)
+ @builder.__send__(mid, level, label, caption)
end
def close_tagged_section(tag, level)
mid = "#{tag}_end"
- if @strategy.respond_to?(mid)
- @strategy.__send__(mid, level)
+ if @builder.respond_to?(mid)
+ @builder.__send__(mid, level)
else
- error "strategy does not support block op: #{mid}"
+ error "builder does not support block op: #{mid}"
end
end
@@ -384,38 +453,38 @@ def compile_ulist(f)
line =~ /\A\s+(\*+)/
current_level = $1.size
if level == current_level
- @strategy.ul_item_end
+ @builder.ul_item_end
# body
- @strategy.ul_item_begin(buf)
+ @builder.ul_item_begin(buf)
elsif level < current_level # down
level_diff = current_level - level
if level_diff != 1
error 'too many *.'
end
level = current_level
- @strategy.ul_begin { level }
- @strategy.ul_item_begin(buf)
+ @builder.ul_begin { level }
+ @builder.ul_item_begin(buf)
elsif level > current_level # up
level_diff = level - current_level
level = current_level
(1..level_diff).to_a.reverse_each do |i|
- @strategy.ul_item_end
- @strategy.ul_end { level + i }
+ @builder.ul_item_end
+ @builder.ul_end { level + i }
end
- @strategy.ul_item_end
+ @builder.ul_item_end
# body
- @strategy.ul_item_begin(buf)
+ @builder.ul_item_begin(buf)
end
end
(1..level).to_a.reverse_each do |i|
- @strategy.ul_item_end
- @strategy.ul_end { i }
+ @builder.ul_item_end
+ @builder.ul_end { i }
end
end
def compile_olist(f)
- @strategy.ol_begin
+ @builder.ol_begin
f.while_match(/\A\s+\d+\.|\A\#@/) do |line|
next if line =~ /\A\#@/
@@ -424,24 +493,24 @@ def compile_olist(f)
f.while_match(/\A\s+(?!\d+\.)\S/) do |cont|
buf.push(text(cont.strip))
end
- @strategy.ol_item(buf, num)
+ @builder.ol_item(buf, num)
end
- @strategy.ol_end
+ @builder.ol_end
end
def compile_dlist(f)
- @strategy.dl_begin
+ @builder.dl_begin
while /\A\s*:/ =~ f.peek
# defer compile_inline to handle footnotes
- @strategy.doc_status[:dt] = true
- @strategy.dt(text(f.gets.sub(/\A\s*:/, '').strip))
- @strategy.doc_status[:dt] = nil
+ @builder.doc_status[:dt] = true
+ @builder.dt(text(f.gets.sub(/\A\s*:/, '').strip))
+ @builder.doc_status[:dt] = nil
desc = f.break(/\A(\S|\s*:|\s+\d+\.\s|\s+\*\s)/).map { |line| text(line.strip) }
- @strategy.dd(desc)
+ @builder.dd(desc)
f.skip_blank_lines
f.skip_comment_lines
end
- @strategy.dl_end
+ @builder.dl_end
end
def compile_paragraph(f)
@@ -450,7 +519,7 @@ def compile_paragraph(f)
break if line.strip.empty?
buf.push(text(line.sub(/^(\t+)\s*/) { |m| '' * m.size }.strip.gsub('', "\t")))
end
- @strategy.paragraph(buf)
+ @builder.paragraph(buf)
end
def read_command(f)
@@ -459,9 +528,9 @@ def read_command(f)
ignore_inline = @non_parsed_commands.include?(name)
@command_name_stack.push(name)
args = parse_args(line.sub(%r{\A//[a-z]+}, '').rstrip.chomp('{'), name)
- @strategy.doc_status[name] = true
+ @builder.doc_status[name] = true
lines = block_open?(line) ? read_block(f, ignore_inline) : nil
- @strategy.doc_status[name] = nil
+ @builder.doc_status[name] = nil
[name, args, lines]
end
@@ -506,8 +575,8 @@ def parse_args(str, _name = nil)
end
def compile_command(syntax, args, lines)
- unless @strategy.respond_to?(syntax.name)
- error "strategy does not support command: //#{syntax.name}"
+ unless @builder.respond_to?(syntax.name)
+ error "builder does not support command: //#{syntax.name}"
compile_unknown_command(args, lines)
return
end
@@ -528,11 +597,11 @@ def compile_command(syntax, args, lines)
end
def compile_unknown_command(args, lines)
- @strategy.unknown_command(args, lines)
+ @builder.unknown_command(args, lines)
end
def compile_block(syntax, args, lines)
- @strategy.__send__(syntax.name, (lines || default_block(syntax)), *args)
+ @builder.__send__(syntax.name, (lines || default_block(syntax)), *args)
end
def default_block(syntax)
@@ -543,7 +612,7 @@ def default_block(syntax)
end
def compile_single(syntax, args)
- @strategy.__send__(syntax.name, *args)
+ @builder.__send__(syntax.name, *args)
end
def replace_fence(str)
@@ -569,7 +638,7 @@ def in_non_escaped_command?
def text(str, block_mode = false)
return '' if str.empty?
- words = replace_fence(str).split(/(@<\w+>\{(?:[^\}\\]|\\.)*?\})/, -1)
+ words = replace_fence(str).split(/(@<\w+>\{(?:[^}\\]|\\.)*?\})/, -1)
words.each do |w|
if w.scan(/@<\w+>/).size > 1 && !/\A@/.match(w)
error "`@' seen but is not valid inline op: #{w}"
@@ -580,7 +649,7 @@ def text(str, block_mode = false)
if in_non_escaped_command? && block_mode
result << revert_replace_fence(words.shift)
else
- result << @strategy.nofunc_text(revert_replace_fence(words.shift))
+ result << @builder.nofunc_text(revert_replace_fence(words.shift))
end
break if words.empty?
result << compile_inline(revert_replace_fence(words.shift.gsub(/\\\}/, '}').gsub(/\\\\/, '\\')))
@@ -589,28 +658,36 @@ def text(str, block_mode = false)
rescue => e
error e.message
end
- public :text # called from strategy
+ public :text # called from builder
def compile_inline(str)
op, arg = /\A@<(\w+)>\{(.*?)\}\z/.match(str).captures
unless inline_defined?(op)
raise CompileError, "no such inline op: #{op}"
end
- unless @strategy.respond_to?("inline_#{op}")
- raise "strategy does not support inline op: @<#{op}>"
+ unless @builder.respond_to?("inline_#{op}")
+ raise "builder does not support inline op: @<#{op}>"
end
- @strategy.__send__("inline_#{op}", arg)
+ @builder.__send__("inline_#{op}", arg)
rescue => e
error e.message
- @strategy.nofunc_text(str)
+ @builder.nofunc_text(str)
+ end
+
+ def in_minicolumn?
+ @builder.in_minicolumn?
+ end
+
+ def minicolumn_block_name?(name)
+ @builder.minicolumn_block_name?(name)
end
def warn(msg)
- @strategy.warn msg
+ @builder.warn msg
end
def error(msg)
- @strategy.error msg
+ @builder.error msg
end
end
end # module ReVIEW
diff --git a/lib/review/configure.rb b/lib/review/configure.rb
index 4428bfff7..20e675338 100644
--- a/lib/review/configure.rb
+++ b/lib/review/configure.rb
@@ -18,7 +18,7 @@ def self.values
'bookname' => 'book', # it defines epub file name also
'booktitle' => 'Re:VIEW Sample Book',
'title' => nil,
- 'aut' => ['anonymous'], # author
+ 'aut' => nil, # author
'prt' => nil, # printer(publisher)
'asn' => nil, # associated name
'ant' => nil, # bibliographic antecedent
@@ -64,6 +64,7 @@ def self.values
'bib_file' => 'bib.re',
'words_file' => nil,
'colophon_order' => %w[aut csl trl dsr ill cov edt pbl contact prt],
+ 'chapterlink' => true,
'externallink' => true,
'join_lines_by_lang' => nil, # experimental. default should be nil
'table_row_separator' => 'tabs',
@@ -106,12 +107,38 @@ def self.values
'lineheight' => 10 * 1.2,
'pdfcrop_pixelize_cmd' => 'pdftocairo -%t -r 90 -f %p -l %p -singlefile %i %O',
'dvipng_cmd' => 'dvipng -T tight -z 9 -p %p -l %p -o %o %i'
+ },
+ 'caption_position' => {
+ 'list' => 'top',
+ 'image' => 'bottom',
+ 'table' => 'top',
+ 'equation' => 'top'
}
]
conf.maker = nil
conf
end
+ def self.create(maker: nil, yamlfile: nil, config: nil)
+ conf = self.values
+ conf.maker = maker
+
+ if yamlfile
+ begin
+ loader = ReVIEW::YAMLLoader.new
+ conf.deep_merge!(loader.load_file(yamlfile))
+ rescue => e
+ error "yaml error #{e.message}"
+ end
+ end
+ # YAML configs will be overridden by command line options.
+ if config
+ conf.deep_merge!(config)
+ end
+
+ conf
+ end
+
def [](key)
maker = self.maker
if maker && self.key?(maker) && self.fetch(maker) && self.fetch(maker).key?(key)
diff --git a/lib/review/converter.rb b/lib/review/converter.rb
index 11cc6e209..38c0bb0fa 100644
--- a/lib/review/converter.rb
+++ b/lib/review/converter.rb
@@ -17,7 +17,7 @@ def convert(file, output_path)
chap_name = File.basename(file, '.*')
chap = @book.chapter(chap_name)
result = @compiler.compile(chap)
- File.open(output_path, 'w') { |f| f.puts result }
+ File.write(output_path, result)
end
end
end
diff --git a/lib/review/epub2html.rb b/lib/review/epub2html.rb
index ed0670193..0689573f8 100644
--- a/lib/review/epub2html.rb
+++ b/lib/review/epub2html.rb
@@ -8,10 +8,15 @@
require 'zip'
require 'rexml/document'
-require 'cgi'
require 'optparse'
require 'review/version'
+begin
+ require 'cgi/escape'
+rescue
+ require 'cgi/util'
+end
+
module ReVIEW
class Epub2Html
def self.execute(*args)
diff --git a/lib/review/epubmaker.rb b/lib/review/epubmaker.rb
index 428690388..c45fa803c 100644
--- a/lib/review/epubmaker.rb
+++ b/lib/review/epubmaker.rb
@@ -18,7 +18,6 @@
require 'review/htmltoc'
require 'review/htmlbuilder'
-require 'review/yamlloader'
require 'rexml/document'
require 'rexml/streamlistener'
require 'epubmaker'
@@ -52,18 +51,9 @@ def log(msg)
end
def load_yaml(yamlfile)
- loader = ReVIEW::YAMLLoader.new
- @config = ReVIEW::Configure.values
- begin
- @config.deep_merge!(loader.load_file(yamlfile))
- rescue => e
- error "yaml error #{e.message}"
- end
-
@producer = Producer.new(@config)
@producer.load(yamlfile)
@config = @producer.config
- @config.maker = 'epubmaker'
end
def self.execute(*args)
@@ -94,13 +84,13 @@ def parse_opts(args)
end
def execute(*args)
- @config = ReVIEW::Configure.values
- @config.maker = 'epubmaker'
cmd_config, yamlfile, exportfile = parse_opts(args)
error "#{yamlfile} not found." unless File.exist?(yamlfile)
+ @config = ReVIEW::Configure.create(maker: 'epubmaker',
+ yamlfile: yamlfile,
+ config: cmd_config)
load_yaml(yamlfile)
- @config.deep_merge!(cmd_config)
update_log_level
log("Loaded yaml file (#{yamlfile}).")
@@ -252,7 +242,7 @@ def copy_images(resdir, destdir, allow_exts = nil)
if @config['epubmaker']['verify_target_images'].present?
@config['epubmaker']['force_include_images'].each do |file|
unless File.exist?(file)
- if file !~ /\Ahttp[s]?:/
+ if file !~ /\Ahttps?:/
warn "#{file} is not found, skip."
end
next
@@ -307,8 +297,7 @@ def build_body(basetmpdir, yamlfile)
basedir = File.dirname(yamlfile)
base_path = Pathname.new(basedir)
- book = ReVIEW::Book.load(basedir)
- book.config = @config
+ book = ReVIEW::Book::Base.new(basedir, config: @config)
@converter = ReVIEW::Converter.new(book, ReVIEW::HTMLBuilder.new)
@compile_errors = nil
@@ -340,9 +329,9 @@ def build_part(part, basetmpdir, htmlfile)
File.open(File.join(basetmpdir, htmlfile), 'w') do |f|
@body = ''
@body << %Q(
'
+
+ if !caption_top?('list') && caption.present?
+ puts %Q(
#{compile_inline(caption)}
)
+ end
+
puts '
'
end
@@ -544,12 +582,13 @@ def talk(lines)
def texequation(lines, id = nil, caption = '')
if id
puts %Q(
)
- texequation_header(id, caption)
+ texequation_header(id, caption) if caption_top?('equation')
end
texequation_body(lines)
if id
+ texequation_header(id, caption) unless caption_top?('equation')
puts '
'
end
end
@@ -617,20 +656,22 @@ def result_metric(array)
def image_image(id, caption, metric)
metrics = parse_metric('html', metric)
puts %Q(
)
begin
- if caption.present?
+ if caption_top?('table') && caption.present?
+ table_header(id, caption)
+ end
+
+ imgtable_image(id, caption, metric)
+
+ if !caption_top?('table') && caption.present?
table_header(id, caption)
end
rescue KeyError
error "no such table: #{id}"
end
- imgtable_image(id, caption, metric)
-
puts '