]> git.evergreen-ils.org Git - working/Evergreen.git/blob - stylesheets/evergreen_docbook_files/docbook-xsl-1.75.2/epub/bin/lib/docbook.rb
Evergreen Version Fix to reflect the new EG releases 2.0.10 and 1.6.1.9
[working/Evergreen.git] / stylesheets / evergreen_docbook_files / docbook-xsl-1.75.2 / epub / bin / lib / docbook.rb
1 require 'fileutils'
2 require 'rexml/parsers/pullparser'
3
4 module DocBook
5
6   class Epub
7     CHECKER = "epubcheck"
8     STYLESHEET = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', "docbook.xsl"))
9     CALLOUT_PATH = File.join('images', 'callouts')
10     CALLOUT_FULL_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', CALLOUT_PATH))
11     CALLOUT_LIMIT = 15
12     CALLOUT_EXT = ".png"
13     XSLT_PROCESSOR = "xsltproc"
14     OUTPUT_DIR = ".epubtmp#{Time.now.to_f.to_s}"
15     MIMETYPE = "application/epub+zip"
16     META_DIR = "META-INF"
17     OEBPS_DIR = "OEBPS"
18     ZIPPER = "zip"
19
20     attr_reader :output_dir
21
22     def initialize(docbook_file, output_dir=OUTPUT_DIR, css_file=nil, customization_layer=nil, embedded_fonts=[])
23       @docbook_file = docbook_file
24       @output_dir = output_dir
25       @meta_dir  = File.join(@output_dir, META_DIR)
26       @oebps_dir = File.join(@output_dir, OEBPS_DIR)
27       @css_file = css_file ? File.expand_path(css_file) : css_file
28       @embedded_fonts = embedded_fonts
29       raise NotImplementedError if @embedded_fonts.length > 1
30       @to_delete = []
31       
32       if customization_layer
33         @stylesheet = File.expand_path(customization_layer)
34       else
35         @stylesheet = STYLESHEET
36       end
37
38       unless File.exist?(@docbook_file)
39         raise ArgumentError.new("File #{@docbook_file} does not exist")
40       end
41     end
42
43     def render_to_file(output_file, verbose=false)
44       render_to_epub(output_file, verbose)
45       bundle_epub(output_file, verbose)
46       cleanup_files(@to_delete)
47     end
48
49     def self.invalid?(file)
50       # Obnoxiously, we can't just check for a non-zero output...
51       cmd = "#{CHECKER} #{file}"
52       output = `#{cmd} 2>&1`
53
54       if $?.to_i == 0
55         return false
56       else  
57         STDERR.puts output if $DEBUG
58         return output
59       end  
60     end
61
62     private
63     def render_to_epub(output_file, verbose)  
64       @collapsed_docbook_file = collapse_docbook()
65
66       chunk_quietly =   "--stringparam chunk.quietly " + (verbose ? '0' : '1')
67       callout_path =    "--stringparam callout.graphics.path #{CALLOUT_PATH}/"
68       callout_limit =   "--stringparam callout.graphics.number.limit #{CALLOUT_LIMIT}"
69       callout_ext =     "--stringparam callout.graphics.extension #{CALLOUT_EXT}" 
70       html_stylesheet = "--stringparam html.stylesheet #{File.basename(@css_file)}" if @css_file
71       base =            "--stringparam base.dir #{OEBPS_DIR}/" 
72       unless @embedded_fonts.empty? 
73         font =            "--stringparam epub.embedded.font \"#{File.basename(@embedded_fonts.first)}\"" 
74       end  
75       meta =            "--stringparam epub.metainf.dir #{META_DIR}/" 
76       oebps =           "--stringparam epub.oebps.dir #{OEBPS_DIR}/" 
77       options = [chunk_quietly, 
78                  callout_path, 
79                  callout_limit, 
80                  callout_ext, 
81                  base, 
82                  font, 
83                  meta, 
84                  oebps, 
85                  html_stylesheet,
86                 ].join(" ")
87       # Double-quote stylesheet & file to help Windows cmd.exe
88       db2epub_cmd = "cd #{@output_dir} && #{XSLT_PROCESSOR} #{options} \"#{@stylesheet}\" \"#{@collapsed_docbook_file}\""
89       STDERR.puts db2epub_cmd if $DEBUG
90       success = system(db2epub_cmd)
91       raise "Could not render as .epub to #{output_file} (#{db2epub_cmd})" unless success
92       @to_delete << Dir["#{@meta_dir}/*"]
93       @to_delete << Dir["#{@oebps_dir}/*"]
94     end  
95
96     def bundle_epub(output_file, verbose)  
97
98       quiet = verbose ? "" : "-q"
99       mimetype_filename = write_mimetype()
100       meta   = File.basename(@meta_dir)
101       oebps  = File.basename(@oebps_dir)
102       images = copy_images()
103       csses  = copy_csses()
104       fonts  = copy_fonts()
105       callouts = copy_callouts()
106       # zip -X -r ../book.epub mimetype META-INF OEBPS
107       # Double-quote stylesheet & file to help Windows cmd.exe
108       zip_cmd = "cd \"#{@output_dir}\" &&  #{ZIPPER} #{quiet} -X -r  \"#{File.expand_path(output_file)}\" \"#{mimetype_filename}\" \"#{meta}\" \"#{oebps}\""
109       puts zip_cmd if $DEBUG
110       success = system(zip_cmd)
111       raise "Could not bundle into .epub file to #{output_file}" unless success
112     end
113
114     # Input must be collapsed because REXML couldn't find figures in files that
115     # were XIncluded or added by ENTITY
116     #   http://sourceforge.net/tracker/?func=detail&aid=2750442&group_id=21935&atid=373747
117     def collapse_docbook
118       collapsed_file = File.join(File.expand_path(File.dirname(@docbook_file)), 
119                                  '.collapsed.' + File.basename(@docbook_file))
120       entity_collapse_command = "xmllint --loaddtd --noent -o '#{collapsed_file}' '#{@docbook_file}'"
121       entity_success = system(entity_collapse_command)
122       raise "Could not collapse named entites in #{@docbook_file}" unless entity_success
123
124       xinclude_collapse_command = "xmllint --xinclude -o '#{collapsed_file}' '#{collapsed_file}'"
125       xinclude_success = system(xinclude_collapse_command)
126       raise "Could not collapse XIncludes in #{@docbook_file}" unless xinclude_success
127
128       @to_delete << collapsed_file
129       return collapsed_file
130     end  
131
132     def copy_callouts
133       new_callout_images = []
134       if has_callouts?
135         calloutglob = "#{CALLOUT_FULL_PATH}/*#{CALLOUT_EXT}"
136         Dir.glob(calloutglob).each {|img|
137           img_new_filename = File.join(@oebps_dir, CALLOUT_PATH, File.basename(img))
138
139           # TODO: What to rescue for these two?
140           FileUtils.mkdir_p(File.dirname(img_new_filename)) 
141           FileUtils.cp(img, img_new_filename)
142           @to_delete << img_new_filename
143           new_callout_images << img
144         }  
145       end  
146       return new_callout_images
147     end
148
149     def copy_fonts
150       new_fonts = []
151       @embedded_fonts.each {|font_file|
152         font_new_filename = File.join(@oebps_dir, File.basename(font_file))
153         FileUtils.cp(font_file, font_new_filename)
154         new_fonts << font_file
155       }
156       return new_fonts
157     end
158
159     def copy_csses
160       if @css_file 
161         css_new_filename = File.join(@oebps_dir, File.basename(@css_file))
162         FileUtils.cp(@css_file, css_new_filename)
163       end
164     end
165
166     def copy_images
167       image_references = get_image_refs()
168       new_images = []
169       image_references.each {|img|
170         # TODO: It'd be cooler if we had a filetype lookup rather than just
171         # extension
172         if img =~ /\.(svg|png|gif|jpe?g|xml)/i
173           img_new_filename = File.join(@oebps_dir, img)
174           img_full = File.join(File.expand_path(File.dirname(@docbook_file)), img)
175
176           # TODO: What to rescue for these two?
177           FileUtils.mkdir_p(File.dirname(img_new_filename)) 
178           puts(img_full + ": " + img_new_filename) if $DEBUG
179           FileUtils.cp(img_full, img_new_filename)
180           @to_delete << img_new_filename
181           new_images << img_full
182         end
183       }  
184       return new_images
185     end
186
187     def write_mimetype
188       mimetype_filename = File.join(@output_dir, "mimetype")
189       File.open(mimetype_filename, "w") {|f| f.print MIMETYPE}
190       @to_delete << mimetype_filename
191       return File.basename(mimetype_filename)
192     end  
193
194     def cleanup_files(file_list)
195       file_list.flatten.each {|f|
196         # Yikes
197         FileUtils.rm_r(f, :force => true )
198       }  
199     end  
200
201     # Returns an Array of all of the (image) @filerefs in a document
202     def get_image_refs
203       parser = REXML::Parsers::PullParser.new(File.new(@collapsed_docbook_file))
204       image_refs = []
205       while parser.has_next?
206         el = parser.pull
207         if el.start_element? and (el[0] == "imagedata" or el[0] == "graphic")
208           image_refs << el[1]['fileref'] 
209         end  
210       end
211       return image_refs
212     end  
213
214     # Returns true if the document has code callouts
215     def has_callouts?
216       parser = REXML::Parsers::PullParser.new(File.new(@collapsed_docbook_file))
217       while parser.has_next?
218         el = parser.pull
219         if el.start_element? and (el[0] == "calloutlist" or el[0] == "co")
220           return true
221         end  
222       end
223       return false
224     end  
225   end
226 end