Index: stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/dbtoepub
===================================================================
--- stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/dbtoepub	(revision 15c7d39533a1eb1da0748011f3378fd0f02ed14d)
+++ stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/dbtoepub	(revision 15c7d39533a1eb1da0748011f3378fd0f02ed14d)
@@ -0,0 +1,76 @@
+#!/usr/bin/env ruby
+# This program converts DocBook documents into .epub files.
+# 
+# Usage: dbtoepub [OPTIONS] [DocBook Files]
+#
+# .epub is defined by the IDPF at www.idpf.org and is made up of 3 standards:
+# - Open Publication Structure (OPS)
+# - Open Packaging Format (OPF) 
+# - Open Container Format (OCF)
+#
+# Specific options:
+#     -c, --css [FILE]                 Use FILE for CSS on generated XHTML.
+#     -d, --debug                      Show debugging output.
+#     -f, --font [OTF FILE]            Embed OTF FILE in .epub.
+#     -h, --help                       Display usage info.
+#     -s, --stylesheet [XSL FILE]      Use XSL FILE as a customization
+#                                        layer (imports epub/docbook.xsl).
+#     -v, --verbose                    Make output verbose.
+
+lib = File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
+$LOAD_PATH.unshift(lib) if File.exist?(lib)
+
+require 'fileutils'
+require 'optparse'
+require 'tmpdir'
+
+require 'docbook'
+
+verbose = false
+debug = false
+css_file = nil
+otf_files = []
+customization_layer = nil
+output_file = nil
+
+#$DEBUG=true
+
+# Set up the OptionParser
+opts = OptionParser.new
+opts.banner = "Usage: #{File.basename($0)} [OPTIONS] [DocBook Files]
+
+#{File.basename($0)} converts DocBook <book> and <article>s into to .epub files.
+
+.epub is defined by the IDPF at www.idpf.org and is made up of 3 standards:
+- Open Publication Structure (OPS)
+- Open Packaging Format (OPF) 
+- Open Container Format (OCF)
+
+Specific options:"
+opts.on("-c", "--css [FILE]", "Use FILE for CSS on generated XHTML.") {|f| css_file = f}
+opts.on("-d", "--debug", "Show debugging output.") {debug = true; verbose = true}
+opts.on("-f", "--font [OTF FILE]", "Embed OTF FILE in .epub.") {|f| otf_files << f}
+opts.on("-h", "--help", "Display usage info.") {puts opts.to_s; exit 0}
+opts.on("-o", "--output [OUTPUT FILE]", "Output ePub file as OUTPUT FILE.") {|f| output_file = f}
+opts.on("-s", "--stylesheet [XSL FILE]", "Use XSL FILE as a customization layer (imports epub/docbook.xsl).") {|f| customization_layer = f}
+opts.on("-v", "--verbose", "Make output verbose.") {verbose = true}
+
+db_files = opts.parse(ARGV)
+if db_files.size == 0
+  puts opts.to_s
+  exit 0
+end
+
+db_files.each {|docbook_file|
+  dir = File.expand_path(File.join(Dir.tmpdir, ".epubtmp#{Time.now.to_f.to_s}"))
+  FileUtils.mkdir_p(dir)
+  e = DocBook::Epub.new(docbook_file, dir, css_file, customization_layer, otf_files)
+
+  if output_file
+    epub_file = output_file
+  else  
+    epub_file = File.basename(docbook_file, ".xml") + ".epub"
+  end  
+  puts "Rendering DocBook file #{docbook_file} to #{epub_file}" if verbose
+  e.render_to_file(epub_file)
+}  
Index: stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/lib/docbook.rb
===================================================================
--- stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/lib/docbook.rb	(revision 15c7d39533a1eb1da0748011f3378fd0f02ed14d)
+++ stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/lib/docbook.rb	(revision 15c7d39533a1eb1da0748011f3378fd0f02ed14d)
@@ -0,0 +1,227 @@
+require 'fileutils'
+require 'rexml/parsers/pullparser'
+
+module DocBook
+
+  class Epub
+    CHECKER = "epubcheck"
+    STYLESHEET = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', "docbook.xsl"))
+    CALLOUT_PATH = File.join('images', 'callouts')
+    CALLOUT_FULL_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', CALLOUT_PATH))
+    CALLOUT_LIMIT = 15
+    CALLOUT_EXT = ".png"
+    XSLT_PROCESSOR = "xsltproc"
+    OUTPUT_DIR = ".epubtmp#{Time.now.to_f.to_s}"
+    MIMETYPE = "application/epub+zip"
+    META_DIR = "META-INF"
+    OEBPS_DIR = "OEBPS"
+    ZIPPER = "zip"
+
+    attr_reader :output_dir
+
+    def initialize(docbook_file, output_dir=OUTPUT_DIR, css_file=nil, customization_layer=nil, embedded_fonts=[])
+      @docbook_file = docbook_file
+      @output_dir = output_dir
+      @meta_dir  = File.join(@output_dir, META_DIR)
+      @oebps_dir = File.join(@output_dir, OEBPS_DIR)
+      @css_file = css_file ? File.expand_path(css_file) : css_file
+      @embedded_fonts = embedded_fonts
+      @to_delete = []
+      
+      if customization_layer
+        @stylesheet = File.expand_path(customization_layer)
+      else
+        @stylesheet = STYLESHEET
+      end
+
+      unless File.exist?(@docbook_file)
+        raise ArgumentError.new("File #{@docbook_file} does not exist")
+      end
+    end
+
+    def render_to_file(output_file, verbose=false)
+      render_to_epub(output_file, verbose)
+      bundle_epub(output_file, verbose)
+      cleanup_files(@to_delete)
+    end
+
+    def self.invalid?(file)
+      # Obnoxiously, we can't just check for a non-zero output...
+      cmd = %Q(#{CHECKER} "#{file}")
+      output = `#{cmd} 2>&1`
+
+      if $?.to_i == 0
+        return false
+      else  
+        STDERR.puts output if $DEBUG
+        return output
+      end  
+    end
+
+    private
+    def render_to_epub(output_file, verbose)  
+      @collapsed_docbook_file = collapse_docbook()
+
+      chunk_quietly =   "--stringparam chunk.quietly " + (verbose ? '0' : '1')
+      callout_path =    "--stringparam callout.graphics.path #{CALLOUT_PATH}/"
+      callout_limit =   "--stringparam callout.graphics.number.limit #{CALLOUT_LIMIT}"
+      callout_ext =     "--stringparam callout.graphics.extension #{CALLOUT_EXT}" 
+      html_stylesheet = "--stringparam html.stylesheet #{File.basename(@css_file)}" if @css_file
+      base =            "--stringparam base.dir #{OEBPS_DIR}/" 
+      unless @embedded_fonts.empty? 
+        embedded_fonts = @embedded_fonts.map {|f| File.basename(f)}.join(',')
+        font =            "--stringparam epub.embedded.fonts \"#{embedded_fonts}\"" 
+      end  
+      meta =            "--stringparam epub.metainf.dir #{META_DIR}/" 
+      oebps =           "--stringparam epub.oebps.dir #{OEBPS_DIR}/" 
+      options = [chunk_quietly, 
+                 callout_path, 
+                 callout_limit, 
+                 callout_ext, 
+                 base, 
+                 font, 
+                 meta, 
+                 oebps, 
+                 html_stylesheet,
+                ].join(" ")
+      # Double-quote stylesheet & file to help Windows cmd.exe
+      db2epub_cmd = %Q(cd "#{@output_dir}" && #{XSLT_PROCESSOR} #{options} "#{@stylesheet}" "#{@collapsed_docbook_file}")
+      STDERR.puts db2epub_cmd if $DEBUG
+      success = system(db2epub_cmd)
+      raise "Could not render as .epub to #{output_file} (#{db2epub_cmd})" unless success
+      @to_delete << Dir["#{@meta_dir}/*"]
+      @to_delete << Dir["#{@oebps_dir}/*"]
+    end  
+
+    def bundle_epub(output_file, verbose)  
+
+      quiet = verbose ? "" : "-q"
+      mimetype_filename = write_mimetype()
+      meta   = File.basename(@meta_dir)
+      oebps  = File.basename(@oebps_dir)
+      images = copy_images()
+      csses  = copy_csses()
+      fonts  = copy_fonts()
+      callouts = copy_callouts()
+      # zip -X -r ../book.epub mimetype META-INF OEBPS
+      # Double-quote stylesheet & file to help Windows cmd.exe
+      zip_cmd = %Q(cd "#{@output_dir}" &&  #{ZIPPER} #{quiet} -X -r  "#{File.expand_path(output_file)}" "#{mimetype_filename}" "#{meta}" "#{oebps}")
+      puts zip_cmd if $DEBUG
+      success = system(zip_cmd)
+      raise "Could not bundle into .epub file to #{output_file}" unless success
+    end
+
+    # Input must be collapsed because REXML couldn't find figures in files that
+    # were XIncluded or added by ENTITY
+    #   http://sourceforge.net/tracker/?func=detail&aid=2750442&group_id=21935&atid=373747
+    def collapse_docbook
+      # Double-quote stylesheet & file to help Windows cmd.exe
+      collapsed_file = File.join(File.expand_path(File.dirname(@docbook_file)), 
+                                 '.collapsed.' + File.basename(@docbook_file))
+      entity_collapse_command = %Q(xmllint --loaddtd --noent -o "#{collapsed_file}" "#{@docbook_file}")
+      entity_success = system(entity_collapse_command)
+      raise "Could not collapse named entites in #{@docbook_file}" unless entity_success
+
+      xinclude_collapse_command = %Q(xmllint --xinclude -o "#{collapsed_file}" "#{collapsed_file}")
+      xinclude_success = system(xinclude_collapse_command)
+      raise "Could not collapse XIncludes in #{@docbook_file}" unless xinclude_success
+
+      @to_delete << collapsed_file
+      return collapsed_file
+    end  
+
+    def copy_callouts
+      new_callout_images = []
+      if has_callouts?
+        calloutglob = "#{CALLOUT_FULL_PATH}/*#{CALLOUT_EXT}"
+        Dir.glob(calloutglob).each {|img|
+          img_new_filename = File.join(@oebps_dir, CALLOUT_PATH, File.basename(img))
+
+          # TODO: What to rescue for these two?
+          FileUtils.mkdir_p(File.dirname(img_new_filename)) 
+          FileUtils.cp(img, img_new_filename)
+          @to_delete << img_new_filename
+          new_callout_images << img
+        }  
+      end  
+      return new_callout_images
+    end
+
+    def copy_fonts
+      new_fonts = []
+      @embedded_fonts.each {|font_file|
+        font_new_filename = File.join(@oebps_dir, File.basename(font_file))
+        FileUtils.cp(font_file, font_new_filename)
+        new_fonts << font_file
+      }
+      return new_fonts
+    end
+
+    def copy_csses
+      if @css_file 
+        css_new_filename = File.join(@oebps_dir, File.basename(@css_file))
+        FileUtils.cp(@css_file, css_new_filename)
+      end
+    end
+
+    def copy_images
+      image_references = get_image_refs()
+      new_images = []
+      image_references.each {|img|
+        # TODO: It'd be cooler if we had a filetype lookup rather than just
+        # extension
+        if img =~ /\.(svg|png|gif|jpe?g|xml)/i
+          img_new_filename = File.join(@oebps_dir, img)
+          img_full = File.join(File.expand_path(File.dirname(@docbook_file)), img)
+
+          # TODO: What to rescue for these two?
+          FileUtils.mkdir_p(File.dirname(img_new_filename)) 
+          puts(img_full + ": " + img_new_filename) if $DEBUG
+          FileUtils.cp(img_full, img_new_filename)
+          @to_delete << img_new_filename
+          new_images << img_full
+        end
+      }  
+      return new_images
+    end
+
+    def write_mimetype
+      mimetype_filename = File.join(@output_dir, "mimetype")
+      File.open(mimetype_filename, "w") {|f| f.print MIMETYPE}
+      @to_delete << mimetype_filename
+      return File.basename(mimetype_filename)
+    end  
+
+    def cleanup_files(file_list)
+      file_list.flatten.each {|f|
+        # Yikes
+        FileUtils.rm_r(f, :force => true )
+      }  
+    end  
+
+    # Returns an Array of all of the (image) @filerefs in a document
+    def get_image_refs
+      parser = REXML::Parsers::PullParser.new(File.new(@collapsed_docbook_file))
+      image_refs = []
+      while parser.has_next?
+        el = parser.pull
+        if el.start_element? and (el[0] == "imagedata" or el[0] == "graphic")
+          image_refs << el[1]['fileref'] 
+        end  
+      end
+      return image_refs.uniq
+    end  
+
+    # Returns true if the document has code callouts
+    def has_callouts?
+      parser = REXML::Parsers::PullParser.new(File.new(@collapsed_docbook_file))
+      while parser.has_next?
+        el = parser.pull
+        if el.start_element? and (el[0] == "calloutlist" or el[0] == "co")
+          return true
+        end  
+      end
+      return false
+    end  
+  end
+end
Index: stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/xslt/obfuscate.xsl
===================================================================
--- stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/xslt/obfuscate.xsl	(revision 15c7d39533a1eb1da0748011f3378fd0f02ed14d)
+++ stylesheets/lfs-xsl/docbook-xsl-1.78.1/epub/bin/xslt/obfuscate.xsl	(revision 15c7d39533a1eb1da0748011f3378fd0f02ed14d)
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
+  <xsl:output method="xml" omit-xml-declaration="no" doctype-public="-//OASIS//DTD DocBook XML V4.4//EN" doctype-system="http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" indent="no"/>
+  <xsl:template match="@*|*|comment()|processing-instruction()">
+    <xsl:copy>
+      <xsl:apply-templates select="@*|node()"/>
+    </xsl:copy>
+  </xsl:template>
+  <xsl:template match="text()">
+    <xsl:value-of select="replace(replace(., '[a-z]', 'x'), '[0-9]', 'd')"/>
+  </xsl:template>
+</xsl:stylesheet>
