repub 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ require 'repub/app/filter'
2
+
3
+ module Repub
4
+ class App
5
+ class PreFilters
6
+ include Filter
7
+
8
+ # Detect and convert source encoding
9
+ # Standard requires it to be UTF-8
10
+ #
11
+ filter :fix_encoding do |s|
12
+ encoding = options[:encoding]
13
+ unless encoding
14
+ log.info "Detecting encoding"
15
+ encoding = UniversalDetector.chardet(s)['encoding']
16
+ end
17
+ if encoding.downcase != 'utf-8'
18
+ log.info "Source encoding appears to be #{encoding}, converting to UTF-8"
19
+ s = Iconv.conv('utf-8', encoding, s)
20
+ end
21
+ s
22
+ end
23
+
24
+ # Convert line endings to LF
25
+ #
26
+ filter :fix_line_endings do |s|
27
+ s.gsub(/\r\n/, "\n")
28
+ end
29
+
30
+ # Fix all elements with broken id attribute
31
+ # In XHTML id must match [A-Za-z][A-Za-z0-9:_.-]*
32
+ # TODO: currently only testing for non-alpha first char...
33
+ #
34
+ filter :fix_ids do |s|
35
+ match = s.scan(/\s+((?:id|name)\s*?=\s*?['"])(\d+[^'"]*)['"]/im)
36
+ unless match.empty?
37
+ log.debug "-- Fixing broken element IDs"
38
+ match.each do |m|
39
+ # fix id so it starts with alpha char
40
+ s.gsub!(m.join(''), m.join('x'))
41
+ # update fragment references
42
+ s.gsub!(/##{m[1]}(['"])/, "#x#{m[1]}\\1")
43
+ end
44
+ end
45
+ s
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -5,7 +5,7 @@ module Repub
5
5
  class App
6
6
  module Profile
7
7
 
8
- PROFILE_KEYS = %w[css encoding fixup helper metadata remove rx selectors].map {|k| k.to_sym}
8
+ PROFILE_KEYS = %w[css encoding helper metadata remove rx selectors].map {|k| k.to_sym}
9
9
 
10
10
  def load_profile(name = nil)
11
11
  name ||= 'default'
@@ -1,3 +1,4 @@
1
- require 'repub/epub/container'
2
- require 'repub/epub/content'
3
- require 'repub/epub/toc'
1
+ require 'repub/epub/container_item'
2
+ require 'repub/epub/ocf'
3
+ require 'repub/epub/opf'
4
+ require 'repub/epub/ncx'
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+
3
+ module Repub
4
+ module Epub
5
+
6
+ # Mixin for stuff that can be added to the ePub package
7
+ #
8
+ module ContainerItem
9
+ attr_accessor :file_path
10
+ attr_accessor :media_type
11
+
12
+ def document?
13
+ ['application/xhtml+xml', 'application/x-dtbook+xml'].include? self.media_type
14
+ end
15
+ end
16
+
17
+ # Wrapper class for ePub items that do not have specialized classes
18
+ # e.g. HTML files, CSSs etc.
19
+ #
20
+ class Item
21
+ include ContainerItem
22
+
23
+ def initialize(file_path, media_type = nil)
24
+ @file_path = file_path.strip
25
+ @media_type = media_type || case @file_path.downcase
26
+ when /.*\.html?$/
27
+ 'application/xhtml+xml'
28
+ when /.*\.css$/
29
+ 'text/css'
30
+ when /.*\.(jpeg|jpg)$/
31
+ 'image/jpeg'
32
+ when /.*\.png$/
33
+ 'image/png'
34
+ when /.*\.gif$/
35
+ 'image/gif'
36
+ when /.*\.svg$/
37
+ 'image/svg+xml'
38
+ when /.*\.ncx$/
39
+ 'application/x-dtbncx+xml'
40
+ when /.*\.opf$/
41
+ 'application/oebps-package+xml'
42
+ else
43
+ raise 'Unknown media type'
44
+ end
45
+ end
46
+ end
47
+
48
+ end
49
+ end
@@ -1,139 +1,137 @@
1
- require 'rubygems'
2
- require 'builder'
3
-
4
- module Repub
5
- module Epub
6
-
7
- class Toc
8
-
9
- def initialize(uid)
10
- @head = Head.new(uid)
11
- @doc_title = DocTitle.new('Untitled')
12
- @nav_map = NavMap.new
13
- end
14
-
15
- def title
16
- @doc_title.text
17
- end
18
-
19
- def title=(text)
20
- @doc_title = DocTitle.new(text)
21
- end
22
-
23
- attr_reader :nav_map
24
-
25
- def to_xml
26
- out = ''
27
- builder = Builder::XmlMarkup.new(:target => out)
28
- builder.instruct!
29
- builder.declare! :DOCTYPE, :ncx, :PUBLIC, "-//NISO//DTD ncx 2005-1//EN", "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"
30
- builder.ncx :xmlns => "http://www.daisy.org/z3986/2005/ncx/", :version => "2005-1" do
31
- @nav_map.calc_depth_and_play_order
32
- @head.depth = @nav_map.depth
33
- @head.to_xml(builder)
34
- @doc_title.to_xml(builder)
35
- @nav_map.to_xml(builder)
36
- end
37
- out
38
- end
39
-
40
- def save(path = 'toc.ncx')
41
- File.open(path, 'w') do |f|
42
- f << to_xml
43
- end
44
- end
45
-
46
- class Head < Struct.new(
47
- :uid
48
- )
49
-
50
- attr_accessor :depth
51
-
52
- def to_xml(builder)
53
- builder.head do
54
- builder.meta :name => "dtb:uid", :content => self.uid
55
- builder.meta :name => "dtb:depth", :content => @depth
56
- builder.meta :name => "dtb:totalPageCount", :content => 0
57
- builder.meta :name => "dtb:maxPageNumber", :content => 0
58
- end
59
- end
60
- end
61
-
62
- class DocTitle < Struct.new(
63
- :text
64
- )
65
-
66
- def to_xml(builder)
67
- builder.docTitle do
68
- builder.text self.text
69
- end
70
- end
71
- end
72
-
73
- class NavMap
74
- class NavPoint < Struct.new(
75
- :title,
76
- :src
77
- )
78
-
79
- def initialize(title, src)
80
- super
81
- @@last_play_order = 0
82
- @play_order = 0
83
- @child_points = []
84
- end
85
-
86
- def add_nav_point(title, src)
87
- nav_point = NavPoint.new(title, src)
88
- @child_points << nav_point
89
- nav_point
90
- end
91
-
92
- def to_xml(builder)
93
- builder.navPoint :id => "navPoint-#{@play_order}", :playOrder => @play_order do
94
- builder.navLabel do
95
- builder.text self.title
96
- end
97
- builder.content :src => self.src
98
- @child_points.each { |child_point| child_point.to_xml(builder) }
99
- end
100
- end
101
-
102
- def calc_depth_and_play_order(nav_map, depth)
103
- nav_map.depth = depth
104
- @play_order = @@last_play_order += 1
105
- @child_points.each { |child_point| child_point.calc_depth_and_play_order(nav_map, depth + 1) }
106
- end
107
- end
108
-
109
- def initialize
110
- @nav_points = []
111
- @depth = 1
112
- end
113
-
114
- def add_nav_point(title, src)
115
- nav_point = NavPoint.new(title, src)
116
- @nav_points << nav_point
117
- nav_point
118
- end
119
-
120
- attr_reader :depth
121
-
122
- def depth=(value)
123
- @depth = value if value > @depth
124
- end
125
-
126
- def calc_depth_and_play_order
127
- @nav_points.each { |nav_point| nav_point.calc_depth_and_play_order(self, 1) }
128
- end
129
-
130
- def to_xml(builder)
131
- builder.navMap do
132
- @nav_points.each { |nav_point| nav_point.to_xml(builder) }
133
- end
134
- end
135
- end
136
- end
137
-
138
- end
139
- end
1
+ require 'rubygems'
2
+ require 'builder'
3
+
4
+ module Repub
5
+ module Epub
6
+
7
+ class NCX
8
+ include ContainerItem
9
+
10
+ def initialize(uid, file_path = 'toc.ncx')
11
+ @file_path = file_path
12
+ @media_type = 'application/x-dtbncx+xml'
13
+ @head = Head.new(uid)
14
+ @doc_title = DocTitle.new('Untitled')
15
+ @nav_map = NavMap.new
16
+ end
17
+
18
+ def title
19
+ @doc_title.text
20
+ end
21
+
22
+ def title=(text)
23
+ @doc_title = DocTitle.new(text)
24
+ end
25
+
26
+ attr_reader :nav_map
27
+
28
+ def to_xml
29
+ out = ''
30
+ builder = Builder::XmlMarkup.new(:target => out)
31
+ builder.instruct!
32
+ builder.declare! :DOCTYPE, :ncx, :PUBLIC, "-//NISO//DTD ncx 2005-1//EN", "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"
33
+ builder.ncx :xmlns => "http://www.daisy.org/z3986/2005/ncx/", :version => "2005-1" do
34
+ @nav_map.calc_depth_and_play_order
35
+ @head.depth = @nav_map.depth
36
+ @head.to_xml(builder)
37
+ @doc_title.to_xml(builder)
38
+ @nav_map.to_xml(builder)
39
+ end
40
+ out
41
+ end
42
+
43
+ def save
44
+ File.open(@file_path, 'w') do |f|
45
+ f << to_xml
46
+ end
47
+ end
48
+
49
+ class Head < Struct.new(
50
+ :uid
51
+ )
52
+
53
+ attr_accessor :depth
54
+
55
+ def to_xml(builder)
56
+ builder.head do
57
+ builder.meta :name => "dtb:uid", :content => self.uid
58
+ builder.meta :name => "dtb:depth", :content => @depth
59
+ builder.meta :name => "dtb:totalPageCount", :content => 0
60
+ builder.meta :name => "dtb:maxPageNumber", :content => 0
61
+ end
62
+ end
63
+ end
64
+
65
+ class DocTitle < Struct.new(
66
+ :text
67
+ )
68
+
69
+ def to_xml(builder)
70
+ builder.docTitle do
71
+ builder.text self.text
72
+ end
73
+ end
74
+ end
75
+
76
+ class NavPoint < Struct.new(
77
+ :title,
78
+ :src
79
+ )
80
+
81
+ def initialize(title, src, points = nil)
82
+ super(title, src)
83
+ @play_order = 0
84
+ @points = points || []
85
+ end
86
+
87
+ attr_accessor :play_order
88
+ attr_accessor :points
89
+
90
+ def to_xml(builder)
91
+ builder.navPoint :id => point_id(@play_order), :playOrder => @play_order do
92
+ builder.navLabel do
93
+ builder.text self.title
94
+ end
95
+ builder.content :src => self.src
96
+ @points.each { |point| point.to_xml(builder) }
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def point_id(play_order)
103
+ "navPoint-#{play_order}"
104
+ end
105
+ end
106
+
107
+ class NavMap < NavPoint
108
+
109
+ def initialize
110
+ super(nil, nil)
111
+ @depth = 1
112
+ end
113
+
114
+ attr_reader :depth
115
+
116
+ def calc_depth_and_play_order
117
+ play_order = 0
118
+ l = lambda do |points, depth|
119
+ @depth = depth if depth > @depth
120
+ points.each do |pt|
121
+ pt.play_order = (play_order += 1)
122
+ l.call(pt.points, depth + 1) unless pt.points.empty?
123
+ end
124
+ end
125
+ l.call(@points, @depth = 1)
126
+ end
127
+
128
+ def to_xml(builder)
129
+ builder.navMap do
130
+ @points.each { |point| point.to_xml(builder) }
131
+ end
132
+ end
133
+ end
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'fileutils'
3
+ require 'builder'
4
+
5
+ module Repub
6
+ module Epub
7
+
8
+ # OEBPS Container Format (OCF) 1.0 wrapper
9
+ # (see http://www.idpf.org/ocf/ocf1.0/download/ocf10.htm)
10
+ #
11
+ class OCF
12
+
13
+ def initialize
14
+ @items = []
15
+ end
16
+
17
+ attr_reader :items
18
+
19
+ def <<(item)
20
+ if item.kind_of? ContainerItem
21
+ @items << item
22
+ elsif item.is_a? String
23
+ @items << Item.new(item)
24
+ else
25
+ raise "Unsupported item class: #{item.class}"
26
+ end
27
+ end
28
+
29
+ def to_xml
30
+ out = ''
31
+ builder = Builder::XmlMarkup.new(:target => out)
32
+ builder.instruct!
33
+ builder.container :xmlns => "urn:oasis:names:tc:opendocument:xmlns:container", :version => "1.0" do
34
+ builder.rootfiles do
35
+ @items.each do |item|
36
+ builder.rootfile 'full-path' => item.file_path, 'media-type' => item.media_type
37
+ end
38
+ end
39
+ end
40
+ out
41
+ end
42
+
43
+ def save
44
+ meta_inf = 'META-INF'
45
+ FileUtils.mkdir_p(meta_inf)
46
+ File.open(File.join(meta_inf, 'container.xml'), 'w') do |f|
47
+ f << to_xml
48
+ end
49
+ end
50
+
51
+ def zip(output_path)
52
+ File.open('mimetype', 'w') do |f|
53
+ f << 'application/epub+zip'
54
+ end
55
+ # mimetype has to be first in the archive
56
+ %x(zip -X9 \"#{output_path}\" mimetype)
57
+ %x(zip -Xr9D \"#{output_path}\" * -xi mimetype)
58
+ end
59
+ end
60
+
61
+ end
62
+ end