I'm not sure how I overwrote the previous page contents. If they can be restored (and this note deleted) that would be good.
CF 2014-07-14
I expanded Keith's epub generator to handle multiple-file books, more .css file and a bit more. The output passes the epubcheck, but YMMV.
############################################################################ # # epubCreator.tsh -- command line tool to create an epub version 3.0 # file from a single text or xhmtml file, an optional cover image and # a list of 0 or more images # by Keith Vetter 2014-03-14 # Mods Clif Flynt, 2014-04-01 # Support for multiple text/html files (multiple chapters) # Support for additional .css file # Support for filename.epub different from "book title.epub" # Support for toc.ncx as well as nav.html # Expanded command line processing # # usage: epubCreator \ # -title Title -author 'last, first' -data file1.txt file2.html file3.xhtml...\ # -cover Cover.jpg -images additional Images -css Style.css -html (1/0) \ # -name BookName # # -title Title for book # -author Name of author as last, first # -data List of data files to include in the text # -cover An image file for the cover # -images Additional images that might be reference by text # -css An optional css file if you want special formatting # -html 1 or 0 to define whether the input data is already in html format # -name The name for the .epub file, if different from title # package require uuid package require fileutil array set EXT {"" "" .png image/png .gif image/gif .jpg image/jpeg .jpeg image/jpeg .svg image/svg+xml} proc parseList {list stateVarName {throwError 1}} { upvar $stateVarName stateArray set errs "" foreach arg $list { if {([string first "-" $arg] == 0) && ([llength $arg] == 1)} { set index [string range $arg 1 end] if {![info exists stateArray($index)]} { if {$throwError} { error "No default for ${stateVarName}($index).\nValid fields are [lsort [array names stateArray]]" } else { lappend errs "No default for ${stateVarName}($index)" } } set cmd set } else { if {[info exists cmd]} { $cmd stateArray($index) $arg set cmd lappend } } } return $errs } proc Init {rawname title author cover images} { global E set guid [::uuid::uuid generate] set E(rawname) $rawname set E(basename) [file tail [file rootname $E(rawname)]] set E(dirname) [file normalize [file dirname $E(rawname)]] set E(output,tempdir) [file join [::fileutil::tempdir] "epub_$guid"] if {$E(name) eq ""} { set E(output,final) [file join $E(dirname) "$E(basename).epub"] } else { set E(output,final) [file join $E(dirname) "$E(name).epub"] } set E(epub) EPUB set E(epub,tempdir) [file join $E(output,tempdir) $E(epub)] set E(html,name) "$E(basename).xhtml" set E(html,tempname) [file join $E(epub,tempdir) "$E(html,name)"] set E(ncx,tempname) [file join $E(epub,tempdir) "toc.ncx"] set E(opf,name) [file join $E(epub) package.opf] set E(opf,tempname) [file join $E(output,tempdir) $E(opf,name)] set E(mimetype) mimetype set E(mimetype,tempname) [file join $E(output,tempdir) $E(mimetype)] set E(meta-inf) META-INF set E(meta-inf,tempdir) [file join $E(output,tempdir) $E(meta-inf)] set E(meta-inf,tempname) [file join $E(meta-inf,tempdir) container.xml] set E(nav,name) nav.xhtml set E(nav,tempname) [file join $E(epub,tempdir) $E(nav,name)] set E(cover,source) $cover set E(cover,name) [file tail $cover] set E(cover,format) $::EXT([file extension $cover]) set E(images) $images set E(date) [clock format [clock seconds] -gmt 1 -format "%Y-%m-%dT%TZ"] file mkdir $E(output,tempdir) file mkdir $E(meta-inf,tempdir) file mkdir $E(epub,tempdir) set E(guid) "ebook:$guid" set E(title) $title set E(author) $author if {$E(css) ne ""} { set E(css,name) $E(css) set E(css,tempname) [file join $E(epub,tempdir) stylesheet.css] set E(opf,stylesheet) {<item href="stylesheet.css" id="css" media-type="text/css"/>} } } proc MakeEpubFiles {} { global E set i 0 if {$E(cover,source) ne ""} { set E(rawname) [list cover.xhtml {*}$E(rawname)] set of [open cover.xhtml w] puts $of [BodyToHtml "<img src=\"[file tail $E(cover,source)]\"></img>"] close $of } foreach rawname $E(rawname) { incr i set basename [file tail [file rootname $rawname]] set E(html,name) "$basename.xhtml" set E(html,tempname) [file join $E(epub,tempdir) "$E(html,name)"] append E(opf,html_items) [subst { <item id="id_html_$i" href="$E(html,name)" media-type="application/xhtml+xml"/>}] append E(opf,ref_items) [subst { <itemref idref="id_html_$i"/>}] append navs [subst $::NAV_XHTML1] append ncxs [subst $::CONTENT_NCX1] if {$E(html)} { WriteAllData $E(html,tempname) $rawname } else { WriteAllData $E(html,tempname) [TextToHtml $rawname] } } WriteAllData $E(mimetype,tempname) "application/epub+zip" WriteAllData $E(meta-inf,tempname) [subst $::CONTAINER_XML] WriteAllData $E(opf,tempname) [MakeOPF] WriteAllData $E(nav,tempname) "[subst $::NAV_XHTML0]\n$navs\n$::NAV_XHTML2" WriteAllData $E(ncx,tempname) "[subst $::CONTENT_NCX0]\n$ncxs\n$::CONTENT_NCX2" if {[info exists E(css,tempname)]} { WriteAllData $E(css,tempname) $E(css) } } proc TextToHtml {rawname} { global E set fin [open $rawname r] set data [read $fin] close $fin if {[string first "<html" $data] == -1} { set data [string map {& & < < > > \x22 " ' '} $data] ; list regsub -all -line {^$} $data {</p><p>} data set data "[subst $::HTML_TEMPLATE]" ; list } return $data } proc BodyToHtml {data} { global E set data "[subst $::HTML_TEMPLATE]" return $data } proc MakeOPF {} { global E set html_items {} set image_items {} set ref_items {} set opf [subst $::CONTENT_OPF] if {$E(cover,source) eq ""} { regsub -all -line {^.*id_cover.*$} $opf "" opf } else { file copy $E(cover,source) $E(epub,tempdir) } ;# Copy any additional images set image_items "" for {set i 0} {$i < [llength $E(images)]} {incr i} { set iname [lindex $E(images) $i] file copy $iname $E(epub,tempdir) set tailname [file tail $iname] set media $::EXT([file extension $iname]) set id "id_image_$i" set line "<item href=\"$tailname\" id=\"$id\" media-type=\"$media\"/>\n" append image_items $line } if {$image_items ne ""} { regsub -line {^.*other images go here.*$} $opf $image_items opf } return $opf } proc ZipEpub {} { global E cd $E(output,tempdir) # Get rid of any previous epub else zip will append to the # existing file, leaving behind elements you thought had been removed catch {file delete $E(output,final)} exec zip -rX $E(output,final) $E(mimetype) $E(meta-inf)/ $E(epub)/ } proc WriteAllData {fname data} { if {[file exists $data]} { set if [open $data] set data [read $if] close $if } set fout [open $fname w]; puts -nonewline $fout $data; close $fout; } proc Cleanup {} { global E file delete -force -- $E(output,tempdir) file delete cover.xhtml } set HTML_TEMPLATE {<?xml version="1.0"?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" xmlns:epub="http://www.idpf.org/2007/ops"> <head> <title>$E(title)</title> <link href="stylesheet.css" type="text/css" rel="stylesheet"/> </head> <body> <p> $data </p> </body> </html> } set CONTAINER_XML {<?xml version="1.0"?> <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container"> <rootfiles> <rootfile media-type="application/oebps-package+xml" full-path="$E(opf,name)" /> </rootfiles> </container> } # NCX format # per: http://www.gbenthien.net/Kindle%20and%20EPUB/ncx.html set CONTENT_NCX0 {<?xml version="1.0" encoding="UTF-8"?> <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="en"> <head> <meta name="dtb:uid" content="$::E(guid)"/> <meta name="dtb:depth" content="1"/> <meta name="dtb:totalPageCount" content="0"/> <meta name="dtb:maxPageNumber" content="0"/> </head> <docTitle><text>$::E(name)</text></docTitle> <docAuthor><text>$::E(author)</text></docAuthor> <navMap> } set CONTENT_NCX1 { <navPoint id="navpoint-$i" playOrder="$i"> <navLabel><text>Chapter $i</text></navLabel> <content src="$::E(html,name)"/> </navPoint> } set CONTENT_NCX2 { </navMap> </ncx> } set CONTENT_OPF {<?xml version="1.0" encoding="UTF-8"?> <package xmlns="http://www.idpf.org/2007/opf" version="3.0" unique-identifier="uuid"> <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf"> <dc:title>$E(title)</dc:title> <dc:creator>$E(author)</dc:creator> <dc:identifier id="uuid">$E(guid)</dc:identifier> <dc:language>en</dc:language> <meta property="dcterms:modified">$E(date)</meta> <meta name="cover" content="id_cover"/> </metadata> <manifest> $::E(opf,stylesheet) <item id="id_cover" href="$E(cover,name)" media-type="$E(cover,format)"/> $::E(opf,html_items) <item id="toc" href="toc.ncx" media-type="application/x-dtbncx+xml" /> $image_items <item id="nav" href="nav.xhtml" media-type="application/xhtml+xml" properties="nav"/> </manifest> <spine toc="toc"> $::E(opf,ref_items) </spine> </package> } set NAV_XHTML0 {<?xml version="1.0" encoding="UTF-8"?> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" xmlns:epub="http://www.idpf.org/2007/ops"> <head> <title>$::E(name)</title> </head> <body> <nav epub:type="toc" id="toc"> <ol> } set NAV_XHTML1 { <li><a href="$E(html,name)">Chapter $i</a></li> } set NAV_XHTML2 { </ol> </nav> </body> </html> } puts "\nepubCreator v0.3\nby Keith Vetter\n" if {[llength $argv] < 3} { puts stderr "usage: epubCreator -title <title> -author <author> -data <data file1> <data file2> ... -cover ?<cover image>? -images ?<other images> ...?" puts stderr "for example:" puts stderr " epubCreator \"Pride and Prejudice\" \"Austen, Jane\" p_and_p.xhtml cover.jpg chapter1.html" return } # set images [lassign $argv title author fname cover] array set E { title {} author {} data {} cover {} images {} css {} html {0} name {} } parseList $argv E 1 Init $E(data) $E(title) $E(author) $E(cover) $E(images) MakeEpubFiles # parray E # puts "TMP: ..$E(output,tempdir).." ZipEpub Cleanup puts "created $E(output,final)" return