+        if self.parent:
+            # don't need an epub
+            return
+
+        epub_file = StringIO()
+        try:
+            epub.transform(BookImportDocProvider(self), self.slug, output_file=epub_file)
+            self.epub_file.save('%s.epub' % self.slug, ContentFile(epub_file.getvalue()))
+            FileRecord(slug=self.slug, type='epub', sha1=sha1(epub_file.getvalue()).hexdigest()).save()
+        except NoDublinCore:
+            pass
+
+        book_descendants = list(self.children.all())
+        while len(book_descendants) > 0:
+            child_book = book_descendants.pop(0)
+            if remove_descendants and child_book.has_epub_file():
+                child_book.epub_file.delete()
+            # save anyway, to refresh short_html
+            child_book.save()
+            book_descendants += list(child_book.children.all())
+
+    def build_txt(self):
+        from StringIO import StringIO
+        from django.core.files.base import ContentFile
+        from librarian import text
+
+        out = StringIO()
+        text.transform(open(self.xml_file.path), out)
+        self.txt_file.save('%s.txt' % self.slug, ContentFile(out.getvalue()))
+
+
+    def build_html(self):
+        from tempfile import NamedTemporaryFile
+        from markupstring import MarkupString
+
+        meta_tags = list(self.tags.filter(
+            category__in=('author', 'epoch', 'genre', 'kind')))
+        book_tag = self.book_tag()
+
+        html_file = NamedTemporaryFile()
+        if html.transform(self.xml_file.path, html_file, parse_dublincore=False):
+            self.html_file.save('%s.html' % self.slug, File(html_file))
+
+            # get ancestor l-tags for adding to new fragments
+            ancestor_tags = []
+            p = self.parent
+            while p:
+                ancestor_tags.append(p.book_tag())
+                p = p.parent
+
+            # Delete old fragments and create them from scratch
+            self.fragments.all().delete()
+            # Extract fragments
+            closed_fragments, open_fragments = html.extract_fragments(self.html_file.path)
+            for fragment in closed_fragments.values():
+                try:
+                    theme_names = [s.strip() for s in fragment.themes.split(',')]
+                except AttributeError:
+                    continue
+                themes = []
+                for theme_name in theme_names:
+                    if not theme_name:
+                        continue
+                    tag, created = Tag.objects.get_or_create(slug=slughifi(theme_name), category='theme')
+                    if created:
+                        tag.name = theme_name
+                        tag.sort_key = theme_name.lower()
+                        tag.save()
+                    themes.append(tag)
+                if not themes:
+                    continue
+
+                text = fragment.to_string()
+                short_text = ''
+                if (len(MarkupString(text)) > 240):
+                    short_text = unicode(MarkupString(text)[:160])
+                new_fragment = Fragment.objects.create(anchor=fragment.id, book=self,
+                    text=text, short_text=short_text)
+
+                new_fragment.save()
+                new_fragment.tags = set(meta_tags + themes + [book_tag] + ancestor_tags)
+            self.save()
+            self.html_built.send(sender=self)
+            return True
+        return False
+
+
+    @classmethod
+    def from_xml_file(cls, xml_file, **kwargs):
+        # use librarian to parse meta-data
+        book_info = dcparser.parse(xml_file)
+
+        if not isinstance(xml_file, File):
+            xml_file = File(open(xml_file))
+
+        try:
+            return cls.from_text_and_meta(xml_file, book_info, **kwargs)
+        finally:
+            xml_file.close()
+
+    @classmethod
+    def from_text_and_meta(cls, raw_file, book_info, overwrite=False, build_epub=True, build_txt=True):
+        import re
+
+        # check for parts before we do anything
+        children = []
+        if hasattr(book_info, 'parts'):
+            for part_url in book_info.parts:
+                base, slug = part_url.rsplit('/', 1)
+                try:
+                    children.append(Book.objects.get(slug=slug))
+                except Book.DoesNotExist, e:
+                    raise Book.DoesNotExist(_('Book with slug = "%s" does not exist.') % slug)
+
+