home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2010 November / maximum-cd-2010-11.iso / DiscContents / calibre-0.7.13.msi / file_1346 (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2010-08-06  |  116.4 KB  |  3,640 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. __license__ = 'GPL v3'
  5. __copyright__ = '2010, Greg Riker <griker at hotmail.com>'
  6. import datetime
  7. import htmlentitydefs
  8. import os
  9. import re
  10. import shutil
  11. import codecs
  12. from collections import namedtuple
  13. from copy import deepcopy
  14. from xml.sax.saxutils import escape
  15. from calibre import prints, prepare_string_for_xml, strftime
  16. from calibre.constants import preferred_encoding
  17. from calibre.customize import CatalogPlugin
  18. from calibre.customize.conversion import OptionRecommendation, DummyReporter
  19. from calibre.ebooks.BeautifulSoup import BeautifulSoup, BeautifulStoneSoup, Tag, NavigableString
  20. from calibre.ptempfile import PersistentTemporaryDirectory
  21. from calibre.utils.date import isoformat, now as nowf
  22. from calibre.utils.logging import default_log as log
  23. FIELDS = [
  24.     'all',
  25.     'author_sort',
  26.     'authors',
  27.     'comments',
  28.     'cover',
  29.     'formats',
  30.     'id',
  31.     'isbn',
  32.     'pubdate',
  33.     'publisher',
  34.     'rating',
  35.     'series_index',
  36.     'series',
  37.     'size',
  38.     'tags',
  39.     'timestamp',
  40.     'title',
  41.     'uuid']
  42. TEMPLATE_ALLOWED_FIELDS = [
  43.     'author_sort',
  44.     'authors',
  45.     'id',
  46.     'isbn',
  47.     'pubdate',
  48.     'publisher',
  49.     'series_index',
  50.     'series',
  51.     'tags',
  52.     'timestamp',
  53.     'title',
  54.     'uuid']
  55.  
  56. class CSV_XML(CatalogPlugin):
  57.     Option = namedtuple('Option', 'option, default, dest, action, help')
  58.     name = 'Catalog_CSV_XML'
  59.     description = 'CSV/XML catalog generator'
  60.     supported_platforms = [
  61.         'windows',
  62.         'osx',
  63.         'linux']
  64.     author = 'Greg Riker'
  65.     version = (1, 0, 0)
  66.     file_types = set([
  67.         'csv',
  68.         'xml'])
  69.     cli_options = [
  70.         Option('--fields', default = 'all', dest = 'fields', action = None, help = _("The fields to output when cataloging books in the database.  Should be a comma-separated list of fields.\nAvailable fields: %s.\nDefault: '%%default'\nApplies to: CSV, XML output formats") % ', '.join(FIELDS)),
  71.         Option('--sort-by', default = 'id', dest = 'sort_by', action = None, help = _("Output field to sort on.\nAvailable fields: author_sort, id, rating, size, timestamp, title.\nDefault: '%default'\nApplies to: CSV, XML output formats"))]
  72.     
  73.     def run(self, path_to_output, opts, db, notification = DummyReporter()):
  74.         self.fmt = path_to_output.rpartition('.')[2]
  75.         self.notification = notification
  76.         if opts.verbose:
  77.             opts_dict = vars(opts)
  78.             log('%s(): Generating %s' % (self.name, self.fmt))
  79.             if opts_dict['search_text']:
  80.                 log(" --search='%s'" % opts_dict['search_text'])
  81.             
  82.             if opts_dict['ids']:
  83.                 log(' Book count: %d' % len(opts_dict['ids']))
  84.                 if opts_dict['search_text']:
  85.                     log(' (--search ignored when a subset of the database is specified)')
  86.                 
  87.             
  88.             if opts_dict['fields']:
  89.                 if opts_dict['fields'] == 'all':
  90.                     log(' Fields: %s' % ', '.join(FIELDS[1:]))
  91.                 else:
  92.                     log(' Fields: %s' % opts_dict['fields'])
  93.             
  94.         
  95.         if opts.ids:
  96.             opts.search_text = None
  97.         
  98.         data = self.search_sort_db(db, opts)
  99.         if not len(data):
  100.             log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
  101.         
  102.         fields = self.get_output_fields(opts)
  103.         if self.fmt == 'csv':
  104.             outfile = codecs.open(path_to_output, 'w', 'utf8')
  105.             outfile.write(u'%s\n' % u','.join(fields))
  106.             for entry in data:
  107.                 outstr = []
  108.                 for field in fields:
  109.                     item = entry[field]
  110.                     if item is None:
  111.                         outstr.append('""')
  112.                         continue
  113.                     elif field == 'formats':
  114.                         fmt_list = []
  115.                         for format in item:
  116.                             fmt_list.append(format.rpartition('.')[2].lower())
  117.                         
  118.                         item = ', '.join(fmt_list)
  119.                     elif field in ('authors', 'tags'):
  120.                         item = ', '.join(item)
  121.                     elif field == 'isbn':
  122.                         item = u'%s' % re.sub('[\\D]', '', item)
  123.                     elif field in ('pubdate', 'timestamp'):
  124.                         item = isoformat(item)
  125.                     elif field == 'comments':
  126.                         item = item.replace(u'\r\n', u' ')
  127.                         item = item.replace(u'\n', u' ')
  128.                     
  129.                     outstr.append(u'"%s"' % unicode(item).replace('"', '""'))
  130.                 
  131.                 outfile.write(u','.join(outstr) + u'\n')
  132.             
  133.             outfile.close()
  134.         elif self.fmt == 'xml':
  135.             etree = etree
  136.             import lxml
  137.             E = E
  138.             import lxml.builder
  139.             root = E.calibredb()
  140.             for r in data:
  141.                 record = E.record()
  142.                 root.append(record)
  143.                 for field in ('id', 'uuid', 'title', 'publisher', 'rating', 'size', 'isbn'):
  144.                     if field in fields:
  145.                         val = r[field]
  146.                         if val is None:
  147.                             continue
  148.                         
  149.                         if not isinstance(val, (str, unicode)):
  150.                             val = unicode(val)
  151.                         
  152.                         item = getattr(E, field)(val)
  153.                         record.append(item)
  154.                         continue
  155.                 
  156.                 if 'authors' in fields:
  157.                     aus = E.authors(sort = r['author_sort'])
  158.                     for au in r['authors']:
  159.                         aus.append(E.author(au))
  160.                     
  161.                     record.append(aus)
  162.                 
  163.                 for field in ('timestamp', 'pubdate'):
  164.                     if field in fields:
  165.                         record.append(getattr(E, field)(r[field].isoformat()))
  166.                         continue
  167.                 
  168.                 if 'tags' in fields and r['tags']:
  169.                     tags = E.tags()
  170.                     for tag in r['tags']:
  171.                         tags.append(E.tag(tag))
  172.                     
  173.                     record.append(tags)
  174.                 
  175.                 if 'comments' in fields and r['comments']:
  176.                     record.append(E.comments(r['comments']))
  177.                 
  178.                 if 'series' in fields and r['series']:
  179.                     record.append(E.series(r['series'], index = str(r['series_index'])))
  180.                 
  181.                 if 'cover' in fields and r['cover']:
  182.                     record.append(E.cover(r['cover'].replace(os.sep, '/')))
  183.                 
  184.                 if 'formats' in fields and r['formats']:
  185.                     fmt = E.formats()
  186.                     for f in r['formats']:
  187.                         fmt.append(E.format(f.replace(os.sep, '/')))
  188.                     
  189.                     record.append(fmt)
  190.                     continue
  191.             
  192.             
  193.             try:
  194.                 f = _[1]
  195.                 f.write(etree.tostring(root, encoding = 'utf-8', xml_declaration = True, pretty_print = True))
  196.             finally:
  197.                 pass
  198.  
  199.         
  200.  
  201.  
  202.  
  203. class BIBTEX(CatalogPlugin):
  204.     Option = namedtuple('Option', 'option, default, dest, action, help')
  205.     name = 'Catalog_BIBTEX'
  206.     description = 'BIBTEX catalog generator'
  207.     supported_platforms = [
  208.         'windows',
  209.         'osx',
  210.         'linux']
  211.     author = 'Sengian'
  212.     version = (1, 0, 0)
  213.     file_types = set([
  214.         'bib'])
  215.     cli_options = [
  216.         Option('--fields', default = 'all', dest = 'fields', action = None, help = _("The fields to output when cataloging books in the database.  Should be a comma-separated list of fields.\nAvailable fields: %s.\nDefault: '%%default'\nApplies to: BIBTEX output format") % ', '.join(FIELDS)),
  217.         Option('--sort-by', default = 'id', dest = 'sort_by', action = None, help = _("Output field to sort on.\nAvailable fields: author_sort, id, rating, size, timestamp, title.\nDefault: '%default'\nApplies to: BIBTEX output format")),
  218.         Option('--create-citation', default = 'True', dest = 'impcit', action = None, help = _("Create a citation for BibTeX entries.\nBoolean value: True, False\nDefault: '%default'\nApplies to: BIBTEX output format")),
  219.         Option('--citation-template', default = '{authors}{id}', dest = 'bib_cit', action = None, help = _("The template for citation creation from database fields.\n Should be a template with {} enclosed fields.\nAvailable fields: %s.\nDefault: '%%default'\nApplies to: BIBTEX output format") % ', '.join(TEMPLATE_ALLOWED_FIELDS)),
  220.         Option('--choose-encoding', default = 'utf8', dest = 'bibfile_enc', action = None, help = _("BibTeX file encoding output.\nAvailable types: utf8, cp1252, ascii.\nDefault: '%default'\nApplies to: BIBTEX output format")),
  221.         Option('--choose-encoding-configuration', default = 'strict', dest = 'bibfile_enctag', action = None, help = _("BibTeX file encoding flag.\nAvailable types: strict, replace, ignore, backslashreplace.\nDefault: '%default'\nApplies to: BIBTEX output format")),
  222.         Option('--entry-type', default = 'book', dest = 'bib_entry', action = None, help = _("Entry type for BibTeX catalog.\nAvailable types: book, misc, mixed.\nDefault: '%default'\nApplies to: BIBTEX output format"))]
  223.     
  224.     def run(self, path_to_output, opts, db, notification = DummyReporter()):
  225.         StringType = StringType
  226.         UnicodeType = UnicodeType
  227.         import types
  228.         preprocess_template = preprocess_template
  229.         import calibre.library.save_to_disk
  230.         bibtex_author_format = bibtex_author_format
  231.         utf8ToBibtex = utf8ToBibtex
  232.         ValidateCitationKey = ValidateCitationKey
  233.         import calibre.utils.bibtex
  234.         
  235.         def create_bibtex_entry(entry, fields, mode, template_citation, asccii_bibtex = None, citation_bibtex = (None, None, True, True)):
  236.             bibtex_entry = []
  237.             if mode != 'misc' and check_entry_book_valid(entry):
  238.                 bibtex_entry.append(u'@book{')
  239.             elif mode != 'book':
  240.                 bibtex_entry.append(u'@misc{')
  241.             else:
  242.                 return ''
  243.             if check_entry_book_valid(entry):
  244.                 bibtex_entry.append(make_bibtex_citation(entry, template_citation, asccii_bibtex))
  245.                 bibtex_entry = [
  246.                     u' '.join(bibtex_entry)]
  247.             
  248.             for field in fields:
  249.                 item = entry[field]
  250.                 if item is None:
  251.                     continue
  252.                 
  253.                 
  254.                 try:
  255.                     if len(item) == 0:
  256.                         continue
  257.                 except TypeError:
  258.                     pass
  259.  
  260.                 if field == 'authors':
  261.                     bibtex_entry.append(u'author = "%s"' % bibtex_author_format(item))
  262.                     continue
  263.                 if field in ('title', 'publisher', 'cover', 'uuid', 'author_sort', 'series'):
  264.                     bibtex_entry.append(u'%s = "%s"' % (field, utf8ToBibtex(item, asccii_bibtex)))
  265.                     continue
  266.                 if field == 'id':
  267.                     bibtex_entry.append(u'calibreid = "%s"' % int(item))
  268.                     continue
  269.                 if field == 'rating':
  270.                     bibtex_entry.append(u'rating = "%s"' % int(item))
  271.                     continue
  272.                 if field == 'size':
  273.                     bibtex_entry.append(u'%s = "%s octets"' % (field, int(item)))
  274.                     continue
  275.                 if field == 'tags':
  276.                     bibtex_entry.append(u'tags = "%s"' % utf8ToBibtex(u', '.join(item), asccii_bibtex))
  277.                     continue
  278.                 if field == 'comments':
  279.                     item = item.replace(u'\r\n', u' ')
  280.                     item = item.replace(u'\n', u' ')
  281.                     bibtex_entry.append(u'note = "%s"' % utf8ToBibtex(item, asccii_bibtex))
  282.                     continue
  283.                 if field == 'isbn':
  284.                     bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[\\D]', u'', item))
  285.                     continue
  286.                 if field == 'formats':
  287.                     item = []([ format.rpartition('.')[2].lower() for format in item ])
  288.                     bibtex_entry.append(u'formats = "%s"' % item)
  289.                     continue
  290.                 []
  291.                 if field == 'series_index':
  292.                     bibtex_entry.append(u'volume = "%s"' % int(item))
  293.                     continue
  294.                 u', '.join
  295.                 if field == 'timestamp':
  296.                     bibtex_entry.append(u'timestamp = "%s"' % isoformat(item).partition('T')[0])
  297.                     continue
  298.                 if field == 'pubdate':
  299.                     bibtex_entry.append(u'year = "%s"' % item.year)
  300.                     bibtex_entry.append(u'month = "%s"' % utf8ToBibtex(strftime('%b', item), asccii_bibtex))
  301.                     continue
  302.             
  303.             bibtex_entry = u',\n    '.join(bibtex_entry)
  304.             bibtex_entry += u' }\n\n'
  305.             return bibtex_entry
  306.  
  307.         
  308.         def check_entry_book_valid(entry):
  309.             for field in [
  310.                 'title',
  311.                 'authors',
  312.                 'publisher']:
  313.                 if entry[field] is None or len(entry[field]) == 0:
  314.                     return False
  315.             
  316.             if entry['pubdate'] is None:
  317.                 return False
  318.             return True
  319.  
  320.         
  321.         def make_bibtex_citation(entry, template_citation, asccii_bibtex):
  322.             
  323.             def tpl_replace(objtplname):
  324.                 tpl_field = re.sub(u'[\\{\\}]', u'', objtplname.group())
  325.                 if tpl_field in TEMPLATE_ALLOWED_FIELDS:
  326.                     if tpl_field in ('pubdate', 'timestamp'):
  327.                         tpl_field = isoformat(entry[tpl_field]).partition('T')[0]
  328.                     elif tpl_field in ('tags', 'authors'):
  329.                         tpl_field = entry[tpl_field][0]
  330.                     elif tpl_field in ('id', 'series_index'):
  331.                         tpl_field = str(entry[tpl_field])
  332.                     else:
  333.                         tpl_field = entry[tpl_field]
  334.                     return tpl_field
  335.                 return u''
  336.  
  337.             if len(entry['isbn']) > 0:
  338.                 template_citation = u'%s' % re.sub(u'[\\D]', u'', entry['isbn'])
  339.             else:
  340.                 template_citation = u'%s' % str(entry['id'])
  341.             if asccii_bibtex:
  342.                 return ValidateCitationKey(template_citation.encode('ascii', 'replace'))
  343.             return ValidateCitationKey(template_citation)
  344.  
  345.         self.fmt = path_to_output.rpartition('.')[2]
  346.         self.notification = notification
  347.         bibfile_enc = [
  348.             'utf8',
  349.             'cp1252',
  350.             'ascii']
  351.         bibfile_enctag = [
  352.             'strict',
  353.             'replace',
  354.             'ignore',
  355.             'backslashreplace']
  356.         bib_entry = [
  357.             'mixed',
  358.             'misc',
  359.             'book']
  360.         
  361.         try:
  362.             bibfile_enc = bibfile_enc[opts.bibfile_enc]
  363.             bibfile_enctag = bibfile_enctag[opts.bibfile_enctag]
  364.             bib_entry = bib_entry[opts.bib_entry]
  365.         except:
  366.             (None, None)
  367.             if opts.bibfile_enc in bibfile_enc:
  368.                 bibfile_enc = opts.bibfile_enc
  369.             else:
  370.                 log(' WARNING: incorrect --choose-encoding flag, revert to default')
  371.                 bibfile_enc = bibfile_enc[0]
  372.             if opts.bibfile_enctag in bibfile_enctag:
  373.                 bibfile_enctag = opts.bibfile_enctag
  374.             else:
  375.                 log(' WARNING: incorrect --choose-encoding-configuration flag, revert to default')
  376.                 bibfile_enctag = bibfile_enctag[0]
  377.             if opts.bib_entry in bib_entry:
  378.                 bib_entry = opts.bib_entry
  379.             else:
  380.                 log(' WARNING: incorrect --entry-type flag, revert to default')
  381.                 bib_entry = bib_entry[0]
  382.  
  383.         if opts.ids:
  384.             opts.search_text = None
  385.         
  386.         data = self.search_sort_db(db, opts)
  387.         if not len(data):
  388.             log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
  389.         
  390.         fields = self.get_output_fields(opts)
  391.         if not len(data):
  392.             log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
  393.         
  394.         if bibfile_enc != 'ascii':
  395.             asccii_bibtex = False
  396.         else:
  397.             asccii_bibtex = True
  398.         if isinstance(opts.impcit, (StringType, UnicodeType)):
  399.             if opts.impcit == 'False':
  400.                 citation_bibtex = False
  401.             elif opts.impcit == 'True':
  402.                 citation_bibtex = True
  403.             else:
  404.                 log(' WARNING: incorrect --create-citation, revert to default')
  405.                 citation_bibtex = True
  406.         else:
  407.             citation_bibtex = opts.impcit
  408.         template_citation = preprocess_template(opts.bib_cit)
  409.         outfile = codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)
  410.         nb_entries = len(data)
  411.         if bib_entry == 'book':
  412.             nb_books = len(filter(check_entry_book_valid, data))
  413.             if nb_books < nb_entries:
  414.                 log(' WARNING: only %d entries in %d are book compatible' % (nb_books, nb_entries))
  415.                 nb_entries = nb_books
  416.             
  417.         
  418.         outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries))
  419.         outfile.write(u'@preamble{"This catalog of %d entries was generated by calibre on %s"}\n\n' % (nb_entries, nowf().strftime('%A, %d. %B %Y %H:%M').decode(preferred_encoding)))
  420.         for entry in data:
  421.             outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation, asccii_bibtex, citation_bibtex))
  422.         
  423.         outfile.close()
  424.  
  425.  
  426.  
  427. class EPUB_MOBI(CatalogPlugin):
  428.     Option = namedtuple('Option', 'option, default, dest, action, help')
  429.     name = 'Catalog_EPUB_MOBI'
  430.     description = 'EPUB/MOBI catalog generator'
  431.     supported_platforms = [
  432.         'windows',
  433.         'osx',
  434.         'linux']
  435.     minimum_calibre_version = (0, 6, 34)
  436.     author = 'Greg Riker'
  437.     version = (0, 0, 1)
  438.     file_types = set([
  439.         'epub',
  440.         'mobi'])
  441.     cli_options = [
  442.         Option('--catalog-title', default = 'My Books', dest = 'catalog_title', action = None, help = _("Title of generated catalog used as title in metadata.\nDefault: '%default'\nApplies to: ePub, MOBI output formats")),
  443.         Option('--debug-pipeline', default = None, dest = 'debug_pipeline', action = None, help = _("Save the output from different stages of the conversion pipeline to the specified directory. Useful if you are unsure at which stage of the conversion process a bug is occurring.\nDefault: '%default'None\nApplies to: ePub, MOBI output formats")),
  444.         Option('--exclude-genre', default = '\\[[\\w ]*\\]', dest = 'exclude_genre', action = None, help = _("Regex describing tags to exclude as genres.\nDefault: '%default' excludes bracketed tags, e.g. '[<tag>]'\nApplies to: ePub, MOBI output formats")),
  445.         Option('--exclude-tags', default = '~,' + _('Catalog'), dest = 'exclude_tags', action = None, help = _("Comma-separated list of tag words indicating book should be excluded from output.  Case-insensitive.\n--exclude-tags=skip will match 'skip this book' and 'Skip will like this'.\nDefault: '%default'\nApplies to: ePub, MOBI output formats")),
  446.         Option('--generate-titles', default = False, dest = 'generate_titles', action = 'store_true', help = _("Include 'Titles' section in catalog.\nDefault: '%default'\nApplies to: ePub, MOBI output formats")),
  447.         Option('--generate-recently-added', default = False, dest = 'generate_recently_added', action = 'store_true', help = _("Include 'Recently Added' section in catalog.\nDefault: '%default'\nApplies to: ePub, MOBI output formats")),
  448.         Option('--note-tag', default = '*', dest = 'note_tag', action = None, help = _("Tag prefix for user notes, e.g. '*Jeff might enjoy reading this'.\nDefault: '%default'\nApplies to: ePub, MOBI output formats")),
  449.         Option('--numbers-as-text', default = False, dest = 'numbers_as_text', action = None, help = _("Sort titles with leading numbers as text, e.g.,\n'2001: A Space Odyssey' sorts as \n'Two Thousand One: A Space Odyssey'.\nDefault: '%default'\nApplies to: ePub, MOBI output formats")),
  450.         Option('--output-profile', default = None, dest = 'output_profile', action = None, help = _("Specifies the output profile.  In some cases, an output profile is required to optimize the catalog for the device.  For example, 'kindle' or 'kindle_dx' creates a structured Table of Contents with Sections and Articles.\nDefault: '%default'\nApplies to: ePub, MOBI output formats")),
  451.         Option('--read-tag', default = '+', dest = 'read_tag', action = None, help = _("Tag indicating book has been read.\nDefault: '%default'\nApplies to: ePub, MOBI output formats"))]
  452.     
  453.     class NumberToText(object):
  454.         ORDINALS = [
  455.             'zeroth',
  456.             'first',
  457.             'second',
  458.             'third',
  459.             'fourth',
  460.             'fifth',
  461.             'sixth',
  462.             'seventh',
  463.             'eighth',
  464.             'ninth']
  465.         lessThanTwenty = [
  466.             '<zero>',
  467.             'one',
  468.             'two',
  469.             'three',
  470.             'four',
  471.             'five',
  472.             'six',
  473.             'seven',
  474.             'eight',
  475.             'nine',
  476.             'ten',
  477.             'eleven',
  478.             'twelve',
  479.             'thirteen',
  480.             'fourteen',
  481.             'fifteen',
  482.             'sixteen',
  483.             'seventeen',
  484.             'eighteen',
  485.             'nineteen']
  486.         tens = [
  487.             '<zero>',
  488.             '<tens>',
  489.             'twenty',
  490.             'thirty',
  491.             'forty',
  492.             'fifty',
  493.             'sixty',
  494.             'seventy',
  495.             'eighty',
  496.             'ninety']
  497.         hundreds = [
  498.             '<zero>',
  499.             'one',
  500.             'two',
  501.             'three',
  502.             'four',
  503.             'five',
  504.             'six',
  505.             'seven',
  506.             'eight',
  507.             'nine']
  508.         
  509.         def __init__(self, number, verbose = False):
  510.             self.number = number
  511.             self.number_as_float = 0
  512.             self.text = ''
  513.             self.verbose = verbose
  514.             self.log = log
  515.             self.numberTranslate()
  516.  
  517.         
  518.         def stringFromInt(self, intToTranslate):
  519.             tensComponentString = ''
  520.             hundredsComponent = intToTranslate - intToTranslate % 100
  521.             tensComponent = intToTranslate % 100
  522.             if hundredsComponent:
  523.                 hundredsComponentString = '%s hundred' % self.hundreds[hundredsComponent / 100]
  524.             else:
  525.                 hundredsComponentString = ''
  526.             if tensComponent < 20:
  527.                 tensComponentString = self.lessThanTwenty[tensComponent]
  528.             else:
  529.                 tensPart = ''
  530.                 onesPart = ''
  531.                 tensPart = self.tens[tensComponent / 10]
  532.                 onesPart = self.lessThanTwenty[tensComponent % 10]
  533.                 if intToTranslate % 10:
  534.                     tensComponentString = '%s-%s' % (tensPart, onesPart)
  535.                 else:
  536.                     tensComponentString = '%s' % tensPart
  537.             result = ''
  538.             if hundredsComponent and not tensComponent:
  539.                 result = hundredsComponentString
  540.             elif not hundredsComponent and tensComponent:
  541.                 result = tensComponentString
  542.             elif hundredsComponent and tensComponent:
  543.                 result = hundredsComponentString + ' ' + tensComponentString
  544.             else:
  545.                 prints(' NumberToText.stringFromInt(): empty result translating %d' % intToTranslate)
  546.             return result
  547.  
  548.         
  549.         def numberTranslate(self):
  550.             hundredsNumber = 0
  551.             thousandsNumber = 0
  552.             hundredsString = ''
  553.             thousandsString = ''
  554.             resultString = ''
  555.             self.suffix = ''
  556.             if self.verbose:
  557.                 self.log('numberTranslate(): %s' % self.number)
  558.             
  559.             if re.search('[st|nd|rd|th]', self.number):
  560.                 self.number = re.sub(',', '', self.number)
  561.                 ordinal_suffix = re.search('[\\D]', self.number)
  562.                 ordinal_number = re.sub('\\D', '', re.sub(',', '', self.number))
  563.                 if self.verbose:
  564.                     self.log('Ordinal: %s' % ordinal_number)
  565.                 
  566.                 self.number_as_float = ordinal_number
  567.                 self.suffix = self.number[ordinal_suffix.start():]
  568.                 if int(ordinal_number) > 9:
  569.                     self.text = '%s' % EPUB_MOBI.NumberToText(ordinal_number).text
  570.                 else:
  571.                     self.text = '%s' % self.ORDINALS[int(ordinal_number)]
  572.             elif re.search(':', self.number):
  573.                 if self.verbose:
  574.                     self.log('Time: %s' % self.number)
  575.                 
  576.                 self.number_as_float = re.sub(':', '.', self.number)
  577.                 time_strings = self.number.split(':')
  578.                 hours = EPUB_MOBI.NumberToText(time_strings[0]).text
  579.                 minutes = EPUB_MOBI.NumberToText(time_strings[1]).text
  580.                 self.text = '%s-%s' % (hours.capitalize(), minutes)
  581.             elif re.search('%', self.number):
  582.                 if self.verbose:
  583.                     self.log('Percent: %s' % self.number)
  584.                 
  585.                 self.number_as_float = self.number.split('%')[0]
  586.                 self.text = EPUB_MOBI.NumberToText(self.number.replace('%', ' percent')).text
  587.             elif re.search('\\.', self.number):
  588.                 if self.verbose:
  589.                     self.log('Decimal: %s' % self.number)
  590.                 
  591.                 self.number_as_float = self.number
  592.                 decimal_strings = self.number.split('.')
  593.                 left = EPUB_MOBI.NumberToText(decimal_strings[0]).text
  594.                 right = EPUB_MOBI.NumberToText(decimal_strings[1]).text
  595.                 self.text = '%s point %s' % (left.capitalize(), right)
  596.             elif re.search('-', self.number):
  597.                 if self.verbose:
  598.                     self.log('Hyphenated: %s' % self.number)
  599.                 
  600.                 self.number_as_float = self.number.split('-')[0]
  601.                 strings = self.number.split('-')
  602.                 if re.search('[0-9]+', strings[0]):
  603.                     left = EPUB_MOBI.NumberToText(strings[0]).text
  604.                     right = strings[1]
  605.                 else:
  606.                     left = strings[0]
  607.                     right = EPUB_MOBI.NumberToText(strings[1]).text
  608.                 self.text = '%s-%s' % (left, right)
  609.             elif re.search(',', self.number) and not re.search('[^0-9,]', self.number):
  610.                 if self.verbose:
  611.                     self.log('Comma(s): %s' % self.number)
  612.                 
  613.                 self.number_as_float = re.sub(',', '', self.number)
  614.                 self.text = EPUB_MOBI.NumberToText(self.number_as_float).text
  615.             elif re.search('[\\D]+', self.number):
  616.                 if self.verbose:
  617.                     self.log('Hybrid: %s' % self.number)
  618.                 
  619.                 number_position = re.search('\\d', self.number).start()
  620.                 text_position = re.search('\\D', self.number).start()
  621.                 if number_position < text_position:
  622.                     number = self.number[:text_position]
  623.                     text = self.number[text_position:]
  624.                     self.text = '%s%s' % (EPUB_MOBI.NumberToText(number).text, text)
  625.                 else:
  626.                     text = self.number[:number_position]
  627.                     number = self.number[number_position:]
  628.                     self.text = '%s%s' % (text, EPUB_MOBI.NumberToText(number).text)
  629.             elif self.verbose:
  630.                 self.log('Clean: %s' % self.number)
  631.             
  632.             
  633.             try:
  634.                 self.float_as_number = float(self.number)
  635.                 number = int(self.number)
  636.             except:
  637.                 return None
  638.  
  639.             if number > 1000000000:
  640.                 self.text = '%d out of range' % number
  641.                 return None
  642.             if number == 1000000000:
  643.                 self.text = 'one billion'
  644.             else:
  645.                 millionsNumber = number / 1000000
  646.                 thousandsNumber = (number - millionsNumber * 1000000) / 1000
  647.                 hundredsNumber = number - millionsNumber * 1000000 - thousandsNumber * 1000
  648.                 if self.verbose:
  649.                     print 'Converting %s %s %s' % (millionsNumber, thousandsNumber, hundredsNumber)
  650.                 
  651.                 if hundredsNumber:
  652.                     hundredsString = self.stringFromInt(hundredsNumber)
  653.                 
  654.                 if thousandsNumber:
  655.                     if number > 1099 and number < 2000:
  656.                         resultString = '%s %s' % (self.lessThanTwenty[number / 100], self.stringFromInt(number % 100))
  657.                         self.text = resultString.strip().capitalize()
  658.                         return None
  659.                     thousandsString = self.stringFromInt(thousandsNumber)
  660.                 
  661.                 if millionsNumber:
  662.                     millionsString = self.stringFromInt(millionsNumber)
  663.                 
  664.                 resultString = ''
  665.                 if millionsNumber:
  666.                     resultString += '%s million ' % millionsString
  667.                 
  668.                 if thousandsNumber:
  669.                     resultString += '%s thousand ' % thousandsString
  670.                 
  671.                 if hundredsNumber:
  672.                     resultString += '%s' % hundredsString
  673.                 
  674.                 if not millionsNumber and not thousandsNumber and not hundredsNumber:
  675.                     resultString = 'zero'
  676.                 
  677.                 if self.verbose:
  678.                     self.log(u'resultString: %s' % resultString)
  679.                 
  680.                 self.text = resultString.strip().capitalize()
  681.  
  682.  
  683.     
  684.     class CatalogBuilder(object):
  685.         DATE_RANGE = [
  686.             30]
  687.         
  688.         def __init__(self, db, opts, plugin, report_progress = DummyReporter(), stylesheet = 'content/stylesheet.css'):
  689.             self._CatalogBuilder__opts = opts
  690.             self._CatalogBuilder__authorClip = opts.authorClip
  691.             self._CatalogBuilder__authors = None
  692.             self._CatalogBuilder__basename = opts.basename
  693.             self._CatalogBuilder__bookmarked_books = None
  694.             self._CatalogBuilder__booksByAuthor = None
  695.             self._CatalogBuilder__booksByDateRead = None
  696.             self._CatalogBuilder__booksByTitle = None
  697.             self._CatalogBuilder__booksByTitle_noSeriesPrefix = None
  698.             self._CatalogBuilder__catalogPath = PersistentTemporaryDirectory('_epub_mobi_catalog', prefix = '')
  699.             self._CatalogBuilder__contentDir = os.path.join(self.catalogPath, 'content')
  700.             self._CatalogBuilder__currentStep = 0
  701.             self._CatalogBuilder__creator = opts.creator
  702.             self._CatalogBuilder__db = db
  703.             self._CatalogBuilder__descriptionClip = opts.descriptionClip
  704.             self._CatalogBuilder__error = None
  705.             self._CatalogBuilder__generateForKindle = None if self.opts.fmt == 'mobi' and self.opts.output_profile and self.opts.output_profile.startswith('kindle') else False
  706.             self._CatalogBuilder__generateRecentlyRead = None if self.opts.generate_recently_added and self.opts.connected_kindle and self.generateForKindle else False
  707.             self._CatalogBuilder__genres = None
  708.             self._CatalogBuilder__genre_tags_dict = None
  709.             self._CatalogBuilder__htmlFileList = []
  710.             self._CatalogBuilder__markerTags = self.getMarkerTags()
  711.             self._CatalogBuilder__ncxSoup = None
  712.             self._CatalogBuilder__playOrder = 1
  713.             self._CatalogBuilder__plugin = plugin
  714.             self._CatalogBuilder__progressInt = 0
  715.             self._CatalogBuilder__progressString = ''
  716.             self._CatalogBuilder__reporter = report_progress
  717.             self._CatalogBuilder__stylesheet = stylesheet
  718.             self._CatalogBuilder__thumbs = None
  719.             self._CatalogBuilder__thumbWidth = 0
  720.             self._CatalogBuilder__thumbHeight = 0
  721.             self._CatalogBuilder__title = opts.catalog_title
  722.             self._CatalogBuilder__totalSteps = 11
  723.             self._CatalogBuilder__useSeriesPrefixInTitlesSection = False
  724.             self._CatalogBuilder__verbose = opts.verbose
  725.             if self.opts.generate_titles:
  726.                 self._CatalogBuilder__totalSteps += 2
  727.             
  728.             if self.opts.generate_recently_added:
  729.                 self._CatalogBuilder__totalSteps += 2
  730.                 if self.generateRecentlyRead:
  731.                     self._CatalogBuilder__totalSteps += 2
  732.                 
  733.             
  734.  
  735.         
  736.         def authorClip(self):
  737.             
  738.             def fget(self):
  739.                 return self._CatalogBuilder__authorClip
  740.  
  741.             
  742.             def fset(self, val):
  743.                 self._CatalogBuilder__authorClip = val
  744.  
  745.             return property(fget = fget, fset = fset)
  746.  
  747.         authorClip = dynamic_property(authorClip)
  748.         
  749.         def authors(self):
  750.             
  751.             def fget(self):
  752.                 return self._CatalogBuilder__authors
  753.  
  754.             
  755.             def fset(self, val):
  756.                 self._CatalogBuilder__authors = val
  757.  
  758.             return property(fget = fget, fset = fset)
  759.  
  760.         authors = dynamic_property(authors)
  761.         
  762.         def basename(self):
  763.             
  764.             def fget(self):
  765.                 return self._CatalogBuilder__basename
  766.  
  767.             
  768.             def fset(self, val):
  769.                 self._CatalogBuilder__basename = val
  770.  
  771.             return property(fget = fget, fset = fset)
  772.  
  773.         basename = dynamic_property(basename)
  774.         
  775.         def bookmarked_books(self):
  776.             
  777.             def fget(self):
  778.                 return self._CatalogBuilder__bookmarked_books
  779.  
  780.             
  781.             def fset(self, val):
  782.                 self._CatalogBuilder__bookmarked_books = val
  783.  
  784.             return property(fget = fget, fset = fset)
  785.  
  786.         bookmarked_books = dynamic_property(bookmarked_books)
  787.         
  788.         def booksByAuthor(self):
  789.             
  790.             def fget(self):
  791.                 return self._CatalogBuilder__booksByAuthor
  792.  
  793.             
  794.             def fset(self, val):
  795.                 self._CatalogBuilder__booksByAuthor = val
  796.  
  797.             return property(fget = fget, fset = fset)
  798.  
  799.         booksByAuthor = dynamic_property(booksByAuthor)
  800.         
  801.         def booksByDateRead(self):
  802.             
  803.             def fget(self):
  804.                 return self._CatalogBuilder__booksByDateRead
  805.  
  806.             
  807.             def fset(self, val):
  808.                 self._CatalogBuilder__booksByDateRead = val
  809.  
  810.             return property(fget = fget, fset = fset)
  811.  
  812.         booksByDateRead = dynamic_property(booksByDateRead)
  813.         
  814.         def booksByTitle(self):
  815.             
  816.             def fget(self):
  817.                 return self._CatalogBuilder__booksByTitle
  818.  
  819.             
  820.             def fset(self, val):
  821.                 self._CatalogBuilder__booksByTitle = val
  822.  
  823.             return property(fget = fget, fset = fset)
  824.  
  825.         booksByTitle = dynamic_property(booksByTitle)
  826.         
  827.         def booksByTitle_noSeriesPrefix(self):
  828.             
  829.             def fget(self):
  830.                 return self._CatalogBuilder__booksByTitle_noSeriesPrefix
  831.  
  832.             
  833.             def fset(self, val):
  834.                 self._CatalogBuilder__booksByTitle_noSeriesPrefix = val
  835.  
  836.             return property(fget = fget, fset = fset)
  837.  
  838.         booksByTitle_noSeriesPrefix = dynamic_property(booksByTitle_noSeriesPrefix)
  839.         
  840.         def catalogPath(self):
  841.             
  842.             def fget(self):
  843.                 return self._CatalogBuilder__catalogPath
  844.  
  845.             
  846.             def fset(self, val):
  847.                 self._CatalogBuilder__catalogPath = val
  848.  
  849.             return property(fget = fget, fset = fset)
  850.  
  851.         catalogPath = dynamic_property(catalogPath)
  852.         
  853.         def contentDir(self):
  854.             
  855.             def fget(self):
  856.                 return self._CatalogBuilder__contentDir
  857.  
  858.             
  859.             def fset(self, val):
  860.                 self._CatalogBuilder__contentDir = val
  861.  
  862.             return property(fget = fget, fset = fset)
  863.  
  864.         contentDir = dynamic_property(contentDir)
  865.         
  866.         def currentStep(self):
  867.             
  868.             def fget(self):
  869.                 return self._CatalogBuilder__currentStep
  870.  
  871.             
  872.             def fset(self, val):
  873.                 self._CatalogBuilder__currentStep = val
  874.  
  875.             return property(fget = fget, fset = fset)
  876.  
  877.         currentStep = dynamic_property(currentStep)
  878.         
  879.         def creator(self):
  880.             
  881.             def fget(self):
  882.                 return self._CatalogBuilder__creator
  883.  
  884.             
  885.             def fset(self, val):
  886.                 self._CatalogBuilder__creator = val
  887.  
  888.             return property(fget = fget, fset = fset)
  889.  
  890.         creator = dynamic_property(creator)
  891.         
  892.         def db(self):
  893.             
  894.             def fget(self):
  895.                 return self._CatalogBuilder__db
  896.  
  897.             return property(fget = fget)
  898.  
  899.         db = dynamic_property(db)
  900.         
  901.         def descriptionClip(self):
  902.             
  903.             def fget(self):
  904.                 return self._CatalogBuilder__descriptionClip
  905.  
  906.             
  907.             def fset(self, val):
  908.                 self._CatalogBuilder__descriptionClip = val
  909.  
  910.             return property(fget = fget, fset = fset)
  911.  
  912.         descriptionClip = dynamic_property(descriptionClip)
  913.         
  914.         def error(self):
  915.             
  916.             def fget(self):
  917.                 return self._CatalogBuilder__error
  918.  
  919.             return property(fget = fget)
  920.  
  921.         error = dynamic_property(error)
  922.         
  923.         def generateForKindle(self):
  924.             
  925.             def fget(self):
  926.                 return self._CatalogBuilder__generateForKindle
  927.  
  928.             
  929.             def fset(self, val):
  930.                 self._CatalogBuilder__generateForKindle = val
  931.  
  932.             return property(fget = fget, fset = fset)
  933.  
  934.         generateForKindle = dynamic_property(generateForKindle)
  935.         
  936.         def generateRecentlyRead(self):
  937.             
  938.             def fget(self):
  939.                 return self._CatalogBuilder__generateRecentlyRead
  940.  
  941.             
  942.             def fset(self, val):
  943.                 self._CatalogBuilder__generateRecentlyRead = val
  944.  
  945.             return property(fget = fget, fset = fset)
  946.  
  947.         generateRecentlyRead = dynamic_property(generateRecentlyRead)
  948.         
  949.         def genres(self):
  950.             
  951.             def fget(self):
  952.                 return self._CatalogBuilder__genres
  953.  
  954.             
  955.             def fset(self, val):
  956.                 self._CatalogBuilder__genres = val
  957.  
  958.             return property(fget = fget, fset = fset)
  959.  
  960.         genres = dynamic_property(genres)
  961.         
  962.         def genre_tags_dict(self):
  963.             
  964.             def fget(self):
  965.                 return self._CatalogBuilder__genre_tags_dict
  966.  
  967.             
  968.             def fset(self, val):
  969.                 self._CatalogBuilder__genre_tags_dict = val
  970.  
  971.             return property(fget = fget, fset = fset)
  972.  
  973.         genre_tags_dict = dynamic_property(genre_tags_dict)
  974.         
  975.         def htmlFileList(self):
  976.             
  977.             def fget(self):
  978.                 return self._CatalogBuilder__htmlFileList
  979.  
  980.             
  981.             def fset(self, val):
  982.                 self._CatalogBuilder__htmlFileList = val
  983.  
  984.             return property(fget = fget, fset = fset)
  985.  
  986.         htmlFileList = dynamic_property(htmlFileList)
  987.         
  988.         def libraryPath(self):
  989.             
  990.             def fget(self):
  991.                 return self._CatalogBuilder__libraryPath
  992.  
  993.             
  994.             def fset(self, val):
  995.                 self._CatalogBuilder__libraryPath = val
  996.  
  997.             return property(fget = fget, fset = fset)
  998.  
  999.         libraryPath = dynamic_property(libraryPath)
  1000.         
  1001.         def markerTags(self):
  1002.             
  1003.             def fget(self):
  1004.                 return self._CatalogBuilder__markerTags
  1005.  
  1006.             
  1007.             def fset(self, val):
  1008.                 self._CatalogBuilder__markerTags = val
  1009.  
  1010.             return property(fget = fget, fset = fset)
  1011.  
  1012.         markerTags = dynamic_property(markerTags)
  1013.         
  1014.         def ncxSoup(self):
  1015.             
  1016.             def fget(self):
  1017.                 return self._CatalogBuilder__ncxSoup
  1018.  
  1019.             
  1020.             def fset(self, val):
  1021.                 self._CatalogBuilder__ncxSoup = val
  1022.  
  1023.             return property(fget = fget, fset = fset)
  1024.  
  1025.         ncxSoup = dynamic_property(ncxSoup)
  1026.         
  1027.         def opts(self):
  1028.             
  1029.             def fget(self):
  1030.                 return self._CatalogBuilder__opts
  1031.  
  1032.             return property(fget = fget)
  1033.  
  1034.         opts = dynamic_property(opts)
  1035.         
  1036.         def playOrder(self):
  1037.             
  1038.             def fget(self):
  1039.                 return self._CatalogBuilder__playOrder
  1040.  
  1041.             
  1042.             def fset(self, val):
  1043.                 self._CatalogBuilder__playOrder = val
  1044.  
  1045.             return property(fget = fget, fset = fset)
  1046.  
  1047.         playOrder = dynamic_property(playOrder)
  1048.         
  1049.         def plugin(self):
  1050.             
  1051.             def fget(self):
  1052.                 return self._CatalogBuilder__plugin
  1053.  
  1054.             return property(fget = fget)
  1055.  
  1056.         plugin = dynamic_property(plugin)
  1057.         
  1058.         def progressInt(self):
  1059.             
  1060.             def fget(self):
  1061.                 return self._CatalogBuilder__progressInt
  1062.  
  1063.             
  1064.             def fset(self, val):
  1065.                 self._CatalogBuilder__progressInt = val
  1066.  
  1067.             return property(fget = fget, fset = fset)
  1068.  
  1069.         progressInt = dynamic_property(progressInt)
  1070.         
  1071.         def progressString(self):
  1072.             
  1073.             def fget(self):
  1074.                 return self._CatalogBuilder__progressString
  1075.  
  1076.             
  1077.             def fset(self, val):
  1078.                 self._CatalogBuilder__progressString = val
  1079.  
  1080.             return property(fget = fget, fset = fset)
  1081.  
  1082.         progressString = dynamic_property(progressString)
  1083.         
  1084.         def reporter(self):
  1085.             
  1086.             def fget(self):
  1087.                 return self._CatalogBuilder__reporter
  1088.  
  1089.             
  1090.             def fset(self, val):
  1091.                 self._CatalogBuilder__reporter = val
  1092.  
  1093.             return property(fget = fget, fset = fset)
  1094.  
  1095.         reporter = dynamic_property(reporter)
  1096.         
  1097.         def stylesheet(self):
  1098.             
  1099.             def fget(self):
  1100.                 return self._CatalogBuilder__stylesheet
  1101.  
  1102.             
  1103.             def fset(self, val):
  1104.                 self._CatalogBuilder__stylesheet = val
  1105.  
  1106.             return property(fget = fget, fset = fset)
  1107.  
  1108.         stylesheet = dynamic_property(stylesheet)
  1109.         
  1110.         def thumbs(self):
  1111.             
  1112.             def fget(self):
  1113.                 return self._CatalogBuilder__thumbs
  1114.  
  1115.             
  1116.             def fset(self, val):
  1117.                 self._CatalogBuilder__thumbs = val
  1118.  
  1119.             return property(fget = fget, fset = fset)
  1120.  
  1121.         thumbs = dynamic_property(thumbs)
  1122.         
  1123.         def thumbWidth(self):
  1124.             
  1125.             def fget(self):
  1126.                 return self._CatalogBuilder__thumbWidth
  1127.  
  1128.             
  1129.             def fset(self, val):
  1130.                 self._CatalogBuilder__thumbWidth = val
  1131.  
  1132.             return property(fget = fget, fset = fset)
  1133.  
  1134.         
  1135.         def thumbHeight(self):
  1136.             
  1137.             def fget(self):
  1138.                 return self._CatalogBuilder__thumbHeight
  1139.  
  1140.             
  1141.             def fset(self, val):
  1142.                 self._CatalogBuilder__thumbHeight = val
  1143.  
  1144.             return property(fget = fget, fset = fset)
  1145.  
  1146.         
  1147.         def title(self):
  1148.             
  1149.             def fget(self):
  1150.                 return self._CatalogBuilder__title
  1151.  
  1152.             
  1153.             def fset(self, val):
  1154.                 self._CatalogBuilder__title = val
  1155.  
  1156.             return property(fget = fget, fset = fset)
  1157.  
  1158.         title = dynamic_property(title)
  1159.         
  1160.         def totalSteps(self):
  1161.             
  1162.             def fget(self):
  1163.                 return self._CatalogBuilder__totalSteps
  1164.  
  1165.             return property(fget = fget)
  1166.  
  1167.         totalSteps = dynamic_property(totalSteps)
  1168.         
  1169.         def useSeriesPrefixInTitlesSection(self):
  1170.             
  1171.             def fget(self):
  1172.                 return self._CatalogBuilder__useSeriesPrefixInTitlesSection
  1173.  
  1174.             
  1175.             def fset(self, val):
  1176.                 self._CatalogBuilder__useSeriesPrefixInTitlesSection = val
  1177.  
  1178.             return property(fget = fget, fset = fset)
  1179.  
  1180.         useSeriesPrefixInTitlesSection = dynamic_property(useSeriesPrefixInTitlesSection)
  1181.         
  1182.         def verbose(self):
  1183.             
  1184.             def fget(self):
  1185.                 return self._CatalogBuilder__verbose
  1186.  
  1187.             
  1188.             def fset(self, val):
  1189.                 self._CatalogBuilder__verbose = val
  1190.  
  1191.             return property(fget = fget, fset = fset)
  1192.  
  1193.         verbose = dynamic_property(verbose)
  1194.         
  1195.         def NOT_READ_SYMBOL(self):
  1196.             
  1197.             def fget(self):
  1198.                 if self.generateForKindle:
  1199.                     return '<span style="color:white">✓</span>'
  1200.                 return '<span style="color:white">%s</span>' % self.opts.read_tag
  1201.  
  1202.             return property(fget = fget)
  1203.  
  1204.         NOT_READ_SYMBOL = dynamic_property(NOT_READ_SYMBOL)
  1205.         
  1206.         def READING_SYMBOL(self):
  1207.             
  1208.             def fget(self):
  1209.                 if self.generateForKindle:
  1210.                     return '<span style="color:black">▷</span>'
  1211.                 return '<span style="color:white">%s</span>' % self.opts.read_tag
  1212.  
  1213.             return property(fget = fget)
  1214.  
  1215.         READING_SYMBOL = dynamic_property(READING_SYMBOL)
  1216.         
  1217.         def READ_SYMBOL(self):
  1218.             
  1219.             def fget(self):
  1220.                 if self.generateForKindle:
  1221.                     return '<span style="color:black">✓</span>'
  1222.                 return '<span style="color:black">%s</span>' % self.opts.read_tag
  1223.  
  1224.             return property(fget = fget)
  1225.  
  1226.         READ_SYMBOL = dynamic_property(READ_SYMBOL)
  1227.         
  1228.         def FULL_RATING_SYMBOL(self):
  1229.             
  1230.             def fget(self):
  1231.                 if self.generateForKindle:
  1232.                     return '★'
  1233.                 return '*'
  1234.  
  1235.             return property(fget = fget)
  1236.  
  1237.         FULL_RATING_SYMBOL = dynamic_property(FULL_RATING_SYMBOL)
  1238.         
  1239.         def EMPTY_RATING_SYMBOL(self):
  1240.             
  1241.             def fget(self):
  1242.                 if self.generateForKindle:
  1243.                     return '☆'
  1244.                 return ' '
  1245.  
  1246.             return property(fget = fget)
  1247.  
  1248.         EMPTY_RATING_SYMBOL = dynamic_property(EMPTY_RATING_SYMBOL)
  1249.         
  1250.         def READ_PROGRESS_SYMBOL(self):
  1251.             
  1252.             def fget(self):
  1253.                 if self.generateForKindle:
  1254.                     return '▪'
  1255.                 return '+'
  1256.  
  1257.             return property(fget = fget)
  1258.  
  1259.         READ_PROGRESS_SYMBOL = dynamic_property(READ_PROGRESS_SYMBOL)
  1260.         
  1261.         def UNREAD_PROGRESS_SYMBOL(self):
  1262.             
  1263.             def fget(self):
  1264.                 if self.generateForKindle:
  1265.                     return '▫'
  1266.                 return '-'
  1267.  
  1268.             return property(fget = fget)
  1269.  
  1270.         UNREAD_PROGRESS_SYMBOL = dynamic_property(UNREAD_PROGRESS_SYMBOL)
  1271.         
  1272.         def buildSources(self):
  1273.             if self.booksByTitle is None:
  1274.                 if not self.fetchBooksByTitle():
  1275.                     return False
  1276.             
  1277.             self.fetchBooksByAuthor()
  1278.             self.fetchBookmarks()
  1279.             self.generateHTMLDescriptions()
  1280.             self.generateHTMLByAuthor()
  1281.             if self.opts.generate_titles:
  1282.                 self.generateHTMLByTitle()
  1283.             
  1284.             if self.opts.generate_recently_added:
  1285.                 self.generateHTMLByDateAdded()
  1286.                 if self.generateRecentlyRead:
  1287.                     self.generateHTMLByDateRead()
  1288.                 
  1289.             
  1290.             self.generateHTMLByTags()
  1291.             self.generateThumbnails()
  1292.             self.generateOPF()
  1293.             self.generateNCXHeader()
  1294.             self.generateNCXDescriptions('Descriptions')
  1295.             self.generateNCXByAuthor('Authors')
  1296.             if self.opts.generate_titles:
  1297.                 self.generateNCXByTitle('Titles')
  1298.             
  1299.             if self.opts.generate_recently_added:
  1300.                 self.generateNCXByDateAdded('Recently Added')
  1301.                 if self.generateRecentlyRead:
  1302.                     self.generateNCXByDateRead('Recently Read')
  1303.                 
  1304.             
  1305.             self.generateNCXByGenre('Genres')
  1306.             self.writeNCX()
  1307.             return True
  1308.  
  1309.         
  1310.         def cleanUp(self):
  1311.             pass
  1312.  
  1313.         
  1314.         def copyResources(self):
  1315.             catalog_resources = P('catalog')
  1316.             files_to_copy = [
  1317.                 ('', 'DefaultCover.jpg'),
  1318.                 ('content', 'stylesheet.css'),
  1319.                 ('images', 'mastheadImage.gif')]
  1320.             for file in files_to_copy:
  1321.                 if file[0] == '':
  1322.                     shutil.copy(os.path.join(catalog_resources, file[1]), self.catalogPath)
  1323.                     continue
  1324.                 shutil.copy(os.path.join(catalog_resources, file[1]), os.path.join(self.catalogPath, file[0]))
  1325.             
  1326.             if self.generateForKindle:
  1327.                 
  1328.                 try:
  1329.                     self.generateMastheadImage(os.path.join(self.catalogPath, 'images/mastheadImage.gif'))
  1330.  
  1331.             
  1332.  
  1333.         
  1334.         def fetchBooksByTitle(self):
  1335.             self.updateProgressFullStep('Fetching database')
  1336.             self.opts.sort_by = 'title'
  1337.             empty_exclude_tags = None if len(self.opts.exclude_tags) else True
  1338.             search_phrase = ''
  1339.             if not empty_exclude_tags:
  1340.                 exclude_tags = self.opts.exclude_tags.split(',')
  1341.                 search_terms = []
  1342.                 for tag in exclude_tags:
  1343.                     search_terms.append('tag:=%s' % tag)
  1344.                 
  1345.                 search_phrase = 'not (%s)' % ' or '.join(search_terms)
  1346.             
  1347.             if self.opts.ids:
  1348.                 self.opts.search_text = search_phrase
  1349.             elif self.opts.search_text:
  1350.                 self.opts.search_text += ' ' + search_phrase
  1351.             else:
  1352.                 self.opts.search_text = search_phrase
  1353.             data = self.plugin.search_sort_db(self.db, self.opts)
  1354.             titles = []
  1355.             for record in data:
  1356.                 this_title = { }
  1357.                 this_title['id'] = record['id']
  1358.                 this_title['title'] = self.convertHTMLEntities(record['title'])
  1359.                 if record['series']:
  1360.                     this_title['series'] = record['series']
  1361.                     this_title['series_index'] = record['series_index']
  1362.                     this_title['title'] = self.generateSeriesTitle(this_title)
  1363.                 else:
  1364.                     this_title['series'] = None
  1365.                     this_title['series_index'] = 0
  1366.                 this_title['title_sort'] = self.generateSortTitle(this_title['title'])
  1367.                 if 'authors' in record:
  1368.                     this_title['authors'] = record['authors']
  1369.                     if record['authors']:
  1370.                         this_title['author'] = ' & '.join(record['authors'])
  1371.                     else:
  1372.                         this_title['author'] = 'Unknown'
  1373.                 
  1374.                 if 'author_sort' in record and record['author_sort'].strip():
  1375.                     this_title['author_sort'] = record['author_sort']
  1376.                 else:
  1377.                     this_title['author_sort'] = self.author_to_author_sort(this_title['author'])
  1378.                 if record['publisher']:
  1379.                     this_title['publisher'] = re.sub('&', '&', record['publisher'])
  1380.                 
  1381.                 this_title['rating'] = None if record['rating'] else 0
  1382.                 this_title['date'] = strftime(u'%B %Y', record['pubdate'].timetuple())
  1383.                 this_title['timestamp'] = record['timestamp']
  1384.                 if record['comments']:
  1385.                     a_offset = record['comments'].find('<div class="user_annotations">')
  1386.                     ad_offset = record['comments'].find('<hr class="annotations_divider" />')
  1387.                     if a_offset >= 0:
  1388.                         record['comments'] = record['comments'][:a_offset]
  1389.                     
  1390.                     if ad_offset >= 0:
  1391.                         record['comments'] = record['comments'][:ad_offset]
  1392.                     
  1393.                     this_title['description'] = self.markdownComments(record['comments'])
  1394.                     paras = BeautifulSoup(this_title['description']).findAll('p')
  1395.                     tokens = []
  1396.                     for p in paras:
  1397.                         for token in p.contents:
  1398.                             if token.string is not None:
  1399.                                 tokens.append(token.string)
  1400.                                 continue
  1401.                         
  1402.                     
  1403.                     this_title['short_description'] = self.generateShortDescription(' '.join(tokens), dest = 'description')
  1404.                 else:
  1405.                     this_title['description'] = None
  1406.                     this_title['short_description'] = None
  1407.                 if record['cover']:
  1408.                     this_title['cover'] = re.sub('&', '&', record['cover'])
  1409.                 
  1410.                 this_title['read'] = False
  1411.                 if record['tags']:
  1412.                     this_title['tags'] = self.processSpecialTags(record['tags'], this_title, self.opts)
  1413.                 
  1414.                 if record['formats']:
  1415.                     formats = []
  1416.                     for format in record['formats']:
  1417.                         formats.append(self.convertHTMLEntities(format))
  1418.                     
  1419.                     this_title['formats'] = formats
  1420.                 
  1421.                 titles.append(this_title)
  1422.             
  1423.             if len(titles):
  1424.                 self.booksByTitle = sorted(titles, key = (lambda x: (x['title_sort'].upper(), x['title_sort'].upper())))
  1425.                 if False and self.verbose:
  1426.                     self.opts.log.info('fetchBooksByTitle(): %d books' % len(self.booksByTitle))
  1427.                     self.opts.log.info(' %-40s %-40s' % ('title', 'title_sort'))
  1428.                     for title in self.booksByTitle:
  1429.                         self.opts.log.info((u' %-40s %-40s' % (title['title'][0:40], title['title_sort'][0:40])).decode('mac-roman'))
  1430.                     
  1431.                 
  1432.                 return True
  1433.             return False
  1434.  
  1435.         
  1436.         def fetchBooksByAuthor(self):
  1437.             self.updateProgressFullStep('Sorting database')
  1438.             self.booksByAuthor = list(self.booksByTitle)
  1439.             self.booksByAuthor.sort(self.author_compare)
  1440.             if False and self.verbose:
  1441.                 self.opts.log.info('fetchBooksByAuthor(): %d books' % len(self.booksByAuthor))
  1442.                 self.opts.log.info(' %-30s %-20s %s' % ('title', 'series', 'series_index'))
  1443.                 for title in self.booksByAuthor:
  1444.                     None((self.opts.log.info % (u' %-30s %-20s%5s ', title['title'][:30] if title['series'] else '', title['series_index'])).encode('utf-8'))
  1445.                 
  1446.                 raise SystemExit
  1447.             self.verbose
  1448.             authors = [ (record['author'], record['author_sort'].capitalize()) for record in self.booksByAuthor ]
  1449.             books_by_current_author = 0
  1450.             current_author = authors[0]
  1451.             multiple_authors = False
  1452.             unique_authors = []
  1453.             for i, author in enumerate(authors):
  1454.                 if author != current_author and i:
  1455.                     if author[0] == current_author[0]:
  1456.                         self.opts.log.warn("Warning: multiple entries for Author '%s' with differing Author Sort metadata:" % author[0])
  1457.                         self.opts.log.warn(" '%s' != '%s'" % (author[1], current_author[1]))
  1458.                     
  1459.                     unique_authors.append((current_author[0], current_author[1].title(), books_by_current_author))
  1460.                     current_author = author
  1461.                     books_by_current_author = 1
  1462.                     continue
  1463.                 if i == 0 and len(authors) == 1:
  1464.                     unique_authors.append((current_author[0], current_author[1].title(), books_by_current_author))
  1465.                     continue
  1466.                 books_by_current_author += 1
  1467.             elif current_author == author or len(authors) > 1 or not multiple_authors:
  1468.                 unique_authors.append((current_author[0], current_author[1].title(), books_by_current_author))
  1469.             
  1470.             if False and self.verbose:
  1471.                 self.opts.log.info('\nfetchBooksByauthor(): %d unique authors' % len(unique_authors))
  1472.                 for author in unique_authors:
  1473.                     self.opts.log.info((u' %-50s %-25s %2d' % (author[0][0:45], author[1][0:20], author[2])).encode('utf-8'))
  1474.                 
  1475.             
  1476.             self.authors = unique_authors
  1477.  
  1478.         
  1479.         def fetchBookmarks(self):
  1480.             Device = Device
  1481.             import calibre.devices.usbms.device
  1482.             Bookmark = Bookmark
  1483.             import calibre.devices.kindle.driver
  1484.             MetaInformation = MetaInformation
  1485.             import calibre.ebooks.metadata
  1486.             MBP_FORMATS = [
  1487.                 u'azw',
  1488.                 u'mobi',
  1489.                 u'prc',
  1490.                 u'txt']
  1491.             mbp_formats = set(MBP_FORMATS)
  1492.             PDR_FORMATS = [
  1493.                 u'pdf']
  1494.             pdr_formats = set(PDR_FORMATS)
  1495.             TAN_FORMATS = [
  1496.                 u'tpz',
  1497.                 u'azw1']
  1498.             tan_formats = set(TAN_FORMATS)
  1499.             
  1500.             class BookmarkDevice(Device):
  1501.                 
  1502.                 def initialize(self, save_template):
  1503.                     self._save_template = save_template
  1504.                     self.SUPPORTS_SUB_DIRS = True
  1505.  
  1506.                 
  1507.                 def save_template(self):
  1508.                     return self._save_template
  1509.  
  1510.  
  1511.             
  1512.             def resolve_bookmark_paths(storage, path_map):
  1513.                 pop_list = []
  1514.                 book_ext = { }
  1515.                 for id in path_map:
  1516.                     file_fmts = set()
  1517.                     for fmt in path_map[id]['fmts']:
  1518.                         file_fmts.add(fmt)
  1519.                     
  1520.                     bookmark_extension = None
  1521.                     if file_fmts.intersection(mbp_formats):
  1522.                         book_extension = list(file_fmts.intersection(mbp_formats))[0]
  1523.                         bookmark_extension = 'mbp'
  1524.                     elif file_fmts.intersection(tan_formats):
  1525.                         book_extension = list(file_fmts.intersection(tan_formats))[0]
  1526.                         bookmark_extension = 'tan'
  1527.                     elif file_fmts.intersection(pdr_formats):
  1528.                         book_extension = list(file_fmts.intersection(pdr_formats))[0]
  1529.                         bookmark_extension = 'pdr'
  1530.                     
  1531.                     if bookmark_extension:
  1532.                         for vol in storage:
  1533.                             bkmk_path = path_map[id]['path'].replace(os.path.abspath('/<storage>'), vol)
  1534.                             bkmk_path = bkmk_path.replace('bookmark', bookmark_extension)
  1535.                             if os.path.exists(bkmk_path):
  1536.                                 path_map[id] = bkmk_path
  1537.                                 book_ext[id] = book_extension
  1538.                                 break
  1539.                                 continue
  1540.                         
  1541.                     pop_list.append(id)
  1542.                 
  1543.                 for id in pop_list:
  1544.                     path_map.pop(id)
  1545.                 
  1546.                 return (path_map, book_ext)
  1547.  
  1548.  
  1549.         
  1550.         def generateHTMLDescriptions(self):
  1551.             self.updateProgressFullStep("'Descriptions'")
  1552.             for title_num, title in enumerate(self.booksByTitle):
  1553.                 if False:
  1554.                     self.opts.log.info('%3s: %s - %s' % (title['id'], title['title'], title['author']))
  1555.                 
  1556.                 self.updateProgressMicroStep('Description %d of %d' % (title_num, len(self.booksByTitle)), float(title_num * 100 / len(self.booksByTitle)) / 100)
  1557.                 soup = self.generateHTMLDescriptionHeader('%s' % title['title'])
  1558.                 body = soup.find('body')
  1559.                 btc = 0
  1560.                 aTag = Tag(soup, 'a')
  1561.                 aTag['name'] = 'book%d' % int(title['id'])
  1562.                 body.insert(btc, aTag)
  1563.                 btc += 1
  1564.                 emTag = Tag(soup, 'em')
  1565.                 if title['series']:
  1566.                     brTag = Tag(soup, 'br')
  1567.                     title_tokens = title['title'].split(': ')
  1568.                     emTag.insert(0, escape(NavigableString(title_tokens[1])))
  1569.                     emTag.insert(1, brTag)
  1570.                     smallTag = Tag(soup, 'small')
  1571.                     smallTag.insert(0, escape(NavigableString(title_tokens[0])))
  1572.                     emTag.insert(2, smallTag)
  1573.                 else:
  1574.                     emTag.insert(0, NavigableString(escape(title['title'])))
  1575.                 titleTag = body.find(attrs = {
  1576.                     'class': 'title' })
  1577.                 titleTag.insert(0, emTag)
  1578.                 authorTag = body.find(attrs = {
  1579.                     'class': 'author' })
  1580.                 aTag = Tag(soup, 'a')
  1581.                 aTag['href'] = '%s.html#%s' % ('ByAlphaAuthor', self.generateAuthorAnchor(title['author']))
  1582.                 aTag.insert(0, title['author'])
  1583.                 if title['read']:
  1584.                     authorTag.insert(0, NavigableString(self.READ_SYMBOL + 'by '))
  1585.                 elif self.opts.connected_kindle and title['id'] in self.bookmarked_books:
  1586.                     authorTag.insert(0, NavigableString(self.READING_SYMBOL + ' by '))
  1587.                 else:
  1588.                     authorTag.insert(0, NavigableString(self.NOT_READ_SYMBOL + 'by '))
  1589.                 authorTag.insert(1, aTag)
  1590.                 if 'tags' in title:
  1591.                     tagsTag = body.find(attrs = {
  1592.                         'class': 'tags' })
  1593.                     ttc = 0
  1594.                     fontTag = Tag(soup, 'font')
  1595.                     fontTag['style'] = 'color:white;font-size:large'
  1596.                     if self.opts.fmt == 'epub':
  1597.                         fontTag['style'] += ';opacity: 0.0'
  1598.                     
  1599.                     fontTag.insert(0, NavigableString('by '))
  1600.                     tagsTag.insert(ttc, fontTag)
  1601.                     ttc += 1
  1602.                     for tag in title['tags']:
  1603.                         aTag = Tag(soup, 'a')
  1604.                         aTag['href'] = 'Genre_%s.html' % re.sub('\\W', '', tag.lower())
  1605.                         aTag.insert(0, escape(NavigableString(tag)))
  1606.                         emTag = Tag(soup, 'em')
  1607.                         emTag.insert(0, aTag)
  1608.                         if ttc < len(title['tags']):
  1609.                             emTag.insert(1, NavigableString(' · '))
  1610.                         
  1611.                         tagsTag.insert(ttc, emTag)
  1612.                         ttc += 1
  1613.                     
  1614.                 
  1615.                 imgTag = Tag(soup, 'img')
  1616.                 if 'cover' in title:
  1617.                     imgTag['src'] = '../images/thumbnail_%d.jpg' % int(title['id'])
  1618.                 else:
  1619.                     imgTag['src'] = '../images/thumbnail_default.jpg'
  1620.                 imgTag['alt'] = 'cover'
  1621.                 thumbnailTag = body.find(attrs = {
  1622.                     'class': 'thumbnail' })
  1623.                 thumbnailTag.insert(0, imgTag)
  1624.                 publisherTag = body.find(attrs = {
  1625.                     'class': 'publisher' })
  1626.                 if 'publisher' in title:
  1627.                     publisherTag.insert(0, NavigableString(title['publisher'] + '<br/>'))
  1628.                 else:
  1629.                     publisherTag.insert(0, NavigableString('<br/>'))
  1630.                 pubdateTag = body.find(attrs = {
  1631.                     'class': 'date' })
  1632.                 if title['date'] is not None:
  1633.                     pubdateTag.insert(0, NavigableString(title['date'] + '<br/>'))
  1634.                 else:
  1635.                     pubdateTag.insert(0, NavigableString('<br/>'))
  1636.                 stars = int(title['rating']) / 2
  1637.                 star_string = self.FULL_RATING_SYMBOL * stars
  1638.                 empty_stars = self.EMPTY_RATING_SYMBOL * (5 - stars)
  1639.                 ratingTag = body.find(attrs = {
  1640.                     'class': 'rating' })
  1641.                 ratingTag.insert(0, NavigableString('%s%s <br/>' % (star_string, empty_stars)))
  1642.                 if 'notes' in title:
  1643.                     notesTag = body.find(attrs = {
  1644.                         'class': 'notes' })
  1645.                     notesTag.insert(0, NavigableString(title['notes'] + '<br/>'))
  1646.                 else:
  1647.                     notes_labelTag = body.find(attrs = {
  1648.                         'class': 'notes_label' })
  1649.                     empty_labelTag = Tag(soup, 'td')
  1650.                     empty_labelTag.insert(0, NavigableString('<br/>'))
  1651.                     notes_labelTag.replaceWith(empty_labelTag)
  1652.                 if 'description' in title and title['description'] > '':
  1653.                     blurbTag = body.find(attrs = {
  1654.                         'class': 'description' })
  1655.                     blurbTag.insert(0, NavigableString(title['description']))
  1656.                 
  1657.                 outfile = open('%s/book_%d.html' % (self.contentDir, int(title['id'])), 'w')
  1658.                 outfile.write(soup.prettify())
  1659.                 outfile.close()
  1660.             
  1661.  
  1662.         
  1663.         def generateHTMLByTitle(self):
  1664.             self.updateProgressFullStep("'Titles'")
  1665.             soup = self.generateHTMLEmptyHeader('Books By Alpha Title')
  1666.             body = soup.find('body')
  1667.             btc = 0
  1668.             aTag = Tag(soup, 'a')
  1669.             aTag['name'] = 'section_start'
  1670.             body.insert(btc, aTag)
  1671.             btc += 1
  1672.             aTag = Tag(soup, 'a')
  1673.             aTag['name'] = 'bytitle'
  1674.             body.insert(btc, aTag)
  1675.             btc += 1
  1676.             divTag = Tag(soup, 'div')
  1677.             dtc = 0
  1678.             current_letter = ''
  1679.             if not self.useSeriesPrefixInTitlesSection:
  1680.                 nspt = deepcopy(self.booksByTitle)
  1681.                 for book in nspt:
  1682.                     if book['series']:
  1683.                         tokens = book['title'].split(': ')
  1684.                         book['title'] = '%s (%s)' % (tokens[1], tokens[0])
  1685.                         book['title_sort'] = self.generateSortTitle(book['title'])
  1686.                         continue
  1687.                 
  1688.                 nspt = sorted(nspt, key = (lambda x: (x['title_sort'].upper(), x['title_sort'].upper())))
  1689.                 self.booksByTitle_noSeriesPrefix = nspt
  1690.                 if False and self.verbose:
  1691.                     self.opts.log.info('no_series_prefix_titles: %d books' % len(nspt))
  1692.                     self.opts.log.info(' %-40s %-40s' % ('title', 'title_sort'))
  1693.                     for title in nspt:
  1694.                         self.opts.log.info((u' %-40s %-40s' % (title['title'][0:40], title['title_sort'][0:40])).encode('utf-8'))
  1695.                     
  1696.                 
  1697.             
  1698.             title_list = self.booksByTitle
  1699.             if not self.useSeriesPrefixInTitlesSection:
  1700.                 title_list = self.booksByTitle_noSeriesPrefix
  1701.             
  1702.             for book in title_list:
  1703.                 if self.letter_or_symbol(book['title_sort'][0]) != current_letter:
  1704.                     current_letter = self.letter_or_symbol(book['title_sort'][0])
  1705.                     pIndexTag = Tag(soup, 'p')
  1706.                     pIndexTag['class'] = 'letter_index'
  1707.                     aTag = Tag(soup, 'a')
  1708.                     aTag['name'] = '%s' % self.letter_or_symbol(book['title_sort'][0])
  1709.                     pIndexTag.insert(0, aTag)
  1710.                     pIndexTag.insert(1, NavigableString(self.letter_or_symbol(book['title_sort'][0])))
  1711.                     divTag.insert(dtc, pIndexTag)
  1712.                     dtc += 1
  1713.                 
  1714.                 pBookTag = Tag(soup, 'p')
  1715.                 ptc = 0
  1716.                 if book['read']:
  1717.                     pBookTag.insert(ptc, NavigableString(self.READ_SYMBOL))
  1718.                     pBookTag['class'] = 'read_book'
  1719.                     ptc += 1
  1720.                 elif book['id'] in self.bookmarked_books:
  1721.                     pBookTag.insert(ptc, NavigableString(self.READING_SYMBOL))
  1722.                     pBookTag['class'] = 'read_book'
  1723.                     ptc += 1
  1724.                 else:
  1725.                     pBookTag['class'] = 'unread_book'
  1726.                     pBookTag.insert(ptc, NavigableString(self.NOT_READ_SYMBOL))
  1727.                     ptc += 1
  1728.                 aTag = Tag(soup, 'a')
  1729.                 aTag['href'] = 'book_%d.html' % int(float(book['id']))
  1730.                 aTag.insert(0, escape(book['title']))
  1731.                 pBookTag.insert(ptc, aTag)
  1732.                 ptc += 1
  1733.                 pBookTag.insert(ptc, NavigableString(' · '))
  1734.                 ptc += 1
  1735.                 emTag = Tag(soup, 'em')
  1736.                 aTag = Tag(soup, 'a')
  1737.                 aTag['href'] = '%s.html#%s' % ('ByAlphaAuthor', self.generateAuthorAnchor(book['author']))
  1738.                 aTag.insert(0, NavigableString(book['author']))
  1739.                 emTag.insert(0, aTag)
  1740.                 pBookTag.insert(ptc, emTag)
  1741.                 ptc += 1
  1742.                 divTag.insert(dtc, pBookTag)
  1743.                 dtc += 1
  1744.             
  1745.             body.insert(btc, divTag)
  1746.             btc += 1
  1747.             outfile_spec = '%s/ByAlphaTitle.html' % self.contentDir
  1748.             outfile = open(outfile_spec, 'w')
  1749.             outfile.write(soup.prettify())
  1750.             outfile.close()
  1751.             self.htmlFileList.append('content/ByAlphaTitle.html')
  1752.  
  1753.         
  1754.         def generateHTMLByAuthor(self):
  1755.             self.updateProgressFullStep("'Authors'")
  1756.             friendly_name = 'By Author'
  1757.             soup = self.generateHTMLEmptyHeader(friendly_name)
  1758.             body = soup.find('body')
  1759.             btc = 0
  1760.             aTag = Tag(soup, 'a')
  1761.             aTag['name'] = 'section_start'
  1762.             body.insert(btc, aTag)
  1763.             btc += 1
  1764.             aTag = Tag(soup, 'a')
  1765.             anchor_name = friendly_name.lower()
  1766.             aTag['name'] = anchor_name.replace(' ', '')
  1767.             body.insert(btc, aTag)
  1768.             btc += 1
  1769.             divTag = Tag(soup, 'div')
  1770.             dtc = 0
  1771.             current_letter = ''
  1772.             current_author = ''
  1773.             current_series = None
  1774.             book_count = 0
  1775.             for book in self.booksByAuthor:
  1776.                 book_count += 1
  1777.                 if self.letter_or_symbol(book['author_sort'][0].upper()) != current_letter:
  1778.                     current_letter = self.letter_or_symbol(book['author_sort'][0].upper())
  1779.                     pIndexTag = Tag(soup, 'p')
  1780.                     pIndexTag['class'] = 'letter_index'
  1781.                     aTag = Tag(soup, 'a')
  1782.                     aTag['name'] = '%sauthors' % self.letter_or_symbol(current_letter)
  1783.                     pIndexTag.insert(0, aTag)
  1784.                     pIndexTag.insert(1, NavigableString(self.letter_or_symbol(book['author_sort'][0].upper())))
  1785.                     divTag.insert(dtc, pIndexTag)
  1786.                     dtc += 1
  1787.                 
  1788.                 if book['author'] != current_author:
  1789.                     current_author = book['author']
  1790.                     non_series_books = 0
  1791.                     current_series = None
  1792.                     pAuthorTag = Tag(soup, 'p')
  1793.                     pAuthorTag['class'] = 'author_index'
  1794.                     emTag = Tag(soup, 'em')
  1795.                     aTag = Tag(soup, 'a')
  1796.                     aTag['name'] = '%s' % self.generateAuthorAnchor(current_author)
  1797.                     aTag.insert(0, NavigableString(current_author))
  1798.                     emTag.insert(0, aTag)
  1799.                     pAuthorTag.insert(0, emTag)
  1800.                     divTag.insert(dtc, pAuthorTag)
  1801.                     dtc += 1
  1802.                 
  1803.                 if not current_series and non_series_books and book['series']:
  1804.                     hrTag = Tag(soup, 'hr')
  1805.                     hrTag['class'] = 'series_divider'
  1806.                     divTag.insert(dtc, hrTag)
  1807.                     dtc += 1
  1808.                 
  1809.                 if book['series'] and book['series'] != current_series:
  1810.                     current_series = book['series']
  1811.                     pSeriesTag = Tag(soup, 'p')
  1812.                     pSeriesTag['class'] = 'series'
  1813.                     pSeriesTag.insert(0, NavigableString(self.NOT_READ_SYMBOL + book['series']))
  1814.                     divTag.insert(dtc, pSeriesTag)
  1815.                     dtc += 1
  1816.                 
  1817.                 if current_series and not book['series']:
  1818.                     current_series = None
  1819.                 
  1820.                 pBookTag = Tag(soup, 'p')
  1821.                 ptc = 0
  1822.                 if book['read']:
  1823.                     pBookTag.insert(ptc, NavigableString(self.READ_SYMBOL))
  1824.                     pBookTag['class'] = 'read_book'
  1825.                     ptc += 1
  1826.                 elif book['id'] in self.bookmarked_books:
  1827.                     pBookTag.insert(ptc, NavigableString(self.READING_SYMBOL))
  1828.                     pBookTag['class'] = 'read_book'
  1829.                     ptc += 1
  1830.                 else:
  1831.                     pBookTag['class'] = 'unread_book'
  1832.                     pBookTag.insert(ptc, NavigableString(self.NOT_READ_SYMBOL))
  1833.                     ptc += 1
  1834.                 aTag = Tag(soup, 'a')
  1835.                 aTag['href'] = 'book_%d.html' % int(float(book['id']))
  1836.                 if current_series:
  1837.                     aTag.insert(0, escape(book['title'][len(book['series']) + 1:]))
  1838.                 else:
  1839.                     aTag.insert(0, escape(book['title']))
  1840.                     non_series_books += 1
  1841.                 pBookTag.insert(ptc, aTag)
  1842.                 ptc += 1
  1843.                 divTag.insert(dtc, pBookTag)
  1844.                 dtc += 1
  1845.             
  1846.             body.insert(btc, divTag)
  1847.             outfile_spec = '%s/ByAlphaAuthor.html' % self.contentDir
  1848.             outfile = open(outfile_spec, 'w')
  1849.             outfile.write(soup.prettify())
  1850.             outfile.close()
  1851.             self.htmlFileList.append('content/ByAlphaAuthor.html')
  1852.  
  1853.         
  1854.         def generateHTMLByDateAdded(self):
  1855.             self.updateProgressFullStep("'Recently Added'")
  1856.             
  1857.             def add_books_to_HTML_by_month(this_months_list, dtc):
  1858.                 if len(this_months_list):
  1859.                     this_months_list.sort(self.author_compare)
  1860.                     date_string = strftime(u'%B %Y', current_date.timetuple())
  1861.                     pIndexTag = Tag(soup, 'p')
  1862.                     pIndexTag['class'] = 'date_index'
  1863.                     aTag = Tag(soup, 'a')
  1864.                     aTag['name'] = 'bda_%s-%s' % (current_date.year, current_date.month)
  1865.                     pIndexTag.insert(0, aTag)
  1866.                     pIndexTag.insert(1, NavigableString(date_string))
  1867.                     divTag.insert(dtc, pIndexTag)
  1868.                     dtc += 1
  1869.                     current_author = None
  1870.                     current_series = None
  1871.                     for new_entry in this_months_list:
  1872.                         if new_entry['author'] != current_author:
  1873.                             current_author = new_entry['author']
  1874.                             non_series_books = 0
  1875.                             current_series = None
  1876.                             pAuthorTag = Tag(soup, 'p')
  1877.                             pAuthorTag['class'] = 'author_index'
  1878.                             emTag = Tag(soup, 'em')
  1879.                             aTag = Tag(soup, 'a')
  1880.                             aTag['name'] = '%s' % self.generateAuthorAnchor(current_author)
  1881.                             aTag.insert(0, NavigableString(current_author))
  1882.                             emTag.insert(0, aTag)
  1883.                             pAuthorTag.insert(0, emTag)
  1884.                             divTag.insert(dtc, pAuthorTag)
  1885.                             dtc += 1
  1886.                         
  1887.                         if not current_series and non_series_books and new_entry['series']:
  1888.                             hrTag = Tag(soup, 'hr')
  1889.                             hrTag['class'] = 'series_divider'
  1890.                             divTag.insert(dtc, hrTag)
  1891.                             dtc += 1
  1892.                         
  1893.                         if new_entry['series'] and new_entry['series'] != current_series:
  1894.                             current_series = new_entry['series']
  1895.                             pSeriesTag = Tag(soup, 'p')
  1896.                             pSeriesTag['class'] = 'series'
  1897.                             pSeriesTag.insert(0, NavigableString(self.NOT_READ_SYMBOL + new_entry['series']))
  1898.                             divTag.insert(dtc, pSeriesTag)
  1899.                             dtc += 1
  1900.                         
  1901.                         if current_series and not new_entry['series']:
  1902.                             current_series = None
  1903.                         
  1904.                         pBookTag = Tag(soup, 'p')
  1905.                         ptc = 0
  1906.                         if new_entry['read']:
  1907.                             pBookTag.insert(ptc, NavigableString(self.READ_SYMBOL))
  1908.                             pBookTag['class'] = 'read_book'
  1909.                             ptc += 1
  1910.                         elif new_entry['id'] in self.bookmarked_books:
  1911.                             pBookTag.insert(ptc, NavigableString(self.READING_SYMBOL))
  1912.                             pBookTag['class'] = 'read_book'
  1913.                             ptc += 1
  1914.                         else:
  1915.                             pBookTag['class'] = 'unread_book'
  1916.                             pBookTag.insert(ptc, NavigableString(self.NOT_READ_SYMBOL))
  1917.                             ptc += 1
  1918.                         aTag = Tag(soup, 'a')
  1919.                         aTag['href'] = 'book_%d.html' % int(float(new_entry['id']))
  1920.                         if current_series:
  1921.                             aTag.insert(0, escape(new_entry['title'][len(new_entry['series']) + 1:]))
  1922.                         else:
  1923.                             aTag.insert(0, escape(new_entry['title']))
  1924.                             non_series_books += 1
  1925.                         pBookTag.insert(ptc, aTag)
  1926.                         ptc += 1
  1927.                         divTag.insert(dtc, pBookTag)
  1928.                         dtc += 1
  1929.                     
  1930.                 
  1931.                 return dtc
  1932.  
  1933.             
  1934.             def add_books_to_HTML_by_date_range(date_range_list, date_range, dtc):
  1935.                 if len(date_range_list):
  1936.                     pIndexTag = Tag(soup, 'p')
  1937.                     pIndexTag['class'] = 'date_index'
  1938.                     aTag = Tag(soup, 'a')
  1939.                     aTag['name'] = 'bda_%s' % date_range.replace(' ', '')
  1940.                     pIndexTag.insert(0, aTag)
  1941.                     pIndexTag.insert(1, NavigableString(date_range))
  1942.                     divTag.insert(dtc, pIndexTag)
  1943.                     dtc += 1
  1944.                     for new_entry in date_range_list:
  1945.                         pBookTag = Tag(soup, 'p')
  1946.                         ptc = 0
  1947.                         if new_entry['read']:
  1948.                             pBookTag.insert(ptc, NavigableString(self.READ_SYMBOL))
  1949.                             pBookTag['class'] = 'read_book'
  1950.                             ptc += 1
  1951.                         elif new_entry['id'] in self.bookmarked_books:
  1952.                             pBookTag.insert(ptc, NavigableString(self.READING_SYMBOL))
  1953.                             pBookTag['class'] = 'read_book'
  1954.                             ptc += 1
  1955.                         else:
  1956.                             pBookTag['class'] = 'unread_book'
  1957.                             pBookTag.insert(ptc, NavigableString(self.NOT_READ_SYMBOL))
  1958.                             ptc += 1
  1959.                         aTag = Tag(soup, 'a')
  1960.                         aTag['href'] = 'book_%d.html' % int(float(new_entry['id']))
  1961.                         aTag.insert(0, escape(new_entry['title']))
  1962.                         pBookTag.insert(ptc, aTag)
  1963.                         ptc += 1
  1964.                         pBookTag.insert(ptc, NavigableString(' · '))
  1965.                         ptc += 1
  1966.                         emTag = Tag(soup, 'em')
  1967.                         aTag = Tag(soup, 'a')
  1968.                         aTag['href'] = '%s.html#%s' % ('ByAlphaAuthor', self.generateAuthorAnchor(new_entry['author']))
  1969.                         aTag.insert(0, NavigableString(new_entry['author']))
  1970.                         emTag.insert(0, aTag)
  1971.                         pBookTag.insert(ptc, emTag)
  1972.                         ptc += 1
  1973.                         divTag.insert(dtc, pBookTag)
  1974.                         dtc += 1
  1975.                     
  1976.                 
  1977.                 return dtc
  1978.  
  1979.             friendly_name = 'Recently Added'
  1980.             soup = self.generateHTMLEmptyHeader(friendly_name)
  1981.             body = soup.find('body')
  1982.             btc = 0
  1983.             aTag = Tag(soup, 'a')
  1984.             aTag['name'] = 'section_start'
  1985.             body.insert(btc, aTag)
  1986.             btc += 1
  1987.             aTag = Tag(soup, 'a')
  1988.             anchor_name = friendly_name.lower()
  1989.             aTag['name'] = anchor_name.replace(' ', '')
  1990.             body.insert(btc, aTag)
  1991.             btc += 1
  1992.             divTag = Tag(soup, 'div')
  1993.             dtc = 0
  1994.             if self.useSeriesPrefixInTitlesSection:
  1995.                 self.booksByDateRange = sorted(self.booksByTitle, key = (lambda x: (x['timestamp'], x['timestamp'])), reverse = True)
  1996.             else:
  1997.                 nspt = deepcopy(self.booksByTitle)
  1998.                 for book in nspt:
  1999.                     if book['series']:
  2000.                         tokens = book['title'].split(': ')
  2001.                         book['title'] = '%s (%s)' % (tokens[1], tokens[0])
  2002.                         book['title_sort'] = self.generateSortTitle(book['title'])
  2003.                         continue
  2004.                 
  2005.                 self.booksByDateRange = sorted(nspt, key = (lambda x: (x['timestamp'], x['timestamp'])), reverse = True)
  2006.             date_range_list = []
  2007.             today_time = nowf().replace(hour = 23, minute = 59, second = 59)
  2008.             books_added_in_date_range = False
  2009.             for i, date in enumerate(self.DATE_RANGE):
  2010.                 date_range_limit = self.DATE_RANGE[i]
  2011.                 if i:
  2012.                     date_range = '%d to %d days ago' % (self.DATE_RANGE[i - 1], self.DATE_RANGE[i])
  2013.                 else:
  2014.                     date_range = 'Last %d days' % self.DATE_RANGE[i]
  2015.                 for book in self.booksByDateRange:
  2016.                     book_time = book['timestamp']
  2017.                     delta = today_time - book_time
  2018.                     if delta.days <= date_range_limit:
  2019.                         date_range_list.append(book)
  2020.                         books_added_in_date_range = True
  2021.                         continue
  2022.                 
  2023.                 dtc = add_books_to_HTML_by_date_range(date_range_list, date_range, dtc)
  2024.                 date_range_list = [
  2025.                     book]
  2026.             
  2027.             if books_added_in_date_range:
  2028.                 hrTag = Tag(soup, 'hr')
  2029.                 divTag.insert(dtc, hrTag)
  2030.                 dtc += 1
  2031.             
  2032.             self.booksByMonth = sorted(self.booksByTitle, key = (lambda x: (x['timestamp'], x['timestamp'])), reverse = True)
  2033.             current_date = datetime.date.fromordinal(1)
  2034.             this_months_list = []
  2035.             for book in self.booksByMonth:
  2036.                 if book['timestamp'].month != current_date.month or book['timestamp'].year != current_date.year:
  2037.                     dtc = add_books_to_HTML_by_month(this_months_list, dtc)
  2038.                     this_months_list = []
  2039.                     current_date = book['timestamp'].date()
  2040.                 
  2041.                 this_months_list.append(book)
  2042.             
  2043.             add_books_to_HTML_by_month(this_months_list, dtc)
  2044.             body.insert(btc, divTag)
  2045.             outfile_spec = '%s/ByDateAdded.html' % self.contentDir
  2046.             outfile = open(outfile_spec, 'w')
  2047.             outfile.write(soup.prettify())
  2048.             outfile.close()
  2049.             self.htmlFileList.append('content/ByDateAdded.html')
  2050.  
  2051.         
  2052.         def generateHTMLByDateRead(self):
  2053.             friendly_name = 'Recently Read'
  2054.             self.updateProgressFullStep("'%s'" % friendly_name)
  2055.             if not self.bookmarked_books:
  2056.                 return None
  2057.             
  2058.             def add_books_to_HTML_by_day(todays_list, dtc):
  2059.                 if len(todays_list):
  2060.                     date_string = strftime(u'%A, %B %d', current_date.timetuple())
  2061.                     pIndexTag = Tag(soup, 'p')
  2062.                     pIndexTag['class'] = 'date_index'
  2063.                     aTag = Tag(soup, 'a')
  2064.                     aTag['name'] = 'bdr_%s-%s-%s' % (current_date.year, current_date.month, current_date.day)
  2065.                     pIndexTag.insert(0, aTag)
  2066.                     pIndexTag.insert(1, NavigableString(date_string))
  2067.                     divTag.insert(dtc, pIndexTag)
  2068.                     dtc += 1
  2069.                     for new_entry in todays_list:
  2070.                         pBookTag = Tag(soup, 'p')
  2071.                         pBookTag['class'] = 'date_read'
  2072.                         ptc = 0
  2073.                         pBookTag.insert(ptc, NavigableString(new_entry['reading_progress']))
  2074.                         ptc += 1
  2075.                         aTag = Tag(soup, 'a')
  2076.                         aTag['href'] = 'book_%d.html' % int(float(new_entry['id']))
  2077.                         aTag.insert(0, escape(new_entry['title']))
  2078.                         pBookTag.insert(ptc, aTag)
  2079.                         ptc += 1
  2080.                         pBookTag.insert(ptc, NavigableString(' · '))
  2081.                         ptc += 1
  2082.                         emTag = Tag(soup, 'em')
  2083.                         aTag = Tag(soup, 'a')
  2084.                         aTag['href'] = '%s.html#%s' % ('ByAlphaAuthor', self.generateAuthorAnchor(new_entry['author']))
  2085.                         aTag.insert(0, NavigableString(new_entry['author']))
  2086.                         emTag.insert(0, aTag)
  2087.                         pBookTag.insert(ptc, emTag)
  2088.                         ptc += 1
  2089.                         divTag.insert(dtc, pBookTag)
  2090.                         dtc += 1
  2091.                     
  2092.                 
  2093.                 return dtc
  2094.  
  2095.             
  2096.             def add_books_to_HTML_by_date_range(date_range_list, date_range, dtc):
  2097.                 if len(date_range_list):
  2098.                     pIndexTag = Tag(soup, 'p')
  2099.                     pIndexTag['class'] = 'date_index'
  2100.                     aTag = Tag(soup, 'a')
  2101.                     aTag['name'] = 'bdr_%s' % date_range.replace(' ', '')
  2102.                     pIndexTag.insert(0, aTag)
  2103.                     pIndexTag.insert(1, NavigableString(date_range))
  2104.                     divTag.insert(dtc, pIndexTag)
  2105.                     dtc += 1
  2106.                     for new_entry in date_range_list:
  2107.                         pBookTag = Tag(soup, 'p')
  2108.                         pBookTag['class'] = 'date_read'
  2109.                         ptc = 0
  2110.                         dots = int((new_entry['percent_read'] + 5) / 10)
  2111.                         dot_string = self.READ_PROGRESS_SYMBOL * dots
  2112.                         empty_dots = self.UNREAD_PROGRESS_SYMBOL * (10 - dots)
  2113.                         pBookTag.insert(ptc, NavigableString('%s%s' % (dot_string, empty_dots)))
  2114.                         ptc += 1
  2115.                         aTag = Tag(soup, 'a')
  2116.                         aTag['href'] = 'book_%d.html' % int(float(new_entry['id']))
  2117.                         aTag.insert(0, escape(new_entry['title']))
  2118.                         pBookTag.insert(ptc, aTag)
  2119.                         ptc += 1
  2120.                         pBookTag.insert(ptc, NavigableString(' · '))
  2121.                         ptc += 1
  2122.                         emTag = Tag(soup, 'em')
  2123.                         aTag = Tag(soup, 'a')
  2124.                         aTag['href'] = '%s.html#%s' % ('ByAlphaAuthor', self.generateAuthorAnchor(new_entry['author']))
  2125.                         aTag.insert(0, NavigableString(new_entry['author']))
  2126.                         emTag.insert(0, aTag)
  2127.                         pBookTag.insert(ptc, emTag)
  2128.                         ptc += 1
  2129.                         divTag.insert(dtc, pBookTag)
  2130.                         dtc += 1
  2131.                     
  2132.                 
  2133.                 return dtc
  2134.  
  2135.             soup = self.generateHTMLEmptyHeader(friendly_name)
  2136.             body = soup.find('body')
  2137.             btc = 0
  2138.             aTag = Tag(soup, 'a')
  2139.             aTag['name'] = 'section_start'
  2140.             body.insert(btc, aTag)
  2141.             btc += 1
  2142.             aTag = Tag(soup, 'a')
  2143.             anchor_name = friendly_name.lower()
  2144.             aTag['name'] = anchor_name.replace(' ', '')
  2145.             body.insert(btc, aTag)
  2146.             btc += 1
  2147.             divTag = Tag(soup, 'div')
  2148.             dtc = 0
  2149.             bookmarked_books = []
  2150.             for bm_book in self.bookmarked_books:
  2151.                 book = self.bookmarked_books[bm_book]
  2152.                 book[1]['bookmark_timestamp'] = book[0].timestamp
  2153.                 
  2154.                 try:
  2155.                     book[1]['percent_read'] = min(float(100 * book[0].last_read / book[0].book_length), 100)
  2156.                 except:
  2157.                     (None, None, (None, None, None, self.bookmarked_books))
  2158.                     book[1]['percent_read'] = 0
  2159.  
  2160.                 bookmarked_books.append(book[1])
  2161.             
  2162.             self.booksByDateRead = sorted(bookmarked_books, key = (lambda x: (x['bookmark_timestamp'], x['bookmark_timestamp'])), reverse = True)
  2163.             current_date = datetime.date.fromordinal(1)
  2164.             todays_list = []
  2165.             for book in self.booksByDateRead:
  2166.                 bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
  2167.                 if bookmark_time.day != current_date.day and bookmark_time.month != current_date.month or bookmark_time.year != current_date.year:
  2168.                     dtc = add_books_to_HTML_by_day(todays_list, dtc)
  2169.                     todays_list = []
  2170.                     current_date = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp']).date()
  2171.                 
  2172.                 todays_list.append(book)
  2173.             
  2174.             add_books_to_HTML_by_day(todays_list, dtc)
  2175.             body.insert(btc, divTag)
  2176.             outfile_spec = '%s/ByDateRead.html' % self.contentDir
  2177.             outfile = open(outfile_spec, 'w')
  2178.             outfile.write(soup.prettify())
  2179.             outfile.close()
  2180.             self.htmlFileList.append('content/ByDateRead.html')
  2181.  
  2182.         
  2183.         def generateHTMLByTags(self):
  2184.             self.updateProgressFullStep("'Genres'")
  2185.             self.genre_tags_dict = self.filterDbTags(self.db.all_tags())
  2186.             genre_list = []
  2187.             for friendly_tag in sorted(self.genre_tags_dict):
  2188.                 tag_list = { }
  2189.                 for book in self.booksByAuthor:
  2190.                     if 'tags' in book and friendly_tag in book['tags']:
  2191.                         this_book = { }
  2192.                         this_book['author'] = book['author']
  2193.                         this_book['title'] = book['title']
  2194.                         this_book['author_sort'] = book['author_sort'].capitalize()
  2195.                         this_book['read'] = book['read']
  2196.                         this_book['id'] = book['id']
  2197.                         this_book['series'] = book['series']
  2198.                         normalized_tag = self.genre_tags_dict[friendly_tag]
  2199.                         genre_tag_list = [ key for genre in genre_list for key in genre ]
  2200.                         if normalized_tag in genre_tag_list:
  2201.                             for existing_genre in genre_list:
  2202.                                 for key in existing_genre:
  2203.                                     new_book = None
  2204.                                     if key == normalized_tag:
  2205.                                         for book in existing_genre[key]:
  2206.                                             if book['title'] == this_book['title']:
  2207.                                                 new_book = False
  2208.                                                 break
  2209.                                                 continue
  2210.                                             []
  2211.                                         else:
  2212.                                             new_book = True
  2213.                                     
  2214.                                     if new_book:
  2215.                                         existing_genre[key].append(this_book)
  2216.                                         continue
  2217.                                 
  2218.                             
  2219.                         else:
  2220.                             tag_list[normalized_tag] = [
  2221.                                 this_book]
  2222.                             genre_list.append(tag_list)
  2223.                     normalized_tag in genre_tag_list
  2224.                 
  2225.             
  2226.             if self.opts.verbose:
  2227.                 self.opts.log.info('     Genre summary: %d active genre tags used in generating catalog with %d titles' % (len(genre_list), len(self.booksByTitle)))
  2228.                 for genre in genre_list:
  2229.                     for key in genre:
  2230.                         [](self.opts.log.info % ('      %s: %d %s', self.getFriendlyGenreTag(key), len(genre[key]) if len(genre[key]) > 1 else 'title'))
  2231.                     
  2232.                 
  2233.             
  2234.             master_genre_list = []
  2235.             for genre_tag_set in genre_list:
  2236.                 for index, genre in enumerate(genre_tag_set):
  2237.                     authors = []
  2238.                     for book in genre_tag_set[genre]:
  2239.                         authors.append((book['author'], book['author_sort']))
  2240.                     
  2241.                     books_by_current_author = 1
  2242.                     current_author = authors[0]
  2243.                     unique_authors = []
  2244.                     for i, author in enumerate(authors):
  2245.                         if author != current_author and i:
  2246.                             unique_authors.append((current_author[0], current_author[1], books_by_current_author))
  2247.                             current_author = author
  2248.                             books_by_current_author = 1
  2249.                             continue
  2250.                         if i == 0 and len(authors) == 1:
  2251.                             unique_authors.append((current_author[0], current_author[1], books_by_current_author))
  2252.                             continue
  2253.                         books_by_current_author += 1
  2254.                     
  2255.                     titles_spanned = None(self.generateHTMLByGenre, genre if index == 0 else False, genre_tag_set[genre], '%s/Genre_%s.html' % (self.contentDir, genre))
  2256.                     tag_file = 'content/Genre_%s.html' % genre
  2257.                     master_genre_list.append({
  2258.                         'tag': genre,
  2259.                         'file': tag_file,
  2260.                         'authors': unique_authors,
  2261.                         'books': genre_tag_set[genre],
  2262.                         'titles_spanned': titles_spanned })
  2263.                 
  2264.             
  2265.             if False and self.opts.verbose:
  2266.                 for genre in master_genre_list:
  2267.                     print "genre['tag']: %s" % genre['tag']
  2268.                     for book in genre['books']:
  2269.                         print book['title']
  2270.                     
  2271.                 
  2272.             
  2273.             self.genres = master_genre_list
  2274.  
  2275.         
  2276.         def generateThumbnails(self):
  2277.             self.updateProgressFullStep("'Thumbnails'")
  2278.             thumbs = [
  2279.                 'thumbnail_default.jpg']
  2280.             image_dir = '%s/images' % self.catalogPath
  2281.             for i, title in enumerate(self.booksByTitle):
  2282.                 self.updateProgressMicroStep('Thumbnail %d of %d' % (i, len(self.booksByTitle)), i / float(len(self.booksByTitle)))
  2283.                 if 'cover' in title and os.path.isfile(title['cover']):
  2284.                     thumbs.append('thumbnail_%d.jpg' % int(title['id']))
  2285.                     thumb_fp = '%s/thumbnail_%d.jpg' % (image_dir, int(title['id']))
  2286.                     thumb_file = 'thumbnail_%d.jpg' % int(title['id'])
  2287.                     if os.path.isfile(thumb_fp):
  2288.                         cover_timestamp = os.path.getmtime(title['cover'])
  2289.                         thumb_timestamp = os.path.getmtime(thumb_fp)
  2290.                         if thumb_timestamp < cover_timestamp:
  2291.                             self.generateThumbnail(title, image_dir, thumb_file)
  2292.                         
  2293.                     else:
  2294.                         self.generateThumbnail(title, image_dir, thumb_file)
  2295.                 os.path.isfile(thumb_fp)
  2296.                 if False and self.verbose:
  2297.                     self.opts.log.warn(" using default cover for '%s'" % title['title'])
  2298.                 
  2299.                 thumb_fp = '%s/thumbnail_default.jpg' % image_dir
  2300.                 cover = '%s/DefaultCover.png' % self.catalogPath
  2301.                 is_ok_to_use_qt = is_ok_to_use_qt
  2302.                 import calibre.gui2
  2303.                 if is_ok_to_use_qt():
  2304.                     QImage = QImage
  2305.                     QColor = QColor
  2306.                     QPainter = QPainter
  2307.                     Qt = Qt
  2308.                     import PyQt4.Qt
  2309.                     cover_img = QImage(I('book.svg'))
  2310.                     i = QImage(cover_img.size(), QImage.Format_ARGB32_Premultiplied)
  2311.                     i.fill(QColor(Qt.white).rgb())
  2312.                     p = QPainter(i)
  2313.                     p.drawImage(0, 0, cover_img)
  2314.                     p.end()
  2315.                     i.save(cover)
  2316.                 elif not os.path.exists(cover):
  2317.                     shutil.copyfile(I('library.png'), cover)
  2318.                 
  2319.                 if os.path.isfile(thumb_fp):
  2320.                     cover_timestamp = os.path.getmtime(cover)
  2321.                     thumb_timestamp = os.path.getmtime(thumb_fp)
  2322.                     if thumb_timestamp < cover_timestamp:
  2323.                         if False and self.verbose:
  2324.                             self.opts.log.warn('updating thumbnail_default for %s' % title['title'])
  2325.                         
  2326.                         title['cover'] = cover
  2327.                         self.generateThumbnail(title, image_dir, 'thumbnail_default.jpg')
  2328.                     
  2329.                 thumb_timestamp < cover_timestamp
  2330.                 if False and self.verbose:
  2331.                     self.opts.log.warn(' generating new thumbnail_default.jpg')
  2332.                 
  2333.                 title['cover'] = cover
  2334.                 self.generateThumbnail(title, image_dir, 'thumbnail_default.jpg')
  2335.             
  2336.             self.thumbs = thumbs
  2337.  
  2338.         
  2339.         def generateOPF(self):
  2340.             self.updateProgressFullStep('Generating OPF')
  2341.             header = '\n                <?xml version="1.0" encoding="UTF-8"?>\n                <package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="calibre_id">\n                    <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n                        <dc:language>en-US</dc:language>\n                        <meta name="calibre:publication_type" content="periodical:default"/>\n                    </metadata>\n                    <manifest></manifest>\n                    <spine toc="ncx"></spine>\n                    <guide></guide>\n                </package>\n                '
  2342.             soup = BeautifulStoneSoup(header, selfClosingTags = [
  2343.                 'item',
  2344.                 'itemref',
  2345.                 'reference'])
  2346.             metadata = soup.find('metadata')
  2347.             mtc = 0
  2348.             titleTag = Tag(soup, 'dc:title')
  2349.             titleTag.insert(0, self.title)
  2350.             metadata.insert(mtc, titleTag)
  2351.             mtc += 1
  2352.             creatorTag = Tag(soup, 'dc:creator')
  2353.             creatorTag.insert(0, self.creator)
  2354.             metadata.insert(mtc, creatorTag)
  2355.             mtc += 1
  2356.             manifest = soup.find('manifest')
  2357.             mtc = 0
  2358.             spine = soup.find('spine')
  2359.             stc = 0
  2360.             guide = soup.find('guide')
  2361.             itemTag = Tag(soup, 'item')
  2362.             itemTag['id'] = 'ncx'
  2363.             itemTag['href'] = '%s.ncx' % self.basename
  2364.             itemTag['media-type'] = 'application/x-dtbncx+xml'
  2365.             manifest.insert(mtc, itemTag)
  2366.             mtc += 1
  2367.             itemTag = Tag(soup, 'item')
  2368.             itemTag['id'] = 'stylesheet'
  2369.             itemTag['href'] = self.stylesheet
  2370.             itemTag['media-type'] = 'text/css'
  2371.             manifest.insert(mtc, itemTag)
  2372.             mtc += 1
  2373.             itemTag = Tag(soup, 'item')
  2374.             itemTag['id'] = 'mastheadimage-image'
  2375.             itemTag['href'] = 'images/mastheadImage.gif'
  2376.             itemTag['media-type'] = 'image/gif'
  2377.             manifest.insert(mtc, itemTag)
  2378.             mtc += 1
  2379.             for thumb in self.thumbs:
  2380.                 itemTag = Tag(soup, 'item')
  2381.                 itemTag['href'] = 'images/%s' % thumb
  2382.                 end = thumb.find('.jpg')
  2383.                 itemTag['id'] = '%s-image' % thumb[:end]
  2384.                 itemTag['media-type'] = 'image/jpeg'
  2385.                 manifest.insert(mtc, itemTag)
  2386.                 mtc += 1
  2387.             
  2388.             sort_descriptions_by = None if self.opts.sort_descriptions_by_author else self.booksByTitle
  2389.             for book in sort_descriptions_by:
  2390.                 itemTag = Tag(soup, 'item')
  2391.                 itemTag['href'] = 'content/book_%d.html' % int(book['id'])
  2392.                 itemTag['id'] = 'book%d' % int(book['id'])
  2393.                 itemTag['media-type'] = 'application/xhtml+xml'
  2394.                 manifest.insert(mtc, itemTag)
  2395.                 mtc += 1
  2396.                 itemrefTag = Tag(soup, 'itemref')
  2397.                 itemrefTag['idref'] = 'book%d' % int(book['id'])
  2398.                 spine.insert(stc, itemrefTag)
  2399.                 stc += 1
  2400.             
  2401.             for file in self.htmlFileList:
  2402.                 itemTag = Tag(soup, 'item')
  2403.                 start = file.find('/') + 1
  2404.                 end = file.find('.')
  2405.                 itemTag['href'] = file
  2406.                 itemTag['id'] = file[start:end].lower()
  2407.                 itemTag['media-type'] = 'application/xhtml+xml'
  2408.                 manifest.insert(mtc, itemTag)
  2409.                 mtc += 1
  2410.                 itemrefTag = Tag(soup, 'itemref')
  2411.                 itemrefTag['idref'] = file[start:end].lower()
  2412.                 spine.insert(stc, itemrefTag)
  2413.                 stc += 1
  2414.             
  2415.             for genre in self.genres:
  2416.                 if False:
  2417.                     self.opts.log.info('adding %s to manifest and spine' % genre['tag'])
  2418.                 
  2419.                 itemTag = Tag(soup, 'item')
  2420.                 start = genre['file'].find('/') + 1
  2421.                 end = genre['file'].find('.')
  2422.                 itemTag['href'] = genre['file']
  2423.                 itemTag['id'] = genre['file'][start:end].lower()
  2424.                 itemTag['media-type'] = 'application/xhtml+xml'
  2425.                 manifest.insert(mtc, itemTag)
  2426.                 mtc += 1
  2427.                 itemrefTag = Tag(soup, 'itemref')
  2428.                 itemrefTag['idref'] = genre['file'][start:end].lower()
  2429.                 spine.insert(stc, itemrefTag)
  2430.                 stc += 1
  2431.             
  2432.             referenceTag = Tag(soup, 'reference')
  2433.             referenceTag['type'] = 'masthead'
  2434.             referenceTag['title'] = 'mastheadimage-image'
  2435.             referenceTag['href'] = 'images/mastheadImage.gif'
  2436.             guide.insert(0, referenceTag)
  2437.             outfile = open('%s/%s.opf' % (self.catalogPath, self.basename), 'w')
  2438.             outfile.write(soup.prettify())
  2439.  
  2440.         
  2441.         def generateNCXHeader(self):
  2442.             self.updateProgressFullStep('NCX header')
  2443.             header = '\n                <?xml version="1.0" encoding="utf-8"?>\n                <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata" version="2005-1" xml:lang="en">\n                </ncx>\n            '
  2444.             soup = BeautifulStoneSoup(header, selfClosingTags = [
  2445.                 'content',
  2446.                 'calibre:meta-img'])
  2447.             ncx = soup.find('ncx')
  2448.             navMapTag = Tag(soup, 'navMap')
  2449.             navPointTag = Tag(soup, 'navPoint')
  2450.             navPointTag['class'] = 'periodical'
  2451.             navPointTag['id'] = 'title'
  2452.             navPointTag['playOrder'] = self.playOrder
  2453.             self.playOrder += 1
  2454.             navLabelTag = Tag(soup, 'navLabel')
  2455.             textTag = Tag(soup, 'text')
  2456.             textTag.insert(0, NavigableString(self.title))
  2457.             navLabelTag.insert(0, textTag)
  2458.             navPointTag.insert(0, navLabelTag)
  2459.             contentTag = Tag(soup, 'content')
  2460.             contentTag['src'] = 'content/book_%d.html' % int(self.booksByTitle[0]['id'])
  2461.             navPointTag.insert(1, contentTag)
  2462.             cmiTag = Tag(soup, 'calibre:meta-img')
  2463.             cmiTag['name'] = 'mastheadImage'
  2464.             cmiTag['src'] = 'images/mastheadImage.gif'
  2465.             navPointTag.insert(2, cmiTag)
  2466.             navMapTag.insert(0, navPointTag)
  2467.             ncx.insert(0, navMapTag)
  2468.             self.ncxSoup = soup
  2469.  
  2470.         
  2471.         def generateNCXDescriptions(self, tocTitle):
  2472.             self.updateProgressFullStep("NCX 'Descriptions'")
  2473.             ncx_soup = self.ncxSoup
  2474.             body = ncx_soup.find('navPoint')
  2475.             btc = len(body.contents)
  2476.             navPointTag = Tag(ncx_soup, 'navPoint')
  2477.             navPointTag['class'] = 'section'
  2478.             navPointTag['id'] = 'bytitle-ID'
  2479.             navPointTag['playOrder'] = self.playOrder
  2480.             self.playOrder += 1
  2481.             navLabelTag = Tag(ncx_soup, 'navLabel')
  2482.             textTag = Tag(ncx_soup, 'text')
  2483.             textTag.insert(0, NavigableString(tocTitle))
  2484.             navLabelTag.insert(0, textTag)
  2485.             nptc = 0
  2486.             navPointTag.insert(nptc, navLabelTag)
  2487.             nptc += 1
  2488.             contentTag = Tag(ncx_soup, 'content')
  2489.             contentTag['src'] = 'content/book_%d.html' % int(self.booksByTitle[0]['id'])
  2490.             navPointTag.insert(nptc, contentTag)
  2491.             nptc += 1
  2492.             sort_descriptions_by = self if self.opts.sort_descriptions_by_author else self.booksByTitle
  2493.             for book in sort_descriptions_by:
  2494.                 navPointVolumeTag = Tag(ncx_soup, 'navPoint')
  2495.                 navPointVolumeTag['class'] = 'article'
  2496.                 navPointVolumeTag['id'] = 'book%dID' % int(book['id'])
  2497.                 navPointVolumeTag['playOrder'] = self.playOrder
  2498.                 self.playOrder += 1
  2499.                 navLabelTag = Tag(ncx_soup, 'navLabel')
  2500.                 textTag = Tag(ncx_soup, 'text')
  2501.                 if book['series']:
  2502.                     tokens = book['title'].split(': ')
  2503.                     if self.generateForKindle:
  2504.                         textTag.insert(0, NavigableString(self.formatNCXText('%s (%s)' % (tokens[1], tokens[0]), dest = 'title')))
  2505.                     else:
  2506.                         textTag.insert(0, NavigableString(self.formatNCXText('%s · %s (%s)' % (tokens[1], book['author'], tokens[0]), dest = 'title')))
  2507.                 elif self.generateForKindle:
  2508.                     title_str = self.formatNCXText('%s' % book['title'], dest = 'title')
  2509.                     if self.opts.connected_kindle and book['id'] in self.bookmarked_books:
  2510.                         title_str += '*'
  2511.                     
  2512.                     textTag.insert(0, NavigableString(title_str))
  2513.                 else:
  2514.                     textTag.insert(0, NavigableString(self.formatNCXText('%s · %s' % (book['title'], book['author']), dest = 'title')))
  2515.                 navLabelTag.insert(0, textTag)
  2516.                 navPointVolumeTag.insert(0, navLabelTag)
  2517.                 contentTag = Tag(ncx_soup, 'content')
  2518.                 contentTag['src'] = 'content/book_%d.html#book%d' % (int(book['id']), int(book['id']))
  2519.                 navPointVolumeTag.insert(1, contentTag)
  2520.                 if self.generateForKindle:
  2521.                     cmTag = Tag(ncx_soup, 'calibre:meta')
  2522.                     cmTag['name'] = 'author'
  2523.                     navStr = '%s | %s' % (self.formatNCXText(book['author'], dest = 'author'), book['date'].split()[1])
  2524.                     if 'tags' in book and len(book['tags']):
  2525.                         navStr = self.formatNCXText(navStr + ' | ' + ' · '.join(sorted(book['tags'])), dest = 'author')
  2526.                     
  2527.                     cmTag.insert(0, NavigableString(navStr))
  2528.                     navPointVolumeTag.insert(2, cmTag)
  2529.                     if book['short_description']:
  2530.                         cmTag = Tag(ncx_soup, 'calibre:meta')
  2531.                         cmTag['name'] = 'description'
  2532.                         cmTag.insert(0, NavigableString(self.formatNCXText(book['short_description'], dest = 'description')))
  2533.                         navPointVolumeTag.insert(3, cmTag)
  2534.                     
  2535.                 
  2536.                 navPointTag.insert(nptc, navPointVolumeTag)
  2537.                 nptc += 1
  2538.             
  2539.             body.insert(btc, navPointTag)
  2540.             btc += 1
  2541.             self.ncxSoup = ncx_soup
  2542.  
  2543.         
  2544.         def generateNCXByTitle(self, tocTitle):
  2545.             self.updateProgressFullStep("NCX 'Titles'")
  2546.             
  2547.             def add_to_books_by_letter(current_book_list):
  2548.                 current_book_list = ' • '.join(current_book_list)
  2549.                 current_book_list = self.formatNCXText(current_book_list, dest = 'description')
  2550.                 books_by_letter.append(current_book_list)
  2551.  
  2552.             soup = self.ncxSoup
  2553.             output = 'ByAlphaTitle'
  2554.             body = soup.find('navPoint')
  2555.             btc = len(body.contents)
  2556.             navPointTag = Tag(soup, 'navPoint')
  2557.             navPointTag['class'] = 'section'
  2558.             navPointTag['id'] = 'byalphatitle-ID'
  2559.             navPointTag['playOrder'] = self.playOrder
  2560.             self.playOrder += 1
  2561.             navLabelTag = Tag(soup, 'navLabel')
  2562.             textTag = Tag(soup, 'text')
  2563.             textTag.insert(0, NavigableString(tocTitle))
  2564.             navLabelTag.insert(0, textTag)
  2565.             nptc = 0
  2566.             navPointTag.insert(nptc, navLabelTag)
  2567.             nptc += 1
  2568.             contentTag = Tag(soup, 'content')
  2569.             contentTag['src'] = 'content/%s.html#section_start' % output
  2570.             navPointTag.insert(nptc, contentTag)
  2571.             nptc += 1
  2572.             books_by_letter = []
  2573.             current_letter = self.letter_or_symbol(title_list[0]['title_sort'][0])
  2574.             title_letters = [
  2575.                 current_letter]
  2576.             current_book_list = []
  2577.             current_book = ''
  2578.             for book in title_list:
  2579.                 if self.letter_or_symbol(book['title_sort'][0]) != current_letter:
  2580.                     add_to_books_by_letter(current_book_list)
  2581.                     current_letter = self.letter_or_symbol(book['title_sort'][0])
  2582.                     title_letters.append(current_letter)
  2583.                     current_book = book['title']
  2584.                     current_book_list = [
  2585.                         book['title']]
  2586.                     continue
  2587.                 None if self.useSeriesPrefixInTitlesSection else (None, None)
  2588.                 if len(current_book_list) < self.descriptionClip and book['title'] != current_book:
  2589.                     current_book = book['title']
  2590.                     current_book_list.append(book['title'])
  2591.                     continue
  2592.             
  2593.             add_to_books_by_letter(current_book_list)
  2594.             for i, books in enumerate(books_by_letter):
  2595.                 navPointByLetterTag = Tag(soup, 'navPoint')
  2596.                 navPointByLetterTag['class'] = 'article'
  2597.                 navPointByLetterTag['id'] = '%sTitles-ID' % title_letters[i].upper()
  2598.                 navPointTag['playOrder'] = self.playOrder
  2599.                 self.playOrder += 1
  2600.                 navLabelTag = Tag(soup, 'navLabel')
  2601.                 textTag = Tag(soup, 'text')
  2602.                 self(textTag.insert, 0(NavigableString % u'Titles beginning with %s' if len(title_letters[i]) > 1 else "'" + title_letters[i] + "'"))
  2603.                 navLabelTag.insert(0, textTag)
  2604.                 navPointByLetterTag.insert(0, navLabelTag)
  2605.                 contentTag = Tag(soup, 'content')
  2606.                 contentTag['src'] = 'content/%s.html#%s' % (output, title_letters[i])
  2607.                 navPointByLetterTag.insert(1, contentTag)
  2608.                 if self.generateForKindle:
  2609.                     cmTag = Tag(soup, 'calibre:meta')
  2610.                     cmTag['name'] = 'description'
  2611.                     cmTag.insert(0, NavigableString(self.formatNCXText(books, dest = 'description')))
  2612.                     navPointByLetterTag.insert(2, cmTag)
  2613.                 
  2614.                 navPointTag.insert(nptc, navPointByLetterTag)
  2615.                 nptc += 1
  2616.             
  2617.             body.insert(btc, navPointTag)
  2618.             btc += 1
  2619.             self.ncxSoup = soup
  2620.  
  2621.         
  2622.         def generateNCXByAuthor(self, tocTitle):
  2623.             self.updateProgressFullStep("NCX 'Authors'")
  2624.             
  2625.             def add_to_author_list(current_author_list, current_letter):
  2626.                 current_author_list = ' • '.join(current_author_list)
  2627.                 current_author_list = self.formatNCXText(current_author_list, dest = 'description')
  2628.                 master_author_list.append((current_author_list, current_letter))
  2629.  
  2630.             soup = self.ncxSoup
  2631.             HTML_file = 'content/ByAlphaAuthor.html'
  2632.             body = soup.find('navPoint')
  2633.             btc = len(body.contents)
  2634.             navPointTag = Tag(soup, 'navPoint')
  2635.             navPointTag['class'] = 'section'
  2636.             file_ID = '%s' % tocTitle.lower()
  2637.             file_ID = file_ID.replace(' ', '')
  2638.             navPointTag['id'] = '%s-ID' % file_ID
  2639.             navPointTag['playOrder'] = self.playOrder
  2640.             self.playOrder += 1
  2641.             navLabelTag = Tag(soup, 'navLabel')
  2642.             textTag = Tag(soup, 'text')
  2643.             textTag.insert(0, NavigableString('%s' % tocTitle))
  2644.             navLabelTag.insert(0, textTag)
  2645.             nptc = 0
  2646.             navPointTag.insert(nptc, navLabelTag)
  2647.             nptc += 1
  2648.             contentTag = Tag(soup, 'content')
  2649.             contentTag['src'] = '%s#section_start' % HTML_file
  2650.             navPointTag.insert(nptc, contentTag)
  2651.             nptc += 1
  2652.             master_author_list = []
  2653.             current_letter = self.letter_or_symbol(self.authors[0][1][0])
  2654.             current_author_list = []
  2655.             for author in self.authors:
  2656.                 if self.letter_or_symbol(author[1][0]) != current_letter:
  2657.                     add_to_author_list(current_author_list, current_letter)
  2658.                     current_letter = self.letter_or_symbol(author[1][0])
  2659.                     current_author_list = [
  2660.                         author[0]]
  2661.                     continue
  2662.                 self
  2663.                 if len(current_author_list) < self.descriptionClip:
  2664.                     current_author_list.append(author[0])
  2665.                     continue
  2666.                 (None, None)
  2667.             
  2668.             add_to_author_list(current_author_list, current_letter)
  2669.             for authors_by_letter in master_author_list:
  2670.                 navPointByLetterTag = Tag(soup, 'navPoint')
  2671.                 navPointByLetterTag['class'] = 'article'
  2672.                 navPointByLetterTag['id'] = '%sauthors-ID' % authors_by_letter[1]
  2673.                 navPointTag['playOrder'] = self.playOrder
  2674.                 self.playOrder += 1
  2675.                 navLabelTag = Tag(soup, 'navLabel')
  2676.                 textTag = Tag(soup, 'text')
  2677.                 textTag.insert(0, NavigableString("Authors beginning with '%s'" % authors_by_letter[1]))
  2678.                 navLabelTag.insert(0, textTag)
  2679.                 navPointByLetterTag.insert(0, navLabelTag)
  2680.                 contentTag = Tag(soup, 'content')
  2681.                 contentTag['src'] = '%s#%sauthors' % (HTML_file, authors_by_letter[1])
  2682.                 navPointByLetterTag.insert(1, contentTag)
  2683.                 if self.generateForKindle:
  2684.                     cmTag = Tag(soup, 'calibre:meta')
  2685.                     cmTag['name'] = 'description'
  2686.                     cmTag.insert(0, NavigableString(authors_by_letter[0]))
  2687.                     navPointByLetterTag.insert(2, cmTag)
  2688.                 
  2689.                 navPointTag.insert(nptc, navPointByLetterTag)
  2690.                 nptc += 1
  2691.             
  2692.             body.insert(btc, navPointTag)
  2693.             btc += 1
  2694.             self.ncxSoup = soup
  2695.  
  2696.         
  2697.         def generateNCXByDateAdded(self, tocTitle):
  2698.             self.updateProgressFullStep("NCX 'Recently Added'")
  2699.             
  2700.             def add_to_master_month_list(current_titles_list):
  2701.                 book_count = len(current_titles_list)
  2702.                 current_titles_list = ' • '.join(current_titles_list)
  2703.                 current_titles_list = self.formatNCXText(current_titles_list, dest = 'description')
  2704.                 master_month_list.append((current_titles_list, current_date, book_count))
  2705.  
  2706.             
  2707.             def add_to_master_date_range_list(current_titles_list):
  2708.                 book_count = len(current_titles_list)
  2709.                 current_titles_list = ' • '.join(current_titles_list)
  2710.                 current_titles_list = self.formatNCXText(current_titles_list, dest = 'description')
  2711.                 master_date_range_list.append((current_titles_list, date_range, book_count))
  2712.  
  2713.             soup = self.ncxSoup
  2714.             HTML_file = 'content/ByDateAdded.html'
  2715.             body = soup.find('navPoint')
  2716.             btc = len(body.contents)
  2717.             navPointTag = Tag(soup, 'navPoint')
  2718.             navPointTag['class'] = 'section'
  2719.             file_ID = '%s' % tocTitle.lower()
  2720.             file_ID = file_ID.replace(' ', '')
  2721.             navPointTag['id'] = '%s-ID' % file_ID
  2722.             navPointTag['playOrder'] = self.playOrder
  2723.             self.playOrder += 1
  2724.             navLabelTag = Tag(soup, 'navLabel')
  2725.             textTag = Tag(soup, 'text')
  2726.             textTag.insert(0, NavigableString('%s' % tocTitle))
  2727.             navLabelTag.insert(0, textTag)
  2728.             nptc = 0
  2729.             navPointTag.insert(nptc, navLabelTag)
  2730.             nptc += 1
  2731.             contentTag = Tag(soup, 'content')
  2732.             contentTag['src'] = '%s#section_start' % HTML_file
  2733.             navPointTag.insert(nptc, contentTag)
  2734.             nptc += 1
  2735.             current_titles_list = []
  2736.             master_date_range_list = []
  2737.             today = datetime.datetime.now()
  2738.             today_time = datetime.datetime(today.year, today.month, today.day)
  2739.             for i, date in enumerate(self.DATE_RANGE):
  2740.                 date_range_limit = self.DATE_RANGE[i]
  2741.                 for book in self.booksByDateRange:
  2742.                     book_time = datetime.datetime(book['timestamp'].year, book['timestamp'].month, book['timestamp'].day)
  2743.                     if (today_time - book_time).days <= date_range_limit:
  2744.                         current_titles_list.append(book['title'])
  2745.                         continue
  2746.                     None if i else (None, None, (None, None, None))
  2747.                 
  2748.                 if current_titles_list:
  2749.                     add_to_master_date_range_list(current_titles_list)
  2750.                 
  2751.                 current_titles_list = [
  2752.                     book['title']]
  2753.             
  2754.             for books_by_date_range in master_date_range_list:
  2755.                 navPointByDateRangeTag = Tag(soup, 'navPoint')
  2756.                 navPointByDateRangeTag['class'] = 'article'
  2757.                 navPointByDateRangeTag['id'] = '%s-ID' % books_by_date_range[1].replace(' ', '')
  2758.                 navPointTag['playOrder'] = self.playOrder
  2759.                 self.playOrder += 1
  2760.                 navLabelTag = Tag(soup, 'navLabel')
  2761.                 textTag = Tag(soup, 'text')
  2762.                 textTag.insert(0, NavigableString(books_by_date_range[1]))
  2763.                 navLabelTag.insert(0, textTag)
  2764.                 navPointByDateRangeTag.insert(0, navLabelTag)
  2765.                 contentTag = Tag(soup, 'content')
  2766.                 contentTag['src'] = '%s#bda_%s' % (HTML_file, books_by_date_range[1].replace(' ', ''))
  2767.                 navPointByDateRangeTag.insert(1, contentTag)
  2768.                 navPointTag.insert(nptc, navPointByDateRangeTag)
  2769.                 nptc += 1
  2770.             
  2771.             current_titles_list = []
  2772.             master_month_list = []
  2773.             current_date = self.booksByMonth[0]['timestamp']
  2774.             for book in self.booksByMonth:
  2775.                 if book['timestamp'].month != current_date.month or book['timestamp'].year != current_date.year:
  2776.                     add_to_master_month_list(current_titles_list)
  2777.                     current_date = book['timestamp'].date()
  2778.                     current_titles_list = [
  2779.                         book['title']]
  2780.                     continue
  2781.                 None if self.generateForKindle else self
  2782.                 current_titles_list.append(book['title'])
  2783.             
  2784.             add_to_master_month_list(current_titles_list)
  2785.             for books_by_month in master_month_list:
  2786.                 datestr = strftime(u'%B %Y', books_by_month[1].timetuple())
  2787.                 navPointByMonthTag = Tag(soup, 'navPoint')
  2788.                 navPointByMonthTag['class'] = 'article'
  2789.                 navPointByMonthTag['id'] = 'bda_%s-%s-ID' % (books_by_month[1].year, books_by_month[1].month)
  2790.                 navPointTag['playOrder'] = self.playOrder
  2791.                 self.playOrder += 1
  2792.                 navLabelTag = Tag(soup, 'navLabel')
  2793.                 textTag = Tag(soup, 'text')
  2794.                 textTag.insert(0, NavigableString(datestr))
  2795.                 navLabelTag.insert(0, textTag)
  2796.                 navPointByMonthTag.insert(0, navLabelTag)
  2797.                 contentTag = Tag(soup, 'content')
  2798.                 contentTag['src'] = '%s#bda_%s-%s' % (HTML_file, books_by_month[1].year, books_by_month[1].month)
  2799.                 navPointByMonthTag.insert(1, contentTag)
  2800.                 navPointTag.insert(nptc, navPointByMonthTag)
  2801.                 nptc += 1
  2802.             
  2803.             body.insert(btc, navPointTag)
  2804.             btc += 1
  2805.             self.ncxSoup = soup
  2806.  
  2807.         
  2808.         def generateNCXByDateRead(self, tocTitle):
  2809.             self.updateProgressFullStep("NCX 'Recently Read'")
  2810.             if not self.booksByDateRead:
  2811.                 return None
  2812.             
  2813.             def add_to_master_day_list(current_titles_list):
  2814.                 book_count = len(current_titles_list)
  2815.                 current_titles_list = ' • '.join(current_titles_list)
  2816.                 current_titles_list = self.formatNCXText(current_titles_list, dest = 'description')
  2817.                 master_day_list.append((current_titles_list, current_date, book_count))
  2818.  
  2819.             
  2820.             def add_to_master_date_range_list(current_titles_list):
  2821.                 book_count = len(current_titles_list)
  2822.                 current_titles_list = ' • '.join(current_titles_list)
  2823.                 current_titles_list = self.formatNCXText(current_titles_list, dest = 'description')
  2824.                 master_date_range_list.append((current_titles_list, date_range, book_count))
  2825.  
  2826.             soup = self.ncxSoup
  2827.             HTML_file = 'content/ByDateRead.html'
  2828.             body = soup.find('navPoint')
  2829.             btc = len(body.contents)
  2830.             navPointTag = Tag(soup, 'navPoint')
  2831.             navPointTag['class'] = 'section'
  2832.             file_ID = '%s' % tocTitle.lower()
  2833.             file_ID = file_ID.replace(' ', '')
  2834.             navPointTag['id'] = '%s-ID' % file_ID
  2835.             navPointTag['playOrder'] = self.playOrder
  2836.             self.playOrder += 1
  2837.             navLabelTag = Tag(soup, 'navLabel')
  2838.             textTag = Tag(soup, 'text')
  2839.             textTag.insert(0, NavigableString('%s' % tocTitle))
  2840.             navLabelTag.insert(0, textTag)
  2841.             nptc = 0
  2842.             navPointTag.insert(nptc, navLabelTag)
  2843.             nptc += 1
  2844.             contentTag = Tag(soup, 'content')
  2845.             contentTag['src'] = '%s#section_start' % HTML_file
  2846.             navPointTag.insert(nptc, contentTag)
  2847.             nptc += 1
  2848.             current_titles_list = []
  2849.             master_date_range_list = []
  2850.             today = datetime.datetime.now()
  2851.             today_time = datetime.datetime(today.year, today.month, today.day)
  2852.             for i, date in enumerate(self.DATE_RANGE):
  2853.                 date_range_limit = self.DATE_RANGE[i]
  2854.                 for book in self.booksByDateRead:
  2855.                     bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
  2856.                     if (today_time - bookmark_time).days <= date_range_limit:
  2857.                         current_titles_list.append(book['title'])
  2858.                         continue
  2859.                     None if i else (None, None, (None, None, self.booksByDateRead))
  2860.                 
  2861.                 if current_titles_list:
  2862.                     add_to_master_date_range_list(current_titles_list)
  2863.                 
  2864.                 current_titles_list = [
  2865.                     book['title']]
  2866.             
  2867.             current_titles_list = []
  2868.             master_day_list = []
  2869.             current_date = datetime.datetime.utcfromtimestamp(self.booksByDateRead[0]['bookmark_timestamp'])
  2870.             for book in self.booksByDateRead:
  2871.                 bookmark_time = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp'])
  2872.                 if bookmark_time.day != current_date.day and bookmark_time.month != current_date.month or bookmark_time.year != current_date.year:
  2873.                     add_to_master_day_list(current_titles_list)
  2874.                     current_date = datetime.datetime.utcfromtimestamp(book['bookmark_timestamp']).date()
  2875.                     current_titles_list = [
  2876.                         book['title']]
  2877.                     continue
  2878.                 current_titles_list.append(book['title'])
  2879.             
  2880.             add_to_master_day_list(current_titles_list)
  2881.             for books_by_day in master_day_list:
  2882.                 datestr = strftime(u'%A, %B %d', books_by_day[1].timetuple())
  2883.                 navPointByDayTag = Tag(soup, 'navPoint')
  2884.                 navPointByDayTag['class'] = 'article'
  2885.                 navPointByDayTag['id'] = 'bdr_%s-%s-%sID' % (books_by_day[1].year, books_by_day[1].month, books_by_day[1].day)
  2886.                 navPointTag['playOrder'] = self.playOrder
  2887.                 self.playOrder += 1
  2888.                 navLabelTag = Tag(soup, 'navLabel')
  2889.                 textTag = Tag(soup, 'text')
  2890.                 textTag.insert(0, NavigableString(datestr))
  2891.                 navLabelTag.insert(0, textTag)
  2892.                 navPointByDayTag.insert(0, navLabelTag)
  2893.                 contentTag = Tag(soup, 'content')
  2894.                 contentTag['src'] = '%s#bdr_%s-%s-%s' % (HTML_file, books_by_day[1].year, books_by_day[1].month, books_by_day[1].day)
  2895.                 navPointByDayTag.insert(1, contentTag)
  2896.                 navPointTag.insert(nptc, navPointByDayTag)
  2897.                 nptc += 1
  2898.             
  2899.             body.insert(btc, navPointTag)
  2900.             btc += 1
  2901.             self.ncxSoup = soup
  2902.  
  2903.         
  2904.         def generateNCXByGenre(self, tocTitle):
  2905.             self.updateProgressFullStep("NCX 'Genres'")
  2906.             if not len(self.genres):
  2907.                 self.opts.log.warn(' No genres found in tags.\n No Genre section added to Catalog')
  2908.                 return None
  2909.             ncx_soup = self.ncxSoup
  2910.             body = ncx_soup.find('navPoint')
  2911.             btc = len(body.contents)
  2912.             navPointTag = Tag(ncx_soup, 'navPoint')
  2913.             navPointTag['class'] = 'section'
  2914.             file_ID = '%s' % tocTitle.lower()
  2915.             file_ID = file_ID.replace(' ', '')
  2916.             navPointTag['id'] = '%s-ID' % file_ID
  2917.             navPointTag['playOrder'] = self.playOrder
  2918.             self.playOrder += 1
  2919.             navLabelTag = Tag(ncx_soup, 'navLabel')
  2920.             textTag = Tag(ncx_soup, 'text')
  2921.             textTag.insert(0, NavigableString('%s' % tocTitle))
  2922.             navLabelTag.insert(0, textTag)
  2923.             nptc = 0
  2924.             navPointTag.insert(nptc, navLabelTag)
  2925.             nptc += 1
  2926.             contentTag = Tag(ncx_soup, 'content')
  2927.             contentTag['src'] = 'content/Genre_%s.html#section_start' % self.genres[0]['tag']
  2928.             navPointTag.insert(nptc, contentTag)
  2929.             nptc += 1
  2930.             for genre in self.genres:
  2931.                 navPointVolumeTag = Tag(ncx_soup, 'navPoint')
  2932.                 navPointVolumeTag['class'] = 'article'
  2933.                 navPointVolumeTag['id'] = 'genre-%s-ID' % genre['tag']
  2934.                 navPointVolumeTag['playOrder'] = self.playOrder
  2935.                 self.playOrder += 1
  2936.                 navLabelTag = Tag(ncx_soup, 'navLabel')
  2937.                 textTag = Tag(ncx_soup, 'text')
  2938.                 normalized_tag = None
  2939.                 for friendly_tag in self.genre_tags_dict:
  2940.                     if self.genre_tags_dict[friendly_tag] == genre['tag']:
  2941.                         normalized_tag = self.genre_tags_dict[friendly_tag]
  2942.                         break
  2943.                         continue
  2944.                     self
  2945.                 
  2946.                 textTag.insert(0, self.formatNCXText(NavigableString(friendly_tag), dest = 'description'))
  2947.                 navLabelTag.insert(0, textTag)
  2948.                 navPointVolumeTag.insert(0, navLabelTag)
  2949.                 contentTag = Tag(ncx_soup, 'content')
  2950.                 contentTag['src'] = 'content/Genre_%s.html#Genre_%s' % (normalized_tag, normalized_tag)
  2951.                 navPointVolumeTag.insert(1, contentTag)
  2952.                 if self.generateForKindle:
  2953.                     cmTag = Tag(ncx_soup, 'calibre:meta')
  2954.                     cmTag['name'] = 'author'
  2955.                     cmTag.insert(0, NavigableString(author_range))
  2956.                     navPointVolumeTag.insert(2, cmTag)
  2957.                     cmTag = Tag(ncx_soup, 'calibre:meta')
  2958.                     cmTag['name'] = 'description'
  2959.                     navPointVolumeTag.insert(3, cmTag)
  2960.                 
  2961.                 navPointTag.insert(nptc, navPointVolumeTag)
  2962.                 nptc += 1
  2963.             
  2964.             body.insert(btc, navPointTag)
  2965.             btc += 1
  2966.             self.ncxSoup = ncx_soup
  2967.  
  2968.         
  2969.         def writeNCX(self):
  2970.             self.updateProgressFullStep('Saving NCX')
  2971.             outfile = open('%s/%s.ncx' % (self.catalogPath, self.basename), 'w')
  2972.             outfile.write(self.ncxSoup.prettify())
  2973.  
  2974.         
  2975.         def author_to_author_sort(self, author):
  2976.             tokens = author.split()
  2977.             tokens = tokens[-1:] + tokens[:-1]
  2978.             if len(tokens) > 1:
  2979.                 tokens[0] += ','
  2980.             
  2981.             return ' '.join(tokens).capitalize()
  2982.  
  2983.         
  2984.         def author_compare(self, x, y):
  2985.             if x['author_sort'].capitalize() > y['author_sort'].capitalize():
  2986.                 return 1
  2987.             if x['author_sort'].capitalize() < y['author_sort'].capitalize():
  2988.                 return -1
  2989.             if x['series'] != y['series']:
  2990.                 if not x['series']:
  2991.                     return -1
  2992.                 if not y['series']:
  2993.                     return 1
  2994.                 if x['title_sort'].lstrip() > y['title_sort'].lstrip():
  2995.                     return 1
  2996.                 return -1
  2997.             x['series'] != y['series']
  2998.             if x['series'] == y['series']:
  2999.                 if float(x['series_index']) > float(y['series_index']):
  3000.                     return 1
  3001.                 if float(x['series_index']) < float(y['series_index']):
  3002.                     return -1
  3003.                 return 0
  3004.             x['series'] == y['series']
  3005.             if x['series'] > y['series']:
  3006.                 return 1
  3007.             return -1
  3008.  
  3009.         
  3010.         def calculateThumbnailSize(self):
  3011.             output_profiles = output_profiles
  3012.             import calibre.customize.ui
  3013.             for x in output_profiles():
  3014.                 if x.short_name == self.opts.output_profile:
  3015.                     self.thumbWidth = int(x.dpi * 0.9)
  3016.                     self.thumbHeight = int(self.thumbWidth * 1.33)
  3017.                     if 'kindle' in x.short_name and self.opts.fmt == 'mobi':
  3018.                         self.thumbWidth = int(self.thumbWidth / 2)
  3019.                         self.thumbHeight = int(self.thumbHeight / 2)
  3020.                     
  3021.                     break
  3022.                     continue
  3023.             
  3024.             if False and self.verbose:
  3025.                 self.opts.log('     DPI = %d; thumbnail dimensions: %d x %d' % (x.dpi, self.thumbWidth, self.thumbHeight))
  3026.             
  3027.  
  3028.         
  3029.         def convertHTMLEntities(self, s):
  3030.             matches = re.findall('&#\\d+;', s)
  3031.             if len(matches) > 0:
  3032.                 hits = set(matches)
  3033.                 for hit in hits:
  3034.                     name = hit[2:-1]
  3035.                     
  3036.                     try:
  3037.                         entnum = int(name)
  3038.                         s = s.replace(hit, unichr(entnum))
  3039.                     continue
  3040.                     except ValueError:
  3041.                         continue
  3042.                     
  3043.  
  3044.                 
  3045.             
  3046.             matches = re.findall('&\\w+;', s)
  3047.             hits = set(matches)
  3048.             amp = '&'
  3049.             if amp in hits:
  3050.                 hits.remove(amp)
  3051.             
  3052.             for hit in hits:
  3053.                 name = hit[1:-1]
  3054.                 if htmlentitydefs.name2codepoint.has_key(name):
  3055.                     s = s.replace(hit, unichr(htmlentitydefs.name2codepoint[name]))
  3056.                     continue
  3057.             
  3058.             s = s.replace(amp, '&')
  3059.             return s
  3060.  
  3061.         
  3062.         def createDirectoryStructure(self):
  3063.             catalogPath = self.catalogPath
  3064.             self.cleanUp()
  3065.             if not os.path.isdir(catalogPath):
  3066.                 os.makedirs(catalogPath)
  3067.             
  3068.             content_path = catalogPath + '/content'
  3069.             if not os.path.isdir(content_path):
  3070.                 os.makedirs(content_path)
  3071.             
  3072.             images_path = catalogPath + '/images'
  3073.             if not os.path.isdir(images_path):
  3074.                 os.makedirs(images_path)
  3075.             
  3076.  
  3077.         
  3078.         def filterDbTags(self, tags):
  3079.             normalized_tags = []
  3080.             friendly_tags = []
  3081.             for tag in tags:
  3082.                 if tag[0] in self.markerTags:
  3083.                     continue
  3084.                 
  3085.                 if re.search(self.opts.exclude_genre, tag):
  3086.                     continue
  3087.                 
  3088.                 if tag == ' ':
  3089.                     continue
  3090.                 
  3091.                 normalized_tags.append(re.sub('\\W', '', tag).lower())
  3092.                 friendly_tags.append(tag)
  3093.             
  3094.             genre_tags_dict = dict(zip(friendly_tags, normalized_tags))
  3095.             normalized_set = set(normalized_tags)
  3096.             for normalized in normalized_set:
  3097.                 if normalized_tags.count(normalized) > 1:
  3098.                     self.opts.log.warn("      Warning: multiple tags resolving to genre '%s':" % normalized)
  3099.                     for key in genre_tags_dict:
  3100.                         if genre_tags_dict[key] == normalized:
  3101.                             self.opts.log.warn('       %s' % key)
  3102.                             continue
  3103.                     
  3104.             
  3105.             return genre_tags_dict
  3106.  
  3107.         
  3108.         def formatNCXText(self, description, dest = None):
  3109.             massaged = unicode(BeautifulStoneSoup(description, convertEntities = BeautifulStoneSoup.HTML_ENTITIES))
  3110.             massaged = re.sub('&', '&', massaged)
  3111.             if massaged.strip() and dest:
  3112.                 return self.generateShortDescription(massaged.strip(), dest = dest)
  3113.             return None
  3114.  
  3115.         
  3116.         def generateAuthorAnchor(self, author):
  3117.             return re.sub('\\W', '', author)
  3118.  
  3119.         
  3120.         def generateHTMLByGenre(self, genre, section_head, books, outfile):
  3121.             soup = self.generateHTMLGenreHeader(genre)
  3122.             body = soup.find('body')
  3123.             btc = 0
  3124.             if section_head:
  3125.                 aTag = Tag(soup, 'a')
  3126.                 aTag['name'] = 'section_start'
  3127.                 body.insert(btc, aTag)
  3128.                 btc += 1
  3129.             
  3130.             aTag = Tag(soup, 'a')
  3131.             aTag['name'] = 'Genre_%s' % genre
  3132.             body.insert(btc, aTag)
  3133.             btc += 1
  3134.             titleTag = body.find(attrs = {
  3135.                 'class': 'title' })
  3136.             titleTag.insert(0, NavigableString('<b><i>%s</i></b>' % escape(self.getFriendlyGenreTag(genre))))
  3137.             divTag = body.find(attrs = {
  3138.                 'class': 'authors' })
  3139.             dtc = 0
  3140.             current_author = ''
  3141.             current_series = None
  3142.             for book in books:
  3143.                 if book['author'] != current_author:
  3144.                     current_author = book['author']
  3145.                     non_series_books = 0
  3146.                     current_series = None
  3147.                     pAuthorTag = Tag(soup, 'p')
  3148.                     pAuthorTag['class'] = 'author_index'
  3149.                     emTag = Tag(soup, 'em')
  3150.                     aTag = Tag(soup, 'a')
  3151.                     aTag['href'] = '%s.html#%s' % ('ByAlphaAuthor', self.generateAuthorAnchor(book['author']))
  3152.                     aTag.insert(0, book['author'])
  3153.                     emTag.insert(0, aTag)
  3154.                     pAuthorTag.insert(0, emTag)
  3155.                     divTag.insert(dtc, pAuthorTag)
  3156.                     dtc += 1
  3157.                 
  3158.                 if not current_series and non_series_books and book['series']:
  3159.                     hrTag = Tag(soup, 'hr')
  3160.                     hrTag['class'] = 'series_divider'
  3161.                     divTag.insert(dtc, hrTag)
  3162.                     dtc += 1
  3163.                 
  3164.                 if book['series'] and book['series'] != current_series:
  3165.                     current_series = book['series']
  3166.                     pSeriesTag = Tag(soup, 'p')
  3167.                     pSeriesTag['class'] = 'series'
  3168.                     pSeriesTag.insert(0, NavigableString(self.NOT_READ_SYMBOL + book['series']))
  3169.                     divTag.insert(dtc, pSeriesTag)
  3170.                     dtc += 1
  3171.                 
  3172.                 if current_series and not book['series']:
  3173.                     current_series = None
  3174.                 
  3175.                 pBookTag = Tag(soup, 'p')
  3176.                 ptc = 0
  3177.                 if book['read']:
  3178.                     pBookTag.insert(ptc, NavigableString(self.READ_SYMBOL))
  3179.                     pBookTag['class'] = 'read_book'
  3180.                     ptc += 1
  3181.                 elif book['id'] in self.bookmarked_books:
  3182.                     pBookTag.insert(ptc, NavigableString(self.READING_SYMBOL))
  3183.                     pBookTag['class'] = 'read_book'
  3184.                     ptc += 1
  3185.                 else:
  3186.                     pBookTag['class'] = 'unread_book'
  3187.                     pBookTag.insert(ptc, NavigableString(self.NOT_READ_SYMBOL))
  3188.                     ptc += 1
  3189.                 aTag = Tag(soup, 'a')
  3190.                 aTag['href'] = 'book_%d.html' % int(float(book['id']))
  3191.                 if current_series:
  3192.                     aTag.insert(0, escape(book['title'][len(book['series']) + 1:]))
  3193.                 else:
  3194.                     aTag.insert(0, escape(book['title']))
  3195.                     non_series_books += 1
  3196.                 pBookTag.insert(ptc, aTag)
  3197.                 ptc += 1
  3198.                 divTag.insert(dtc, pBookTag)
  3199.                 dtc += 1
  3200.             
  3201.             outfile = open(outfile, 'w')
  3202.             outfile.write(soup.prettify())
  3203.             outfile.close()
  3204.             if len(books) > 1:
  3205.                 titles_spanned = [
  3206.                     (books[0]['author'], books[0]['title']),
  3207.                     (books[-1]['author'], books[-1]['title'])]
  3208.             else:
  3209.                 titles_spanned = [
  3210.                     (books[0]['author'], books[0]['title'])]
  3211.             return titles_spanned
  3212.  
  3213.         
  3214.         def generateHTMLDescriptionHeader(self, title):
  3215.             title_border = None if self.opts.fmt == 'epub' else '<div class="hr"><blockquote><hr/></blockquote></div>'
  3216.             header = '\n            <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n            <html xmlns="http://www.w3.org/1999/xhtml" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">\n            <head>\n            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n                <link rel="stylesheet" type="text/css" href="stylesheet.css" media="screen" />\n            <title></title>\n            </head>\n            <body>\n            <p class="title"></p>\n            {0}\n            <p class="author"></p>\n            <!--p class="series"></p-->\n            <p class="tags"> </p>\n            <table width="100%" border="0">\n              <tr>\n                <td class="thumbnail" rowspan="7"></td>\n                <td> </td>\n                <td> </td>\n              </tr>\n              <tr>\n                <td> </td>\n                <td> </td>\n              </tr>\n              <tr>\n                <td>Publisher</td>\n                <td class="publisher"></td>\n              </tr>\n              <tr>\n                <td>Published</td>\n                <td class="date"></td>\n              </tr>\n              <tr>\n                <td>Rating</td>\n                <td class="rating"></td>\n              </tr>\n              <tr>\n                <td class="notes_label">Notes</td>\n                <td class="notes"></td>\n              </tr>\n              <tr>\n                <td> </td>\n                <td> </td>\n              </tr>\n            </table>\n            <blockquote><hr/></blockquote>\n            <div class="description"></div>\n            </body>\n            </html>\n            '.format(title_border)
  3217.             soup = BeautifulSoup(header, selfClosingTags = [
  3218.                 'mbp:pagebreak'])
  3219.             titleTag = soup.find('title')
  3220.             titleTag.insert(0, NavigableString(escape(title)))
  3221.             return soup
  3222.  
  3223.         
  3224.         def generateHTMLEmptyHeader(self, title):
  3225.             header = '\n                <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n                <html xmlns="http://www.w3.org/1999/xhtml" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">\n                <head>\n                <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n                    <link rel="stylesheet" type="text/css" href="stylesheet.css" media="screen" />\n                <title></title>\n                </head>\n                <body>\n                </body>\n                </html>\n                '
  3226.             soup = BeautifulSoup(header)
  3227.             titleTag = soup.find('title')
  3228.             titleTag.insert(0, NavigableString(title))
  3229.             return soup
  3230.  
  3231.         
  3232.         def generateHTMLGenreHeader(self, title):
  3233.             header = '\n                <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n                <html xmlns="http://www.w3.org/1999/xhtml" xmlns:calibre="http://calibre.kovidgoyal.net/2009/metadata">\n                <head>\n                <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\n                    <link rel="stylesheet" type="text/css" href="stylesheet.css" media="screen" />\n                <title></title>\n                </head>\n                <body>\n                    <p class="title"></p>\n                    <div class="hr"><blockquote><hr/></blockquote></div>\n                    <div class="authors"></div>\n                </body>\n                </html>\n                '
  3234.             soup = BeautifulSoup(header)
  3235.             titleTag = soup.find('title')
  3236.             titleTag.insert(0, escape(NavigableString(title)))
  3237.             return soup
  3238.  
  3239.         
  3240.         def generateMastheadImage(self, out_path):
  3241.             load_defaults = load_defaults
  3242.             import calibre.ebooks.conversion.config
  3243.             fontconfig = fontconfig
  3244.             import calibre.utils.fonts
  3245.             font_path = default_font = P('fonts/liberation/LiberationSerif-Bold.ttf')
  3246.             recs = load_defaults('mobi_output')
  3247.             masthead_font_family = recs.get('masthead_font', 'Default')
  3248.             if masthead_font_family != 'Default':
  3249.                 masthead_font = fontconfig.files_for_family(masthead_font_family)
  3250.                 if 'normal' in masthead_font:
  3251.                     font_path = masthead_font['normal'][0]
  3252.                 
  3253.             
  3254.             if not font_path or not os.access(font_path, os.R_OK):
  3255.                 font_path = default_font
  3256.             
  3257.             MI_WIDTH = 600
  3258.             MI_HEIGHT = 60
  3259.             
  3260.             try:
  3261.                 Image = Image
  3262.                 ImageDraw = ImageDraw
  3263.                 ImageFont = ImageFont
  3264.                 import PIL
  3265.                 (Image, ImageDraw, ImageFont)
  3266.             except ImportError:
  3267.                 import Image
  3268.                 import ImageDraw
  3269.                 import ImageFont
  3270.  
  3271.             img = Image.new('RGB', (MI_WIDTH, MI_HEIGHT), 'white')
  3272.             draw = ImageDraw.Draw(img)
  3273.             
  3274.             try:
  3275.                 font = ImageFont.truetype(font_path, 48)
  3276.             except:
  3277.                 self.opts.log.error("     Failed to load user-specifed font '%s'" % font_path)
  3278.                 font = ImageFont.truetype(default_font, 48)
  3279.  
  3280.             text = self.title.encode('utf-8')
  3281.             (width, height) = draw.textsize(text, font = font)
  3282.             left = max(int((MI_WIDTH - width) / 2), 0)
  3283.             top = max(int((MI_HEIGHT - height) / 2), 0)
  3284.             draw.text((left, top), text, fill = (0, 0, 0), font = font)
  3285.             img.save(open(out_path, 'wb'), 'GIF')
  3286.  
  3287.         
  3288.         def generateSeriesTitle(self, title):
  3289.             if float(title['series_index']) - int(title['series_index']):
  3290.                 series_title = '%s %4.2f: %s' % (title['series'], title['series_index'], title['title'])
  3291.             else:
  3292.                 series_title = '%s %d: %s' % (title['series'], title['series_index'], title['title'])
  3293.             return series_title
  3294.  
  3295.         
  3296.         def generateShortDescription(self, description, dest = None):
  3297.             
  3298.             def shortDescription(description, limit):
  3299.                 short_description = ''
  3300.                 words = description.split()
  3301.                 for word in words:
  3302.                     short_description += word + ' '
  3303.                     if len(short_description) > limit:
  3304.                         short_description += '...'
  3305.                         return short_description
  3306.                 
  3307.  
  3308.             if not description:
  3309.                 return None
  3310.             if dest == 'title':
  3311.                 return description
  3312.             if dest == 'author':
  3313.                 if self.authorClip and len(description) < self.authorClip:
  3314.                     return description
  3315.                 return shortDescription(description, self.authorClip)
  3316.             dest == 'author'
  3317.             if dest == 'description':
  3318.                 if self.descriptionClip and len(description) < self.descriptionClip:
  3319.                     return description
  3320.                 return shortDescription(description, self.descriptionClip)
  3321.             dest == 'description'
  3322.             print " returning description with unspecified destination '%s'" % description
  3323.             raise RuntimeError
  3324.  
  3325.         
  3326.         def generateSortTitle(self, title):
  3327.             title_sort = title_sort
  3328.             import calibre.ebooks.metadata
  3329.             title_words = title_sort(title).split()
  3330.             translated = []
  3331.             for i, word in enumerate(title_words):
  3332.                 if i == 0:
  3333.                     if self.opts.numbers_as_text and re.match('[0-9]+', word[0]):
  3334.                         translated.append(EPUB_MOBI.NumberToText(word).text.capitalize())
  3335.                     elif re.match('[0-9]+', word[0]):
  3336.                         word = word.replace(',', '')
  3337.                         suffix = re.search('[\\D]', word)
  3338.                         if suffix:
  3339.                             word = '%10.0f%s' % (float(word[:suffix.start()]), word[suffix.start():])
  3340.                         else:
  3341.                             word = '%10.0f' % float(word)
  3342.                     
  3343.                     if self.letter_or_symbol(word[0]) != word[0]:
  3344.                         if not word[0] > 'A':
  3345.                             if ord(word[0]) < ord(word[0]):
  3346.                                 pass
  3347.                             elif ord(word[0]) < ord('A'):
  3348.                                 translated.append('/')
  3349.                             
  3350.                         
  3351.                     translated.append(word.capitalize())
  3352.                     continue
  3353.                 if re.search('[0-9]+', word[0]):
  3354.                     word = word.replace(',', '')
  3355.                     suffix = re.search('[\\D]', word)
  3356.                     if suffix:
  3357.                         word = '%10.0f%s' % (float(word[:suffix.start()]), word[suffix.start():])
  3358.                     else:
  3359.                         word = '%10.0f' % float(word)
  3360.                 
  3361.                 translated.append(word)
  3362.             
  3363.             return ' '.join(translated)
  3364.  
  3365.         
  3366.         def generateThumbnail(self, title, image_dir, thumb_file):
  3367.             Image = Image
  3368.             import calibre.utils.magick
  3369.             
  3370.             try:
  3371.                 img = Image()
  3372.                 img.open(title['cover'])
  3373.                 img.thumbnail(self.thumbWidth, self.thumbHeight)
  3374.                 img.save(os.path.join(image_dir, thumb_file))
  3375.             except:
  3376.                 self.opts.log.error('generateThumbnail(): Error with %s' % title['title'])
  3377.  
  3378.  
  3379.         
  3380.         def getMarkerTags(self):
  3381.             markerTags = []
  3382.             markerTags.extend(self.opts.exclude_tags.split(','))
  3383.             markerTags.extend(self.opts.note_tag.split(','))
  3384.             markerTags.extend(self.opts.read_tag.split(','))
  3385.             return markerTags
  3386.  
  3387.         
  3388.         def letter_or_symbol(self, char):
  3389.             if not re.search('[a-zA-Z]', char):
  3390.                 return 'Symbols'
  3391.             return char
  3392.  
  3393.         
  3394.         def getFriendlyGenreTag(self, genre):
  3395.             for friendly_tag in self.genre_tags_dict:
  3396.                 if self.genre_tags_dict[friendly_tag] == genre:
  3397.                     return friendly_tag
  3398.             
  3399.  
  3400.         
  3401.         def markdownComments(self, comments):
  3402.             for lost_cr in re.finditer('([a-z])([\\.\\?!])([A-Z])', comments):
  3403.                 comments = comments.replace(lost_cr.group(), '%s%s\n\n%s' % (lost_cr.group(1), lost_cr.group(2), lost_cr.group(3)))
  3404.             
  3405.             if not isinstance(comments, unicode):
  3406.                 comments = comments.decode('utf-8', 'replace')
  3407.             
  3408.             soup = BeautifulSoup(comments)
  3409.             elems = soup.findAll('div')
  3410.             for elem in elems:
  3411.                 elem.extract()
  3412.             
  3413.             comments = soup.renderContents(None)
  3414.             if re.search('\n\n', comments):
  3415.                 soup = BeautifulSoup()
  3416.                 split_ps = comments.split(u'\n\n')
  3417.                 tsc = 0
  3418.                 for p in split_ps:
  3419.                     pTag = Tag(soup, 'p')
  3420.                     pTag.insert(0, p)
  3421.                     soup.insert(tsc, pTag)
  3422.                     tsc += 1
  3423.                 
  3424.                 comments = soup.renderContents(None)
  3425.             
  3426.             comments = re.sub('[\r\n]', '<br />', comments)
  3427.             comments = re.sub('--', '—', comments)
  3428.             soup = BeautifulSoup(comments)
  3429.             result = BeautifulSoup()
  3430.             rtc = 0
  3431.             open_pTag = False
  3432.             all_tokens = list(soup.contents)
  3433.             for token in all_tokens:
  3434.                 if type(token) is NavigableString:
  3435.                     if not open_pTag:
  3436.                         pTag = Tag(result, 'p')
  3437.                         open_pTag = True
  3438.                         ptc = 0
  3439.                     
  3440.                     pTag.insert(ptc, prepare_string_for_xml(token))
  3441.                     ptc += 1
  3442.                     continue
  3443.                 if token.name in ('br', 'b', 'i', 'em'):
  3444.                     if not open_pTag:
  3445.                         pTag = Tag(result, 'p')
  3446.                         open_pTag = True
  3447.                         ptc = 0
  3448.                     
  3449.                     pTag.insert(ptc, token)
  3450.                     ptc += 1
  3451.                     continue
  3452.                 if open_pTag:
  3453.                     result.insert(rtc, pTag)
  3454.                     rtc += 1
  3455.                     open_pTag = False
  3456.                     ptc = 0
  3457.                 
  3458.                 sub_tokens = list(token.contents)
  3459.                 for sub_token in sub_tokens:
  3460.                     if type(sub_token) is NavigableString:
  3461.                         sub_token.replaceWith(prepare_string_for_xml(sub_token))
  3462.                         continue
  3463.                 
  3464.                 result.insert(rtc, token)
  3465.                 rtc += 1
  3466.             
  3467.             if open_pTag:
  3468.                 result.insert(rtc, pTag)
  3469.             
  3470.             paras = result.findAll('p')
  3471.             for p in paras:
  3472.                 p['class'] = 'description'
  3473.             
  3474.             for elem in elems:
  3475.                 result.insert(rtc, elem)
  3476.                 rtc += 1
  3477.             
  3478.             return result.renderContents(encoding = None)
  3479.  
  3480.         
  3481.         def processSpecialTags(self, tags, this_title, opts):
  3482.             tag_list = []
  3483.             for tag in tags:
  3484.                 tag = self.convertHTMLEntities(tag)
  3485.                 if tag.startswith(opts.note_tag):
  3486.                     this_title['notes'] = tag[len(self.opts.note_tag):]
  3487.                     continue
  3488.                 if tag == opts.read_tag:
  3489.                     this_title['read'] = True
  3490.                     continue
  3491.                 if re.search(opts.exclude_genre, tag):
  3492.                     continue
  3493.                     continue
  3494.                 tag_list.append(tag)
  3495.             
  3496.             return tag_list
  3497.  
  3498.         
  3499.         def updateProgressFullStep(self, description):
  3500.             self.currentStep += 1
  3501.             self.progressString = description
  3502.             self.progressInt = float((self.currentStep - 1) / self.totalSteps)
  3503.             self.reporter(self.progressInt, self.progressString)
  3504.             if self.opts.cli_environment:
  3505.                 self.opts.log(u'%3.0f%% %s' % (self.progressInt * 100, self.progressString))
  3506.             
  3507.  
  3508.         
  3509.         def updateProgressMicroStep(self, description, micro_step_pct):
  3510.             step_range = 100 / self.totalSteps
  3511.             self.progressString = description
  3512.             coarse_progress = float((self.currentStep - 1) / self.totalSteps)
  3513.             fine_progress = float(micro_step_pct * step_range / 100)
  3514.             self.progressInt = coarse_progress + fine_progress
  3515.             self.reporter(self.progressInt, self.progressString)
  3516.  
  3517.         
  3518.         class NotImplementedError:
  3519.             
  3520.             def __init__(self, error):
  3521.                 self.error = error
  3522.  
  3523.             
  3524.             def logerror(self):
  3525.                 self.opts.log.info('%s not implemented' % self.error)
  3526.  
  3527.  
  3528.  
  3529.     
  3530.     def run(self, path_to_output, opts, db, notification = DummyReporter()):
  3531.         opts.log = log
  3532.         opts.fmt = self.fmt = path_to_output.rpartition('.')[2]
  3533.         opts.creator = 'calibre'
  3534.         opts.connected_kindle = False
  3535.         op = opts.output_profile
  3536.         if op is None:
  3537.             op = 'default'
  3538.         
  3539.         if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower():
  3540.             opts.connected_kindle = True
  3541.             if opts.connected_device['serial'] and opts.connected_device['serial'][:4] in ('B004', 'B005'):
  3542.                 op = 'kindle_dx'
  3543.             else:
  3544.                 op = 'kindle'
  3545.         
  3546.         opts.descriptionClip = None if op.endswith('dx') or 'kindle' not in op else 100
  3547.         opts.authorClip = None if op.endswith('dx') or 'kindle' not in op else 60
  3548.         opts.output_profile = op
  3549.         opts.basename = 'Catalog'
  3550.         opts.cli_environment = not hasattr(opts, 'sync')
  3551.         opts.sort_descriptions_by_author = True
  3552.         build_log = []
  3553.         None(None % (build_log.append, u'%s(): Generating %s %sin %s environment', self.name, self.fmt if opts.output_profile else '' if opts.cli_environment else 'GUI'))
  3554.         if opts.exclude_genre.strip() == '':
  3555.             opts.exclude_genre = '\\[^.\\]'
  3556.             build_log.append(" converting empty exclude_genre to '\\[^.\\]'")
  3557.         
  3558.         if opts.connected_device['name']:
  3559.             if opts.connected_device['serial']:
  3560.                 build_log.append(u" connected_device: '%s' #%s%s " % (opts.connected_device['name'], opts.connected_device['serial'][0:4], 'x' * (len(opts.connected_device['serial']) - 4)))
  3561.                 for storage in opts.connected_device['storage']:
  3562.                     if storage:
  3563.                         build_log.append(u'  mount point: %s' % storage)
  3564.                         continue
  3565.                 
  3566.             else:
  3567.                 build_log.append(u" connected_device: '%s'" % opts.connected_device['name'])
  3568.                 for storage in opts.connected_device['storage']:
  3569.                     if storage:
  3570.                         build_log.append(u'  mount point: %s' % storage)
  3571.                         continue
  3572.                 
  3573.         
  3574.         opts_dict = vars(opts)
  3575.         if opts_dict['ids']:
  3576.             build_log.append(' book count: %d' % len(opts_dict['ids']))
  3577.         
  3578.         sections_list = [
  3579.             'Descriptions',
  3580.             'Authors']
  3581.         if opts.generate_titles:
  3582.             sections_list.append('Titles')
  3583.         
  3584.         if opts.generate_recently_added:
  3585.             sections_list.append('Recently Added')
  3586.         
  3587.         if not opts.exclude_genre.strip() == '.':
  3588.             sections_list.append('Genres')
  3589.         
  3590.         build_log.append(u' Sections: %s' % ', '.join(sections_list))
  3591.         keys = opts_dict.keys()
  3592.         keys.sort()
  3593.         build_log.append(' opts:')
  3594.         for key in keys:
  3595.             if key in ('catalog_title', 'authorClip', 'connected_kindle', 'descriptionClip', 'exclude_genre', 'exclude_tags', 'note_tag', 'numbers_as_text', 'read_tag', 'search_text', 'sort_by', 'sort_descriptions_by_author', 'sync'):
  3596.                 build_log.append('  %s: %s' % (key, opts_dict[key]))
  3597.                 continue
  3598.         
  3599.         if opts.verbose:
  3600.             log('\n'.join((lambda .0: for line in .0:
  3601. line)(build_log)))
  3602.         
  3603.         self.opts = opts
  3604.         catalog = self.CatalogBuilder(db, opts, self, report_progress = notification)
  3605.         if opts.verbose:
  3606.             log.info(' Begin catalog source generation')
  3607.         
  3608.         catalog.createDirectoryStructure()
  3609.         catalog.copyResources()
  3610.         catalog.calculateThumbnailSize()
  3611.         catalog_source_built = catalog.buildSources()
  3612.         if opts.verbose:
  3613.             if catalog_source_built:
  3614.                 log.info(' Completed catalog source generation\n')
  3615.             else:
  3616.                 log.warn(' No database hits with supplied criteria')
  3617.         
  3618.         if catalog_source_built:
  3619.             recommendations = []
  3620.             recommendations.append(('comments', '\n'.join((lambda .0: for line in .0:
  3621. line)(build_log)), OptionRecommendation.HIGH))
  3622.             dp = getattr(opts, 'debug_pipeline', None)
  3623.             if dp is not None:
  3624.                 recommendations.append(('debug_pipeline', dp, OptionRecommendation.HIGH))
  3625.             
  3626.             if opts.fmt == 'mobi' and opts.output_profile and opts.output_profile.startswith('kindle'):
  3627.                 recommendations.append(('output_profile', opts.output_profile, OptionRecommendation.HIGH))
  3628.                 recommendations.append(('no_inline_toc', True, OptionRecommendation.HIGH))
  3629.                 recommendations.append(('book_producer', opts.output_profile, OptionRecommendation.HIGH))
  3630.             
  3631.             Plumber = Plumber
  3632.             import calibre.ebooks.conversion.plumber
  3633.             plumber = Plumber(os.path.join(catalog.catalogPath, opts.basename + '.opf'), path_to_output, log, report_progress = notification, abort_after_input_dump = False)
  3634.             plumber.merge_ui_recommendations(recommendations)
  3635.             plumber.run()
  3636.             return 0
  3637.         return 1
  3638.  
  3639.  
  3640.