asciidoctor-epub3 1.5.0.alpha.9 → 1.5.0.alpha.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d8c660366a3ffa12ff1f0b2638413c240da220d07949a69493f4743c42287f5c
4
- data.tar.gz: 229d4a8ea185962d0c9fb7563423905fad27c62a6a20a126e44aabb935da0c98
3
+ metadata.gz: db1c0f666f96e3a7506d02d7b2254b457f53357d8aec827e181260f34074cf4d
4
+ data.tar.gz: 39f647ab1aaa940bb0f71c7666e04d38ac6b239bc5e1c263149146d5b8b6dd68
5
5
  SHA512:
6
- metadata.gz: 3c2c23900d2e5d2c08b16ccb2d0e80e62f3f3da8623a3cc84dae1d65620fd4ff64675124d8a08be2aa4da2c6043d7399e131e1a2a6b582b8d23ada23c4c598ad
7
- data.tar.gz: 6097d5e1f9a1942bd8933b23c94b26cff099025345cf030296584d848ebbbaade4669c14bcf369ab07fd65a8ecba736d9bfded24cc2d62930501c0883ada6d7a
6
+ metadata.gz: 84708935a105312bf101367e9261460f19e827c3c5a5ea7ecfad8cad1dbd60554bc6e0772dfa6bef29ed9d40fc17ceafd718a32dab7d7b3e44a89f8cbccb4597
7
+ data.tar.gz: fe86150318b86702dda3a94da387f3f860e87b298629e85e9b953d17fd6ee969e6b48644b20d722e3fabe0af275e7e4460ed343d1d5e1628821e951f07c9ed11
data/.yardopts CHANGED
@@ -1,12 +1,12 @@
1
1
  --charset UTF-8
2
2
  --readme README.adoc
3
+ --no-private
3
4
  --hide-api private
4
- --plugin tomdoc
5
- --title "Asciidoctor EPUB3 API Documentation"
6
- --output-dir rdoc
5
+ --title "Asciidoctor EPUB3 API Docs"
6
+ --output-dir apidoc
7
+ --exclude /core_ext(?:\.rb$|/)
7
8
  lib/**/*.rb
8
9
  -
9
10
  CHANGELOG.adoc
10
- CONTRIBUTING.adoc
11
11
  LICENSE.adoc
12
12
  NOTICE.adoc
data/CHANGELOG.adoc CHANGED
@@ -5,6 +5,19 @@
5
5
  This document provides a high-level view of the changes to the {project-name} by release.
6
6
  For a detailed view of what has changed, refer to the {uri-repo}/commits/master[commit history] on GitHub.
7
7
 
8
+ == 1.5.0.alpha.10 (2020-01-20) - @slonopotamus
9
+
10
+ * fix deep xrefs between chapters when using Asciidoctor 2 (#210)
11
+ * switch from epubcheck to epubcheck-ruby (#224)
12
+ * set up a test suite (#11)
13
+ * set up rubocop to enforce a consistent code style (#223)
14
+ * use GitHub Actions for CI and release process (#218)
15
+ * fix JS causing malformed XML that prevented footnotes from being displayed in Calibre (#207)
16
+ * fix installing on Windows (#213, #216)
17
+ * upgrade pygments.rb to 1.2.1 (#216)
18
+ * gepub dependency is no longer locked to 1.0.2 and will use latest 1.0.x version
19
+ * fix `-a ebook-validate` not working on Windows (#232)
20
+
8
21
  == 1.5.0.alpha.9 (2019-04-04) - @mojavelinux
9
22
 
10
23
  * allow converter to be used with Asciidoctor 2 (#185)
data/Gemfile CHANGED
@@ -1,15 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Look in asciidoctor-epub3.gemspec for runtime and development dependencies.
4
6
  gemspec
5
7
 
8
+ gem 'asciidoctor', ENV['ASCIIDOCTOR_VERSION'], require: false if ENV.key? 'ASCIIDOCTOR_VERSION'
9
+
6
10
  group :optional do
7
- gem 'epubcheck', '3.0.1'
8
- if (ruby_version = Gem::Version.new RUBY_VERSION) < (Gem::Version.new '2.0.0')
9
- gem 'kindlegen', '2.9.4'
10
- gem 'pygments.rb', '0.6.3'
11
- else
12
- gem 'kindlegen', '3.0.3'
13
- gem 'pygments.rb', '1.1.2'
14
- end
11
+ gem 'epubcheck-ruby', '4.1.1.0'
12
+ gem 'kindlegen', (Gem::Version.new RUBY_VERSION) < (Gem::Version.new '2.4.0') ? '3.0.3' : '3.0.5'
13
+ gem 'pygments.rb', '1.2.1'
14
+ end
15
+
16
+ group :docs do
17
+ gem 'yard', require: false
15
18
  end
data/README.adoc CHANGED
@@ -1,6 +1,6 @@
1
1
  = {project-name}: A _native_ EPUB3 converter for AsciiDoc
2
2
  Dan Allen <https://github.com/mojavelinux[@mojavelinux]>; Sarah White <https://github.com/graphitefriction[@graphitefriction]>
3
- v1.5.0.alpha.9, 2019-04-04
3
+ v1.5.0.alpha.10, 2020-01-20
4
4
  // Settings:
5
5
  :experimental:
6
6
  :idprefix:
@@ -30,6 +30,7 @@ endif::[]
30
30
  :uri-gem: http://rubygems.org/gems/asciidoctor-epub3
31
31
  :uri-repo: {uri-project}
32
32
  :uri-issues: {uri-repo}/issues
33
+ :uri-ci: {uri-repo}/actions?query=workflow%3ACI
33
34
  :uri-discuss: http://discuss.asciidoctor.org
34
35
  :uri-rvm: https://rvm.io
35
36
  :uri-asciidoctor: http://asciidoctor.org
@@ -50,6 +51,7 @@ endif::[]
50
51
  ifdef::status[]
51
52
  image:https://img.shields.io/gem/v/asciidoctor-epub3.svg[Latest Release,link={uri-gem}]
52
53
  image:https://img.shields.io/badge/license-MIT-blue.svg[MIT License,link=#copyright]
54
+ image:{uri-repo}/workflows/CI/badge.svg[GitHub Actions,link={uri-ci}]
53
55
  endif::[]
54
56
 
55
57
  {project-name} is a set of Asciidoctor extensions for converting AsciiDoc documents directly to the EPUB3 and KF8/MOBI e-book formats.
@@ -249,7 +251,7 @@ However, for the time being, the include directive fills this role.
249
251
 
250
252
  == Prerequisites
251
253
 
252
- All that's needed to use {project-name} is Ruby (1.9.3 or above; 2.2.x recommended) and a few Ruby gems, which we'll explain how to install in the next section.
254
+ All that's needed to use {project-name} is Ruby 2.3 or newer and a few Ruby gems, which we'll explain how to install in the next section.
253
255
 
254
256
  To check if you have Ruby available, use the `ruby` command to query the installed version:
255
257
 
@@ -467,10 +469,10 @@ Next, let's validate the EPUB3 archive to ensure it built correctly.
467
469
  .Validation success
468
470
  [.output]
469
471
  ....
470
- Epubcheck Version 3.0.1
471
-
472
- Validating against EPUB version 3.0
472
+ Validating using EPUB version 3.0.1 rules.
473
473
  No errors or warnings detected.
474
+ Messages: 0 fatal / 0 errors / 0 warnings / 0 info
475
+ EPUBCheck completed
474
476
  ....
475
477
 
476
478
  If the EPUB3 archive contains any errors, they will be output in your terminal.
@@ -546,7 +548,7 @@ For more information about this attribute and other related attributes, see {uri
546
548
  Specifies the ebook format to generate (epub3 or kf8, default: epub3)
547
549
 
548
550
  *-a ebook-validate* ::
549
- Runs Epubcheck 3.0.1 to validate output file against the EPUB3 specification
551
+ Runs {uri-epubcheck}[EpubCheck] to validate output file against the EPUB3 specification
550
552
 
551
553
  *-a ebook-compress=<0|1|2|none|standard|huffdic>* ::
552
554
  Controls the compression type used by kindlegen (0=none [default if unset], 1=standard [default if empty], 2=huffdic)
data/Rakefile CHANGED
@@ -1,81 +1,5 @@
1
- # -*- encoding: utf-8 -*-
2
- require File.expand_path '../lib/asciidoctor-epub3/version', __FILE__
1
+ # frozen_string_literal: true
3
2
 
4
- require 'rake/clean'
5
-
6
- default_tasks = []
7
-
8
- begin
9
- require 'bundler/gem_tasks'
10
- default_tasks << :build
11
-
12
- # Enhance the release task to create an explicit commit for the release
13
- #Rake::Task[:release].enhance [:commit_release]
14
-
15
- # NOTE you don't need to push after updating version and committing locally
16
- # WARNING no longer works; it's now necessary to get master in a state ready for tagging
17
- task :commit_release do
18
- Bundler::GemHelper.new.send :guard_clean
19
- sh %(git commit --allow-empty -a -m 'Release #{Asciidoctor::Epub3::VERSION}')
20
- end
21
- rescue LoadError
22
- end
23
-
24
- begin
25
- require 'rdoc/task'
26
- Rake::RDocTask.new do |t|
27
- t.rdoc_dir = 'rdoc'
28
- t.title = %(Asciidoctor EPUB3 #{Asciidoctor::Epub3::VERSION})
29
- t.main = %(README.adoc)
30
- t.rdoc_files.include 'README.adoc', 'LICENSE.adoc', 'NOTICE.adoc', 'lib/**/*.rb', 'bin/**/*'
31
- end
32
- rescue LoadError
33
- end
34
-
35
- =begin NOT CURRENTLY IN USE
36
- begin
37
- require 'rake/testtask'
38
- Rake::TestTask.new do |t|
39
- t.libs << 'test'
40
- t.pattern = 'test/**/*_test.rb'
41
- t.verbose = true
42
- t.warning = true
43
- if RUBY_VERSION >= '2'
44
- t.options = '--tty=no'
45
- end
46
- end
47
- default_tasks << :test
48
- rescue LoadError
49
- end
50
-
51
- begin
52
- require 'cucumber'
53
- require 'cucumber/rake/task'
54
- CUKE_RESULTS_FILE = 'feature-results.html'
55
- ARUBA_TMP_DIR = 'tmp'
56
- CLEAN << CUKE_RESULTS_FILE if File.file? CUKE_RESULTS_FILE
57
- CLEAN << ARUBA_TMP_DIR if File.directory? ARUBA_TMP_DIR
58
- desc 'Run features'
59
- Cucumber::Rake::Task.new :features do |t|
60
- opts = %(features --format html -o #{CUKE_RESULTS_FILE} --format progress -x --tags ~@pending)
61
- opts = %(#{opts} --tags #{ENV['TAGS']}) if ENV['TAGS']
62
- t.cucumber_opts = opts
63
- t.fork = false
64
- end
65
-
66
- desc 'Run features tagged as work-in-progress (@wip)'
67
- Cucumber::Rake::Task.new 'features:wip' do |t|
68
- #t.cucumber_opts = %(features --format html -o #{CUKE_RESULTS_FILE} --format pretty -x -s --tags @wip)
69
- t.cucumber_opts = %(features --format html -o #{CUKE_RESULTS_FILE} --format progress -x --tags @wip)
70
- t.fork = false
71
- end
72
-
73
- default_tasks << :features
74
- task :cucumber => :features
75
- task 'cucumber:wip' => 'features:wip'
76
- task :wip => 'features:wip'
77
- rescue LoadError
78
- end
79
- =end
80
-
81
- task :default => default_tasks unless default_tasks.empty?
3
+ $default_tasks = [] # rubocop:disable Style/GlobalVars
4
+ Dir.glob('tasks/*.rake').each {|file| load file }
5
+ task default: $default_tasks unless $default_tasks.empty? # rubocop:disable Style/GlobalVars
@@ -1,4 +1,5 @@
1
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  require File.expand_path('lib/asciidoctor-epub3/version', File.dirname(__FILE__))
3
4
  require 'open3' unless defined? Open3
4
5
 
@@ -30,10 +31,13 @@ An extension for Asciidoctor that converts AsciiDoc documents to EPUB3 and KF8/M
30
31
 
31
32
  s.require_paths = ['lib']
32
33
 
33
- s.add_development_dependency 'rake', '~> 12.3.2'
34
+ s.add_development_dependency 'rake', '~> 12.3.0'
35
+ s.add_development_dependency 'rspec', '~> 3.9.0'
36
+ s.add_development_dependency 'rubocop', '~> 0.78.0'
37
+ s.add_development_dependency 'rubocop-rspec', '~> 1.37.0'
34
38
 
35
39
  s.add_runtime_dependency 'asciidoctor', '>= 1.5.0', '< 3.0.0'
36
- s.add_runtime_dependency 'gepub', '~> 1.0.2'
37
- s.add_runtime_dependency 'thread_safe', '~> 0.3.6'
38
- s.add_runtime_dependency 'concurrent-ruby', '~> 1.1.5'
40
+ s.add_runtime_dependency 'concurrent-ruby', '~> 1.1.0'
41
+ s.add_runtime_dependency 'gepub', '~> 1.0.0'
42
+ s.add_runtime_dependency 'thread_safe', '~> 0.3.0'
39
43
  end
data/bin/adb-push-ebook CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  ADB = ENV['ADB'] || 'adb'
4
5
  TARGETS = {
5
6
  '.epub' => '/sdcard/',
6
- '.mobi' => '/sdcard/Android/data/com.amazon.kindle/files/'
7
+ '.mobi' => '/sdcard/Android/data/com.amazon.kindle/files/',
7
8
  }
8
9
 
9
10
  unless ::File.executable? ADB
@@ -20,7 +21,7 @@ if (payload_file_ext = File.extname payload_file).empty?
20
21
  transfers = TARGETS.map do |(ext, target_dir)|
21
22
  {
22
23
  src: %(#{payload_file}#{ext}),
23
- dest: target_dir
24
+ dest: target_dir,
24
25
  }
25
26
  end
26
27
  else
@@ -28,7 +29,7 @@ else
28
29
  end
29
30
 
30
31
  transfers.each do |transfer|
31
- Open3.popen2e(Shellwords.join [ADB, 'push', transfer[:src], transfer[:dest]]) do |input, output, wait_thr|
32
+ Open3.popen2e Shellwords.join([ADB, 'push', transfer[:src], transfer[:dest]]) do |_input, output, _wait_thr|
32
33
  output.each {|line| puts line }
33
34
  end if File.file? transfer[:src]
34
35
  end
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- if File.exist?(asciidoctor_epub3 = (File.expand_path '../../lib/asciidoctor-epub3', __FILE__))
4
+ if File.exist? asciidoctor_epub3 = (File.expand_path '../lib/asciidoctor-epub3', __dir__)
4
5
  require asciidoctor_epub3
5
6
  else
6
7
  require 'asciidoctor-epub3'
@@ -9,7 +10,7 @@ require 'asciidoctor/cli'
9
10
 
10
11
  options = Asciidoctor::Cli::Options.new backend: 'epub3'
11
12
 
12
- # FIXME provide an API in Asciidoctor for sub-components to print version information
13
+ # FIXME: provide an API in Asciidoctor for sub-components to print version information
13
14
  unless ARGV != ['-v'] && (ARGV & ['-V', '--version']).empty?
14
15
  require_relative '../lib/asciidoctor-epub3/version'
15
16
  $stdout.write %(Asciidoctor EPUB3 #{Asciidoctor::Epub3::VERSION} using )
@@ -22,7 +23,7 @@ unless ARGV != ['-v'] && (ARGV & ['-V', '--version']).empty?
22
23
  exit 0
23
24
  end
24
25
 
25
- # FIXME This is a really bizarre API. Please make me simpler.
26
+ # FIXME: This is a really bizarre API. Please make me simpler.
26
27
  case (result = options.parse! ARGV)
27
28
  when Integer
28
29
  exit result
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'asciidoctor'
2
4
  require 'asciidoctor/extensions'
3
5
  require 'gepub'
@@ -1,158 +1,158 @@
1
- # encoding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require_relative 'spine_item_processor'
3
4
  require_relative 'font_icon_map'
4
5
 
5
6
  module Asciidoctor
6
- module Epub3
7
-
8
- # Public: The main converter for the epub3 backend that handles packaging the
9
- # EPUB3 or KF8 publication file.
10
- class Converter
11
- include ::Asciidoctor::Converter
12
- include ::Asciidoctor::Writer
13
-
14
- register_for 'epub3'
15
-
16
- def initialize backend, opts
17
- super
18
- basebackend 'html'
19
- outfilesuffix '.epub' # dummy outfilesuffix since it may be .mobi
20
- htmlsyntax 'xml'
21
- @validate = false
22
- @extract = false
23
- end
7
+ module Epub3
8
+ # Public: The main converter for the epub3 backend that handles packaging the
9
+ # EPUB3 or KF8 publication file.
10
+ class Converter
11
+ include ::Asciidoctor::Converter
12
+ include ::Asciidoctor::Writer
13
+
14
+ register_for 'epub3'
15
+
16
+ def initialize backend, opts
17
+ super
18
+ basebackend 'html'
19
+ outfilesuffix '.epub' # dummy outfilesuffix since it may be .mobi
20
+ htmlsyntax 'xml'
21
+ @validate = false
22
+ @extract = false
23
+ end
24
24
 
25
- def convert node, name = nil
26
- if (name ||= node.node_name) == 'document'
27
- @validate = node.attr? 'ebook-validate'
28
- @extract = node.attr? 'ebook-extract'
29
- @compress = node.attr 'ebook-compress'
30
- Packager.new node, (node.references[:spine_items] || [node]), node.attributes['ebook-format'].to_sym
31
- # converting an element from the spine document, such as an inline node in the doctitle
32
- elsif name.start_with? 'inline_'
33
- (@content_converter ||= ::Asciidoctor::Converter::Factory.default.create('epub3-xhtml5')).convert node, name
34
- else
35
- raise ::ArgumentError, %(Encountered unexpected node in epub3 package converter: #{name})
25
+ def convert node, name = nil
26
+ if (name ||= node.node_name) == 'document'
27
+ @validate = node.attr? 'ebook-validate'
28
+ @extract = node.attr? 'ebook-extract'
29
+ @compress = node.attr 'ebook-compress'
30
+ Packager.new node, (node.references[:spine_items] || [node]), node.attributes['ebook-format'].to_sym
31
+ # converting an element from the spine document, such as an inline node in the doctitle
32
+ elsif name.start_with? 'inline_'
33
+ (@content_converter ||= ::Asciidoctor::Converter::Factory.default.create 'epub3-xhtml5').convert node, name
34
+ else
35
+ raise ::ArgumentError, %(Encountered unexpected node in epub3 package converter: #{name})
36
+ end
37
+ end
38
+
39
+ # FIXME: we have to package in write because we don't have access to target before this point
40
+ def write packager, target
41
+ packager.package validate: @validate, extract: @extract, compress: @compress, target: target
42
+ nil
43
+ end
36
44
  end
37
- end
38
45
 
39
- # FIXME we have to package in write because we don't have access to target before this point
40
- def write packager, target
41
- packager.package validate: @validate, extract: @extract, compress: @compress, target: target
42
- nil
43
- end
44
- end
46
+ # Public: The converter for the epub3 backend that converts the individual
47
+ # content documents in an EPUB3 publication.
48
+ class ContentConverter
49
+ include ::Asciidoctor::Converter
45
50
 
46
- # Public: The converter for the epub3 backend that converts the individual
47
- # content documents in an EPUB3 publication.
48
- class ContentConverter
49
- include ::Asciidoctor::Converter
51
+ register_for 'epub3-xhtml5'
50
52
 
51
- register_for 'epub3-xhtml5'
53
+ LF = ?\n
54
+ NoBreakSpace = '&#xa0;'
55
+ ThinNoBreakSpace = '&#x202f;'
56
+ RightAngleQuote = '&#x203a;'
57
+ CalloutStartNum = %(\u2460)
52
58
 
53
- LF = ?\n
54
- NoBreakSpace = '&#xa0;'
55
- ThinNoBreakSpace = '&#x202f;'
56
- RightAngleQuote = '&#x203a;'
57
- CalloutStartNum = %(\u2460)
59
+ CharEntityRx = /&#(\d{2,6});/
60
+ XmlElementRx = /<\/?.+?>/
61
+ TrailingPunctRx = /[[:punct:]]$/
58
62
 
59
- CharEntityRx = /&#(\d{2,6});/
60
- XmlElementRx = /<\/?.+?>/
61
- TrailingPunctRx = /[[:punct:]]$/
63
+ FromHtmlSpecialCharsMap = {
64
+ '&lt;' => '<',
65
+ '&gt;' => '>',
66
+ '&amp;' => '&',
67
+ }
62
68
 
63
- FromHtmlSpecialCharsMap = {
64
- '&lt;' => '<',
65
- '&gt;' => '>',
66
- '&amp;' => '&'
67
- }
69
+ FromHtmlSpecialCharsRx = /(?:#{FromHtmlSpecialCharsMap.keys * '|'})/
68
70
 
69
- FromHtmlSpecialCharsRx = /(?:#{FromHtmlSpecialCharsMap.keys * '|'})/
71
+ ToHtmlSpecialCharsMap = {
72
+ '&' => '&amp;',
73
+ '<' => '&lt;',
74
+ '>' => '&gt;',
75
+ }
70
76
 
71
- ToHtmlSpecialCharsMap = {
72
- '&' => '&amp;',
73
- '<' => '&lt;',
74
- '>' => '&gt;'
75
- }
77
+ ToHtmlSpecialCharsRx = /[#{ToHtmlSpecialCharsMap.keys.join}]/
76
78
 
77
- ToHtmlSpecialCharsRx = /[#{ToHtmlSpecialCharsMap.keys.join}]/
79
+ OpenParagraphTagRx = /^<p>/
80
+ CloseParagraphTagRx = /<\/p>$/
78
81
 
79
- OpenParagraphTagRx = /^<p>/
80
- CloseParagraphTagRx = /<\/p>$/
82
+ def initialize backend, opts
83
+ super
84
+ basebackend 'html'
85
+ outfilesuffix '.xhtml'
86
+ htmlsyntax 'xml'
87
+ @xrefs_seen = ::Set.new
88
+ @icon_names = []
89
+ end
81
90
 
82
- def initialize backend, opts
83
- super
84
- basebackend 'html'
85
- outfilesuffix '.xhtml'
86
- htmlsyntax 'xml'
87
- @xrefs_seen = ::Set.new
88
- @icon_names = []
89
- end
91
+ def convert node, name = nil
92
+ if respond_to? name ||= node.node_name
93
+ send name, node
94
+ else
95
+ warn %(asciidoctor: WARNING: conversion missing in epub3 backend for #{name})
96
+ end
97
+ end
90
98
 
91
- def convert node, name = nil
92
- if respond_to?(name ||= node.node_name)
93
- send name, node
94
- else
95
- warn %(asciidoctor: WARNING: conversion missing in epub3 backend for #{name})
96
- end
97
- end
99
+ def document node
100
+ docid = node.id
101
+ pubtype = node.attr 'publication-type', 'book'
98
102
 
99
- def document node
100
- docid = node.id
101
- pubtype = node.attr 'publication-type', 'book'
102
-
103
- if (doctitle = node.doctitle partition: true, use_fallback: true).subtitle?
104
- title = %(#{doctitle.main} )
105
- subtitle = doctitle.subtitle
106
- else
107
- # HACK until we get proper handling of title-only in CSS
108
- title = ''
109
- subtitle = doctitle.combined
110
- end
103
+ if (doctitle = node.doctitle partition: true, use_fallback: true).subtitle?
104
+ title = %(#{doctitle.main} )
105
+ subtitle = doctitle.subtitle
106
+ else
107
+ # HACK: until we get proper handling of title-only in CSS
108
+ title = ''
109
+ subtitle = doctitle.combined
110
+ end
111
111
 
112
- doctitle_sanitized = (node.doctitle sanitize: true, use_fallback: true).to_s
113
- subtitle_formatted = subtitle.split.map {|w| %(<b>#{w}</b>) } * ' '
114
-
115
- if pubtype == 'book'
116
- byline = ''
117
- else
118
- author = node.attr 'author'
119
- username = node.attr 'username', 'default'
120
- imagesdir = (node.references[:spine].attr 'imagesdir', '.').chomp '/'
121
- imagesdir = imagesdir == '.' ? '' : %(#{imagesdir}/)
122
- byline = %(<p class="byline"><img src="#{imagesdir}avatars/#{username}.jpg"/> <b class="author">#{author}</b></p>#{LF})
123
- end
112
+ doctitle_sanitized = (node.doctitle sanitize: true, use_fallback: true).to_s
113
+ subtitle_formatted = subtitle.split.map {|w| %(<b>#{w}</b>) } * ' '
124
114
 
125
- mark_last_paragraph node unless pubtype == 'book'
126
- content = node.content
127
-
128
- # NOTE must run after content is resolved
129
- # TODO perhaps create dynamic CSS file?
130
- if @icon_names.empty?
131
- icon_css_head = icon_css_scoped = ''
132
- else
133
- icon_defs = @icon_names.map {|name|
134
- %(.i-#{name}::before { content: "#{FontIconMap[name.tr('-', '_').to_sym]}"; })
135
- } * LF
136
- icon_css_head = %(<style>
115
+ if pubtype == 'book'
116
+ byline = ''
117
+ else
118
+ author = node.attr 'author'
119
+ username = node.attr 'username', 'default'
120
+ imagesdir = (node.references[:spine].attr 'imagesdir', '.').chomp '/'
121
+ imagesdir = imagesdir == '.' ? '' : %(#{imagesdir}/)
122
+ byline = %(<p class="byline"><img src="#{imagesdir}avatars/#{username}.jpg"/> <b class="author">#{author}</b></p>#{LF})
123
+ end
124
+
125
+ mark_last_paragraph node unless pubtype == 'book'
126
+ content = node.content
127
+
128
+ # NOTE must run after content is resolved
129
+ # TODO perhaps create dynamic CSS file?
130
+ if @icon_names.empty?
131
+ icon_css_head = icon_css_scoped = ''
132
+ else
133
+ icon_defs = @icon_names.map {|name|
134
+ %(.i-#{name}::before { content: "#{FontIconMap[name.tr('-', '_').to_sym]}"; })
135
+ } * LF
136
+ icon_css_head = %(<style>
137
137
  #{icon_defs}
138
138
  </style>
139
139
  )
140
- # NOTE Namo Pubtree requires icon CSS to be repeated inside <body> (or in a linked stylesheet); wrap in div to hide from Aldiko
141
- icon_css_scoped = (node.attr? 'ebook-format', 'kf8') ? '' : %(<div style="display: none" aria-hidden="true"><style scoped="scoped">
140
+ # NOTE Namo Pubtree requires icon CSS to be repeated inside <body> (or in a linked stylesheet); wrap in div to hide from Aldiko
141
+ icon_css_scoped = (node.attr? 'ebook-format', 'kf8') ? '' : %(<div style="display: none" aria-hidden="true"><style scoped="scoped">
142
142
  #{icon_defs}
143
143
  </style></div>
144
144
  )
145
- end
145
+ end
146
146
 
147
- # NOTE kindlegen seems to mangle the <header> element, so we wrap its content in a div
148
- lines = [%(<!DOCTYPE html>
149
- <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="#{lang = (node.attr 'lang', 'en')}" lang="#{lang}">
147
+ # NOTE kindlegen seems to mangle the <header> element, so we wrap its content in a div
148
+ lines = [%(<!DOCTYPE html>
149
+ <html xmlns="http://www.w3.org/1999/xhtml" xmlns:epub="http://www.idpf.org/2007/ops" xml:lang="#{lang = node.attr 'lang', 'en'}" lang="#{lang}">
150
150
  <head>
151
151
  <meta charset="UTF-8"/>
152
152
  <title>#{doctitle_sanitized}</title>
153
153
  <link rel="stylesheet" type="text/css" href="styles/epub3.css"/>
154
154
  <link rel="stylesheet" type="text/css" href="styles/epub3-css3-only.css" media="(min-device-width: 0px)"/>
155
- #{icon_css_head}<script type="text/javascript">
155
+ #{icon_css_head}<script type="text/javascript"><![CDATA[
156
156
  document.addEventListener('DOMContentLoaded', function(event, reader) {
157
157
  if (!(reader = navigator.epubReadingSystem)) {
158
158
  if (navigator.userAgent.indexOf(' calibre/') >= 0) reader = { name: 'calibre-desktop' };
@@ -160,485 +160,476 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
160
160
  }
161
161
  document.body.setAttribute('class', reader.name.toLowerCase().replace(/ /g, '-'));
162
162
  });
163
- </script>
163
+ ]]></script>
164
164
  </head>
165
165
  <body>
166
166
  <section class="chapter" title="#{doctitle_sanitized.gsub '"', '&quot;'}" epub:type="chapter" id="#{docid}">
167
167
  #{icon_css_scoped}<header>
168
168
  <div class="chapter-header">
169
- #{byline}<h1 class="chapter-title">#{title}#{subtitle ? %[<small class="subtitle">#{subtitle_formatted}</small>] : ''}</h1>
169
+ #{byline}<h1 class="chapter-title">#{title}#{subtitle ? %(<small class="subtitle">#{subtitle_formatted}</small>) : ''}</h1>
170
170
  </div>
171
171
  </header>
172
172
  #{content})]
173
173
 
174
- if node.footnotes?
175
- # NOTE kindlegen seems to mangle the <footer> element, so we wrap its content in a div
176
- lines << '<footer>
174
+ if node.footnotes?
175
+ # NOTE kindlegen seems to mangle the <footer> element, so we wrap its content in a div
176
+ lines << '<footer>
177
177
  <div class="chapter-footer">
178
178
  <div class="footnotes">'
179
- node.footnotes.each do |footnote|
180
- lines << %(<aside id="note-#{footnote.index}" epub:type="footnote">
179
+ node.footnotes.each do |footnote|
180
+ lines << %(<aside id="note-#{footnote.index}" epub:type="footnote">
181
181
  <p><sup class="noteref"><a href="#noteref-#{footnote.index}">#{footnote.index}</a></sup> #{footnote.text}</p>
182
182
  </aside>)
183
- end
184
- lines << '</div>
183
+ end
184
+ lines << '</div>
185
185
  </div>
186
186
  </footer>'
187
- end
187
+ end
188
188
 
189
- lines << '</section>
189
+ lines << '</section>
190
190
  </body>
191
191
  </html>'
192
192
 
193
- lines * LF
194
- end
193
+ lines * LF
194
+ end
195
195
 
196
- # NOTE embedded is used for AsciiDoc table cell content
197
- def embedded node
198
- node.content
199
- end
196
+ # NOTE embedded is used for AsciiDoc table cell content
197
+ def embedded node
198
+ node.content
199
+ end
200
200
 
201
- def section node
202
- hlevel = node.level + 1
203
- epub_type_attr = node.special ? %( epub:type="#{node.sectname}") : ''
204
- div_classes = [%(sect#{node.level}), node.role].compact
205
- title = node.title
206
- title_sanitized = xml_sanitize title
207
- if node.document.header? || node.level != 1 || node != node.document.first_section
208
- %(<section class="#{div_classes * ' '}" title="#{title_sanitized}"#{epub_type_attr}>
209
- <h#{hlevel} id="#{node.id}">#{title}</h#{hlevel}>#{(content = node.content).empty? ? '' : %[
210
- #{content}]}
201
+ def section node
202
+ hlevel = node.level + 1
203
+ epub_type_attr = node.special ? %( epub:type="#{node.sectname}") : ''
204
+ div_classes = [%(sect#{node.level}), node.role].compact
205
+ title = node.title
206
+ title_sanitized = xml_sanitize title
207
+ if node.document.header? || node.level != 1 || node != node.document.first_section
208
+ %(<section class="#{div_classes * ' '}" title="#{title_sanitized}"#{epub_type_attr}>
209
+ <h#{hlevel} id="#{node.id}">#{title}</h#{hlevel}>#{(content = node.content).empty? ? '' : %(
210
+ #{content})}
211
211
  </section>)
212
- else
213
- # document has no level-0 heading and this heading serves as the document title
214
- node.content
215
- end
216
- end
212
+ else
213
+ # document has no level-0 heading and this heading serves as the document title
214
+ node.content
215
+ end
216
+ end
217
217
 
218
- # TODO support use of quote block as abstract
219
- def preamble node
220
- if (first_block = node.blocks[0]) && first_block.style == 'abstract'
221
- abstract first_block
222
- # REVIEW should we treat the preamble as an abstract in general?
223
- elsif first_block && node.blocks.size == 1
224
- abstract first_block
225
- else
226
- node.content
227
- end
228
- end
218
+ # TODO: support use of quote block as abstract
219
+ def preamble node
220
+ if (first_block = node.blocks[0]) && first_block.style == 'abstract'
221
+ abstract first_block
222
+ # REVIEW: should we treat the preamble as an abstract in general?
223
+ elsif first_block && node.blocks.size == 1
224
+ abstract first_block
225
+ else
226
+ node.content
227
+ end
228
+ end
229
229
 
230
- def open node
231
- id_attr = node.id ? %( id="#{node.id}") : nil
232
- class_attr = node.role ? %( class="#{node.role}") : nil
233
- if id_attr || class_attr
234
- %(<div#{id_attr}#{class_attr}>
230
+ def open node
231
+ id_attr = node.id ? %( id="#{node.id}") : nil
232
+ class_attr = node.role ? %( class="#{node.role}") : nil
233
+ if id_attr || class_attr
234
+ %(<div#{id_attr}#{class_attr}>
235
235
  #{convert_content node}
236
236
  </div>)
237
- else
238
- convert_content node
239
- end
240
- end
237
+ else
238
+ convert_content node
239
+ end
240
+ end
241
241
 
242
- def abstract node
243
- %(<div class="abstract" epub:type="preamble">
242
+ def abstract node
243
+ %(<div class="abstract" epub:type="preamble">
244
244
  #{convert_content node}
245
245
  </div>)
246
- end
246
+ end
247
247
 
248
- def paragraph node
249
- role = node.role
250
- # stack-head is the alternative to the default, inline-head (where inline means "run-in")
251
- head_stop = node.attr 'head-stop', (role && (node.has_role? 'stack-head') ? nil : '.')
252
- head = node.title? ? %(<strong class="head">#{title = node.title}#{head_stop && title !~ TrailingPunctRx ? head_stop : ''}</strong> ) : ''
253
- if role
254
- node.set_option 'hardbreaks' if node.has_role? 'signature'
255
- %(<p class="#{role}">#{head}#{node.content}</p>)
256
- else
257
- %(<p>#{head}#{node.content}</p>)
258
- end
259
- end
248
+ def paragraph node
249
+ role = node.role
250
+ # stack-head is the alternative to the default, inline-head (where inline means "run-in")
251
+ head_stop = node.attr 'head-stop', (role && (node.has_role? 'stack-head') ? nil : '.')
252
+ head = node.title? ? %(<strong class="head">#{title = node.title}#{head_stop && title !~ TrailingPunctRx ? head_stop : ''}</strong> ) : ''
253
+ if role
254
+ node.set_option 'hardbreaks' if node.has_role? 'signature'
255
+ %(<p class="#{role}">#{head}#{node.content}</p>)
256
+ else
257
+ %(<p>#{head}#{node.content}</p>)
258
+ end
259
+ end
260
260
 
261
- def pass node
262
- content = node.content
263
- if content == '<?hard-pagebreak?>'
264
- '<hr epub:type="pagebreak" class="pagebreak"/>'
265
- else
266
- content
267
- end
268
- end
261
+ def pass node
262
+ content = node.content
263
+ if content == '<?hard-pagebreak?>'
264
+ '<hr epub:type="pagebreak" class="pagebreak"/>'
265
+ else
266
+ content
267
+ end
268
+ end
269
269
 
270
- def admonition node
271
- id_attr = node.id ? %( id="#{node.id}") : ''
272
- if node.title?
273
- title = node.title
274
- title_sanitized = xml_sanitize title
275
- title_attr = %( title="#{node.caption}: #{title_sanitized}")
276
- title_el = %(<h2>#{title}</h2>
270
+ def admonition node
271
+ id_attr = node.id ? %( id="#{node.id}") : ''
272
+ if node.title?
273
+ title = node.title
274
+ title_sanitized = xml_sanitize title
275
+ title_attr = %( title="#{node.caption}: #{title_sanitized}")
276
+ title_el = %(<h2>#{title}</h2>
277
277
  )
278
- else
279
- title_attr = %( title="#{node.caption}")
280
- title_el = ''
281
- end
278
+ else
279
+ title_attr = %( title="#{node.caption}")
280
+ title_el = ''
281
+ end
282
282
 
283
- type = node.attr 'name'
284
- epub_type = case type
285
- when 'tip'
286
- 'help'
287
- when 'note'
288
- 'note'
289
- when 'important', 'warning', 'caution'
290
- 'warning'
291
- end
292
- %(<aside#{id_attr} class="admonition #{type}"#{title_attr} epub:type="#{epub_type}">
283
+ type = node.attr 'name'
284
+ epub_type = case type
285
+ when 'tip'
286
+ 'help'
287
+ when 'note'
288
+ 'note'
289
+ when 'important', 'warning', 'caution'
290
+ 'warning'
291
+ end
292
+ %(<aside#{id_attr} class="admonition #{type}"#{title_attr} epub:type="#{epub_type}">
293
293
  #{title_el}<div class="content">
294
294
  #{convert_content node}
295
295
  </div>
296
296
  </aside>)
297
- end
297
+ end
298
298
 
299
- def example node
300
- id_attr = node.id ? %( id="#{node.id}") : ''
301
- title_div = node.title? ? %(<div class="example-title">#{node.title}</div>
299
+ def example node
300
+ id_attr = node.id ? %( id="#{node.id}") : ''
301
+ title_div = node.title? ? %(<div class="example-title">#{node.title}</div>
302
302
  ) : ''
303
- %(<div#{id_attr} class="example">
303
+ %(<div#{id_attr} class="example">
304
304
  #{title_div}<div class="example-content">
305
305
  #{convert_content node}
306
306
  </div>
307
307
  </div>)
308
- end
308
+ end
309
309
 
310
- def floating_title node
311
- tag_name = %(h#{node.level + 1})
312
- id_attribute = node.id ? %( id="#{node.id}") : ''
313
- %(<#{tag_name}#{id_attribute} class="#{['discrete', node.role].compact * ' '}">#{node.title}</#{tag_name}>)
314
- end
310
+ def floating_title node
311
+ tag_name = %(h#{node.level + 1})
312
+ id_attribute = node.id ? %( id="#{node.id}") : ''
313
+ %(<#{tag_name}#{id_attribute} class="#{['discrete', node.role].compact * ' '}">#{node.title}</#{tag_name}>)
314
+ end
315
315
 
316
- def listing node
317
- figure_classes = ['listing']
318
- figure_classes << 'coalesce' if node.option? 'unbreakable'
319
- pre_classes = node.style == 'source' ? ['source', %(language-#{node.attr 'language'})] : ['screen']
320
- title_div = node.title? ? %(<figcaption>#{node.captioned_title}</figcaption>
316
+ def listing node
317
+ figure_classes = ['listing']
318
+ figure_classes << 'coalesce' if node.option? 'unbreakable'
319
+ pre_classes = node.style == 'source' ? ['source', %(language-#{node.attr 'language'})] : ['screen']
320
+ title_div = node.title? ? %(<figcaption>#{node.captioned_title}</figcaption>
321
321
  ) : ''
322
- # patches conums to fix extra or missing leading space
323
- # TODO remove patch once upgrading to Asciidoctor 1.5.6
324
- %(<figure class="#{figure_classes * ' '}">
322
+ # patches conums to fix extra or missing leading space
323
+ # TODO remove patch once upgrading to Asciidoctor 1.5.6
324
+ %(<figure class="#{figure_classes * ' '}">
325
325
  #{title_div}<pre class="#{pre_classes * ' '}"><code>#{(node.content || '').gsub(/(?<! )<i class="conum"| +<i class="conum"/, ' <i class="conum"')}</code></pre>
326
326
  </figure>)
327
- end
327
+ end
328
328
 
329
- # QUESTION should we wrap the <pre> in either <div> or <figure>?
330
- def literal node
331
- %(<pre class="screen">#{node.content}</pre>)
332
- end
329
+ # QUESTION should we wrap the <pre> in either <div> or <figure>?
330
+ def literal node
331
+ %(<pre class="screen">#{node.content}</pre>)
332
+ end
333
333
 
334
- def page_break node
335
- '<hr epub:type="pagebreak" class="pagebreak"/>'
336
- end
334
+ def page_break _node
335
+ '<hr epub:type="pagebreak" class="pagebreak"/>'
336
+ end
337
337
 
338
- def thematic_break node
339
- '<hr class="thematicbreak"/>'
340
- end
338
+ def thematic_break _node
339
+ '<hr class="thematicbreak"/>'
340
+ end
341
341
 
342
- def quote node
343
- id_attr = %( id="#{node.id}") if node.id
344
- class_attr = (role = node.role) ? %( class="blockquote #{role}") : ' class="blockquote"'
342
+ def quote node
343
+ id_attr = %( id="#{node.id}") if node.id
344
+ class_attr = (role = node.role) ? %( class="blockquote #{role}") : ' class="blockquote"'
345
345
 
346
- footer_content = []
347
- if (attribution = node.attr 'attribution')
348
- footer_content << attribution
349
- end
346
+ footer_content = []
347
+ if (attribution = node.attr 'attribution')
348
+ footer_content << attribution
349
+ end
350
350
 
351
- if (citetitle = node.attr 'citetitle')
352
- citetitle_sanitized = xml_sanitize citetitle
353
- footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
354
- end
351
+ if (citetitle = node.attr 'citetitle')
352
+ citetitle_sanitized = xml_sanitize citetitle
353
+ footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
354
+ end
355
355
 
356
- if node.title?
357
- footer_content << %(<span class="context">#{node.title}</span>)
358
- end
356
+ footer_content << %(<span class="context">#{node.title}</span>) if node.title?
359
357
 
360
- footer_tag = footer_content.empty? ? '' : %(
358
+ footer_tag = footer_content.empty? ? '' : %(
361
359
  <footer>~ #{footer_content * ' '}</footer>)
362
- content = (convert_content node).strip.
363
- sub(OpenParagraphTagRx, '<p><span class="open-quote">“</span>').
364
- sub(CloseParagraphTagRx, '<span class="close-quote">”</span></p>')
365
- %(<div#{id_attr}#{class_attr}>
360
+ content = ((convert_content node).strip.sub OpenParagraphTagRx, '<p><span class="open-quote">“</span>').sub CloseParagraphTagRx, '<span class="close-quote">”</span></p>'
361
+ %(<div#{id_attr}#{class_attr}>
366
362
  <blockquote>
367
363
  #{content}#{footer_tag}
368
364
  </blockquote>
369
365
  </div>)
370
- end
366
+ end
371
367
 
372
- def verse node
373
- id_attr = %( id="#{node.id}") if node.id
374
- class_attr = (role = node.role) ? %( class="verse #{role}") : ' class="verse"'
368
+ def verse node
369
+ id_attr = %( id="#{node.id}") if node.id
370
+ class_attr = (role = node.role) ? %( class="verse #{role}") : ' class="verse"'
375
371
 
376
- footer_content = []
377
- if (attribution = node.attr 'attribution')
378
- footer_content << attribution
379
- end
372
+ footer_content = []
373
+ if (attribution = node.attr 'attribution')
374
+ footer_content << attribution
375
+ end
380
376
 
381
- if (citetitle = node.attr 'citetitle')
382
- citetitle_sanitized = xml_sanitize citetitle
383
- footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
384
- end
377
+ if (citetitle = node.attr 'citetitle')
378
+ citetitle_sanitized = xml_sanitize citetitle
379
+ footer_content << %(<cite title="#{citetitle_sanitized}">#{citetitle}</cite>)
380
+ end
385
381
 
386
- footer_tag = footer_content.size > 0 ? %(
382
+ footer_tag = !footer_content.empty? ? %(
387
383
  <span class="attribution">~ #{footer_content * ', '}</span>) : ''
388
- %(<div#{id_attr}#{class_attr}>
384
+ %(<div#{id_attr}#{class_attr}>
389
385
  <pre>#{node.content}#{footer_tag}</pre>
390
386
  </div>)
391
- end
387
+ end
392
388
 
393
- def sidebar node
394
- classes = ['sidebar']
395
- if node.title?
396
- classes << 'titled'
397
- title = node.title
398
- title_sanitized = xml_sanitize title
399
- title_attr = %( title="#{title_sanitized}")
400
- title_el = %(<h2>#{title}</h2>
389
+ def sidebar node
390
+ classes = ['sidebar']
391
+ if node.title?
392
+ classes << 'titled'
393
+ title = node.title
394
+ title_sanitized = xml_sanitize title
395
+ title_attr = %( title="#{title_sanitized}")
396
+ title_el = %(<h2>#{title}</h2>
401
397
  )
402
- else
403
- title_attr = title_el = ''
404
- end
398
+ else
399
+ title_attr = title_el = ''
400
+ end
405
401
 
406
- %(<aside class="#{classes * ' '}"#{title_attr} epub:type="sidebar">
402
+ %(<aside class="#{classes * ' '}"#{title_attr} epub:type="sidebar">
407
403
  #{title_el}<div class="content">
408
404
  #{convert_content node}
409
405
  </div>
410
406
  </aside>)
411
- end
407
+ end
412
408
 
413
- def table node
414
- lines = [%(<div class="table">)]
415
- lines << %(<div class="content">)
416
- table_id_attr = node.id ? %( id="#{node.id}") : ''
417
- frame_class = {
418
- 'all' => 'table-framed',
419
- 'topbot' => 'table-framed-topbot',
420
- 'sides' => 'table-framed-sides',
421
- 'none' => ''
422
- }
423
- grid_class = {
424
- 'all' => 'table-grid',
425
- 'rows' => 'table-grid-rows',
426
- 'cols' => 'table-grid-cols',
427
- 'none' => ''
428
- }
429
- table_classes = %W(table #{frame_class[node.attr 'frame'] || frame_class['topbot']} #{grid_class[node.attr 'grid'] || grid_class['rows']})
430
- if (role = node.role)
431
- table_classes << role
432
- end
433
- table_class_attr = %( class="#{table_classes * ' '}")
434
- table_styles = []
435
- unless (node.option? 'autowidth') && !(node.attr? 'width', nil, false)
436
- table_styles << %(width: #{node.attr 'tablepcwidth'}%)
437
- end
438
- table_style_attr = table_styles.size > 0 ? %( style="#{table_styles * '; '}") : ''
439
-
440
- lines << %(<table#{table_id_attr}#{table_class_attr}#{table_style_attr}>)
441
- lines << %(<caption>#{node.captioned_title}</caption>) if node.title?
442
- if (node.attr 'rowcount') > 0
443
- lines << '<colgroup>'
444
- #if node.option? 'autowidth'
445
- tag = %(<col/>)
446
- node.columns.size.times do
447
- lines << tag
448
- end
449
- #else
450
- # node.columns.each do |col|
451
- # lines << %(<col style="width: #{col.attr 'colpcwidth'}%"/>)
452
- # end
453
- #end
454
- lines << '</colgroup>'
455
- [:head, :foot, :body].select {|tsec| !node.rows[tsec].empty? }.each do |tsec|
456
- lines << %(<t#{tsec}>)
457
- node.rows[tsec].each do |row|
458
- lines << '<tr>'
459
- row.each do |cell|
460
- if tsec == :head
461
- cell_content = cell.text
462
- else
463
- case cell.style
464
- when :asciidoc
465
- cell_content = %(<div class="embed">#{cell.content}</div>)
466
- when :verse
467
- cell_content = %(<div class="verse">#{cell.text}</div>)
468
- when :literal
469
- cell_content = %(<div class="literal"><pre>#{cell.text}</pre></div>)
470
- else
471
- cell_content = ''
472
- cell.content.each do |text|
473
- cell_content = %(#{cell_content}<p>#{text}</p>)
409
+ def table node
410
+ lines = [%(<div class="table">)]
411
+ lines << %(<div class="content">)
412
+ table_id_attr = node.id ? %( id="#{node.id}") : ''
413
+ frame_class = {
414
+ 'all' => 'table-framed',
415
+ 'topbot' => 'table-framed-topbot',
416
+ 'sides' => 'table-framed-sides',
417
+ 'none' => '',
418
+ }
419
+ grid_class = {
420
+ 'all' => 'table-grid',
421
+ 'rows' => 'table-grid-rows',
422
+ 'cols' => 'table-grid-cols',
423
+ 'none' => '',
424
+ }
425
+ table_classes = %W[table #{frame_class[node.attr 'frame'] || frame_class['topbot']} #{grid_class[node.attr 'grid'] || grid_class['rows']}]
426
+ if (role = node.role)
427
+ table_classes << role
428
+ end
429
+ table_class_attr = %( class="#{table_classes * ' '}")
430
+ table_styles = []
431
+ table_styles << %(width: #{node.attr 'tablepcwidth'}%) unless (node.option? 'autowidth') && !(node.attr? 'width', nil, false)
432
+ table_style_attr = !table_styles.empty? ? %( style="#{table_styles * '; '}") : ''
433
+
434
+ lines << %(<table#{table_id_attr}#{table_class_attr}#{table_style_attr}>)
435
+ lines << %(<caption>#{node.captioned_title}</caption>) if node.title?
436
+ if (node.attr 'rowcount') > 0
437
+ lines << '<colgroup>'
438
+ #if node.option? 'autowidth'
439
+ tag = %(<col/>)
440
+ node.columns.size.times do
441
+ lines << tag
442
+ end
443
+ #else
444
+ # node.columns.each do |col|
445
+ # lines << %(<col style="width: #{col.attr 'colpcwidth'}%"/>)
446
+ # end
447
+ #end
448
+ lines << '</colgroup>'
449
+ [:head, :foot, :body].reject {|tsec| node.rows[tsec].empty? }.each do |tsec|
450
+ lines << %(<t#{tsec}>)
451
+ node.rows[tsec].each do |row|
452
+ lines << '<tr>'
453
+ row.each do |cell|
454
+ if tsec == :head
455
+ cell_content = cell.text
456
+ else
457
+ case cell.style
458
+ when :asciidoc
459
+ cell_content = %(<div class="embed">#{cell.content}</div>)
460
+ when :verse
461
+ cell_content = %(<div class="verse">#{cell.text}</div>)
462
+ when :literal
463
+ cell_content = %(<div class="literal"><pre>#{cell.text}</pre></div>)
464
+ else
465
+ cell_content = ''
466
+ cell.content.each do |text|
467
+ cell_content = %(#{cell_content}<p>#{text}</p>)
468
+ end
469
+ end
474
470
  end
475
- end
476
- end
477
471
 
478
- cell_tag_name = (tsec == :head || cell.style == :header ? 'th' : 'td')
479
- cell_classes = []
480
- if (halign = cell.attr 'halign') && halign != 'left'
481
- cell_classes << 'halign-left'
482
- end
483
- if (halign = cell.attr 'valign') && halign != 'top'
484
- cell_classes << 'valign-top'
472
+ cell_tag_name = tsec == :head || cell.style == :header ? 'th' : 'td'
473
+ cell_classes = []
474
+ if (halign = cell.attr 'halign') && halign != 'left'
475
+ cell_classes << 'halign-left'
476
+ end
477
+ if (halign = cell.attr 'valign') && halign != 'top'
478
+ cell_classes << 'valign-top'
479
+ end
480
+ cell_class_attr = !cell_classes.empty? ? %( class="#{cell_classes * ' '}") : ''
481
+ cell_colspan_attr = cell.colspan ? %( colspan="#{cell.colspan}") : ''
482
+ cell_rowspan_attr = cell.rowspan ? %( rowspan="#{cell.rowspan}") : ''
483
+ cell_style_attr = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'}") : ''
484
+ lines << %(<#{cell_tag_name}#{cell_class_attr}#{cell_colspan_attr}#{cell_rowspan_attr}#{cell_style_attr}>#{cell_content}</#{cell_tag_name}>)
485
+ end
486
+ lines << '</tr>'
485
487
  end
486
- cell_class_attr = cell_classes.size > 0 ? %( class="#{cell_classes * ' '}") : ''
487
- cell_colspan_attr = cell.colspan ? %( colspan="#{cell.colspan}") : ''
488
- cell_rowspan_attr = cell.rowspan ? %( rowspan="#{cell.rowspan}") : ''
489
- cell_style_attr = (node.document.attr? 'cellbgcolor') ? %( style="background-color: #{node.document.attr 'cellbgcolor'}") : ''
490
- lines << %(<#{cell_tag_name}#{cell_class_attr}#{cell_colspan_attr}#{cell_rowspan_attr}#{cell_style_attr}>#{cell_content}</#{cell_tag_name}>)
488
+ lines << %(</t#{tsec}>)
491
489
  end
492
- lines << '</tr>'
493
490
  end
494
- lines << %(</t#{tsec}>)
495
- end
496
- end
497
- lines << '</table>
491
+ lines << '</table>
498
492
  </div>
499
493
  </div>'
500
- lines * LF
501
- end
494
+ lines * LF
495
+ end
502
496
 
503
- def colist node
504
- lines = ['<div class="callout-list">
497
+ def colist node
498
+ lines = ['<div class="callout-list">
505
499
  <ol>']
506
- num = CalloutStartNum
507
- node.items.each_with_index do |item, i|
508
- lines << %(<li><i class="conum" data-value="#{i + 1}">#{num}</i> #{item.text}</li>)
509
- num = num.next
510
- end
511
- lines << '</ol>
500
+ num = CalloutStartNum
501
+ node.items.each_with_index do |item, i|
502
+ lines << %(<li><i class="conum" data-value="#{i + 1}">#{num}</i> #{item.text}</li>)
503
+ num = num.next
504
+ end
505
+ lines << '</ol>
512
506
  </div>'
513
- end
507
+ end
514
508
 
515
- # TODO add complex class if list has nested blocks
516
- def dlist node
517
- lines = []
518
- case (style = node.style)
519
- when 'itemized', 'ordered'
520
- list_tag_name = (style == 'itemized' ? 'ul' : 'ol')
521
- role = node.role
522
- subject_stop = node.attr 'subject-stop', (role && (node.has_role? 'stack') ? nil : ':')
523
- # QUESTION should we just use itemized-list and ordered-list as the class here? or just list?
524
- div_classes = [%(#{style}-list), role].compact
525
- list_class_attr = (node.option? 'brief') ? ' class="brief"' : ''
526
- lines << %(<div class="#{div_classes * ' '}">
509
+ # TODO: add complex class if list has nested blocks
510
+ def dlist node
511
+ lines = []
512
+ case (style = node.style)
513
+ when 'itemized', 'ordered'
514
+ list_tag_name = style == 'itemized' ? 'ul' : 'ol'
515
+ role = node.role
516
+ subject_stop = node.attr 'subject-stop', (role && (node.has_role? 'stack') ? nil : ':')
517
+ # QUESTION should we just use itemized-list and ordered-list as the class here? or just list?
518
+ div_classes = [%(#{style}-list), role].compact
519
+ list_class_attr = (node.option? 'brief') ? ' class="brief"' : ''
520
+ lines << %(<div class="#{div_classes * ' '}">
527
521
  <#{list_tag_name}#{list_class_attr}#{list_tag_name == 'ol' && (node.option? 'reversed') ? ' reversed="reversed"' : ''}>)
528
- node.items.each do |subjects, dd|
529
- # consists of one term (a subject) and supporting content
530
- subject = [*subjects].first.text
531
- subject_plain = xml_sanitize subject, :plain
532
- subject_element = %(<strong class="subject">#{subject}#{subject_stop && subject_plain !~ TrailingPunctRx ? subject_stop : ''}</strong>)
533
- lines << '<li>'
534
- if dd
535
- # NOTE: must wrap remaining text in a span to help webkit justify the text properly
536
- lines << %(<span class="principal">#{subject_element}#{dd.text? ? %[ <span class="supporting">#{dd.text}</span>] : ''}</span>)
537
- lines << dd.content if dd.blocks?
538
- else
539
- lines << %(<span class="principal">#{subject_element}</span>)
540
- end
541
- lines << '</li>'
542
- end
543
- lines << %(</#{list_tag_name}>
522
+ node.items.each do |subjects, dd|
523
+ # consists of one term (a subject) and supporting content
524
+ subject = [*subjects].first.text
525
+ subject_plain = xml_sanitize subject, :plain
526
+ subject_element = %(<strong class="subject">#{subject}#{subject_stop && subject_plain !~ TrailingPunctRx ? subject_stop : ''}</strong>)
527
+ lines << '<li>'
528
+ if dd
529
+ # NOTE: must wrap remaining text in a span to help webkit justify the text properly
530
+ lines << %(<span class="principal">#{subject_element}#{dd.text? ? %( <span class="supporting">#{dd.text}</span>) : ''}</span>)
531
+ lines << dd.content if dd.blocks?
532
+ else
533
+ lines << %(<span class="principal">#{subject_element}</span>)
534
+ end
535
+ lines << '</li>'
536
+ end
537
+ lines << %(</#{list_tag_name}>
544
538
  </div>)
545
- else
546
- lines << '<div class="description-list">
539
+ else
540
+ lines << '<div class="description-list">
547
541
  <dl>'
548
- node.items.each do |terms, dd|
549
- [*terms].each do |dt|
550
- lines << %(<dt>
542
+ node.items.each do |terms, dd|
543
+ [*terms].each do |dt|
544
+ lines << %(<dt>
551
545
  <span class="term">#{dt.text}</span>
552
546
  </dt>)
553
- end
554
- if dd
555
- lines << '<dd>'
556
- if dd.blocks?
557
- lines << %(<span class="principal">#{dd.text}</span>) if dd.text?
558
- lines << dd.content
559
- else
560
- lines << %(<span class="principal">#{dd.text}</span>)
547
+ end
548
+ next unless dd
549
+ lines << '<dd>'
550
+ if dd.blocks?
551
+ lines << %(<span class="principal">#{dd.text}</span>) if dd.text?
552
+ lines << dd.content
553
+ else
554
+ lines << %(<span class="principal">#{dd.text}</span>)
555
+ end
556
+ lines << '</dd>'
561
557
  end
562
- lines << '</dd>'
558
+ lines << '</dl>
559
+ </div>'
563
560
  end
561
+ lines * LF
564
562
  end
565
- lines << '</dl>
566
- </div>'
567
- end
568
- lines * LF
569
- end
570
563
 
571
- def olist node
572
- complex = false
573
- div_classes = ['ordered-list', node.style, node.role].compact
574
- ol_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
575
- ol_class_attr = ol_classes.empty? ? '' : %( class="#{ol_classes * ' '}")
576
- ol_start_attr = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : ''
577
- id_attribute = node.id ? %( id="#{node.id}") : ''
578
- lines = [%(<div#{id_attribute} class="#{div_classes * ' '}">)]
579
- lines << %(<h3 class="list-heading">#{node.title}</h3>) if node.title?
580
- lines << %(<ol#{ol_class_attr}#{ol_start_attr}#{(node.option? 'reversed') ? ' reversed="reversed"' : ''}>)
581
- node.items.each do |item|
582
- lines << %(<li>
564
+ def olist node
565
+ complex = false
566
+ div_classes = ['ordered-list', node.style, node.role].compact
567
+ ol_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
568
+ ol_class_attr = ol_classes.empty? ? '' : %( class="#{ol_classes * ' '}")
569
+ ol_start_attr = (node.attr? 'start') ? %( start="#{node.attr 'start'}") : ''
570
+ id_attribute = node.id ? %( id="#{node.id}") : ''
571
+ lines = [%(<div#{id_attribute} class="#{div_classes * ' '}">)]
572
+ lines << %(<h3 class="list-heading">#{node.title}</h3>) if node.title?
573
+ lines << %(<ol#{ol_class_attr}#{ol_start_attr}#{(node.option? 'reversed') ? ' reversed="reversed"' : ''}>)
574
+ node.items.each do |item|
575
+ lines << %(<li>
583
576
  <span class="principal">#{item.text}</span>)
584
- if item.blocks?
585
- lines << item.content
586
- complex = true unless item.blocks.size == 1 && ::Asciidoctor::List === item.blocks[0]
587
- end
588
- lines << '</li>'
589
- end
590
- if complex
591
- div_classes << 'complex'
592
- lines[0] = %(<div class="#{div_classes * ' '}">)
593
- end
594
- lines << '</ol>
577
+ if item.blocks?
578
+ lines << item.content
579
+ complex = true unless item.blocks.size == 1 && ::Asciidoctor::List === item.blocks[0]
580
+ end
581
+ lines << '</li>'
582
+ end
583
+ if complex
584
+ div_classes << 'complex'
585
+ lines[0] = %(<div class="#{div_classes * ' '}">)
586
+ end
587
+ lines << '</ol>
595
588
  </div>'
596
- lines * LF
597
- end
589
+ lines * LF
590
+ end
598
591
 
599
- def ulist node
600
- complex = false
601
- div_classes = ['itemized-list', node.style, node.role].compact
602
- ul_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
603
- ul_class_attr = ul_classes.empty? ? '' : %( class="#{ul_classes * ' '}")
604
- id_attribute = node.id ? %( id="#{node.id}") : ''
605
- lines = [%(<div#{id_attribute} class="#{div_classes * ' '}">)]
606
- lines << %(<h3 class="list-heading">#{node.title}</h3>) if node.title?
607
- lines << %(<ul#{ul_class_attr}>)
608
- node.items.each do |item|
609
- lines << %(<li>
592
+ def ulist node
593
+ complex = false
594
+ div_classes = ['itemized-list', node.style, node.role].compact
595
+ ul_classes = [node.style, ((node.option? 'brief') ? 'brief' : nil)].compact
596
+ ul_class_attr = ul_classes.empty? ? '' : %( class="#{ul_classes * ' '}")
597
+ id_attribute = node.id ? %( id="#{node.id}") : ''
598
+ lines = [%(<div#{id_attribute} class="#{div_classes * ' '}">)]
599
+ lines << %(<h3 class="list-heading">#{node.title}</h3>) if node.title?
600
+ lines << %(<ul#{ul_class_attr}>)
601
+ node.items.each do |item|
602
+ lines << %(<li>
610
603
  <span class="principal">#{item.text}</span>)
611
- if item.blocks?
612
- lines << item.content
613
- complex = true unless item.blocks.size == 1 && ::Asciidoctor::List === item.blocks[0]
614
- end
615
- lines << '</li>'
616
- end
617
- if complex
618
- div_classes << 'complex'
619
- lines[0] = %(<div class="#{div_classes * ' '}">)
620
- end
621
- lines << '</ul>
604
+ if item.blocks?
605
+ lines << item.content
606
+ complex = true unless item.blocks.size == 1 && ::Asciidoctor::List === item.blocks[0]
607
+ end
608
+ lines << '</li>'
609
+ end
610
+ if complex
611
+ div_classes << 'complex'
612
+ lines[0] = %(<div class="#{div_classes * ' '}">)
613
+ end
614
+ lines << '</ul>
622
615
  </div>'
623
- lines * LF
624
- end
625
-
626
- def image node
627
- target = node.attr 'target'
628
- type = (::File.extname target)[1..-1]
629
- id_attr = node.id ? %( id="#{node.id}") : ''
630
- img_attrs = [%(alt="#{node.attr 'alt'}")]
631
- case type
632
- when 'svg'
633
- img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
634
- # TODO make this a convenience method on document
635
- epub_properties = (node.document.attributes['epub-properties'] ||= [])
636
- epub_properties << 'svg' unless epub_properties.include? 'svg'
637
- else
638
- if node.attr? 'scaledwidth'
639
- img_attrs << %(style="width: #{node.attr 'scaledwidth'}")
616
+ lines * LF
640
617
  end
641
- end
618
+
619
+ def image node
620
+ target = node.attr 'target'
621
+ type = (::File.extname target)[1..-1]
622
+ id_attr = node.id ? %( id="#{node.id}") : ''
623
+ img_attrs = [%(alt="#{node.attr 'alt'}")]
624
+ case type
625
+ when 'svg'
626
+ img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
627
+ # TODO: make this a convenience method on document
628
+ epub_properties = (node.document.attributes['epub-properties'] ||= [])
629
+ epub_properties << 'svg' unless epub_properties.include? 'svg'
630
+ else
631
+ img_attrs << %(style="width: #{node.attr 'scaledwidth'}") if node.attr? 'scaledwidth'
632
+ end
642
633
  =begin
643
634
  # NOTE to set actual width and height, use CSS width and height
644
635
  if type == 'svg'
@@ -656,303 +647,300 @@ document.addEventListener('DOMContentLoaded', function(event, reader) {
656
647
  end
657
648
  end
658
649
  =end
659
- %(<figure#{id_attr} class="image#{prepend_space node.role}">
650
+ %(<figure#{id_attr} class="image#{prepend_space node.role}">
660
651
  <div class="content">
661
652
  <img src="#{node.image_uri node.attr('target')}" #{img_attrs * ' '}/>
662
- </div>#{node.title? ? %[
663
- <figcaption>#{node.captioned_title}</figcaption>] : ''}
653
+ </div>#{node.title? ? %(
654
+ <figcaption>#{node.captioned_title}</figcaption>) : ''}
664
655
  </figure>)
665
- end
656
+ end
666
657
 
667
- def inline_anchor node
668
- target = node.target
669
- case node.type
670
- when :xref # TODO would be helpful to know what type the target is (e.g., bibref)
671
- doc, refid, text, path = node.document, ((node.attr 'refid') || target), node.text, (node.attr 'path')
672
- # NOTE if path is non-nil, we have an inter-document xref
673
- # QUESTION should we drop the id attribute for an inter-document xref?
674
- if path
675
- # ex. chapter-id#section-id
676
- if node.attr 'fragment'
677
- refdoc_id, refdoc_refid = refid.split '#', 2
678
- if refdoc_id == refdoc_refid
679
- target = target[0...(target.index '#')]
680
- id_attr = %( id="xref--#{refdoc_id}")
658
+ def inline_anchor node
659
+ target = node.target
660
+ case node.type
661
+ when :xref # TODO: would be helpful to know what type the target is (e.g., bibref)
662
+ doc, refid, text, path = node.document, ((node.attr 'refid') || target), node.text, (node.attr 'path')
663
+ # NOTE if path is non-nil, we have an inter-document xref
664
+ # QUESTION should we drop the id attribute for an inter-document xref?
665
+ if path
666
+ # ex. chapter-id#section-id
667
+ if node.attr 'fragment'
668
+ refdoc_id, refdoc_refid = refid.split '#', 2
669
+ if refdoc_id == refdoc_refid
670
+ target = target[0...(target.index '#')]
671
+ id_attr = %( id="xref--#{refdoc_id}")
672
+ else
673
+ id_attr = %( id="xref--#{refdoc_id}--#{refdoc_refid}")
674
+ end
675
+ # ex. chapter-id#
676
+ else
677
+ refdoc_id = refdoc_refid = refid
678
+ # inflate key to spine item root (e.g., transform chapter-id to chapter-id#chapter-id)
679
+ refid = %(#{refid}##{refid})
680
+ id_attr = %( id="xref--#{refdoc_id}")
681
+ end
682
+ id_attr = '' unless @xrefs_seen.add? refid
683
+ refdoc = doc.references[:spine_items].find {|it| refdoc_id == (it.id || (it.attr 'docname')) }
684
+ if refdoc
685
+ if (refs = refdoc.references[:refs]) && ::Asciidoctor::AbstractNode === (ref = refs[refdoc_refid])
686
+ text ||= ::Asciidoctor::Document === ref ? ((ref.attr 'docreftext') || ref.doctitle) : ref.xreftext((@xrefstyle ||= (doc.attr 'xrefstyle')))
687
+ elsif (xreftext = refdoc.references[:ids][refdoc_refid])
688
+ text ||= xreftext
689
+ else
690
+ warn %(asciidoctor: WARNING: #{::File.basename doc.attr('docfile')}: invalid reference to unknown anchor in #{refdoc_id} chapter: #{refdoc_refid})
691
+ end
692
+ else
693
+ warn %(asciidoctor: WARNING: #{::File.basename doc.attr('docfile')}: invalid reference to anchor in unknown chapter: #{refdoc_id})
694
+ end
681
695
  else
682
- id_attr = %( id="xref--#{refdoc_id}--#{refdoc_refid}")
696
+ id_attr = (@xrefs_seen.add? refid) ? %( id="xref-#{refid}") : ''
697
+ if (refs = doc.references[:refs])
698
+ if ::Asciidoctor::AbstractNode === (ref = refs[refid])
699
+ xreftext = text || ref.xreftext((@xrefstyle ||= (doc.attr 'xrefstyle')))
700
+ end
701
+ else
702
+ xreftext = doc.references[:ids][refid]
703
+ end
704
+
705
+ if xreftext
706
+ text ||= xreftext
707
+ else
708
+ # FIXME: we get false negatives for reference to bibref when using Asciidoctor < 1.5.6
709
+ warn %(asciidoctor: WARNING: #{::File.basename doc.attr('docfile')}: invalid reference to unknown local anchor (or valid bibref): #{refid})
710
+ end
683
711
  end
684
- # ex. chapter-id#
685
- else
686
- refdoc_id = refdoc_refid = refid
687
- # inflate key to spine item root (e.g., transform chapter-id to chapter-id#chapter-id)
688
- refid = %(#{refid}##{refid})
689
- id_attr = %( id="xref--#{refdoc_id}")
690
- end
691
- id_attr = '' unless @xrefs_seen.add? refid
692
- refdoc = doc.references[:spine_items].find {|it| refdoc_id == (it.id || (it.attr 'docname')) }
693
- if refdoc
694
- # QUESTION should we invoke xreftext for references in other documents?
695
- if (refs = refdoc.references[:refs]) && ::Asciidoctor::Document === (ref = refs[refdoc_refid])
696
- text ||= (ref.attr 'docreftext') || ref.doctitle
697
- elsif (xreftext = refdoc.references[:ids][refdoc_refid])
698
- text ||= xreftext
712
+ %(<a#{id_attr} href="#{target}" class="xref">#{text || "[#{refid}]"}</a>)
713
+ when :ref
714
+ %(<a id="#{target}"></a>)
715
+ when :link
716
+ %(<a href="#{target}" class="link">#{node.text}</a>)
717
+ when :bibref
718
+ if @xrefs_seen.include? target
719
+ %(<a id="#{target}" href="#xref-#{target}">[#{target}]</a>)
699
720
  else
700
- warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to unknown anchor in #{refdoc_id} chapter: #{refdoc_refid})
701
- end
702
- else
703
- warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to anchor in unknown chapter: #{refdoc_id})
704
- end
705
- else
706
- id_attr = (@xrefs_seen.add? refid) ? %( id="xref-#{refid}") : ''
707
- if (refs = doc.references[:refs])
708
- if ::Asciidoctor::AbstractNode === (ref = refs[refid])
709
- xreftext = text || ref.xreftext((@xrefstyle ||= (doc.attr 'xrefstyle')))
721
+ %(<a id="#{target}"></a>[#{target}])
710
722
  end
711
- else
712
- xreftext = doc.references[:ids][refid]
713
723
  end
724
+ end
714
725
 
715
- if xreftext
716
- text ||= xreftext
717
- else
718
- # FIXME we get false negatives for reference to bibref when using Asciidoctor < 1.5.6
719
- warn %(asciidoctor: WARNING: #{::File.basename(doc.attr 'docfile')}: invalid reference to unknown local anchor (or valid bibref): #{refid})
720
- end
721
- end
722
- %(<a#{id_attr} href="#{target}" class="xref">#{text || "[#{refid}]"}</a>)
723
- when :ref
724
- %(<a id="#{target}"></a>)
725
- when :link
726
- %(<a href="#{target}" class="link">#{node.text}</a>)
727
- when :bibref
728
- if @xrefs_seen.include? target
729
- %(<a id="#{target}" href="#xref-#{target}">[#{target}]</a>)
730
- else
731
- %(<a id="#{target}"></a>[#{target}])
726
+ def inline_break node
727
+ %(#{node.text}<br/>)
732
728
  end
733
- end
734
- end
735
729
 
736
- def inline_break node
737
- %(#{node.text}<br/>)
738
- end
730
+ def inline_button node
731
+ %(<b class="button">[<span class="label">#{node.text}</span>]</b>)
732
+ end
739
733
 
740
- def inline_button node
741
- %(<b class="button">[<span class="label">#{node.text}</span>]</b>)
742
- end
734
+ def inline_callout node
735
+ num = CalloutStartNum
736
+ int_num = node.text.to_i
737
+ (int_num - 1).times { num = num.next }
738
+ %(<i class="conum" data-value="#{int_num}">#{num}</i>)
739
+ end
743
740
 
744
- def inline_callout node
745
- num = CalloutStartNum
746
- int_num = node.text.to_i
747
- (int_num - 1).times { num = num.next }
748
- %(<i class="conum" data-value="#{int_num}">#{num}</i>)
749
- end
741
+ def inline_footnote node
742
+ if (index = node.attr 'index')
743
+ %(<sup class="noteref">[<a id="noteref-#{index}" href="#note-#{index}" epub:type="noteref">#{index}</a>]</sup>)
744
+ elsif node.type == :xref
745
+ %(<mark class="noteref" title="Unresolved note reference">#{node.text}</mark>)
746
+ end
747
+ end
750
748
 
751
- def inline_footnote node
752
- if (index = node.attr 'index')
753
- %(<sup class="noteref">[<a id="noteref-#{index}" href="#note-#{index}" epub:type="noteref">#{index}</a>]</sup>)
754
- elsif node.type == :xref
755
- %(<mark class="noteref" title="Unresolved note reference">#{node.text}</mark>)
756
- end
757
- end
749
+ def inline_image node
750
+ if node.type == 'icon'
751
+ @icon_names << (icon_name = node.target)
752
+ i_classes = ['icon', %(i-#{icon_name})]
753
+ i_classes << %(icon-#{node.attr 'size'}) if node.attr? 'size'
754
+ i_classes << %(icon-flip-#{(node.attr 'flip')[0]}) if node.attr? 'flip'
755
+ i_classes << %(icon-rotate-#{node.attr 'rotate'}) if node.attr? 'rotate'
756
+ i_classes << node.role if node.role?
757
+ %(<i class="#{i_classes * ' '}"></i>)
758
+ else
759
+ target = node.image_uri node.target
760
+ img_attrs = [%(alt="#{node.attr 'alt'}"), %(class="inline#{node.role? ? " #{node.role}" : ''}")]
761
+ if target.end_with? '.svg'
762
+ img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
763
+ # TODO: make this a convenience method on document
764
+ epub_properties = (node.document.attributes['epub-properties'] ||= [])
765
+ epub_properties << 'svg' unless epub_properties.include? 'svg'
766
+ elsif node.attr? 'scaledwidth'
767
+ img_attrs << %(style="width: #{node.attr 'scaledwidth'}")
768
+ end
769
+ %(<img src="#{target}" #{img_attrs * ' '}/>)
770
+ end
771
+ end
758
772
 
759
- def inline_image node
760
- if node.type == 'icon'
761
- @icon_names << (icon_name = node.target)
762
- i_classes = ['icon', %(i-#{icon_name})]
763
- i_classes << %(icon-#{node.attr 'size'}) if node.attr? 'size'
764
- i_classes << %(icon-flip-#{(node.attr 'flip')[0]}) if node.attr? 'flip'
765
- i_classes << %(icon-rotate-#{node.attr 'rotate'}) if node.attr? 'rotate'
766
- i_classes << node.role if node.role?
767
- %(<i class="#{i_classes * ' '}"></i>)
768
- else
769
- target = node.image_uri node.target
770
- img_attrs = [%(alt="#{node.attr 'alt'}"), %(class="inline#{node.role? ? " #{node.role}" : ''}")]
771
- if target.end_with? '.svg'
772
- img_attrs << %(style="width: #{node.attr 'scaledwidth', '100%'}")
773
- # TODO make this a convenience method on document
774
- epub_properties = (node.document.attributes['epub-properties'] ||= [])
775
- epub_properties << 'svg' unless epub_properties.include? 'svg'
776
- elsif node.attr? 'scaledwidth'
777
- img_attrs << %(style="width: #{node.attr 'scaledwidth'}")
778
- end
779
- %(<img src="#{target}" #{img_attrs * ' '}/>)
780
- end
781
- end
773
+ def inline_indexterm node
774
+ node.type == :visible ? node.text : ''
775
+ end
782
776
 
783
- def inline_indexterm node
784
- node.type == :visible ? node.text : ''
785
- end
777
+ def inline_kbd node
778
+ if (keys = node.attr 'keys').size == 1
779
+ %(<kbd>#{keys[0]}</kbd>)
780
+ else
781
+ key_combo = keys.map {|key| %(<kbd>#{key}</kbd>) }.join '+'
782
+ %(<span class="keyseq">#{key_combo}</span>)
783
+ end
784
+ end
786
785
 
787
- def inline_kbd node
788
- if (keys = node.attr 'keys').size == 1
789
- %(<kbd>#{keys[0]}</kbd>)
790
- else
791
- key_combo = keys.map {|key| %(<kbd>#{key}</kbd>) }.join '+'
792
- %(<span class="keyseq">#{key_combo}</span>)
793
- end
794
- end
786
+ def inline_menu node
787
+ menu = node.attr 'menu'
788
+ # NOTE we swap right angle quote with chevron right from FontAwesome using CSS
789
+ caret = %(#{NoBreakSpace}<span class="caret">#{RightAngleQuote}</span> )
790
+ if !(submenus = node.attr 'submenus').empty?
791
+ submenu_path = submenus.map {|submenu| %(<span class="submenu">#{submenu}</span>#{caret}) }.join.chop
792
+ %(<span class="menuseq"><span class="menu">#{menu}</span>#{caret}#{submenu_path} <span class="menuitem">#{node.attr 'menuitem'}</span></span>)
793
+ elsif (menuitem = node.attr 'menuitem')
794
+ %(<span class="menuseq"><span class="menu">#{menu}</span>#{caret}<span class="menuitem">#{menuitem}</span></span>)
795
+ else
796
+ %(<span class="menu">#{menu}</span>)
797
+ end
798
+ end
795
799
 
796
- def inline_menu node
797
- menu = node.attr 'menu'
798
- # NOTE we swap right angle quote with chevron right from FontAwesome using CSS
799
- caret = %(#{NoBreakSpace}<span class="caret">#{RightAngleQuote}</span> )
800
- if !(submenus = node.attr 'submenus').empty?
801
- submenu_path = submenus.map {|submenu| %(<span class="submenu">#{submenu}</span>#{caret}) }.join.chop
802
- %(<span class="menuseq"><span class="menu">#{menu}</span>#{caret}#{submenu_path} <span class="menuitem">#{node.attr 'menuitem'}</span></span>)
803
- elsif (menuitem = node.attr 'menuitem')
804
- %(<span class="menuseq"><span class="menu">#{menu}</span>#{caret}<span class="menuitem">#{menuitem}</span></span>)
805
- else
806
- %(<span class="menu">#{menu}</span>)
807
- end
808
- end
800
+ def inline_quoted node
801
+ case node.type
802
+ when :strong
803
+ %(<strong>#{node.text}</strong>)
804
+ when :emphasis
805
+ %(<em>#{node.text}</em>)
806
+ when :monospaced
807
+ %(<code class="literal">#{node.text}</code>)
808
+ when :double
809
+ #%(&#x201c;#{node.text}&#x201d;)
810
+ %(“#{node.text})
811
+ when :single
812
+ #%(&#x2018;#{node.text}&#x2019;)
813
+ %(‘#{node.text}’)
814
+ when :superscript
815
+ %(<sup>#{node.text}</sup>)
816
+ when :subscript
817
+ %(<sub>#{node.text}</sub>)
818
+ else
819
+ node.text
820
+ end
821
+ end
809
822
 
810
- def inline_quoted node
811
- case node.type
812
- when :strong
813
- %(<strong>#{node.text}</strong>)
814
- when :emphasis
815
- %(<em>#{node.text}</em>)
816
- when :monospaced
817
- %(<code class="literal">#{node.text}</code>)
818
- when :double
819
- #%(&#x201c;#{node.text}&#x201d;)
820
- %(“#{node.text}”)
821
- when :single
822
- #%(&#x2018;#{node.text}&#x2019;)
823
- %(‘#{node.text}’)
824
- when :superscript
825
- %(<sup>#{node.text}</sup>)
826
- when :subscript
827
- %(<sub>#{node.text}</sub>)
828
- else
829
- node.text
830
- end
831
- end
823
+ def convert_content node
824
+ node.content_model == :simple ? %(<p>#{node.content}</p>) : node.content
825
+ end
832
826
 
833
- def convert_content node
834
- node.content_model == :simple ? %(<p>#{node.content}</p>) : node.content
835
- end
827
+ # FIXME: merge into with xml_sanitize helper
828
+ def xml_sanitize value, target = :attribute
829
+ sanitized = (value.include? '<') ? value.gsub(XmlElementRx, '').strip.tr_s(' ', ' ') : value
830
+ if target == :plain && (sanitized.include? ';')
831
+ sanitized = sanitized.gsub(CharEntityRx) { [$1.to_i].pack 'U*' } if sanitized.include? '&#'
832
+ sanitized = sanitized.gsub FromHtmlSpecialCharsRx, FromHtmlSpecialCharsMap
833
+ elsif target == :attribute
834
+ sanitized = sanitized.gsub '"', '&quot;' if sanitized.include? '"'
835
+ end
836
+ sanitized
837
+ end
836
838
 
837
- # FIXME merge into with xml_sanitize helper
838
- def xml_sanitize value, target = :attribute
839
- sanitized = (value.include? '<') ? value.gsub(XmlElementRx, '').strip.tr_s(' ', ' ') : value
840
- if target == :plain && (sanitized.include? ';')
841
- sanitized = sanitized.gsub(CharEntityRx) { [$1.to_i].pack 'U*' } if sanitized.include? '&#'
842
- sanitized = sanitized.gsub(FromHtmlSpecialCharsRx, FromHtmlSpecialCharsMap)
843
- elsif target == :attribute
844
- sanitized = sanitized.gsub '"', '&quot;' if sanitized.include? '"'
845
- end
846
- sanitized
847
- end
839
+ # TODO: make check for last content paragraph a feature of Asciidoctor
840
+ def mark_last_paragraph root
841
+ return unless (last_block = root.blocks[-1])
842
+ last_block = last_block.blocks[-1] while last_block.context == :section && last_block.blocks?
843
+ if last_block.context == :paragraph
844
+ last_block.attributes['role'] = last_block.role? ? %(#{last_block.role} last) : 'last'
845
+ end
846
+ nil
847
+ end
848
848
 
849
- # TODO make check for last content paragraph a feature of Asciidoctor
850
- def mark_last_paragraph root
851
- return unless (last_block = root.blocks[-1])
852
- while last_block.context == :section && last_block.blocks?
853
- last_block = last_block.blocks[-1]
854
- end
855
- if last_block.context == :paragraph
856
- last_block.attributes['role'] = last_block.role? ? %(#{last_block.role} last) : 'last'
849
+ # Prepend a space to the value if it's non-nil, otherwise return empty string.
850
+ def prepend_space value
851
+ value ? %( #{value}) : ''
852
+ end
857
853
  end
858
- nil
859
- end
860
854
 
861
- # Prepend a space to the value if it's non-nil, otherwise return empty string.
862
- def prepend_space value
863
- value ? %( #{value}) : ''
864
- end
865
- end
866
-
867
- class DocumentIdGenerator
868
- ReservedIds = %w(cover nav ncx)
869
- CharRefRx = /&(?:([a-zA-Z][a-zA-Z]+\d{0,2})|#(\d\d\d{0,4})|#x([\da-fA-F][\da-fA-F][\da-fA-F]{0,3}));/
870
- if defined? __dir__
871
- InvalidIdCharsRx = /[^\p{Word}]+/
872
- LeadingDigitRx = /^\p{Nd}/
873
- else
874
- InvalidIdCharsRx = /[^[:word:]]+/
875
- LeadingDigitRx = /^[[:digit:]]/
876
- end
877
- class << self
878
- def generate_id doc, pre = nil, sep = nil
879
- synthetic = false
880
- unless (id = doc.id)
881
- # NOTE we assume pre is a valid ID prefix and that pre and sep only contain valid ID chars
882
- pre ||= '_'
883
- sep = sep ? sep.chr : '_'
884
- if doc.header?
885
- id = doc.doctitle sanitize: true
886
- id = id.gsub CharRefRx do
887
- $1 ? ($1 == 'amp' ? 'and' : sep) : ((d = $2 ? $2.to_i : $3.hex) == 8217 ? '' : ([d].pack 'U*'))
888
- end if id.include? '&'
889
- id = id.downcase.gsub InvalidIdCharsRx, sep
890
- if id.empty?
891
- id, synthetic = nil, true
892
- else
893
- unless sep.empty?
894
- if (id = id.tr_s sep, sep).end_with? sep
895
- if id == sep
896
- id, synthetic = nil, true
897
- else
898
- id = (id.start_with? sep) ? id[1..-2] : id.chop
855
+ class DocumentIdGenerator
856
+ ReservedIds = %w(cover nav ncx)
857
+ CharRefRx = /&(?:([a-zA-Z][a-zA-Z]+\d{0,2})|#(\d\d\d{0,4})|#x([\da-fA-F][\da-fA-F][\da-fA-F]{0,3}));/
858
+ if defined? __dir__
859
+ InvalidIdCharsRx = /[^\p{Word}]+/
860
+ LeadingDigitRx = /^\p{Nd}/
861
+ else
862
+ InvalidIdCharsRx = /[^[:word:]]+/
863
+ LeadingDigitRx = /^[[:digit:]]/
864
+ end
865
+ class << self
866
+ def generate_id doc, pre = nil, sep = nil
867
+ synthetic = false
868
+ unless (id = doc.id)
869
+ # NOTE we assume pre is a valid ID prefix and that pre and sep only contain valid ID chars
870
+ pre ||= '_'
871
+ sep = sep ? sep.chr : '_'
872
+ if doc.header?
873
+ id = doc.doctitle sanitize: true
874
+ id = id.gsub CharRefRx do
875
+ $1 ? ($1 == 'amp' ? 'and' : sep) : ((d = $2 ? $2.to_i : $3.hex) == 8217 ? '' : ([d].pack 'U*'))
876
+ end if id.include? '&'
877
+ id = id.downcase.gsub InvalidIdCharsRx, sep
878
+ if id.empty?
879
+ id, synthetic = nil, true
880
+ else
881
+ unless sep.empty?
882
+ if (id = id.tr_s sep, sep).end_with? sep
883
+ if id == sep
884
+ id, synthetic = nil, true
885
+ else
886
+ id = (id.start_with? sep) ? id[1..-2] : id.chop
887
+ end
888
+ elsif id.start_with? sep
889
+ id = id[1..-1]
890
+ end
891
+ end
892
+ unless synthetic
893
+ if pre.empty?
894
+ id = %(_#{id}) if LeadingDigitRx =~ id
895
+ elsif !(id.start_with? pre)
896
+ id = %(#{pre}#{id})
897
+ end
899
898
  end
900
- elsif id.start_with? sep
901
- id = id[1..-1]
902
- end
903
- end
904
- unless synthetic
905
- if pre.empty?
906
- id = %(_#{id}) if LeadingDigitRx =~ id
907
- elsif !(id.start_with? pre)
908
- id = %(#{pre}#{id})
909
899
  end
900
+ elsif (first_section = doc.first_section)
901
+ id = first_section.id
902
+ else
903
+ synthetic = true
910
904
  end
905
+ id = %(#{pre}document#{sep}#{doc.object_id}) if synthetic
911
906
  end
912
- elsif (first_section = doc.first_section)
913
- id = first_section.id
914
- else
915
- synthetic = true
907
+ warn %(asciidoctor: ERROR: chapter uses a reserved ID: #{id}) if !synthetic && (ReservedIds.include? id)
908
+ id
916
909
  end
917
- id = %(#{pre}document#{sep}#{doc.object_id}) if synthetic
918
910
  end
919
- warn %(asciidoctor: ERROR: chapter uses a reserved ID: #{id}) if !synthetic && (ReservedIds.include? id)
920
- id
921
911
  end
922
- end
923
- end
924
912
 
925
- require_relative 'packager'
913
+ require_relative 'packager'
926
914
 
927
- Extensions.register do
928
- if (document = @document).backend == 'epub3'
929
- document.attributes['spine'] = ''
930
- document.set_attribute 'listing-caption', 'Listing'
931
- if !(defined? ::AsciidoctorJ) && (::Gem::try_activate 'pygments.rb')
932
- if document.set_attribute 'source-highlighter', 'pygments'
933
- document.set_attribute 'pygments-css', 'style'
934
- document.set_attribute 'pygments-style', 'bw'
935
- end
936
- end
937
- case (ebook_format = document.attributes['ebook-format'])
938
- when 'epub3', 'kf8'
939
- # all good
940
- when 'mobi'
941
- ebook_format = document.attributes['ebook-format'] = 'kf8'
942
- else
943
- # QUESTION should we display a warning?
944
- ebook_format = document.attributes['ebook-format'] = 'epub3'
945
- end
946
- document.attributes[%(ebook-format-#{ebook_format})] = ''
947
- # Only fire SpineItemProcessor for top-level include directives
948
- include_processor SpineItemProcessor.new(document)
949
- treeprocessor do
950
- process do |doc|
951
- doc.id = DocumentIdGenerator.generate_id doc, (doc.attr 'idprefix'), (doc.attr 'idseparator')
952
- nil
915
+ Extensions.register do
916
+ if (document = @document).backend == 'epub3'
917
+ document.attributes['spine'] = ''
918
+ document.set_attribute 'listing-caption', 'Listing'
919
+ if !(defined? ::AsciidoctorJ) && (::Gem.try_activate 'pygments.rb')
920
+ if document.set_attribute 'source-highlighter', 'pygments'
921
+ document.set_attribute 'pygments-css', 'style'
922
+ document.set_attribute 'pygments-style', 'bw'
923
+ end
924
+ end
925
+ case (ebook_format = document.attributes['ebook-format'])
926
+ when 'epub3', 'kf8'
927
+ # all good
928
+ when 'mobi'
929
+ ebook_format = document.attributes['ebook-format'] = 'kf8'
930
+ else
931
+ # QUESTION should we display a warning?
932
+ ebook_format = document.attributes['ebook-format'] = 'epub3'
933
+ end
934
+ document.attributes[%(ebook-format-#{ebook_format})] = ''
935
+ # Only fire SpineItemProcessor for top-level include directives
936
+ include_processor SpineItemProcessor.new(document)
937
+ treeprocessor do
938
+ process do |doc|
939
+ doc.id = DocumentIdGenerator.generate_id doc, (doc.attr 'idprefix'), (doc.attr 'idseparator')
940
+ nil
941
+ end
942
+ end
953
943
  end
954
944
  end
955
945
  end
956
946
  end
957
- end
958
- end