asciibook 0.0.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/README.adoc +122 -0
- data/book_template/asciibook.yml +5 -0
- data/book_template/book.adoc +3 -0
- data/exe/asciibook +7 -0
- data/lib/asciibook.rb +19 -1
- data/lib/asciibook/asciidoctor_ext/abstract_node.rb +5 -0
- data/lib/asciibook/book.rb +153 -0
- data/lib/asciibook/builders/base_builder.rb +24 -0
- data/lib/asciibook/builders/epub_builder.rb +84 -0
- data/lib/asciibook/builders/html_builder.rb +45 -0
- data/lib/asciibook/builders/mobi_builder.rb +21 -0
- data/lib/asciibook/builders/pdf_builder.rb +154 -0
- data/lib/asciibook/command.rb +85 -0
- data/lib/asciibook/converter.rb +303 -0
- data/lib/asciibook/page.rb +30 -0
- data/lib/asciibook/version.rb +1 -1
- data/templates/admonition.html +4 -0
- data/templates/audio.html +7 -0
- data/templates/colist.html +7 -0
- data/templates/dlist.html +12 -0
- data/templates/document.html +29 -0
- data/templates/embedded.html +20 -0
- data/templates/example.html +4 -0
- data/templates/footnotes.html +7 -0
- data/templates/image.html +11 -0
- data/templates/index.html +40 -0
- data/templates/inline_anchor.html +12 -0
- data/templates/inline_callout.html +1 -0
- data/templates/inline_footnote.html +1 -0
- data/templates/inline_image.html +3 -0
- data/templates/inline_indexterm.html +5 -0
- data/templates/inline_quoted.html +24 -0
- data/templates/listing.html +4 -0
- data/templates/literal.html +1 -0
- data/templates/olist.html +8 -0
- data/templates/page_break.html +1 -0
- data/templates/paragraph.html +1 -0
- data/templates/pass.html +1 -0
- data/templates/preamble.html +3 -0
- data/templates/quote.html +9 -0
- data/templates/section.html +4 -0
- data/templates/sidebar.html +6 -0
- data/templates/stem.html +9 -0
- data/templates/table.html +41 -0
- data/templates/thematic_break.html +1 -0
- data/templates/ulist.html +8 -0
- data/templates/verse.html +9 -0
- data/templates/video.html +10 -0
- data/theme/epub/epub.css +6 -0
- data/theme/epub/layout.html +14 -0
- data/theme/html/html.css +190 -0
- data/theme/html/html.js +29 -0
- data/theme/html/layout.html +91 -0
- data/theme/mobi/layout.html +13 -0
- data/theme/mobi/mobi.css +2 -0
- data/theme/pdf/config.yml +6 -0
- data/theme/pdf/footer.html +11 -0
- data/theme/pdf/header.html +3 -0
- data/theme/pdf/layout.html +14 -0
- data/theme/pdf/pdf.css +3 -0
- data/theme/pdf/toc.xsl +81 -0
- data/theme/share/default.css +174 -0
- data/theme/share/highlight.css +216 -0
- data/theme/share/normalize.css +349 -0
- metadata +119 -21
- data/.gitignore +0 -9
- data/.travis.yml +0 -4
- data/Gemfile +0 -4
- data/README.md +0 -41
- data/Rakefile +0 -10
- data/asciibook.gemspec +0 -25
@@ -0,0 +1,45 @@
|
|
1
|
+
module Asciibook
|
2
|
+
module Builders
|
3
|
+
class HtmlBuilder < BaseBuilder
|
4
|
+
def initialize(book)
|
5
|
+
super
|
6
|
+
@dest_dir = File.join(@book.dest_dir, 'html')
|
7
|
+
@theme_dir = File.join(@book.theme_dir, 'html')
|
8
|
+
end
|
9
|
+
|
10
|
+
def build
|
11
|
+
FileUtils.mkdir_p @dest_dir
|
12
|
+
FileUtils.rm_r Dir.glob("#{@dest_dir}/*")
|
13
|
+
|
14
|
+
generate_pages
|
15
|
+
copy_assets
|
16
|
+
end
|
17
|
+
|
18
|
+
def generate_pages
|
19
|
+
layout = Liquid::Template.parse(File.read(File.join(@theme_dir, 'layout.html')))
|
20
|
+
@book.pages.each do |page|
|
21
|
+
File.open(File.join(@dest_dir, page.path), 'w') do |file|
|
22
|
+
file.write layout.render({
|
23
|
+
'book' => @book.to_hash,
|
24
|
+
'page' => page.to_hash
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def copy_assets
|
31
|
+
@book.assets.each do |path|
|
32
|
+
copy_file(path, @book.base_dir, @dest_dir)
|
33
|
+
end
|
34
|
+
|
35
|
+
Dir.glob('**/*.{jpb,png,gif,svg,css,js}', File::FNM_CASEFOLD, base: @theme_share_dir).each do |path|
|
36
|
+
copy_file(path, @theme_share_dir, @dest_dir)
|
37
|
+
end
|
38
|
+
|
39
|
+
Dir.glob('**/*.{jpb,png,gif,svg,css,js}', File::FNM_CASEFOLD, base: @theme_dir).each do |path|
|
40
|
+
copy_file(path, @theme_dir, @dest_dir)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Asciibook
|
2
|
+
module Builders
|
3
|
+
class MobiBuilder < EpubBuilder
|
4
|
+
def initialize(book)
|
5
|
+
super
|
6
|
+
|
7
|
+
@dest_dir = File.join(@book.dest_dir, 'mobi')
|
8
|
+
@theme_dir = File.join(@book.theme_dir, 'mobi')
|
9
|
+
end
|
10
|
+
|
11
|
+
def build
|
12
|
+
super
|
13
|
+
|
14
|
+
epub_file = File.join(@dest_dir, "#{@book.basename}.epub")
|
15
|
+
system 'kindlegen', epub_file
|
16
|
+
|
17
|
+
FileUtils.rm epub_file
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module Asciibook
|
2
|
+
module Builders
|
3
|
+
class PdfBuilder < BaseBuilder
|
4
|
+
def initialize(book)
|
5
|
+
super
|
6
|
+
@dest_dir = File.join(@book.dest_dir, 'pdf')
|
7
|
+
@tmp_dir = File.join(@dest_dir, 'tmp')
|
8
|
+
@theme_dir = File.join(@book.theme_dir, 'pdf')
|
9
|
+
@theme_config = YAML.safe_load(File.read(File.join(@theme_dir, 'config.yml')))
|
10
|
+
end
|
11
|
+
|
12
|
+
def build
|
13
|
+
prepare_workdir
|
14
|
+
generate_pages
|
15
|
+
copy_assets
|
16
|
+
generate_header_footer
|
17
|
+
generate_pdf
|
18
|
+
#clean_workdir
|
19
|
+
end
|
20
|
+
|
21
|
+
def prepare_workdir
|
22
|
+
FileUtils.mkdir_p @tmp_dir
|
23
|
+
FileUtils.rm_r Dir.glob("#{@tmp_dir}/*")
|
24
|
+
end
|
25
|
+
|
26
|
+
def clean_workdir
|
27
|
+
FileUtils.rm_r @tmp_dir
|
28
|
+
end
|
29
|
+
|
30
|
+
def generate_pages
|
31
|
+
layout = Liquid::Template.parse(File.read(File.join(@theme_dir, 'layout.html')))
|
32
|
+
@book.pages.each do |page|
|
33
|
+
File.open(File.join(@tmp_dir, page.path), 'w') do |file|
|
34
|
+
file.write layout.render({
|
35
|
+
'book' => @book.to_hash,
|
36
|
+
'page' => page.to_hash
|
37
|
+
})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def copy_assets
|
43
|
+
@book.assets.each do |path|
|
44
|
+
copy_file(path, @book.base_dir, @tmp_dir)
|
45
|
+
end
|
46
|
+
|
47
|
+
Dir.glob('**/*.{jpb,png,gif,svg,css,js}', File::FNM_CASEFOLD, base: @theme_share_dir).each do |path|
|
48
|
+
copy_file(path, @theme_share_dir, @tmp_dir)
|
49
|
+
end
|
50
|
+
|
51
|
+
Dir.glob('**/*.{jpb,png,gif,svg,css,js}', File::FNM_CASEFOLD, base: @theme_dir).each do |path|
|
52
|
+
copy_file(path, @theme_dir, @tmp_dir)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def generate_header_footer
|
57
|
+
layout = Liquid::Template.parse <<~EOF
|
58
|
+
<!DOCTYPE html>
|
59
|
+
<html>
|
60
|
+
<head>
|
61
|
+
<script>
|
62
|
+
function subst() {
|
63
|
+
var vars = {};
|
64
|
+
var query_strings_from_url = document.location.search.substring(1).split('&');
|
65
|
+
for (var query_string in query_strings_from_url) {
|
66
|
+
if (query_strings_from_url.hasOwnProperty(query_string)) {
|
67
|
+
var temp_var = query_strings_from_url[query_string].split('=', 2);
|
68
|
+
vars[temp_var[0]] = decodeURI(temp_var[1]);
|
69
|
+
}
|
70
|
+
}
|
71
|
+
var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
|
72
|
+
for (var css_class in css_selector_classes) {
|
73
|
+
if (css_selector_classes.hasOwnProperty(css_class)) {
|
74
|
+
var element = document.getElementsByClassName(css_selector_classes[css_class]);
|
75
|
+
for (var j = 0; j < element.length; ++j) {
|
76
|
+
element[j].textContent = vars[css_selector_classes[css_class]];
|
77
|
+
}
|
78
|
+
}
|
79
|
+
}
|
80
|
+
}
|
81
|
+
</script>
|
82
|
+
<style>
|
83
|
+
html, body {
|
84
|
+
margin: 0;
|
85
|
+
padding: 0;
|
86
|
+
}
|
87
|
+
</style>
|
88
|
+
</head>
|
89
|
+
<body onload="subst()">
|
90
|
+
{{ content }}
|
91
|
+
</body>
|
92
|
+
</html>
|
93
|
+
EOF
|
94
|
+
|
95
|
+
File.open(File.join(@tmp_dir, 'header.html'), 'w') do |file|
|
96
|
+
file.write layout.render('content' => File.read(File.join(@theme_dir, 'header.html')))
|
97
|
+
end
|
98
|
+
|
99
|
+
File.open(File.join(@tmp_dir, 'footer.html'), 'w') do |file|
|
100
|
+
file.write layout.render('content' => File.read(File.join(@theme_dir, 'footer.html')))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def generate_pdf
|
105
|
+
command = ['wkhtmltopdf']
|
106
|
+
command << '--header-html' << File.expand_path('header.html', @tmp_dir)
|
107
|
+
command << '--footer-html' << File.expand_path('footer.html', @tmp_dir)
|
108
|
+
command << '--margin-top' << @theme_config.fetch('margin_top', 10).to_s
|
109
|
+
command << '--margin-left' << @theme_config.fetch('margin_left', 10).to_s
|
110
|
+
command << '--margin-right' << @theme_config.fetch('margin_right', 10).to_s
|
111
|
+
command << '--margin-bottom' << @theme_config.fetch('margin_bottom', 10).to_s
|
112
|
+
command << '--header-spacing' << @theme_config.fetch('header_spacing', 0).to_s
|
113
|
+
command << '--footer-spacing' << @theme_config.fetch('footer_spacing', 0).to_s
|
114
|
+
|
115
|
+
if @book.cover_image_path
|
116
|
+
prepare_cover
|
117
|
+
command << 'cover' << 'cover.html'
|
118
|
+
end
|
119
|
+
|
120
|
+
@book.pages.each do |page|
|
121
|
+
if page.node.is_a?(Asciidoctor::Section) && page.node.sectname == 'toc'
|
122
|
+
prepare_toc_xsl(page)
|
123
|
+
command << 'toc' << '--xsl-style-sheet' << 'toc.xsl'
|
124
|
+
else
|
125
|
+
command << page.path
|
126
|
+
end
|
127
|
+
end
|
128
|
+
filename = "#{@book.basename}.pdf"
|
129
|
+
command << filename
|
130
|
+
command << { chdir: @tmp_dir }
|
131
|
+
system(*command)
|
132
|
+
|
133
|
+
FileUtils.cp File.join(@tmp_dir, filename), @dest_dir
|
134
|
+
end
|
135
|
+
|
136
|
+
def prepare_cover
|
137
|
+
File.open(File.join(@tmp_dir, 'cover.html'), 'w') do |file|
|
138
|
+
file.write <<~EOF
|
139
|
+
<img src="#{@book.cover_image_path}" />
|
140
|
+
EOF
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def prepare_toc_xsl(page)
|
145
|
+
File.open(File.join(@tmp_dir, 'toc.xsl'), 'w') do |file|
|
146
|
+
file.write Liquid::Template.parse(File.read(File.join(@theme_dir, 'toc.xsl'))).render({
|
147
|
+
'book' => @book.to_hash,
|
148
|
+
'page' => page.to_hash
|
149
|
+
})
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "asciibook"
|
2
|
+
require "mercenary"
|
3
|
+
|
4
|
+
module Asciibook
|
5
|
+
class Command
|
6
|
+
def self.execute(argv)
|
7
|
+
p = Mercenary::Program.new(:asciibook)
|
8
|
+
|
9
|
+
p.version Asciibook::VERSION
|
10
|
+
p.description 'Asciibook is a ebook generator from Asciidoc to html/pdf/epub/mobi'
|
11
|
+
p.syntax 'asciibook <command> [options]'
|
12
|
+
|
13
|
+
p.command(:new) do |c|
|
14
|
+
c.description 'Create a new book scaffold in PATH'
|
15
|
+
c.syntax 'new PATH'
|
16
|
+
c.action do |args, options|
|
17
|
+
path = args[0]
|
18
|
+
if path
|
19
|
+
FileUtils.mkdir_p path
|
20
|
+
template_dir = File.expand_path('../../../book_template', __FILE__)
|
21
|
+
files = Dir.glob('*', base: template_dir).map { |file| File.join(template_dir, file) }
|
22
|
+
# TODO: confirm if file exists
|
23
|
+
FileUtils.cp_r files, path
|
24
|
+
else
|
25
|
+
abort "Please specify PATH to create book"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
p.command(:init) do |c|
|
31
|
+
c.description 'Init a asciibook config for a Asciidoc file'
|
32
|
+
c.syntax 'init FILE'
|
33
|
+
c.action do |args, options|
|
34
|
+
source = args[0]
|
35
|
+
if File.file?(source)
|
36
|
+
dir = File.dirname source
|
37
|
+
filename = File.basename source
|
38
|
+
File.open(File.join(dir, 'asciibook.yml'), 'w') do |file|
|
39
|
+
file.write <<~EOF
|
40
|
+
source: #{filename}
|
41
|
+
#formats: ['html', 'pdf', 'epub', 'mobi']
|
42
|
+
#theme_dir:
|
43
|
+
#template_dir:
|
44
|
+
#page_level: 1
|
45
|
+
EOF
|
46
|
+
end
|
47
|
+
else
|
48
|
+
abort "Please specify the Asciidoc document to build"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
p.command(:build) do |c|
|
54
|
+
c.description 'Build book'
|
55
|
+
c.syntax 'build [FILE|DIR]'
|
56
|
+
c.option :formats, '--format FORMAT1[,FORMAT2[,FORMAT3...]]', Array, 'Formats you want to build, allow: html,pdf,epub,mobi, default is all.'
|
57
|
+
c.option :theme_dir, '--theme-dir DIR', 'Theme dir.'
|
58
|
+
c.option :template_dir, '--template-dir DIR', 'Template dir.'
|
59
|
+
c.option :dest_dir, '--dest-dir DIR', 'Destination dir.'
|
60
|
+
c.option :page_level, '--page-level NUM', Integer, 'Page split base on section level, default is 1.'
|
61
|
+
c.action do |args, options|
|
62
|
+
source = args[0] || '.'
|
63
|
+
if File.directory?(source)
|
64
|
+
config_options = YAML.safe_load(File.read(File.join(source, 'asciibook.yml'))).reduce({}) do |hash, (key, value)|
|
65
|
+
hash[key.to_sym] = %w(source theme_dir template_dir dest_dir).include?(key) ? File.join(source, value) : value
|
66
|
+
hash
|
67
|
+
end
|
68
|
+
options = config_options.merge(options)
|
69
|
+
Asciibook::Book.load_file(options.delete(:source), options).build
|
70
|
+
elsif File.file?(source)
|
71
|
+
Asciibook::Book.load_file(source, options).build
|
72
|
+
else
|
73
|
+
abort "Build target '#{source}' neither a folder nor a file"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
p.action do |args, _|
|
79
|
+
puts p
|
80
|
+
end
|
81
|
+
|
82
|
+
p.go(argv)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
module Asciibook
|
2
|
+
class Converter < Asciidoctor::Converter::Base
|
3
|
+
register_for "asciibook"
|
4
|
+
|
5
|
+
DEFAULT_TEMPLATE_PATH = File.expand_path('../../../templates', __FILE__)
|
6
|
+
|
7
|
+
def initialize(backend, options = {})
|
8
|
+
super
|
9
|
+
init_backend_traits outfilesuffix: '.html', basebackend: 'html'
|
10
|
+
@template_dirs = (options[:template_dirs] || []).unshift(DEFAULT_TEMPLATE_PATH)
|
11
|
+
@templates = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def convert(node, transform = node.node_name, options = {})
|
15
|
+
get_template(transform).render 'node' => node_to_hash(node)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def get_template(name)
|
21
|
+
return @templates[name] if @templates[name]
|
22
|
+
|
23
|
+
@template_dirs.reverse.each do |template_dir|
|
24
|
+
path = File.join template_dir, "#{name}.html"
|
25
|
+
if File.exist?(path)
|
26
|
+
@templates[name] = Liquid::Template.parse(File.read(path))
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
unless @templates[name]
|
32
|
+
raise "Template not found #{name}"
|
33
|
+
end
|
34
|
+
|
35
|
+
@templates[name]
|
36
|
+
end
|
37
|
+
|
38
|
+
def node_to_hash(node)
|
39
|
+
case node
|
40
|
+
when Asciidoctor::Document
|
41
|
+
document_to_hash(node)
|
42
|
+
when Asciidoctor::Section
|
43
|
+
section_to_hash(node)
|
44
|
+
when Asciidoctor::Block
|
45
|
+
block_to_hash(node)
|
46
|
+
when Asciidoctor::List
|
47
|
+
list_to_hash(node)
|
48
|
+
when Asciidoctor::Table
|
49
|
+
table_to_hash(node)
|
50
|
+
when Asciidoctor::Inline
|
51
|
+
inline_to_hash(node)
|
52
|
+
else
|
53
|
+
raise "Uncatched type #{node} #{node.attributes}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def abstract_node_to_hash(node)
|
58
|
+
{
|
59
|
+
'context' => node.context.to_s,
|
60
|
+
'node_name' => node.node_name,
|
61
|
+
'id' => node.id,
|
62
|
+
'attributes' => node.attributes
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def abstract_block_to_hash(node)
|
67
|
+
abstract_node_to_hash(node).merge!({
|
68
|
+
'level' => node.level,
|
69
|
+
'title' => node.title,
|
70
|
+
'caption' => node.caption,
|
71
|
+
'captioned_title' => node.captioned_title,
|
72
|
+
'style' => node.style,
|
73
|
+
'content' => node_content(node),
|
74
|
+
'xreftext' => node.xreftext
|
75
|
+
})
|
76
|
+
end
|
77
|
+
|
78
|
+
def node_content(node)
|
79
|
+
case node
|
80
|
+
when Asciidoctor::Document, Asciidoctor::Section
|
81
|
+
if node.node_name == 'section' && node.sectname == 'toc'
|
82
|
+
outline(node.document)
|
83
|
+
elsif node.node_name == 'section' && node.sectname == 'index'
|
84
|
+
get_template('index').render('indexterms' => node.document.references[:indexterms])
|
85
|
+
else
|
86
|
+
content = node.blocks.select { |b| b.page.nil? }.map {|b| b.convert }.join("\n")
|
87
|
+
|
88
|
+
if node.page && node.page.footnotes.any?
|
89
|
+
content += get_template('footnotes').render('footnotes' => node.page.footnotes)
|
90
|
+
end
|
91
|
+
|
92
|
+
content
|
93
|
+
end
|
94
|
+
else
|
95
|
+
case node.node_name
|
96
|
+
when 'listing'
|
97
|
+
if node.style == 'source' && node.document.syntax_highlighter
|
98
|
+
node.document.syntax_highlighter.format node, node.attributes['language'], { css_mode: :class }
|
99
|
+
else
|
100
|
+
"<pre>#{node.content}</pre>"
|
101
|
+
end
|
102
|
+
else
|
103
|
+
node.content
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def document_to_hash(node)
|
109
|
+
title = node.attributes['doctitle'] && node.doctitle(partition: true)
|
110
|
+
abstract_block_to_hash(node).merge!({
|
111
|
+
'title' => title&.main,
|
112
|
+
'subtitle' => title&.subtitle,
|
113
|
+
'outline' => outline(node),
|
114
|
+
'authors' => node.authors.map { |author| author.to_h.map { |key, value| [key.to_s, value] }.to_h }
|
115
|
+
})
|
116
|
+
end
|
117
|
+
|
118
|
+
def section_to_hash(node)
|
119
|
+
abstract_block_to_hash(node).merge!({
|
120
|
+
'index' => node.index,
|
121
|
+
'number' => node.number,
|
122
|
+
'sectname' => node.sectname,
|
123
|
+
'special' => node.special,
|
124
|
+
'numbered' => node.numbered,
|
125
|
+
'sectnum' => node.sectnum
|
126
|
+
})
|
127
|
+
end
|
128
|
+
|
129
|
+
def block_to_hash(node)
|
130
|
+
abstract_block_to_hash(node).merge!({
|
131
|
+
'blockname' => node.blockname
|
132
|
+
})
|
133
|
+
end
|
134
|
+
|
135
|
+
def list_to_hash(node)
|
136
|
+
case node.context
|
137
|
+
when :dlist
|
138
|
+
abstract_block_to_hash(node).merge!({
|
139
|
+
'items' => node.items.map { |terms, item|
|
140
|
+
{
|
141
|
+
'terms' => terms.map {|term| listitem_to_hash(term) },
|
142
|
+
'description' => listitem_to_hash(item)
|
143
|
+
}
|
144
|
+
}
|
145
|
+
})
|
146
|
+
else
|
147
|
+
abstract_block_to_hash(node).merge!({
|
148
|
+
'items' => node.blocks.map { |item| listitem_to_hash(item) }
|
149
|
+
})
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def listitem_to_hash(node)
|
154
|
+
abstract_block_to_hash(node).merge!({
|
155
|
+
'text' => (node.text? ? node.text : nil)
|
156
|
+
})
|
157
|
+
end
|
158
|
+
|
159
|
+
def table_to_hash(node)
|
160
|
+
abstract_block_to_hash(node).merge!({
|
161
|
+
'columns' => node.columns,
|
162
|
+
'rows' => {
|
163
|
+
'head' => node.rows.head.map { |row| row.map {|cell| cell_to_hash(cell) } },
|
164
|
+
'body' => node.rows.body.map { |row| row.map {|cell| cell_to_hash(cell) } },
|
165
|
+
'foot' => node.rows.foot.map { |row| row.map {|cell| cell_to_hash(cell) } }
|
166
|
+
}
|
167
|
+
})
|
168
|
+
end
|
169
|
+
|
170
|
+
def cell_to_hash(node)
|
171
|
+
abstract_node_to_hash(node).merge!({
|
172
|
+
'text' => node.text,
|
173
|
+
'content' => node.content,
|
174
|
+
'style' => node.style,
|
175
|
+
'colspan' => node.colspan,
|
176
|
+
'rowspan' => node.rowspan
|
177
|
+
})
|
178
|
+
end
|
179
|
+
|
180
|
+
def inline_to_hash(node)
|
181
|
+
data = abstract_node_to_hash(node).merge!({
|
182
|
+
'text' => node.text,
|
183
|
+
'type' => node.type.to_s,
|
184
|
+
'target' => node.target,
|
185
|
+
'xreftext' => node.xreftext
|
186
|
+
})
|
187
|
+
|
188
|
+
case node.node_name
|
189
|
+
when 'inline_anchor'
|
190
|
+
data['text'] ||= node.document.references[:refs][node.attributes['refid']]&.xreftext || "[#{node.attributes['refid']}]"
|
191
|
+
|
192
|
+
if (node.type == :xref) && (target_node = node.document.references[:refs][node.attributes['refid']])
|
193
|
+
data['target'] = if target_node.page
|
194
|
+
target_node.page.path
|
195
|
+
else
|
196
|
+
"#{find_page_node(target_node).page&.path}#{node.target}"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
when 'inline_footnote'
|
200
|
+
if page = find_page_node(node).page
|
201
|
+
if index = page.footnotes.find_index { |footnote| footnote['text'] == node.text }
|
202
|
+
footnote = page.footnotes[index]
|
203
|
+
data['id'] = nil
|
204
|
+
data['index'] = footnote['index']
|
205
|
+
data['target'] = "#_footnotedef_#{footnote['index']}"
|
206
|
+
else
|
207
|
+
index = page.footnotes.count + 1
|
208
|
+
page.footnotes.push({
|
209
|
+
'index' => index,
|
210
|
+
'text' => node.text,
|
211
|
+
'refid' => "_footnoteref_#{index}",
|
212
|
+
'id' => "_footnotedef_#{index}"
|
213
|
+
})
|
214
|
+
data['id'] = "_footnoteref_#{index}"
|
215
|
+
data['index'] = index
|
216
|
+
data['target'] = "#_footnotedef_#{index}"
|
217
|
+
end
|
218
|
+
else
|
219
|
+
index = node.attr 'index'
|
220
|
+
data['id'] = (node.type == :xref ? nil : "_footnoteref_#{index}")
|
221
|
+
data['index'] = index
|
222
|
+
data['target'] = "#_footnotedef_#{index}"
|
223
|
+
end
|
224
|
+
when 'inline_indexterm'
|
225
|
+
node.document.references[:indexterms] ||= []
|
226
|
+
node.document.references[:indexcount] ||= 0
|
227
|
+
indexterms = node.document.references[:indexterms]
|
228
|
+
indexcount = node.document.references[:indexcount] += 1
|
229
|
+
id = "_indexterm_#{indexcount}"
|
230
|
+
target = "#{find_page_node(node).page&.path}#_indexterm_#{indexcount}"
|
231
|
+
if node.type == :visible
|
232
|
+
register_term(indexterms, [node.text], target)
|
233
|
+
else
|
234
|
+
register_term(indexterms, node.attributes['terms'], target)
|
235
|
+
end
|
236
|
+
|
237
|
+
data['id'] = id
|
238
|
+
end
|
239
|
+
|
240
|
+
data
|
241
|
+
end
|
242
|
+
|
243
|
+
def register_term(indexterms, terms, target)
|
244
|
+
items = indexterms
|
245
|
+
item = nil
|
246
|
+
terms.each_with_index do |term, index|
|
247
|
+
term_index = items.find_index { |item| item['term'] == term }
|
248
|
+
if term_index
|
249
|
+
item = items[term_index]
|
250
|
+
else
|
251
|
+
item = {
|
252
|
+
'term' => term,
|
253
|
+
'targets' => [],
|
254
|
+
'items' => []
|
255
|
+
}
|
256
|
+
items.push item
|
257
|
+
items.sort_by! { |item| item['term'] }
|
258
|
+
end
|
259
|
+
|
260
|
+
if index == terms.size - 1
|
261
|
+
# save targe now
|
262
|
+
item['targets'].push target
|
263
|
+
else
|
264
|
+
# go deeper
|
265
|
+
items = item['items']
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
def find_page_node(node)
|
271
|
+
page_node = node
|
272
|
+
|
273
|
+
until page_node.page or page_node.parent.nil?
|
274
|
+
page_node = page_node.parent
|
275
|
+
end
|
276
|
+
|
277
|
+
page_node
|
278
|
+
end
|
279
|
+
|
280
|
+
def outline(node)
|
281
|
+
result = ''
|
282
|
+
if node.sections.any? && node.level < (node.document.attributes['toclevels'] || 2).to_i
|
283
|
+
result << "<ol>"
|
284
|
+
node.sections.each do |section|
|
285
|
+
result << "<li>"
|
286
|
+
target = if section.page
|
287
|
+
"#{section.page.path}"
|
288
|
+
else
|
289
|
+
"#{find_page_node(section).page&.path}##{section.id}"
|
290
|
+
end
|
291
|
+
result << %Q(<a href="#{target}">)
|
292
|
+
result << "#{section.sectnum} " if section.numbered && section.level < (node.document.attributes['sectnumlevels'] || 3).to_i
|
293
|
+
result << section.title
|
294
|
+
result << "</a>"
|
295
|
+
result << outline(section)
|
296
|
+
result << "</li>"
|
297
|
+
end
|
298
|
+
result << "</ol>"
|
299
|
+
end
|
300
|
+
result
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|