class GEPUB::Book

Book is the class to hold data in EPUB files.

It can generate and parse EPUB2/EPUB3 files.

Book delegates many methods to objects in other class, so you can't find them in Book#methods or in ri/rdoc documentation. Their descriptions are below.

Package Attributes

Book#version (delegated to Package#version)

returns OPF version.

Book#version=, Book#set_version (delegated to Package#version=)

set OPF version

Book#unique_identifier (delegated to Package#unique_identifier)

return unique_identifier ID value. identifier itself can be get by Book#identifier

Metadata

Metadata items(e.g. title, creator, publisher, etc) are GEPUB::Meta objects.

Book#identifier (delegated to Package#identifier)

return GEPUB::Meta object of unique identifier.

Book#identifier=(identifier) (delegated to Package#identifier=)

set identifier (i.e. url, uuid, ISBN) as unique-identifier of EPUB.

Book#set_main_id(identifier, id = nil, type = nil) (delegated to Package#set_main_id)

same as identifier=, but can specify id (in the opf xml) and identifier type(i.e. URL, uuid, ISBN, etc)

Book#add_identifier(string, id, type=nil) (delegated to Metadata#add_identifier)

Set an identifier metadata. It it not unique-identifier in opf. Many EPUB files do not set identifier other than unique-identifier.

Book#add_title(content, id: nil, title_type: nil) (delegated to Metadata#add_title)

add title metadata. title_type candidates is defined in TITLE_TYPES.

Book#title(content, id = nil, title_type = nil) (delegated to Metadata#title)

clear all titles and then add title.

Book#title (delegated to Metadata)

returns 'main' title Meta object. 'main' title is determined by this order:

  1. title-type is 'main'

  2. display-seq is smallest

  3. appears first in opf file

Book#title_list (delegated to Metadata)

returns titles list by display-seq or defined order. the title without display-seq is appear after titles with display-seq.

Book#add_creator(content, id = nil, role = 'aut') (delegated to Metadata#add_creator)

add creator.

Book#creator

returns 'main' creator Meta object. 'main' creator is determined as following:

  1. display-seq is smallest

  2. appears first in opf file

Book#creator_list (delegated to Metadata)

returns creators list by display-seq or defined order. the creators without display-seq is appear after creators with display-seq.

Book#add_contributor(content, id = nil, role = 'aut') (delegated to Metadata#add_contributor)

add contributor.

Book#contributor(content, id = nil, role = 'aut') (delegated to Metadata#contributor)

returns 'main' contributor. 'main' contributor determined as following:

  1. display-seq is smallest

  2. appears first in opf file

Book#contributors_list (delegated to Metadata)

returns contributors list by display-seq or defined order. the contributors without display-seq is appear after contributors with display-seq.

Book#lastmodified(date) (delegated to Metadata#lastmodified)

set last modified date. date is a Time, DateTime or string that can be parsed by DateTime#parse.

Book#modified_now (delegated to Metadata#modified_now)

set last modified date to current time.

Book#lastmodified (delegated to Metadata#lastmodified)

returns Meta object contains last modified time.

setting and reading other metadata: publisher, language, coverage, date, description, format, relation, rights, source, subject, type (delegated to Metadata)

they all have methods like: publisher(which returns 'main' publisher), add_publisher(content, id) (which add publisher), publisher= (clears and set publisher), and publisher_list(returns publisher Meta object in display-seq order).

Book#page_progression_direction= (delegated to Spine#page_progression_direction=)

set page-proression-direction attribute to spine.

Constants

CONTAINER
CONTAINER_NS
MIMETYPE
MIMETYPE_CONTENTS
ROOTFILE_PATTERN

Public Class Methods

new(path='OEBPS/package.opf', attributes = {}, &block) click to toggle source

creates new empty Book object. usually you do not need to specify any arguments.

# File lib/gepub/book.rb, line 115
def initialize(path='OEBPS/package.opf', attributes = {}, &block)
  if File.extname(path) != '.opf'
    warn 'GEPUB::Book#new interface changed. You must supply path to package.opf as first argument. If you want to set title, please use GEPUB::Book#title='
  end
  @package = Package.new(path, attributes)
  @toc = []
  @landmarks = []
  if block
    block.arity < 1 ? instance_eval(&block) : block[self]        
  end
end
parse(io) click to toggle source

Parses existing EPUB2/EPUB3 files from an IO object, and creates new Book object.

book = self.parse(File.new('some.epub'))
# File lib/gepub/book.rb, line 97
def self.parse(io)
  files = {}
  package = nil
  package_path = nil
  book = nil
  Zip::InputStream::open(io) {
    |zis|
    package, package_path = parse_container(zis, files)
    check_consistency_of_package(package, package_path)
    parse_files_into_package(files, package)
    book = Book.new(package.path)
    book.instance_eval { @package = package; @optional_files = files }
  }
  book
end
rootfile_from_container(rootfile) click to toggle source
# File lib/gepub/book.rb, line 87
def self.rootfile_from_container(rootfile)
  doc = Nokogiri::XML::Document.parse(rootfile)
  ns = doc.root.namespaces
  defaultns = ns.select{ |_name, value| value == CONTAINER_NS }.to_a[0][0]
  doc.css("#{defaultns}|rootfiles > #{defaultns}|rootfile")[0]['full-path']
end

Private Class Methods

check_consistency_of_package(package, package_path) click to toggle source
# File lib/gepub/book.rb, line 404
def self.check_consistency_of_package(package, package_path)
  if package.nil?
    raise 'this container do not cotains publication information file'
  end

  if package_path != package.path
    warn "inconsistend EPUB file: container says opf is #{package_path}, but actually #{package.path}"
  end
end
parse_container(zis, files) click to toggle source
# File lib/gepub/book.rb, line 379
def self.parse_container(zis, files) 
  package_path = nil
  package = nil
  while entry = zis.get_next_entry
    if !entry.directory?
      files[entry.name] = zis.read
      case entry.name
      when MIMETYPE then
        if files[MIMETYPE] != MIMETYPE_CONTENTS
          warn "#{MIMETYPE} is not valid: should be #{MIMETYPE_CONTENTS} but was #{files[MIMETYPE]}"
        end
        files.delete(MIMETYPE)
      when CONTAINER then
        package_path = rootfile_from_container(files[CONTAINER])
        files.delete(CONTAINER)
      when ROOTFILE_PATTERN then
        package = Package.parse_opf(files[entry.name], entry.name)
        files.delete(entry.name)
      end
    end
  end
  return package, package_path
end
parse_files_into_package(files, package) click to toggle source
# File lib/gepub/book.rb, line 415
def self.parse_files_into_package(files, package)
  files.each {
    |k, content|
    item = package.manifest.item_by_href(k.sub(/^#{package.contents_prefix}/,''))
    if !item.nil?
      files.delete(k)
      item.add_raw_content(content)
    end
  }
end

Public Instance Methods

add_item(href, deprecated_content = nil, deprecated_id = nil, deprecated_attributes = nil, content: nil, id: nil,media_type: nil,fallback: nil,properties: nil,media_overlay: nil,toc_text: nil,property: nil, attributes: {}) click to toggle source

add an item(i.e. html, images, audios, etc) to Book. the added item will be referenced by the first argument in the EPUB container.

# File lib/gepub/book_add_item.rb, line 7
def add_item(href, deprecated_content = nil, deprecated_id = nil, deprecated_attributes = nil, content: nil, 
             id: nil,media_type: nil,fallback: nil,properties: nil,media_overlay: nil,toc_text: nil,property: nil,
             attributes: {})
  content, id, attributes = handle_deprecated_add_item_arguments(deprecated_content, deprecated_id, deprecated_attributes, content, id, attributes)
  add_item_internal(href, content: content, item_attributes: { id: id,media_type: media_type,fallback: fallback,properties: properties,media_overlay: media_overlay,toc_text: toc_text,property: property }, attributes: attributes, ordered: false)
end
add_optional_file(path, io_or_filename) click to toggle source

Add an optional file to the container

# File lib/gepub/book.rb, line 134
def add_optional_file(path, io_or_filename)
  io = io_or_filename
  if io_or_filename.class == String
    io = File.new(io_or_filename)
  end
  io.binmode
  (@optional_files ||= {})[path] = io.read
end
add_ordered_item(href, deprecated_content = nil, deprecated_id = nil, deprecated_attributes = nil, content:nil, id: nil,media_type: nil,fallback: nil,properties: nil,media_overlay: nil,toc_text: nil,property: nil, attributes: {}) click to toggle source

same as add_item, but the item will be added to spine of the EPUB.

# File lib/gepub/book_add_item.rb, line 15
def add_ordered_item(href, deprecated_content = nil, deprecated_id = nil, deprecated_attributes = nil,  content:nil,
                     id: nil,media_type: nil,fallback: nil,properties: nil,media_overlay: nil,toc_text: nil,property: nil,
                     attributes: {})
  content, id, attributes = handle_deprecated_add_item_arguments(deprecated_content, deprecated_id, deprecated_attributes, content, id, attributes)
  add_item_internal(href, content: content, item_attributes: { id: id,media_type: media_type,fallback: fallback,properties: properties,media_overlay: media_overlay,toc_text: toc_text,property: property }, attributes: attributes, ordered: true)
end
add_tocdata(toc_yaml) click to toggle source

add tocdata like this : [ {link: chapter1.xhtml, text: 'Capter 1', level: 1} ] . if item corresponding to the link does not exists, error will be thrown.

# File lib/gepub/book.rb, line 251
def add_tocdata(toc_yaml)
  newtoc = []
  toc_yaml.each do |toc_entry|
    href, id = toc_entry[:link].split('#')
    item = @package.manifest.item_by_href(href)
    throw "#{href} does not exist." if item.nil?
    newtoc.push({item: item, id: id, text: toc_entry[:text], level: toc_entry[:level] })
  end
  @toc = @toc + newtoc
end
cleanup() click to toggle source

clenup and maintain consistency of metadata and items included in the Book object.

# File lib/gepub/book.rb, line 178
def cleanup
  cleanup_for_epub2
  cleanup_for_epub3
end
container_xml() click to toggle source
# File lib/gepub/book.rb, line 237
    def container_xml
      <<EOF

<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
  <rootfiles>
    <rootfile full-path="#{@package.path}" media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>
EOF
    end
generate_epub(path_to_epub) click to toggle source

writes EPUB to file. if file exists, it will be overwritten.

# File lib/gepub/book.rb, line 228
def generate_epub(path_to_epub)
  cleanup
  File.delete(path_to_epub) if File.exist?(path_to_epub)
  Zip::OutputStream::open(path_to_epub) {
    |epub|
    write_to_epub_container(epub)
  }
end
generate_epub_stream() click to toggle source

generates and returns StringIO contains EPUB.

# File lib/gepub/book.rb, line 219
def generate_epub_stream
  cleanup
  Zip::OutputStream::write_buffer(StringIO.new) do
    |epub|
    write_to_epub_container(epub)
  end
end
generate_nav_doc(title = 'Table of Contents') click to toggle source
# File lib/gepub/book.rb, line 262
def generate_nav_doc(title = 'Table of Contents')
  add_item('nav.xhtml', id: 'nav', content: StringIO.new(nav_doc(title))).add_property('nav')
end
get_handler_of(media_type) click to toggle source

get handler item which defined in bindings for media type,

# File lib/gepub/book.rb, line 162
def get_handler_of(media_type)
  items[@package.bindings.handler_by_media_type[media_type]]
end
method_missing(name, *args, &block) click to toggle source
# File lib/gepub/book.rb, line 166
               def method_missing(name, *args, &block)
  @package.send(name, *args, &block)
end
nav_doc(title = 'Table of Contents') click to toggle source
ncx_xml() click to toggle source
# File lib/gepub/book.rb, line 343
def ncx_xml
  builder = Nokogiri::XML::Builder.new {
    |xml|
    xml.ncx('xmlns' => 'http://www.daisy.org/z3986/2005/ncx/', 'version' => '2005-1') {
      xml.head {
        xml.meta('name' => 'dtb:uid', 'content' => "#{self.identifier}") 
        xml.meta('name' => 'dtb:depth', 'content' => '1')
        xml.meta('name' => 'dtb:totalPageCount','content' => '0')
        xml.meta('name' => 'dtb:maxPageNumber', 'content' => '0')
      }
      xml.docTitle {
        xml.text_ "#{@package.metadata.title}"
      }
      count = 1
      xml.navMap {
        @toc.each {
          |x|
          xml.navPoint('id' => "#{x[:item].itemid}_#{x[:id]}", 'playOrder' => "#{count}") {
            xml.navLabel {
              xml.text_  "#{x[:text]}"
            }
            if x[:id].nil?
              xml.content('src' => "#{x[:item].href}")
            else
              xml.content('src' => "#{x[:item].href}##{x[:id]}")
            end
          }
          count += 1
        }
      }
    }
  }
  builder.to_xml(:encoding => 'utf-8')
end
optional_files() click to toggle source

Get optional(not required in EPUB specification) files in the container.

# File lib/gepub/book.rb, line 129
def optional_files
  @optional_files || {}
end
ordered(&block) click to toggle source

should call ordered() with block. within the block, all item added by add_item will be added to spine also.

# File lib/gepub/book.rb, line 172
def ordered(&block)
  @package.ordered(&block)
end
set_singleton_methods_to_item(item) click to toggle source
# File lib/gepub/book.rb, line 143
def set_singleton_methods_to_item(item)
  toc = @toc
  metaclass = (class << item;self;end)
  metaclass.send(:define_method, :toc, Proc.new {
    toc
  })
  landmarks = @landmarks
  metaclass.send(:define_method, :landmarks, Proc.new {
    landmarks
  })
  bindings = @package.bindings
  metaclass.send(:define_method, :bindings, Proc.new {
    bindings
  })
                           
end
write_landmarks(xml_doc, landmarks) click to toggle source
# File lib/gepub/book.rb, line 303
def write_landmarks xml_doc, landmarks
  xml_doc.ol {
    landmarks.each {
      |landmark|
      id = landmark[:id].nil? ? "" : "##{x[:id]}"
      landmark_title = landmark[:title]
      xml_doc.li {
        xml_doc.a({'href' => landmark[:item].href + id, 'epub:type' => landmark[:type]}, landmark_title)
      }
    }
  }
end
write_to_epub_container(epub) click to toggle source

write EPUB to stream specified by the argument.

# File lib/gepub/book.rb, line 184
def write_to_epub_container(epub)
  mod_time = Zip::DOSTime.now
  unless (last_mod = lastmodified).nil?
    tm = last_mod.content
    mod_time = Zip::DOSTime.local(tm.year, tm.month, tm.day, tm.hour, tm.min, tm.sec)
  end

  mimetype_entry = Zip::Entry.new(nil, 'mimetype', nil, nil, nil, nil, nil, nil, mod_time)
  epub.put_next_entry(mimetype_entry, nil, nil, Zip::Entry::STORED)
  epub << "application/epub+zip"

  entries = {}
  optional_files.each {
    |k, content|
    entries[k] = content
  }

  entries['META-INF/container.xml'] = container_xml
  entries[@package.path] = opf_xml
  @package.manifest.item_list.each {
    |_k, item|
    if item.content != nil
      entries[@package.contents_prefix + item.href] = item.content
    end
  }

  entries.sort_by { |k,_v| k }.each {
    |k,v|
    zip_entry = Zip::Entry.new(nil, k, nil, nil, nil, nil, nil, nil, mod_time)
    epub.put_next_entry(zip_entry)
    epub << v.force_encoding('us-ascii')
  }
end
write_toc(xml_doc, tocs) click to toggle source

write toc

# File lib/gepub/book.rb, line 286
def write_toc xml_doc, tocs
  return if tocs.empty?
  xml_doc.ol {
    tocs.each {
      |x|
      id = x[:id].nil? ? "" : "##{x[:id]}"
      toc_text = x[:text]
      toc_text = x[:item].href if toc_text.nil? or toc_text == ''
      xml_doc.li {
        xml_doc.a({'href' => x[:item].href + id} ,toc_text)
        if x[:child_stack] && x[:child_stack][:tocs].size > 0
          write_toc(xml_doc, x[:child_stack][:tocs])
        end
      }
    }
  }
end

Private Instance Methods

add_item_internal(href, content: nil, item_attributes: , attributes: {}, ordered: ) click to toggle source
# File lib/gepub/book.rb, line 460
def add_item_internal(href, content: nil, item_attributes: , attributes: {}, ordered: )
  id = item_attributes.delete(:id)
  item = 
    if ordered
      @package.add_ordered_item(href,attributes: attributes, id:id, content: content)
    else
      @package.add_item(href, attributes: attributes, id: id, content: content)
    end
  set_singleton_methods_to_item(item)
  item_attributes.each do |attr, val|
    next if val.nil?
    method_name = if attr == :toc_text
                    ""
                  elsif attr == :property
                    "add_"
                  else
                    "set_"
                  end + attr.to_s
    item.send(method_name, val)
  end
  item
end
cleanup_for_epub2() click to toggle source
# File lib/gepub/book.rb, line 427
def  cleanup_for_epub2
  if version.to_f < 3.0 || @package.epub_backward_compat
    if @package.manifest.item_list.select {
      |_x,item|
      item.media_type == 'application/x-dtbncx+xml'
    }.size == 0
      if (@toc.size == 0 && !@package.spine.itemref_list.empty?)
        @toc << { :item => @package.manifest.item_list[@package.spine.itemref_list[0].idref] }
      end
      add_item('toc.ncx', id: 'ncx', content: StringIO.new(ncx_xml))
    end
  end
end
cleanup_for_epub3() click to toggle source
# File lib/gepub/book.rb, line 440
def cleanup_for_epub3
  if version.to_f >=3.0
    @package.metadata.modified_now unless @package.metadata.lastmodified_updated?
    
    if @package.manifest.item_list.select {
      |_href, item|
      (item.properties||[]).member? 'nav'
      }.size == 0
      generate_nav_doc
    end
    
    @package.spine.remove_with_idlist @package.manifest.item_list.map {
      |_href, item|
      item.fallback
    }.reject(&:nil?)
  end
end
handle_deprecated_add_item_arguments(deprecated_content, deprecated_id, deprecated_attributes, content, id, attributes) click to toggle source
# File lib/gepub/book.rb, line 483
def handle_deprecated_add_item_arguments(deprecated_content, deprecated_id, deprecated_attributes, content, id, attributes) 
  if deprecated_content
    msg = 'deprecated argument; use content keyword argument instead of 2nd argument' 
    fail msg if content
    warn msg
    content = deprecated_content
  end
  if deprecated_id
    msg = 'deprecated argument; use id keyword argument instead of 3rd argument' 
    fail msg if id
    warn msg
    id = deprecated_id
  end
  if deprecated_attributes
    msg = 'deprecated argument; use argument keyword attributes instead of 4th argument' 
    fail msg if attributes.size > 0
    warn msg
    attributes = deprecated_attributes
  end
  return content, id, attributes
end