Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'toc-from-headings'

  • Loading branch information...
commit 94d751f32612d570ebd0eeaf61abbd3bf192909a 2 parents a26d82b + 7ced3e6
@avdgaag authored
View
4 HISTORY.md
@@ -1,5 +1,9 @@
# History
+## 0.4.0 (unreleased)
+
+* Generate ePub table of contents from markdown headings
+
## 0.3.0
* Only run simplecov on demand
View
9 README.md
@@ -104,6 +104,15 @@ configuration to your `config.yml` file:
toc: true
+Note that this only affects the human-readable table of contents, represented
+as a page in your book. Rpub will alway generate the .epub table of contents
+for you, which contains the machine-readable references to your chapters. It
+will, by default, reference all chapter titles and subheadings for you, but
+you can customize the number of levels that will be included using the
+following in your `config.yml` file:
+
+ max_level: 3
+
#### Custom layout and styles
When you compile a set of Markdown files to an ePub file, rpub uses a default
View
5 lib/rpub/chapter.rb
@@ -17,6 +17,11 @@ def initialize(content, number, layout)
@content, @number, @layout = content, number, layout
@document = Kramdown::Document.new(content, KRAMDOWN_OPTIONS.merge(:template => layout))
end
+
+ # @return [Kramdown::Element] Toc elements hierarchy
+ def toc
+ Kramdown::Converter::Toc.convert(@document.root).first
+ end
# @return [String] Unique identifier for this chapter.
def uid
View
44 lib/rpub/epub/toc.rb
@@ -4,7 +4,9 @@ class Toc < XmlFile
attr_reader :book
def initialize(book)
- @book = book
+ @book = book
+ @play_order = -1
+ @max_level = book.config.fetch(:max_level) { 2 }
super()
end
@@ -13,23 +15,45 @@ def render
xml.declare! :DOCTYPE, :ncx, :PUBLIC, "-//W3C//DTD XHTML 1.1//EN", 'http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd'
xml.ncx :xmlns => 'http://www.daisy.org/z3986/2005/ncx/', :version => '2005-1' do
xml.head do
- xml.meta :name => 'dtb:uid', :content => book.uid
- xml.meta :name => 'dtb:depth', :content => '1'
+ xml.meta :name => 'dtb:uid', :content => book.uid
+ xml.meta :name => 'dtb:depth', :content => @max_level
xml.meta :name => 'dtb:totalPageCount', :content => '0'
- xml.meta :name => 'dtb:maxPageNumber', :content => '0'
+ xml.meta :name => 'dtb:maxPageNumber', :content => '0'
end
xml.docTitle { xml.text book.title }
xml.navMap do
- book.chapters.each_with_index do |chapter, n|
- xml.navPoint :id => chapter.xml_id, :playOrder => n do
- xml.navLabel { xml.text chapter.title }
- xml.content :src => chapter.filename
- end
+ book.chapters.each do |chapter|
+ nav_points_nested_by_level chapter.toc, chapter.filename
end
end
end
end
+
+ private
+
+ def next_play_order
+ @play_order += 1
+ end
+
+ def nav_points_nested_by_level(heading, filename, level = 1)
+ heading.children.each do |heading|
+ html_id = heading.attr[:id]
+ source = filename+'#'+html_id
+ if heading.value.options[:level] <= @max_level
+ nav_point html_id, next_play_order, heading.value.options[:raw_text], source do
+ nav_points_nested_by_level heading, filename, level + 1
+ end
+ end
+ end
+ end
+
+ def nav_point(id, play_order, text, filename)
+ xml.navPoint :id => id, :playOrder => play_order do
+ xml.navLabel { xml.text text }
+ xml.content :src => filename
+ yield if block_given?
+ end
+ end
end
end
end
-
View
25 spec/rpub/epub/toc_spec.rb
@@ -2,12 +2,13 @@
describe Rpub::Epub::Toc do
let(:chapters) { [] }
- let(:book) { double('book', :uid => 'foo', :title => 'title', :chapters => chapters) }
+ let(:config) { {} }
+ let(:book) { double('book', :uid => 'foo', :title => 'title', :chapters => chapters, :config => config) }
let(:subject) { described_class.new(book).render }
it { should have_xpath('/xmlns:ncx') }
it { should have_xpath('/xmlns:ncx/xmlns:head/xmlns:meta[@name="dtb:uid"][@content="foo"]') }
- it { should have_xpath('/xmlns:ncx/xmlns:head/xmlns:meta[@name="dtb:depth"][@content="1"]') }
+ it { should have_xpath('/xmlns:ncx/xmlns:head/xmlns:meta[@name="dtb:depth"][@content="2"]') }
it { should have_xpath('/xmlns:ncx/xmlns:head/xmlns:meta[@name="dtb:totalPageCount"][@content="0"]') }
it { should have_xpath('/xmlns:ncx/xmlns:head/xmlns:meta[@name="dtb:maxPageNumber"][@content="0"]') }
it { should have_xpath('/xmlns:ncx/xmlns:docTitle/xmlns:text[text()="title"]') }
@@ -17,9 +18,23 @@
end
context 'with chapters' do
- let(:chapters) { [double('chapter', :title => 'chapter title', :filename => 'filename', :xml_id => 'id')] }
- it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint[@id="id"]') }
+ let(:heading1) { double('heading', :children => [heading2], :value => double('value', :options => { :level => 1, :raw_text => 'chapter title' }), :attr => { :id => 'foo' }) }
+ let(:heading2) { double('heading', :children => [], :value => double('value', :options => { :level => 2, :raw_text => 'chapter title 2' }), :attr => { :id => 'bar' }) }
+ let(:toc) { double('toc', :children => [heading1])}
+ let(:chapters) { [double('chapter', :title => 'chapter title', :filename => 'filename', :xml_id => 'id', :toc => toc)] }
+ it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint[@id="foo"]') }
it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:navLabel/xmlns:text[text()="chapter title"]') }
- it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:content[@src="filename"]') }
+ it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:content[@src="filename#foo"]') }
+ it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:navPoint[@id="bar"]') }
+ it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:navPoint/xmlns:navLabel/xmlns:text[text()="chapter title 2"]') }
+ it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:navPoint/xmlns:content[@src="filename#bar"]') }
+
+ context 'with low max_level' do
+ let(:config) { { :max_level => 1 } }
+ it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint[@id="foo"]') }
+ it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:navLabel/xmlns:text[text()="chapter title"]') }
+ it { should have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:content[@src="filename#foo"]') }
+ it { should_not have_xpath('/xmlns:ncx/xmlns:navMap/xmlns:navPoint/xmlns:navPoint') }
+ end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.