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