diff --git a/data/style/epubdefault.css b/data/style/epubdefault.css
new file mode 100644
index 000000000..178a7edc3
--- /dev/null
+++ b/data/style/epubdefault.css
@@ -0,0 +1,15 @@
+div.recipe, div.index { font-style: Times, Serif; font-size: 12pt; margin-left: 7%; margin-right: 10%; padding: 1em; margin-top: 1em;}
+div.recipe img {float: right; padding: 1em}
+body {}
+span.label { font-weight: bold;min-width: 10em; display: inline-block;}
+p.title { font-size: 120%; text-align: center}
+p.title span.label {display: none}
+div.header p {margin-top: 0; margin-bottom: 0.2em}
+div.ing { padding: 1em; border: solid 1px; background-color: #eef}
+ul.ing { padding-left: 0.6em; }
+li.ing { list-style: none; border-top: 0.3em }
+div.ingamount{ display:inline-block; min-width:3em;}
+div.ingunit{ display:inline-block; min-width:3em;}
+img{ max-width: 90%; display: block; margin-left: auto; margin-right: auto; }
+div.header{margin-top: 1em; margin-bottom: 1em;}
+div.ing h2{margin-top: 0}
diff --git a/gourmet/plugins/import_export/epub.gourmet-plugin b/gourmet/plugins/import_export/epub.gourmet-plugin
new file mode 100644
index 000000000..0976066ce
--- /dev/null
+++ b/gourmet/plugins/import_export/epub.gourmet-plugin
@@ -0,0 +1,8 @@
+[Gourmet Plugin]
+Module=epub_plugin
+Version=1.0
+API_Version=1.0
+_Name=EPub Export
+_Comment=Create an epub book from recipes.
+_Category=Importer/Exporter
+Authors=Sven Steckmann
diff --git a/gourmet/plugins/import_export/epub_plugin/__init__.py b/gourmet/plugins/import_export/epub_plugin/__init__.py
new file mode 100644
index 000000000..e8fac24c0
--- /dev/null
+++ b/gourmet/plugins/import_export/epub_plugin/__init__.py
@@ -0,0 +1,3 @@
+import epub_exporter_plugin
+
+plugins = [epub_exporter_plugin.EpubExporterPlugin]
diff --git a/gourmet/plugins/import_export/epub_plugin/epub_exporter.py b/gourmet/plugins/import_export/epub_plugin/epub_exporter.py
new file mode 100644
index 000000000..4888fd3a4
--- /dev/null
+++ b/gourmet/plugins/import_export/epub_plugin/epub_exporter.py
@@ -0,0 +1,302 @@
+import os, re
+import xml.sax.saxutils
+from gettext import gettext as _
+from gourmet import convert,gglobals
+from gourmet.exporters.exporter import ExporterMultirec, exporter_mult
+
+from ebooklib import epub
+from string import Template
+
+RECIPE_HEADER = Template('''
+
+
+
+ $title
+
+
+''')
+RECIPE_FOOT= ""
+
+EPUB_DEFAULT_CSS="epubdefault.css"
+
+
+class EpubWriter():
+ """This class contains all things to write an epub and is a small wrapper
+ around the EbookLib which is capable of producing epub files and maybe
+ kindle in the future (it is under heavy development).
+ """
+ def __init__(self, outFileName):
+ """
+ @param outFileName The filename + path the ebook is written to on finish.
+ """
+ self.outFileName = outFileName
+ self.lang = "en"
+ self.recipeCss = None
+
+ self.imgCount = 0
+ self.recipeCount = 0
+
+ self.ebook = epub.EpubBook()
+ self.spine = ['nav']
+ self.toc = []
+
+ # set metadata
+ self.ebook.set_identifier("Cookme") # TODO: Something meaningful or time?
+ self.ebook.set_title("My Cookbook")
+ self.ebook.set_language(self.lang)
+
+ # This is normally the publisher, makes less sense for the moment
+ # ebook.add_metadata('DC', 'publisher', "Gourmet")
+
+ # TODO: Add real author from somewhere
+ self.ebook.add_author("Gourmet")
+
+ # This adds the field also known as keywords in some programs.
+ self.ebook.add_metadata('DC', 'subject', "cooking")
+
+ def addRecipeCssFromFile(self, filename):
+ """ Adds the CSS file from filename to the book. The style will be added
+ to the books root.
+ @param filename The file the css is read from and attached
+ @return The internal name within the ebook to reference this file.
+ """
+ cssFileName = "Style/recipe.css"
+
+ style = open(filename, 'rb').read()
+ recipe_css = epub.EpubItem( uid="style"
+ , file_name=cssFileName
+ , media_type="text/css"
+ , content=style)
+ self.ebook.add_item(recipe_css)
+ self.recipeCss = recipe_css
+ return cssFileName;
+
+ def addJpegImage(self, imageData):
+ """Adds a jpeg image from the imageData array to the book and returns
+ the reference name for the image to be used in html.
+ @param imageData Image data in format jpeg
+ @return The name of the image to be used in html
+ """
+ epimg = epub.EpubImage()
+ epimg.file_name = "grf/image_%i.jpg" % self.imgCount
+ self.imgCount += 1
+ epimg.media_type = "image/jpeg"
+ epimg.set_content(imageData)
+ self.ebook.add_item(epimg)
+ return epimg.file_name;
+
+ def getFileForRecipeID(self, id, ext=".xhtml"):
+ """
+ Returns a filename to reference a specific recipe
+ @param id The id which is also passed during addRecipeText
+ @return A filename for reference
+ """
+ return "recipe_%i%s" % (id,ext)
+
+ def addRecipeText(self, uniqueId, title, text):
+ """ Adds the recipe text as a chapter.
+ """
+ uniqueName = self.getFileForRecipeID(uniqueId, ext="")
+ fileName = self.getFileForRecipeID(uniqueId)
+ self.recipeCount += 1
+
+ c1 = epub.EpubHtml(title=title, file_name=fileName, lang=self.lang)
+ c1.content = text.encode('utf-8')
+ c1.add_item( self.recipeCss )
+
+ # add chapter
+ self.ebook.add_item(c1)
+ self.spine.append(c1)
+
+ # define Table Of Contents
+ self.toc.append( epub.Link(fileName, title, uniqueName) )
+
+ def finish(self):
+ """ Finish the book and writes it to the disk.
+ """
+ self.ebook.toc = self.toc
+
+ # add default NCX and Nav file
+ self.ebook.add_item(epub.EpubNcx())
+ self.ebook.add_item(epub.EpubNav())
+
+ self.ebook.spine = self.spine
+
+ epub.write_epub(self.outFileName, self.ebook, {})
+
+class epub_exporter (exporter_mult):
+ def __init__ (self, rd, r, out, conv=None,
+ doc=None,
+ # exporter_mult args
+ mult=1,
+ change_units=True,
+ ):
+ self.doc = doc
+
+ # This document will be appended by the strings to join them in the
+ # last step and pass it to the ebookwriter.
+ self.preparedDocument = []
+
+ #self.link_generator=link_generator
+ exporter_mult.__init__(self, rd, r, out,
+ conv=conv,
+ imgcount=1,
+ mult=mult,
+ change_units=change_units,
+ do_markup=True,
+ use_ml=True)
+
+ def htmlify (self, text):
+ t=text.strip()
+ #t=xml.sax.saxutils.escape(t)
+ t="%s
"%t
+ t=re.sub('\n\n+','
',t)
+ t=re.sub('\n','
',t)
+ return t
+
+ def get_title(self):
+ """Returns the title of the book in an unescaped format"""
+ title = self._grab_attr_(self.r,'title')
+ if not title: title = _('Recipe')
+ return unicode(title)
+
+ def write_head (self):
+ self.preparedDocument.append(
+ RECIPE_HEADER.substitute(title=self.get_title()) )
+ self.preparedDocument.append("
%s
"
+ % xml.sax.saxutils.escape(self.get_title()))
+
+ def write_image (self, image):
+ imagePath = self.doc.addJpegImage( image)
+ self.preparedDocument.append('' % imagePath)
+
+ def write_inghead (self):
+ self.preparedDocument.append('%s
'%_('Ingredients'))
+
+ def write_text (self, label, text):
+ attr = gglobals.NAME_TO_ATTR.get(label,label)
+ if attr == 'instructions':
+ self.preparedDocument.append('' % (attr,label,label,self.htmlify(text)))
+ else:
+ self.preparedDocument.append('%s
%s' % (attr,label,label,self.htmlify(text)))
+
+ def handle_italic (self, chunk): return "" + chunk + ""
+ def handle_bold (self, chunk): return "" + chunk + ""
+ def handle_underline (self, chunk): return "" + chunk + ""
+
+ def write_attr_head (self):
+ self.preparedDocument.append("")
+
+ def write_grouphead (self, name):
+ self.preparedDocument.append("%s"%name)
+
+ def write_groupfoot (self):
+ pass
+
+ def _write_ing_impl(self, amount, unit, item, link, optional):
+ self.preparedDocument.append('
- ')
+
+ # Escape all incoming things first.
+ (amount, unit, item) = tuple([xml.sax.saxutils.escape("%s"%o) if o else "" for o in [amount, unit, item]])
+
+ self.preparedDocument.append('
%s
' % (amount if len(amount) != 0 else " "))
+ self.preparedDocument.append('%s
' % (unit if len(unit) != 0 else " "))
+
+ if item:
+ if link:
+ self.preparedDocument.append( "%s"% (link, item ))
+ else:
+ self.preparedDocument.append(item)
+
+ if optional:
+ self.preparedDocument.append("(%s)"%_('optional'))
+ self.preparedDocument.append(" \n")
+
+ def write_ingref (self, amount, unit, item, refid, optional):
+ refFile = self.doc.getFileForRecipeID(refid)
+ self._write_ing_impl(amount, unit, item, refFile, optional)
+
+ def write_ing (self, amount=1, unit=None,
+ item=None, key=None, optional=False):
+ self._write_ing_impl(amount, unit, item, None, optional)
+
+ def write_ingfoot (self):
+ self.preparedDocument.append('
\n
\n')
+
+ def write_foot (self):
+ self.preparedDocument.append(RECIPE_FOOT)
+
+ self._grab_attr_(self.r,'id')
+ self.doc.addRecipeText(self._grab_attr_(self.r,'id'), self.get_title(), "".join(self.preparedDocument) )
+
+class website_exporter (ExporterMultirec):
+ def __init__ (self, rd, recipe_table, out, conv=None, ext='epub', copy_css=True,
+ css=os.path.join(gglobals.style_dir,EPUB_DEFAULT_CSS),
+ index_rows=['title','category','cuisine','rating','yields'],
+ change_units=False,
+ mult=1):
+
+ self.doc = EpubWriter(out)
+ self.doc.addRecipeCssFromFile(css)
+
+ self.ext=ext
+
+ self.index_rows=index_rows
+ self.exportargs={ 'change_units':change_units,
+ 'mult':mult,
+ 'doc':self.doc}
+
+ if conv:
+ self.exportargs['conv']=conv
+ ExporterMultirec.__init__(self, rd, recipe_table, out,
+ one_file=True,
+ open_files=False,
+ ext=self.ext,
+ exporter=epub_exporter,
+ exporter_kwargs=self.exportargs)
+
+ def recipe_hook (self, rec, filename, exporter):
+ """Add index entry"""
+ # TODO: Do some cool things here.
+ pass
+
+ def write_footer (self):
+ self.doc.finish()
+
diff --git a/gourmet/plugins/import_export/epub_plugin/epub_exporter_plugin.py b/gourmet/plugins/import_export/epub_plugin/epub_exporter_plugin.py
new file mode 100644
index 000000000..6fbae03f2
--- /dev/null
+++ b/gourmet/plugins/import_export/epub_plugin/epub_exporter_plugin.py
@@ -0,0 +1,27 @@
+from gourmet.plugin import ExporterPlugin
+import epub_exporter
+from gettext import gettext as _
+
+EPUBFILE = _('Epub File')
+
+class EpubExporterPlugin (ExporterPlugin):
+ label = _('Exporting epub')
+ sublabel = _('Exporting recipes an epub file in directory %(file)s')
+ single_completed_string = _('Recipe saved as epub file %(file)s')
+ filetype_desc = EPUBFILE
+ saveas_filters = [EPUBFILE,['application/epub+zip'],['*.epub']]
+ saveas_single_filters = [EPUBFILE,['application/epub+zip'],['*.epub']]
+
+ def get_multiple_exporter (self, args):
+ return epub_exporter.website_exporter(
+ args['rd'],
+ args['rv'],
+ args['file'],
+ #args['conv'],
+ )
+
+ def do_single_export (self, args) :
+ print "Single export not supported!"
+
+ def run_extra_prefs_dialog (self):
+ pass