mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Delay loading for catalog plugins
This commit is contained in:
parent
abbc03eeb9
commit
891f76d653
@ -11,6 +11,8 @@ from calibre.constants import numeric_version
|
|||||||
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
from calibre.ebooks.metadata.archive import ArchiveExtract, get_cbz_metadata
|
||||||
from calibre.ebooks.html.to_zip import HTML2ZIP
|
from calibre.ebooks.html.to_zip import HTML2ZIP
|
||||||
|
|
||||||
|
plugins = []
|
||||||
|
|
||||||
# To archive plugins {{{
|
# To archive plugins {{{
|
||||||
|
|
||||||
class PML2PMLZ(FileTypePlugin):
|
class PML2PMLZ(FileTypePlugin):
|
||||||
@ -118,6 +120,7 @@ class TXT2TXTZ(FileTypePlugin):
|
|||||||
# No images so just import the TXT file.
|
# No images so just import the TXT file.
|
||||||
return path_to_ebook
|
return path_to_ebook
|
||||||
|
|
||||||
|
plugins += [HTML2ZIP, PML2PMLZ, TXT2TXTZ, ArchiveExtract,]
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Metadata reader plugins {{{
|
# Metadata reader plugins {{{
|
||||||
@ -400,6 +403,10 @@ class ZipMetadataReader(MetadataReaderPlugin):
|
|||||||
def get_metadata(self, stream, ftype):
|
def get_metadata(self, stream, ftype):
|
||||||
from calibre.ebooks.metadata.zip import get_metadata
|
from calibre.ebooks.metadata.zip import get_metadata
|
||||||
return get_metadata(stream)
|
return get_metadata(stream)
|
||||||
|
|
||||||
|
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||||
|
x.__name__.endswith('MetadataReader')]
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
# Metadata writer plugins {{{
|
# Metadata writer plugins {{{
|
||||||
@ -500,8 +507,12 @@ class TXTZMetadataWriter(MetadataWriterPlugin):
|
|||||||
from calibre.ebooks.metadata.extz import set_metadata
|
from calibre.ebooks.metadata.extz import set_metadata
|
||||||
set_metadata(stream, mi)
|
set_metadata(stream, mi)
|
||||||
|
|
||||||
|
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
||||||
|
x.__name__.endswith('MetadataWriter')]
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
# Conversion plugins {{{
|
||||||
from calibre.ebooks.conversion.plugins.comic_input import ComicInput
|
from calibre.ebooks.conversion.plugins.comic_input import ComicInput
|
||||||
from calibre.ebooks.conversion.plugins.djvu_input import DJVUInput
|
from calibre.ebooks.conversion.plugins.djvu_input import DJVUInput
|
||||||
from calibre.ebooks.conversion.plugins.epub_input import EPUBInput
|
from calibre.ebooks.conversion.plugins.epub_input import EPUBInput
|
||||||
@ -541,65 +552,6 @@ from calibre.ebooks.conversion.plugins.html_output import HTMLOutput
|
|||||||
from calibre.ebooks.conversion.plugins.htmlz_output import HTMLZOutput
|
from calibre.ebooks.conversion.plugins.htmlz_output import HTMLZOutput
|
||||||
from calibre.ebooks.conversion.plugins.snb_output import SNBOutput
|
from calibre.ebooks.conversion.plugins.snb_output import SNBOutput
|
||||||
|
|
||||||
from calibre.customize.profiles import input_profiles, output_profiles
|
|
||||||
|
|
||||||
from calibre.devices.apple.driver import ITUNES
|
|
||||||
from calibre.devices.hanlin.driver import HANLINV3, HANLINV5, BOOX, SPECTRA
|
|
||||||
from calibre.devices.blackberry.driver import BLACKBERRY, PLAYBOOK
|
|
||||||
from calibre.devices.cybook.driver import CYBOOK, ORIZON
|
|
||||||
from calibre.devices.eb600.driver import (EB600, COOL_ER, SHINEBOOK,
|
|
||||||
POCKETBOOK360, GER2, ITALICA, ECLICTO, DBOOK, INVESBOOK,
|
|
||||||
BOOQ, ELONEX, POCKETBOOK301, MENTOR, POCKETBOOK602,
|
|
||||||
POCKETBOOK701, POCKETBOOK360P, PI2)
|
|
||||||
from calibre.devices.iliad.driver import ILIAD
|
|
||||||
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
|
||||||
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
|
|
||||||
from calibre.devices.kindle.driver import (KINDLE, KINDLE2, KINDLE_DX,
|
|
||||||
KINDLE_FIRE)
|
|
||||||
from calibre.devices.nook.driver import NOOK, NOOK_COLOR
|
|
||||||
from calibre.devices.prs505.driver import PRS505
|
|
||||||
from calibre.devices.prst1.driver import PRST1
|
|
||||||
from calibre.devices.user_defined.driver import USER_DEFINED
|
|
||||||
from calibre.devices.android.driver import ANDROID, S60, WEBOS
|
|
||||||
from calibre.devices.nokia.driver import N770, N810, E71X, E52
|
|
||||||
from calibre.devices.eslick.driver import ESLICK, EBK52
|
|
||||||
from calibre.devices.nuut2.driver import NUUT2
|
|
||||||
from calibre.devices.iriver.driver import IRIVER_STORY
|
|
||||||
from calibre.devices.binatone.driver import README
|
|
||||||
from calibre.devices.hanvon.driver import (N516, EB511, ALEX, AZBOOKA, THEBOOK,
|
|
||||||
LIBREAIR, ODYSSEY)
|
|
||||||
from calibre.devices.edge.driver import EDGE
|
|
||||||
from calibre.devices.teclast.driver import (TECLAST_K3, NEWSMY, IPAPYRUS,
|
|
||||||
SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O, STASH, WEXLER)
|
|
||||||
from calibre.devices.sne.driver import SNE
|
|
||||||
from calibre.devices.misc import (PALMPRE, AVANT, SWEEX, PDNOVEL,
|
|
||||||
GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, LUMIREAD, ALURATEK_COLOR,
|
|
||||||
TREKSTOR, EEEREADER, NEXTBOOK, ADAM, MOOVYBOOK, COBY, EX124G)
|
|
||||||
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
|
|
||||||
from calibre.devices.kobo.driver import KOBO
|
|
||||||
from calibre.devices.bambook.driver import BAMBOOK
|
|
||||||
from calibre.devices.boeye.driver import BOEYE_BEX, BOEYE_BDX
|
|
||||||
|
|
||||||
from calibre.library.catalog import CSV_XML, EPUB_MOBI, BIBTEX
|
|
||||||
from calibre.ebooks.epub.fix.unmanifested import Unmanifested
|
|
||||||
from calibre.ebooks.epub.fix.epubcheck import Epubcheck
|
|
||||||
|
|
||||||
plugins = [HTML2ZIP, PML2PMLZ, TXT2TXTZ, ArchiveExtract, CSV_XML, EPUB_MOBI, BIBTEX, Unmanifested,
|
|
||||||
Epubcheck, ]
|
|
||||||
|
|
||||||
# New metadata download plugins {{{
|
|
||||||
from calibre.ebooks.metadata.sources.google import GoogleBooks
|
|
||||||
from calibre.ebooks.metadata.sources.amazon import Amazon
|
|
||||||
from calibre.ebooks.metadata.sources.openlibrary import OpenLibrary
|
|
||||||
from calibre.ebooks.metadata.sources.isbndb import ISBNDB
|
|
||||||
from calibre.ebooks.metadata.sources.overdrive import OverDrive
|
|
||||||
from calibre.ebooks.metadata.sources.douban import Douban
|
|
||||||
from calibre.ebooks.metadata.sources.ozon import Ozon
|
|
||||||
|
|
||||||
plugins += [GoogleBooks, Amazon, OpenLibrary, ISBNDB, OverDrive, Douban, Ozon]
|
|
||||||
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
plugins += [
|
plugins += [
|
||||||
ComicInput,
|
ComicInput,
|
||||||
DJVUInput,
|
DJVUInput,
|
||||||
@ -642,6 +594,66 @@ plugins += [
|
|||||||
HTMLZOutput,
|
HTMLZOutput,
|
||||||
SNBOutput,
|
SNBOutput,
|
||||||
]
|
]
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Catalog plugins {{{
|
||||||
|
from calibre.library.catalogs.csv_xml import CSV_XML
|
||||||
|
from calibre.library.catalogs.bibtex import BIBTEX
|
||||||
|
from calibre.library.catalogs.epub_mobi import EPUB_MOBI
|
||||||
|
plugins += [CSV_XML, BIBTEX, EPUB_MOBI]
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# EPUB Fix plugins {{{
|
||||||
|
from calibre.ebooks.epub.fix.unmanifested import Unmanifested
|
||||||
|
from calibre.ebooks.epub.fix.epubcheck import Epubcheck
|
||||||
|
plugins += [Unmanifested, Epubcheck]
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Profiles {{{
|
||||||
|
from calibre.customize.profiles import input_profiles, output_profiles
|
||||||
|
plugins += input_profiles + output_profiles
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Device driver plugins {{{
|
||||||
|
from calibre.devices.apple.driver import ITUNES
|
||||||
|
from calibre.devices.hanlin.driver import HANLINV3, HANLINV5, BOOX, SPECTRA
|
||||||
|
from calibre.devices.blackberry.driver import BLACKBERRY, PLAYBOOK
|
||||||
|
from calibre.devices.cybook.driver import CYBOOK, ORIZON
|
||||||
|
from calibre.devices.eb600.driver import (EB600, COOL_ER, SHINEBOOK,
|
||||||
|
POCKETBOOK360, GER2, ITALICA, ECLICTO, DBOOK, INVESBOOK,
|
||||||
|
BOOQ, ELONEX, POCKETBOOK301, MENTOR, POCKETBOOK602,
|
||||||
|
POCKETBOOK701, POCKETBOOK360P, PI2)
|
||||||
|
from calibre.devices.iliad.driver import ILIAD
|
||||||
|
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
||||||
|
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
|
||||||
|
from calibre.devices.kindle.driver import (KINDLE, KINDLE2, KINDLE_DX,
|
||||||
|
KINDLE_FIRE)
|
||||||
|
from calibre.devices.nook.driver import NOOK, NOOK_COLOR
|
||||||
|
from calibre.devices.prs505.driver import PRS505
|
||||||
|
from calibre.devices.prst1.driver import PRST1
|
||||||
|
from calibre.devices.user_defined.driver import USER_DEFINED
|
||||||
|
from calibre.devices.android.driver import ANDROID, S60, WEBOS
|
||||||
|
from calibre.devices.nokia.driver import N770, N810, E71X, E52
|
||||||
|
from calibre.devices.eslick.driver import ESLICK, EBK52
|
||||||
|
from calibre.devices.nuut2.driver import NUUT2
|
||||||
|
from calibre.devices.iriver.driver import IRIVER_STORY
|
||||||
|
from calibre.devices.binatone.driver import README
|
||||||
|
from calibre.devices.hanvon.driver import (N516, EB511, ALEX, AZBOOKA, THEBOOK,
|
||||||
|
LIBREAIR, ODYSSEY)
|
||||||
|
from calibre.devices.edge.driver import EDGE
|
||||||
|
from calibre.devices.teclast.driver import (TECLAST_K3, NEWSMY, IPAPYRUS,
|
||||||
|
SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O, STASH, WEXLER)
|
||||||
|
from calibre.devices.sne.driver import SNE
|
||||||
|
from calibre.devices.misc import (PALMPRE, AVANT, SWEEX, PDNOVEL,
|
||||||
|
GEMEI, VELOCITYMICRO, PDNOVEL_KOBO, LUMIREAD, ALURATEK_COLOR,
|
||||||
|
TREKSTOR, EEEREADER, NEXTBOOK, ADAM, MOOVYBOOK, COBY, EX124G)
|
||||||
|
from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG
|
||||||
|
from calibre.devices.kobo.driver import KOBO
|
||||||
|
from calibre.devices.bambook.driver import BAMBOOK
|
||||||
|
from calibre.devices.boeye.driver import BOEYE_BEX, BOEYE_BDX
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Order here matters. The first matched device is the one used.
|
# Order here matters. The first matched device is the one used.
|
||||||
plugins += [
|
plugins += [
|
||||||
HANLINV3,
|
HANLINV3,
|
||||||
@ -716,11 +728,20 @@ plugins += [
|
|||||||
BOEYE_BDX,
|
BOEYE_BDX,
|
||||||
USER_DEFINED,
|
USER_DEFINED,
|
||||||
]
|
]
|
||||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
# }}}
|
||||||
x.__name__.endswith('MetadataReader')]
|
|
||||||
plugins += [x for x in list(locals().values()) if isinstance(x, type) and \
|
# New metadata download plugins {{{
|
||||||
x.__name__.endswith('MetadataWriter')]
|
from calibre.ebooks.metadata.sources.google import GoogleBooks
|
||||||
plugins += input_profiles + output_profiles
|
from calibre.ebooks.metadata.sources.amazon import Amazon
|
||||||
|
from calibre.ebooks.metadata.sources.openlibrary import OpenLibrary
|
||||||
|
from calibre.ebooks.metadata.sources.isbndb import ISBNDB
|
||||||
|
from calibre.ebooks.metadata.sources.overdrive import OverDrive
|
||||||
|
from calibre.ebooks.metadata.sources.douban import Douban
|
||||||
|
from calibre.ebooks.metadata.sources.ozon import Ozon
|
||||||
|
|
||||||
|
plugins += [GoogleBooks, Amazon, OpenLibrary, ISBNDB, OverDrive, Douban, Ozon]
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
# Interface Actions {{{
|
# Interface Actions {{{
|
||||||
|
|
||||||
@ -1623,3 +1644,4 @@ plugins += [
|
|||||||
]
|
]
|
||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class PluginWidget(QWidget, Ui_Form):
|
|||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
|
|
||||||
def initialize(self, name, db): #not working properly to update
|
def initialize(self, name, db): #not working properly to update
|
||||||
from calibre.library.catalog import FIELDS
|
from calibre.library.catalogs import FIELDS
|
||||||
|
|
||||||
self.all_fields = [x for x in FIELDS if x != 'all']
|
self.all_fields = [x for x in FIELDS if x != 'all']
|
||||||
#add custom columns
|
#add custom columns
|
||||||
|
@ -21,7 +21,7 @@ class PluginWidget(QWidget, Ui_Form):
|
|||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
QWidget.__init__(self, parent)
|
QWidget.__init__(self, parent)
|
||||||
self.setupUi(self)
|
self.setupUi(self)
|
||||||
from calibre.library.catalog import FIELDS
|
from calibre.library.catalogs import FIELDS
|
||||||
self.all_fields = []
|
self.all_fields = []
|
||||||
for x in FIELDS:
|
for x in FIELDS:
|
||||||
if x != 'all':
|
if x != 'all':
|
||||||
|
File diff suppressed because it is too large
Load Diff
20
src/calibre/library/catalogs/__init__.py
Normal file
20
src/calibre/library/catalogs/__init__.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
|
||||||
|
FIELDS = ['all', 'title', 'title_sort', 'author_sort', 'authors', 'comments',
|
||||||
|
'cover', 'formats','id', 'isbn', 'ondevice', 'pubdate', 'publisher',
|
||||||
|
'rating', 'series_index', 'series', 'size', 'tags', 'timestamp',
|
||||||
|
'uuid', 'languages']
|
||||||
|
|
||||||
|
#Allowed fields for template
|
||||||
|
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', 'title_sort',
|
||||||
|
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
|
||||||
|
|
||||||
|
|
396
src/calibre/library/catalogs/bibtex.py
Normal file
396
src/calibre/library/catalogs/bibtex.py
Normal file
@ -0,0 +1,396 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re, codecs
|
||||||
|
from collections import namedtuple
|
||||||
|
from types import StringType, UnicodeType
|
||||||
|
|
||||||
|
from calibre import (strftime)
|
||||||
|
from calibre.customize import CatalogPlugin
|
||||||
|
from calibre.library.catalogs import FIELDS, TEMPLATE_ALLOWED_FIELDS
|
||||||
|
from calibre.utils.logging import default_log as log
|
||||||
|
from calibre.customize.conversion import DummyReporter
|
||||||
|
from calibre.constants import preferred_encoding
|
||||||
|
|
||||||
|
|
||||||
|
class BIBTEX(CatalogPlugin):
|
||||||
|
'BIBTEX catalog generator'
|
||||||
|
|
||||||
|
Option = namedtuple('Option', 'option, default, dest, action, help')
|
||||||
|
|
||||||
|
name = 'Catalog_BIBTEX'
|
||||||
|
description = 'BIBTEX catalog generator'
|
||||||
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
author = 'Sengian'
|
||||||
|
version = (1, 0, 0)
|
||||||
|
file_types = set(['bib'])
|
||||||
|
|
||||||
|
cli_options = [
|
||||||
|
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.\n'
|
||||||
|
'Available fields: %(fields)s.\n'
|
||||||
|
'plus user-created custom fields.\n'
|
||||||
|
'Example: %(opt)s=title,authors,tags\n'
|
||||||
|
"Default: '%%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")%dict(
|
||||||
|
fields=', '.join(FIELDS), opt='--fields')),
|
||||||
|
|
||||||
|
Option('--sort-by',
|
||||||
|
default = 'id',
|
||||||
|
dest = 'sort_by',
|
||||||
|
action = None,
|
||||||
|
help = _('Output field to sort on.\n'
|
||||||
|
'Available fields: author_sort, id, rating, size, timestamp, title.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--create-citation',
|
||||||
|
default = 'True',
|
||||||
|
dest = 'impcit',
|
||||||
|
action = None,
|
||||||
|
help = _('Create a citation for BibTeX entries.\n'
|
||||||
|
'Boolean value: True, False\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--add-files-path',
|
||||||
|
default = 'True',
|
||||||
|
dest = 'addfiles',
|
||||||
|
action = None,
|
||||||
|
help = _('Create a file entry if formats is selected for BibTeX entries.\n'
|
||||||
|
'Boolean value: True, False\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
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.\n'
|
||||||
|
'Available fields: %s.\n'
|
||||||
|
"Default: '%%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")%', '.join(TEMPLATE_ALLOWED_FIELDS)),
|
||||||
|
|
||||||
|
Option('--choose-encoding',
|
||||||
|
default = 'utf8',
|
||||||
|
dest = 'bibfile_enc',
|
||||||
|
action = None,
|
||||||
|
help = _('BibTeX file encoding output.\n'
|
||||||
|
'Available types: utf8, cp1252, ascii.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--choose-encoding-configuration',
|
||||||
|
default = 'strict',
|
||||||
|
dest = 'bibfile_enctag',
|
||||||
|
action = None,
|
||||||
|
help = _('BibTeX file encoding flag.\n'
|
||||||
|
'Available types: strict, replace, ignore, backslashreplace.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format")),
|
||||||
|
|
||||||
|
Option('--entry-type',
|
||||||
|
default = 'book',
|
||||||
|
dest = 'bib_entry',
|
||||||
|
action = None,
|
||||||
|
help = _('Entry type for BibTeX catalog.\n'
|
||||||
|
'Available types: book, misc, mixed.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: BIBTEX output format"))]
|
||||||
|
|
||||||
|
def run(self, path_to_output, opts, db, notification=DummyReporter()):
|
||||||
|
from calibre.utils.date import isoformat
|
||||||
|
from calibre.utils.html2text import html2text
|
||||||
|
from calibre.utils.bibtex import BibTeX
|
||||||
|
from calibre.library.save_to_disk import preprocess_template
|
||||||
|
from calibre.utils.date import now as nowf
|
||||||
|
|
||||||
|
def create_bibtex_entry(entry, fields, mode, template_citation,
|
||||||
|
bibtexdict, db, citation_bibtex=True, calibre_files=True):
|
||||||
|
|
||||||
|
#Bibtex doesn't like UTF-8 but keep unicode until writing
|
||||||
|
#Define starting chain or if book valid strict and not book return a Fail string
|
||||||
|
|
||||||
|
bibtex_entry = []
|
||||||
|
if mode != "misc" and check_entry_book_valid(entry) :
|
||||||
|
bibtex_entry.append(u'@book{')
|
||||||
|
elif mode != "book" :
|
||||||
|
bibtex_entry.append(u'@misc{')
|
||||||
|
else :
|
||||||
|
#case strict book
|
||||||
|
return ''
|
||||||
|
|
||||||
|
if citation_bibtex :
|
||||||
|
# Citation tag
|
||||||
|
bibtex_entry.append(make_bibtex_citation(entry, template_citation,
|
||||||
|
bibtexdict))
|
||||||
|
bibtex_entry = [u' '.join(bibtex_entry)]
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
if field.startswith('#'):
|
||||||
|
item = db.get_field(entry['id'],field,index_is_id=True)
|
||||||
|
if isinstance(item, (bool, float, int)):
|
||||||
|
item = repr(item)
|
||||||
|
elif field == 'title_sort':
|
||||||
|
item = entry['sort']
|
||||||
|
else:
|
||||||
|
item = entry[field]
|
||||||
|
|
||||||
|
#check if the field should be included (none or empty)
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if len(item) == 0 :
|
||||||
|
continue
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if field == 'authors' :
|
||||||
|
bibtex_entry.append(u'author = "%s"' % bibtexdict.bibtex_author_format(item))
|
||||||
|
|
||||||
|
elif field == 'id' :
|
||||||
|
bibtex_entry.append(u'calibreid = "%s"' % int(item))
|
||||||
|
|
||||||
|
elif field == 'rating' :
|
||||||
|
bibtex_entry.append(u'rating = "%s"' % int(item))
|
||||||
|
|
||||||
|
elif field == 'size' :
|
||||||
|
bibtex_entry.append(u'%s = "%s octets"' % (field, int(item)))
|
||||||
|
|
||||||
|
elif field == 'tags' :
|
||||||
|
#A list to flatten
|
||||||
|
bibtex_entry.append(u'tags = "%s"' % bibtexdict.utf8ToBibtex(u', '.join(item)))
|
||||||
|
|
||||||
|
elif field == 'comments' :
|
||||||
|
#\n removal
|
||||||
|
item = item.replace(u'\r\n',u' ')
|
||||||
|
item = item.replace(u'\n',u' ')
|
||||||
|
#html to text
|
||||||
|
try:
|
||||||
|
item = html2text(item)
|
||||||
|
except:
|
||||||
|
log.warn("Failed to convert comments to text")
|
||||||
|
bibtex_entry.append(u'note = "%s"' % bibtexdict.utf8ToBibtex(item))
|
||||||
|
|
||||||
|
elif field == 'isbn' :
|
||||||
|
# Could be 9, 10 or 13 digits
|
||||||
|
bibtex_entry.append(u'isbn = "%s"' % re.sub(u'[0-9xX]', u'', item))
|
||||||
|
|
||||||
|
elif field == 'formats' :
|
||||||
|
#Add file path if format is selected
|
||||||
|
formats = [format.rpartition('.')[2].lower() for format in item]
|
||||||
|
bibtex_entry.append(u'formats = "%s"' % u', '.join(formats))
|
||||||
|
if calibre_files:
|
||||||
|
files = [u':%s:%s' % (format, format.rpartition('.')[2].upper())\
|
||||||
|
for format in item]
|
||||||
|
bibtex_entry.append(u'file = "%s"' % u', '.join(files))
|
||||||
|
|
||||||
|
elif field == 'series_index' :
|
||||||
|
bibtex_entry.append(u'volume = "%s"' % int(item))
|
||||||
|
|
||||||
|
elif field == 'timestamp' :
|
||||||
|
bibtex_entry.append(u'timestamp = "%s"' % isoformat(item).partition('T')[0])
|
||||||
|
|
||||||
|
elif field == 'pubdate' :
|
||||||
|
bibtex_entry.append(u'year = "%s"' % item.year)
|
||||||
|
bibtex_entry.append(u'month = "%s"' % bibtexdict.utf8ToBibtex(strftime("%b", item)))
|
||||||
|
|
||||||
|
elif field.startswith('#') and isinstance(item, basestring):
|
||||||
|
bibtex_entry.append(u'custom_%s = "%s"' % (field[1:],
|
||||||
|
bibtexdict.utf8ToBibtex(item)))
|
||||||
|
|
||||||
|
elif isinstance(item, basestring):
|
||||||
|
# elif field in ['title', 'publisher', 'cover', 'uuid', 'ondevice',
|
||||||
|
# 'author_sort', 'series', 'title_sort'] :
|
||||||
|
bibtex_entry.append(u'%s = "%s"' % (field, bibtexdict.utf8ToBibtex(item)))
|
||||||
|
|
||||||
|
bibtex_entry = u',\n '.join(bibtex_entry)
|
||||||
|
bibtex_entry += u' }\n\n'
|
||||||
|
|
||||||
|
return bibtex_entry
|
||||||
|
|
||||||
|
def check_entry_book_valid(entry):
|
||||||
|
#Check that the required fields are ok for a book entry
|
||||||
|
for field in ['title', 'authors', 'publisher'] :
|
||||||
|
if entry[field] is None or len(entry[field]) == 0 :
|
||||||
|
return False
|
||||||
|
if entry['pubdate'] is None :
|
||||||
|
return False
|
||||||
|
else :
|
||||||
|
return True
|
||||||
|
|
||||||
|
def make_bibtex_citation(entry, template_citation, bibtexclass):
|
||||||
|
|
||||||
|
#define a function to replace the template entry by its value
|
||||||
|
def tpl_replace(objtplname) :
|
||||||
|
|
||||||
|
tpl_field = re.sub(u'[\{\}]', u'', objtplname.group())
|
||||||
|
|
||||||
|
if tpl_field in TEMPLATE_ALLOWED_FIELDS :
|
||||||
|
if tpl_field in ['pubdate', 'timestamp'] :
|
||||||
|
tpl_field = isoformat(entry[tpl_field]).partition('T')[0]
|
||||||
|
elif tpl_field in ['tags', 'authors'] :
|
||||||
|
tpl_field =entry[tpl_field][0]
|
||||||
|
elif tpl_field in ['id', 'series_index'] :
|
||||||
|
tpl_field = str(entry[tpl_field])
|
||||||
|
else :
|
||||||
|
tpl_field = entry[tpl_field]
|
||||||
|
return tpl_field
|
||||||
|
else:
|
||||||
|
return u''
|
||||||
|
|
||||||
|
if len(template_citation) >0 :
|
||||||
|
tpl_citation = bibtexclass.utf8ToBibtex(
|
||||||
|
bibtexclass.ValidateCitationKey(re.sub(u'\{[^{}]*\}',
|
||||||
|
tpl_replace, template_citation)))
|
||||||
|
|
||||||
|
if len(tpl_citation) >0 :
|
||||||
|
return tpl_citation
|
||||||
|
|
||||||
|
if len(entry["isbn"]) > 0 :
|
||||||
|
template_citation = u'%s' % re.sub(u'[\D]',u'', entry["isbn"])
|
||||||
|
|
||||||
|
else :
|
||||||
|
template_citation = u'%s' % str(entry["id"])
|
||||||
|
|
||||||
|
return bibtexclass.ValidateCitationKey(template_citation)
|
||||||
|
|
||||||
|
self.fmt = path_to_output.rpartition('.')[2]
|
||||||
|
self.notification = notification
|
||||||
|
|
||||||
|
# Combobox options
|
||||||
|
bibfile_enc = ['utf8', 'cp1252', 'ascii']
|
||||||
|
bibfile_enctag = ['strict', 'replace', 'ignore', 'backslashreplace']
|
||||||
|
bib_entry = ['mixed', 'misc', 'book']
|
||||||
|
|
||||||
|
# Needed beacause CLI return str vs int by widget
|
||||||
|
try:
|
||||||
|
bibfile_enc = bibfile_enc[opts.bibfile_enc]
|
||||||
|
bibfile_enctag = bibfile_enctag[opts.bibfile_enctag]
|
||||||
|
bib_entry = bib_entry[opts.bib_entry]
|
||||||
|
except:
|
||||||
|
if opts.bibfile_enc in bibfile_enc :
|
||||||
|
bibfile_enc = opts.bibfile_enc
|
||||||
|
else :
|
||||||
|
log.warn("Incorrect --choose-encoding flag, revert to default")
|
||||||
|
bibfile_enc = bibfile_enc[0]
|
||||||
|
if opts.bibfile_enctag in bibfile_enctag :
|
||||||
|
bibfile_enctag = opts.bibfile_enctag
|
||||||
|
else :
|
||||||
|
log.warn("Incorrect --choose-encoding-configuration flag, revert to default")
|
||||||
|
bibfile_enctag = bibfile_enctag[0]
|
||||||
|
if opts.bib_entry in bib_entry :
|
||||||
|
bib_entry = opts.bib_entry
|
||||||
|
else :
|
||||||
|
log.warn("Incorrect --entry-type flag, revert to default")
|
||||||
|
bib_entry = bib_entry[0]
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
opts_dict = vars(opts)
|
||||||
|
log("%s(): Generating %s" % (self.name,self.fmt))
|
||||||
|
if opts.connected_device['is_device_connected']:
|
||||||
|
log(" connected_device: %s" % opts.connected_device['name'])
|
||||||
|
if opts_dict['search_text']:
|
||||||
|
log(" --search='%s'" % opts_dict['search_text'])
|
||||||
|
|
||||||
|
if opts_dict['ids']:
|
||||||
|
log(" Book count: %d" % len(opts_dict['ids']))
|
||||||
|
if opts_dict['search_text']:
|
||||||
|
log(" (--search ignored when a subset of the database is specified)")
|
||||||
|
|
||||||
|
if opts_dict['fields']:
|
||||||
|
if opts_dict['fields'] == 'all':
|
||||||
|
log(" Fields: %s" % ', '.join(FIELDS[1:]))
|
||||||
|
else:
|
||||||
|
log(" Fields: %s" % opts_dict['fields'])
|
||||||
|
|
||||||
|
log(" Output file will be encoded in %s with %s flag" % (bibfile_enc, bibfile_enctag))
|
||||||
|
|
||||||
|
log(" BibTeX entry type is %s with a citation like '%s' flag" % (bib_entry, opts_dict['bib_cit']))
|
||||||
|
|
||||||
|
# If a list of ids are provided, don't use search_text
|
||||||
|
if opts.ids:
|
||||||
|
opts.search_text = None
|
||||||
|
|
||||||
|
data = self.search_sort_db(db, opts)
|
||||||
|
|
||||||
|
if not len(data):
|
||||||
|
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
|
||||||
|
|
||||||
|
# Get the requested output fields as a list
|
||||||
|
fields = self.get_output_fields(db, opts)
|
||||||
|
|
||||||
|
if not len(data):
|
||||||
|
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
|
||||||
|
|
||||||
|
#Initialize BibTeX class
|
||||||
|
bibtexc = BibTeX()
|
||||||
|
|
||||||
|
#Entries writing after Bibtex formating (or not)
|
||||||
|
if bibfile_enc != 'ascii' :
|
||||||
|
bibtexc.ascii_bibtex = False
|
||||||
|
else :
|
||||||
|
bibtexc.ascii_bibtex = True
|
||||||
|
|
||||||
|
#Check citation choice and go to default in case of bad CLI
|
||||||
|
if isinstance(opts.impcit, (StringType, UnicodeType)) :
|
||||||
|
if opts.impcit == 'False' :
|
||||||
|
citation_bibtex= False
|
||||||
|
elif opts.impcit == 'True' :
|
||||||
|
citation_bibtex= True
|
||||||
|
else :
|
||||||
|
log.warn("Incorrect --create-citation, revert to default")
|
||||||
|
citation_bibtex= True
|
||||||
|
else :
|
||||||
|
citation_bibtex= opts.impcit
|
||||||
|
|
||||||
|
#Check add file entry and go to default in case of bad CLI
|
||||||
|
if isinstance(opts.addfiles, (StringType, UnicodeType)) :
|
||||||
|
if opts.addfiles == 'False' :
|
||||||
|
addfiles_bibtex = False
|
||||||
|
elif opts.addfiles == 'True' :
|
||||||
|
addfiles_bibtex = True
|
||||||
|
else :
|
||||||
|
log.warn("Incorrect --add-files-path, revert to default")
|
||||||
|
addfiles_bibtex= True
|
||||||
|
else :
|
||||||
|
addfiles_bibtex = opts.addfiles
|
||||||
|
|
||||||
|
#Preprocess for error and light correction
|
||||||
|
template_citation = preprocess_template(opts.bib_cit)
|
||||||
|
|
||||||
|
#Open output and write entries
|
||||||
|
with codecs.open(path_to_output, 'w', bibfile_enc, bibfile_enctag)\
|
||||||
|
as outfile:
|
||||||
|
#File header
|
||||||
|
nb_entries = len(data)
|
||||||
|
|
||||||
|
#check in book strict if all is ok else throw a warning into log
|
||||||
|
if bib_entry == 'book' :
|
||||||
|
nb_books = len(filter(check_entry_book_valid, data))
|
||||||
|
if nb_books < nb_entries :
|
||||||
|
log.warn("Only %d entries in %d are book compatible" % (nb_books, nb_entries))
|
||||||
|
nb_entries = nb_books
|
||||||
|
|
||||||
|
# If connected device, add 'On Device' values to data
|
||||||
|
if opts.connected_device['is_device_connected'] and 'ondevice' in fields:
|
||||||
|
for entry in data:
|
||||||
|
entry['ondevice'] = db.catalog_plugin_on_device_temp_mapping[entry['id']]['ondevice']
|
||||||
|
|
||||||
|
outfile.write(u'%%%Calibre catalog\n%%%{0} entries in catalog\n\n'.format(nb_entries))
|
||||||
|
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)))
|
||||||
|
|
||||||
|
for entry in data:
|
||||||
|
outfile.write(create_bibtex_entry(entry, fields, bib_entry, template_citation,
|
||||||
|
bibtexc, db, citation_bibtex, addfiles_bibtex))
|
||||||
|
|
214
src/calibre/library/catalogs/csv_xml.py
Normal file
214
src/calibre/library/catalogs/csv_xml.py
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re, codecs, os
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from calibre.customize import CatalogPlugin
|
||||||
|
from calibre.library.catalogs import FIELDS
|
||||||
|
from calibre.utils.logging import default_log as log
|
||||||
|
from calibre.customize.conversion import DummyReporter
|
||||||
|
|
||||||
|
class CSV_XML(CatalogPlugin):
|
||||||
|
'CSV/XML catalog generator'
|
||||||
|
|
||||||
|
Option = namedtuple('Option', 'option, default, dest, action, help')
|
||||||
|
|
||||||
|
name = 'Catalog_CSV_XML'
|
||||||
|
description = 'CSV/XML catalog generator'
|
||||||
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
author = 'Greg Riker'
|
||||||
|
version = (1, 0, 0)
|
||||||
|
file_types = set(['csv','xml'])
|
||||||
|
|
||||||
|
cli_options = [
|
||||||
|
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.\n'
|
||||||
|
'Available fields: %(fields)s,\n'
|
||||||
|
'plus user-created custom fields.\n'
|
||||||
|
'Example: %(opt)s=title,authors,tags\n'
|
||||||
|
"Default: '%%default'\n"
|
||||||
|
"Applies to: CSV, XML output formats")%dict(
|
||||||
|
fields=', '.join(FIELDS), opt='--fields')),
|
||||||
|
|
||||||
|
Option('--sort-by',
|
||||||
|
default = 'id',
|
||||||
|
dest = 'sort_by',
|
||||||
|
action = None,
|
||||||
|
help = _('Output field to sort on.\n'
|
||||||
|
'Available fields: author_sort, id, rating, size, timestamp, title_sort\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: CSV, XML output formats"))]
|
||||||
|
|
||||||
|
def run(self, path_to_output, opts, db, notification=DummyReporter()):
|
||||||
|
from calibre.utils.date import isoformat
|
||||||
|
from calibre.utils.html2text import html2text
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
self.fmt = path_to_output.rpartition('.')[2]
|
||||||
|
self.notification = notification
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
opts_dict = vars(opts)
|
||||||
|
log("%s(): Generating %s" % (self.name,self.fmt.upper()))
|
||||||
|
if opts.connected_device['is_device_connected']:
|
||||||
|
log(" connected_device: %s" % opts.connected_device['name'])
|
||||||
|
if opts_dict['search_text']:
|
||||||
|
log(" --search='%s'" % opts_dict['search_text'])
|
||||||
|
|
||||||
|
if opts_dict['ids']:
|
||||||
|
log(" Book count: %d" % len(opts_dict['ids']))
|
||||||
|
if opts_dict['search_text']:
|
||||||
|
log(" (--search ignored when a subset of the database is specified)")
|
||||||
|
|
||||||
|
if opts_dict['fields']:
|
||||||
|
if opts_dict['fields'] == 'all':
|
||||||
|
log(" Fields: %s" % ', '.join(FIELDS[1:]))
|
||||||
|
else:
|
||||||
|
log(" Fields: %s" % opts_dict['fields'])
|
||||||
|
|
||||||
|
# If a list of ids are provided, don't use search_text
|
||||||
|
if opts.ids:
|
||||||
|
opts.search_text = None
|
||||||
|
|
||||||
|
data = self.search_sort_db(db, opts)
|
||||||
|
|
||||||
|
if not len(data):
|
||||||
|
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
|
||||||
|
#raise SystemExit(1)
|
||||||
|
|
||||||
|
# Get the requested output fields as a list
|
||||||
|
fields = self.get_output_fields(db, opts)
|
||||||
|
|
||||||
|
# If connected device, add 'On Device' values to data
|
||||||
|
if opts.connected_device['is_device_connected'] and 'ondevice' in fields:
|
||||||
|
for entry in data:
|
||||||
|
entry['ondevice'] = db.catalog_plugin_on_device_temp_mapping[entry['id']]['ondevice']
|
||||||
|
|
||||||
|
if self.fmt == 'csv':
|
||||||
|
outfile = codecs.open(path_to_output, 'w', 'utf8')
|
||||||
|
|
||||||
|
# Write a UTF-8 BOM
|
||||||
|
outfile.write('\xef\xbb\xbf')
|
||||||
|
|
||||||
|
# Output the field headers
|
||||||
|
outfile.write(u'%s\n' % u','.join(fields))
|
||||||
|
|
||||||
|
# Output the entry fields
|
||||||
|
for entry in data:
|
||||||
|
outstr = []
|
||||||
|
for field in fields:
|
||||||
|
if field.startswith('#'):
|
||||||
|
item = db.get_field(entry['id'],field,index_is_id=True)
|
||||||
|
elif field == 'title_sort':
|
||||||
|
item = entry['sort']
|
||||||
|
else:
|
||||||
|
item = entry[field]
|
||||||
|
|
||||||
|
if item is None:
|
||||||
|
outstr.append('""')
|
||||||
|
continue
|
||||||
|
elif field == 'formats':
|
||||||
|
fmt_list = []
|
||||||
|
for format in item:
|
||||||
|
fmt_list.append(format.rpartition('.')[2].lower())
|
||||||
|
item = ', '.join(fmt_list)
|
||||||
|
elif field in ['authors','tags']:
|
||||||
|
item = ', '.join(item)
|
||||||
|
elif field == 'isbn':
|
||||||
|
# Could be 9, 10 or 13 digits
|
||||||
|
item = u'%s' % re.sub(r'[\D]', '', item)
|
||||||
|
elif field in ['pubdate', 'timestamp']:
|
||||||
|
item = isoformat(item)
|
||||||
|
elif field == 'comments':
|
||||||
|
item = item.replace(u'\r\n',u' ')
|
||||||
|
item = item.replace(u'\n',u' ')
|
||||||
|
|
||||||
|
# Convert HTML to markdown text
|
||||||
|
if type(item) is unicode:
|
||||||
|
opening_tag = re.search('<(\w+)(\x20|>)',item)
|
||||||
|
if opening_tag:
|
||||||
|
closing_tag = re.search('<\/%s>$' % opening_tag.group(1), item)
|
||||||
|
if closing_tag:
|
||||||
|
item = html2text(item)
|
||||||
|
|
||||||
|
outstr.append(u'"%s"' % unicode(item).replace('"','""'))
|
||||||
|
|
||||||
|
outfile.write(u','.join(outstr) + u'\n')
|
||||||
|
outfile.close()
|
||||||
|
|
||||||
|
elif self.fmt == 'xml':
|
||||||
|
from lxml.builder import E
|
||||||
|
|
||||||
|
root = E.calibredb()
|
||||||
|
for r in data:
|
||||||
|
record = E.record()
|
||||||
|
root.append(record)
|
||||||
|
|
||||||
|
for field in fields:
|
||||||
|
if field.startswith('#'):
|
||||||
|
val = db.get_field(r['id'],field,index_is_id=True)
|
||||||
|
if not isinstance(val, (str, unicode)):
|
||||||
|
val = unicode(val)
|
||||||
|
item = getattr(E, field.replace('#','_'))(val)
|
||||||
|
record.append(item)
|
||||||
|
|
||||||
|
for field in ('id', 'uuid', 'publisher', 'rating', 'size',
|
||||||
|
'isbn','ondevice'):
|
||||||
|
if field in fields:
|
||||||
|
val = r[field]
|
||||||
|
if not val:
|
||||||
|
continue
|
||||||
|
if not isinstance(val, (str, unicode)):
|
||||||
|
val = unicode(val)
|
||||||
|
item = getattr(E, field)(val)
|
||||||
|
record.append(item)
|
||||||
|
|
||||||
|
if 'title' in fields:
|
||||||
|
title = E.title(r['title'], sort=r['sort'])
|
||||||
|
record.append(title)
|
||||||
|
|
||||||
|
if 'authors' in fields:
|
||||||
|
aus = E.authors(sort=r['author_sort'])
|
||||||
|
for au in r['authors']:
|
||||||
|
aus.append(E.author(au))
|
||||||
|
record.append(aus)
|
||||||
|
|
||||||
|
for field in ('timestamp', 'pubdate'):
|
||||||
|
if field in fields:
|
||||||
|
record.append(getattr(E, field)(r[field].isoformat()))
|
||||||
|
|
||||||
|
if 'tags' in fields and r['tags']:
|
||||||
|
tags = E.tags()
|
||||||
|
for tag in r['tags']:
|
||||||
|
tags.append(E.tag(tag))
|
||||||
|
record.append(tags)
|
||||||
|
|
||||||
|
if 'comments' in fields and r['comments']:
|
||||||
|
record.append(E.comments(r['comments']))
|
||||||
|
|
||||||
|
if 'series' in fields and r['series']:
|
||||||
|
record.append(E.series(r['series'],
|
||||||
|
index=str(r['series_index'])))
|
||||||
|
|
||||||
|
if 'cover' in fields and r['cover']:
|
||||||
|
record.append(E.cover(r['cover'].replace(os.sep, '/')))
|
||||||
|
|
||||||
|
if 'formats' in fields and r['formats']:
|
||||||
|
fmt = E.formats()
|
||||||
|
for f in r['formats']:
|
||||||
|
fmt.append(E.format(f.replace(os.sep, '/')))
|
||||||
|
record.append(fmt)
|
||||||
|
|
||||||
|
with open(path_to_output, 'w') as f:
|
||||||
|
f.write(etree.tostring(root, encoding='utf-8',
|
||||||
|
xml_declaration=True, pretty_print=True))
|
||||||
|
|
365
src/calibre/library/catalogs/epub_mobi.py
Normal file
365
src/calibre/library/catalogs/epub_mobi.py
Normal file
@ -0,0 +1,365 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
from __future__ import (unicode_literals, division, absolute_import,
|
||||||
|
print_function)
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2012, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import os
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
from calibre import strftime
|
||||||
|
from calibre.constants import DEBUG
|
||||||
|
from calibre.customize import CatalogPlugin
|
||||||
|
from calibre.customize.conversion import OptionRecommendation, DummyReporter
|
||||||
|
from calibre.utils.logging import default_log as log
|
||||||
|
|
||||||
|
Option = namedtuple('Option', 'option, default, dest, action, help')
|
||||||
|
|
||||||
|
class EPUB_MOBI(CatalogPlugin):
|
||||||
|
'ePub catalog generator'
|
||||||
|
|
||||||
|
name = 'Catalog_EPUB_MOBI'
|
||||||
|
description = 'EPUB/MOBI catalog generator'
|
||||||
|
supported_platforms = ['windows', 'osx', 'linux']
|
||||||
|
minimum_calibre_version = (0, 7, 40)
|
||||||
|
author = 'Greg Riker'
|
||||||
|
version = (1, 0, 0)
|
||||||
|
file_types = set(['epub','mobi'])
|
||||||
|
|
||||||
|
THUMB_SMALLEST = "1.0"
|
||||||
|
THUMB_LARGEST = "2.0"
|
||||||
|
|
||||||
|
cli_options = [Option('--catalog-title', # {{{
|
||||||
|
default = 'My Books',
|
||||||
|
dest = 'catalog_title',
|
||||||
|
action = None,
|
||||||
|
help = _('Title of generated catalog used as title in metadata.\n'
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
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.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--exclude-book-marker',
|
||||||
|
default=':',
|
||||||
|
dest='exclude_book_marker',
|
||||||
|
action = None,
|
||||||
|
help=_("field:pattern specifying custom field/contents indicating book should be excluded.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to ePub, MOBI output formats")),
|
||||||
|
Option('--exclude-genre',
|
||||||
|
default='\[.+\]',
|
||||||
|
dest='exclude_genre',
|
||||||
|
action = None,
|
||||||
|
help=_("Regex describing tags to exclude as genres.\n" "Default: '%default' excludes bracketed tags, e.g. '[<tag>]'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--exclude-tags',
|
||||||
|
default=('~,'+_('Catalog')),
|
||||||
|
dest='exclude_tags',
|
||||||
|
action = None,
|
||||||
|
help=_("Comma-separated list of tag words indicating book should be excluded from output. "
|
||||||
|
"For example: 'skip' will match 'skip this book' and 'Skip will like this'. "
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--generate-authors',
|
||||||
|
default=False,
|
||||||
|
dest='generate_authors',
|
||||||
|
action = 'store_true',
|
||||||
|
help=_("Include 'Authors' section in catalog.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--generate-descriptions',
|
||||||
|
default=False,
|
||||||
|
dest='generate_descriptions',
|
||||||
|
action = 'store_true',
|
||||||
|
help=_("Include 'Descriptions' section in catalog.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--generate-genres',
|
||||||
|
default=False,
|
||||||
|
dest='generate_genres',
|
||||||
|
action = 'store_true',
|
||||||
|
help=_("Include 'Genres' section in catalog.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--generate-titles',
|
||||||
|
default=False,
|
||||||
|
dest='generate_titles',
|
||||||
|
action = 'store_true',
|
||||||
|
help=_("Include 'Titles' section in catalog.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--generate-series',
|
||||||
|
default=False,
|
||||||
|
dest='generate_series',
|
||||||
|
action = 'store_true',
|
||||||
|
help=_("Include 'Series' section in catalog.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--generate-recently-added',
|
||||||
|
default=False,
|
||||||
|
dest='generate_recently_added',
|
||||||
|
action = 'store_true',
|
||||||
|
help=_("Include 'Recently Added' section in catalog.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--header-note-source-field',
|
||||||
|
default='',
|
||||||
|
dest='header_note_source_field',
|
||||||
|
action = None,
|
||||||
|
help=_("Custom field containing note text to insert in Description header.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--merge-comments',
|
||||||
|
default='::',
|
||||||
|
dest='merge_comments',
|
||||||
|
action = None,
|
||||||
|
help=_("<custom field>:[before|after]:[True|False] specifying:\n"
|
||||||
|
" <custom field> Custom field containing notes to merge with Comments\n"
|
||||||
|
" [before|after] Placement of notes with respect to Comments\n"
|
||||||
|
" [True|False] - A horizontal rule is inserted between notes and Comments\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to ePub, MOBI output formats")),
|
||||||
|
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.\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
Option('--read-book-marker',
|
||||||
|
default='tag:+',
|
||||||
|
dest='read_book_marker',
|
||||||
|
action = None,
|
||||||
|
help=_("field:pattern indicating book has been read.\n" "Default: '%default'\n"
|
||||||
|
"Applies to ePub, MOBI output formats")),
|
||||||
|
Option('--thumb-width',
|
||||||
|
default='1.0',
|
||||||
|
dest='thumb_width',
|
||||||
|
action = None,
|
||||||
|
help=_("Size hint (in inches) for book covers in catalog.\n"
|
||||||
|
"Range: 1.0 - 2.0\n"
|
||||||
|
"Default: '%default'\n"
|
||||||
|
"Applies to ePub, MOBI output formats")),
|
||||||
|
Option('--wishlist-tag',
|
||||||
|
default='Wishlist',
|
||||||
|
dest='wishlist_tag',
|
||||||
|
action = None,
|
||||||
|
help=_("Tag indicating book to be displayed as wishlist item.\n" "Default: '%default'\n"
|
||||||
|
"Applies to: ePub, MOBI output formats")),
|
||||||
|
]
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
def run(self, path_to_output, opts, db, notification=DummyReporter()):
|
||||||
|
from calibre.library.catalogs.epub_mobi_builder import CatalogBuilder
|
||||||
|
opts.log = log
|
||||||
|
opts.fmt = self.fmt = path_to_output.rpartition('.')[2]
|
||||||
|
|
||||||
|
# Add local options
|
||||||
|
opts.creator = '%s, %s %s, %s' % (strftime('%A'), strftime('%B'), strftime('%d').lstrip('0'), strftime('%Y'))
|
||||||
|
opts.creator_sort_as = '%s %s' % ('calibre', strftime('%Y-%m-%d'))
|
||||||
|
opts.connected_kindle = False
|
||||||
|
|
||||||
|
# Finalize output_profile
|
||||||
|
op = opts.output_profile
|
||||||
|
if op is None:
|
||||||
|
op = 'default'
|
||||||
|
|
||||||
|
if opts.connected_device['name'] and 'kindle' in opts.connected_device['name'].lower():
|
||||||
|
opts.connected_kindle = True
|
||||||
|
if opts.connected_device['serial'] and \
|
||||||
|
opts.connected_device['serial'][:4] in ['B004','B005']:
|
||||||
|
op = "kindle_dx"
|
||||||
|
else:
|
||||||
|
op = "kindle"
|
||||||
|
opts.descriptionClip = 380 if op.endswith('dx') or 'kindle' not in op else 100
|
||||||
|
opts.authorClip = 100 if op.endswith('dx') or 'kindle' not in op else 60
|
||||||
|
opts.output_profile = op
|
||||||
|
|
||||||
|
opts.basename = "Catalog"
|
||||||
|
opts.cli_environment = not hasattr(opts,'sync')
|
||||||
|
|
||||||
|
# Hard-wired to always sort descriptions by author, with series after non-series
|
||||||
|
opts.sort_descriptions_by_author = True
|
||||||
|
|
||||||
|
build_log = []
|
||||||
|
|
||||||
|
build_log.append(u"%s(): Generating %s %sin %s environment" %
|
||||||
|
(self.name,self.fmt,'for %s ' % opts.output_profile if opts.output_profile else '',
|
||||||
|
'CLI' if opts.cli_environment else 'GUI'))
|
||||||
|
|
||||||
|
# If exclude_genre is blank, assume user wants all genre tags included
|
||||||
|
if opts.exclude_genre.strip() == '':
|
||||||
|
opts.exclude_genre = '\[^.\]'
|
||||||
|
build_log.append(" converting empty exclude_genre to '\[^.\]'")
|
||||||
|
|
||||||
|
if opts.connected_device['is_device_connected'] and \
|
||||||
|
opts.connected_device['kind'] == 'device':
|
||||||
|
if opts.connected_device['serial']:
|
||||||
|
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)))
|
||||||
|
for storage in opts.connected_device['storage']:
|
||||||
|
if storage:
|
||||||
|
build_log.append(u" mount point: %s" % storage)
|
||||||
|
else:
|
||||||
|
build_log.append(u" connected_device: '%s'" % opts.connected_device['name'])
|
||||||
|
try:
|
||||||
|
for storage in opts.connected_device['storage']:
|
||||||
|
if storage:
|
||||||
|
build_log.append(u" mount point: %s" % storage)
|
||||||
|
except:
|
||||||
|
build_log.append(u" (no mount points)")
|
||||||
|
else:
|
||||||
|
build_log.append(u" connected_device: '%s'" % opts.connected_device['name'])
|
||||||
|
|
||||||
|
opts_dict = vars(opts)
|
||||||
|
if opts_dict['ids']:
|
||||||
|
build_log.append(" book count: %d" % len(opts_dict['ids']))
|
||||||
|
|
||||||
|
sections_list = []
|
||||||
|
if opts.generate_authors:
|
||||||
|
sections_list.append('Authors')
|
||||||
|
if opts.generate_titles:
|
||||||
|
sections_list.append('Titles')
|
||||||
|
if opts.generate_series:
|
||||||
|
sections_list.append('Series')
|
||||||
|
if opts.generate_genres:
|
||||||
|
sections_list.append('Genres')
|
||||||
|
if opts.generate_recently_added:
|
||||||
|
sections_list.append('Recently Added')
|
||||||
|
if opts.generate_descriptions:
|
||||||
|
sections_list.append('Descriptions')
|
||||||
|
|
||||||
|
if not sections_list:
|
||||||
|
if opts.cli_environment:
|
||||||
|
opts.log.warn('*** No Section switches specified, enabling all Sections ***')
|
||||||
|
opts.generate_authors = True
|
||||||
|
opts.generate_titles = True
|
||||||
|
opts.generate_series = True
|
||||||
|
opts.generate_genres = True
|
||||||
|
opts.generate_recently_added = True
|
||||||
|
opts.generate_descriptions = True
|
||||||
|
sections_list = ['Authors','Titles','Series','Genres','Recently Added','Descriptions']
|
||||||
|
else:
|
||||||
|
opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***')
|
||||||
|
return ["No Included Sections","No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"]
|
||||||
|
if opts.fmt == 'mobi' and sections_list == ['Descriptions']:
|
||||||
|
warning = _("\n*** Adding 'By Authors' Section required for MOBI output ***")
|
||||||
|
opts.log.warn(warning)
|
||||||
|
sections_list.insert(0,'Authors')
|
||||||
|
opts.generate_authors = True
|
||||||
|
|
||||||
|
opts.log(u" Sections: %s" % ', '.join(sections_list))
|
||||||
|
opts.section_list = sections_list
|
||||||
|
|
||||||
|
# Limit thumb_width to 1.0" - 2.0"
|
||||||
|
try:
|
||||||
|
if float(opts.thumb_width) < float(self.THUMB_SMALLEST):
|
||||||
|
log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST))
|
||||||
|
opts.thumb_width = self.THUMB_SMALLEST
|
||||||
|
if float(opts.thumb_width) > float(self.THUMB_LARGEST):
|
||||||
|
log.warning("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_LARGEST))
|
||||||
|
opts.thumb_width = self.THUMB_LARGEST
|
||||||
|
opts.thumb_width = "%.2f" % float(opts.thumb_width)
|
||||||
|
except:
|
||||||
|
log.error("coercing thumb_width from '%s' to '%s'" % (opts.thumb_width,self.THUMB_SMALLEST))
|
||||||
|
opts.thumb_width = "1.0"
|
||||||
|
|
||||||
|
# Display opts
|
||||||
|
keys = opts_dict.keys()
|
||||||
|
keys.sort()
|
||||||
|
build_log.append(" opts:")
|
||||||
|
for key in keys:
|
||||||
|
if key in ['catalog_title','authorClip','connected_kindle','descriptionClip',
|
||||||
|
'exclude_book_marker','exclude_genre','exclude_tags',
|
||||||
|
'header_note_source_field','merge_comments',
|
||||||
|
'output_profile','read_book_marker',
|
||||||
|
'search_text','sort_by','sort_descriptions_by_author','sync',
|
||||||
|
'thumb_width','wishlist_tag']:
|
||||||
|
build_log.append(" %s: %s" % (key, repr(opts_dict[key])))
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
log('\n'.join(line for line in build_log))
|
||||||
|
|
||||||
|
self.opts = opts
|
||||||
|
|
||||||
|
# Launch the Catalog builder
|
||||||
|
catalog = CatalogBuilder(db, opts, self, report_progress=notification)
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
log.info(" Begin catalog source generation")
|
||||||
|
catalog.createDirectoryStructure()
|
||||||
|
catalog.copyResources()
|
||||||
|
catalog.calculateThumbnailSize()
|
||||||
|
catalog_source_built = catalog.buildSources()
|
||||||
|
|
||||||
|
if opts.verbose:
|
||||||
|
if catalog_source_built:
|
||||||
|
log.info(" Completed catalog source generation\n")
|
||||||
|
else:
|
||||||
|
log.error(" *** Terminated catalog generation, check log for details ***")
|
||||||
|
|
||||||
|
if catalog_source_built:
|
||||||
|
recommendations = []
|
||||||
|
recommendations.append(('remove_fake_margins', False,
|
||||||
|
OptionRecommendation.HIGH))
|
||||||
|
if DEBUG:
|
||||||
|
recommendations.append(('comments', '\n'.join(line for line in build_log),
|
||||||
|
OptionRecommendation.HIGH))
|
||||||
|
else:
|
||||||
|
recommendations.append(('comments', '', OptionRecommendation.HIGH))
|
||||||
|
|
||||||
|
dp = getattr(opts, 'debug_pipeline', None)
|
||||||
|
if dp is not None:
|
||||||
|
recommendations.append(('debug_pipeline', dp,
|
||||||
|
OptionRecommendation.HIGH))
|
||||||
|
|
||||||
|
if opts.fmt == 'mobi' and opts.output_profile and opts.output_profile.startswith("kindle"):
|
||||||
|
recommendations.append(('output_profile', opts.output_profile,
|
||||||
|
OptionRecommendation.HIGH))
|
||||||
|
recommendations.append(('no_inline_toc', True,
|
||||||
|
OptionRecommendation.HIGH))
|
||||||
|
recommendations.append(('book_producer',opts.output_profile,
|
||||||
|
OptionRecommendation.HIGH))
|
||||||
|
|
||||||
|
# If cover exists, use it
|
||||||
|
cpath = None
|
||||||
|
try:
|
||||||
|
search_text = 'title:"%s" author:%s' % (
|
||||||
|
opts.catalog_title.replace('"', '\\"'), 'calibre')
|
||||||
|
matches = db.search(search_text, return_matches=True)
|
||||||
|
if matches:
|
||||||
|
cpath = db.cover(matches[0], index_is_id=True, as_path=True)
|
||||||
|
if cpath and os.path.exists(cpath):
|
||||||
|
recommendations.append(('cover', cpath,
|
||||||
|
OptionRecommendation.HIGH))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Run ebook-convert
|
||||||
|
from calibre.ebooks.conversion.plumber import Plumber
|
||||||
|
plumber = Plumber(os.path.join(catalog.catalogPath,
|
||||||
|
opts.basename + '.opf'), path_to_output, log, report_progress=notification,
|
||||||
|
abort_after_input_dump=False)
|
||||||
|
plumber.merge_ui_recommendations(recommendations)
|
||||||
|
plumber.run()
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.remove(cpath)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# returns to gui2.actions.catalog:catalog_generated()
|
||||||
|
return catalog.error
|
||||||
|
|
4040
src/calibre/library/catalogs/epub_mobi_builder.py
Normal file
4040
src/calibre/library/catalogs/epub_mobi_builder.py
Normal file
File diff suppressed because it is too large
Load Diff
217
src/calibre/library/catalogs/utils.py
Normal file
217
src/calibre/library/catalogs/utils.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||||
|
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2010, Greg Riker'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from calibre import prints
|
||||||
|
from calibre.utils.logging import default_log as log
|
||||||
|
|
||||||
|
class NumberToText(object): # {{{
|
||||||
|
'''
|
||||||
|
Converts numbers to text
|
||||||
|
4.56 => four point fifty-six
|
||||||
|
456 => four hundred fifty-six
|
||||||
|
4:56 => four fifty-six
|
||||||
|
'''
|
||||||
|
ORDINALS = ['zeroth','first','second','third','fourth','fifth','sixth','seventh','eighth','ninth']
|
||||||
|
lessThanTwenty = ["<zero>","one","two","three","four","five","six","seven","eight","nine",
|
||||||
|
"ten","eleven","twelve","thirteen","fourteen","fifteen","sixteen","seventeen",
|
||||||
|
"eighteen","nineteen"]
|
||||||
|
tens = ["<zero>","<tens>","twenty","thirty","forty","fifty","sixty","seventy","eighty","ninety"]
|
||||||
|
hundreds = ["<zero>","one","two","three","four","five","six","seven","eight","nine"]
|
||||||
|
|
||||||
|
def __init__(self, number, verbose=False):
|
||||||
|
self.number = number
|
||||||
|
self.number_as_float = 0.0
|
||||||
|
self.text = ''
|
||||||
|
self.verbose = verbose
|
||||||
|
self.log = log
|
||||||
|
self.numberTranslate()
|
||||||
|
|
||||||
|
def stringFromInt(self, intToTranslate):
|
||||||
|
# Convert intToTranslate to string
|
||||||
|
# intToTranslate is a three-digit number
|
||||||
|
|
||||||
|
tensComponentString = ""
|
||||||
|
hundredsComponent = intToTranslate - (intToTranslate % 100)
|
||||||
|
tensComponent = intToTranslate % 100
|
||||||
|
|
||||||
|
# Build the hundreds component
|
||||||
|
if hundredsComponent:
|
||||||
|
hundredsComponentString = "%s hundred" % self.hundreds[hundredsComponent/100]
|
||||||
|
else:
|
||||||
|
hundredsComponentString = ""
|
||||||
|
|
||||||
|
# Build the tens component
|
||||||
|
if tensComponent < 20:
|
||||||
|
tensComponentString = self.lessThanTwenty[tensComponent]
|
||||||
|
else:
|
||||||
|
tensPart = ""
|
||||||
|
onesPart = ""
|
||||||
|
|
||||||
|
# Get the tens part
|
||||||
|
tensPart = self.tens[tensComponent / 10]
|
||||||
|
onesPart = self.lessThanTwenty[tensComponent % 10]
|
||||||
|
|
||||||
|
if intToTranslate % 10:
|
||||||
|
tensComponentString = "%s-%s" % (tensPart, onesPart)
|
||||||
|
else:
|
||||||
|
tensComponentString = "%s" % tensPart
|
||||||
|
|
||||||
|
# Concatenate the results
|
||||||
|
result = ''
|
||||||
|
if hundredsComponent and not tensComponent:
|
||||||
|
result = hundredsComponentString
|
||||||
|
elif not hundredsComponent and tensComponent:
|
||||||
|
result = tensComponentString
|
||||||
|
elif hundredsComponent and tensComponent:
|
||||||
|
result = hundredsComponentString + " " + tensComponentString
|
||||||
|
else:
|
||||||
|
prints(" NumberToText.stringFromInt(): empty result translating %d" % intToTranslate)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def numberTranslate(self):
|
||||||
|
hundredsNumber = 0
|
||||||
|
thousandsNumber = 0
|
||||||
|
hundredsString = ""
|
||||||
|
thousandsString = ""
|
||||||
|
resultString = ""
|
||||||
|
self.suffix = ''
|
||||||
|
|
||||||
|
if self.verbose: self.log("numberTranslate(): %s" % self.number)
|
||||||
|
|
||||||
|
# Special case ordinals
|
||||||
|
if re.search('[st|nd|rd|th]',self.number):
|
||||||
|
self.number = re.sub(',','',self.number)
|
||||||
|
ordinal_suffix = re.search('[\D]', self.number)
|
||||||
|
ordinal_number = re.sub('\D','',re.sub(',','',self.number))
|
||||||
|
if self.verbose: self.log("Ordinal: %s" % ordinal_number)
|
||||||
|
self.number_as_float = ordinal_number
|
||||||
|
self.suffix = self.number[ordinal_suffix.start():]
|
||||||
|
if int(ordinal_number) > 9:
|
||||||
|
# Some typos (e.g., 'twentyth'), acceptable
|
||||||
|
self.text = '%s' % (NumberToText(ordinal_number).text)
|
||||||
|
else:
|
||||||
|
self.text = '%s' % (self.ORDINALS[int(ordinal_number)])
|
||||||
|
|
||||||
|
# Test for time
|
||||||
|
elif re.search(':',self.number):
|
||||||
|
if self.verbose: self.log("Time: %s" % self.number)
|
||||||
|
self.number_as_float = re.sub(':','.',self.number)
|
||||||
|
time_strings = self.number.split(":")
|
||||||
|
hours = NumberToText(time_strings[0]).text
|
||||||
|
minutes = NumberToText(time_strings[1]).text
|
||||||
|
self.text = '%s-%s' % (hours.capitalize(), minutes)
|
||||||
|
|
||||||
|
# Test for %
|
||||||
|
elif re.search('%', self.number):
|
||||||
|
if self.verbose: self.log("Percent: %s" % self.number)
|
||||||
|
self.number_as_float = self.number.split('%')[0]
|
||||||
|
self.text = NumberToText(self.number.replace('%',' percent')).text
|
||||||
|
|
||||||
|
# Test for decimal
|
||||||
|
elif re.search('\.',self.number):
|
||||||
|
if self.verbose: self.log("Decimal: %s" % self.number)
|
||||||
|
self.number_as_float = self.number
|
||||||
|
decimal_strings = self.number.split(".")
|
||||||
|
left = NumberToText(decimal_strings[0]).text
|
||||||
|
right = NumberToText(decimal_strings[1]).text
|
||||||
|
self.text = '%s point %s' % (left.capitalize(), right)
|
||||||
|
|
||||||
|
# Test for hypenated
|
||||||
|
elif re.search('-', self.number):
|
||||||
|
if self.verbose: self.log("Hyphenated: %s" % self.number)
|
||||||
|
self.number_as_float = self.number.split('-')[0]
|
||||||
|
strings = self.number.split('-')
|
||||||
|
if re.search('[0-9]+', strings[0]):
|
||||||
|
left = NumberToText(strings[0]).text
|
||||||
|
right = strings[1]
|
||||||
|
else:
|
||||||
|
left = strings[0]
|
||||||
|
right = NumberToText(strings[1]).text
|
||||||
|
self.text = '%s-%s' % (left, right)
|
||||||
|
|
||||||
|
# Test for only commas and numbers
|
||||||
|
elif re.search(',', self.number) and not re.search('[^0-9,]',self.number):
|
||||||
|
if self.verbose: self.log("Comma(s): %s" % self.number)
|
||||||
|
self.number_as_float = re.sub(',','',self.number)
|
||||||
|
self.text = NumberToText(self.number_as_float).text
|
||||||
|
|
||||||
|
# Test for hybrid e.g., 'K2, 2nd, 10@10'
|
||||||
|
elif re.search('[\D]+', self.number):
|
||||||
|
if self.verbose: self.log("Hybrid: %s" % self.number)
|
||||||
|
# Split the token into number/text
|
||||||
|
number_position = re.search('\d',self.number).start()
|
||||||
|
text_position = re.search('\D',self.number).start()
|
||||||
|
if number_position < text_position:
|
||||||
|
number = self.number[:text_position]
|
||||||
|
text = self.number[text_position:]
|
||||||
|
self.text = '%s%s' % (NumberToText(number).text,text)
|
||||||
|
else:
|
||||||
|
text = self.number[:number_position]
|
||||||
|
number = self.number[number_position:]
|
||||||
|
self.text = '%s%s' % (text, NumberToText(number).text)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if self.verbose: self.log("Clean: %s" % self.number)
|
||||||
|
try:
|
||||||
|
self.float_as_number = float(self.number)
|
||||||
|
number = int(self.number)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
if number > 10**9:
|
||||||
|
self.text = "%d out of range" % number
|
||||||
|
return
|
||||||
|
|
||||||
|
if number == 10**9:
|
||||||
|
self.text = "one billion"
|
||||||
|
else :
|
||||||
|
# Isolate the three-digit number groups
|
||||||
|
millionsNumber = number/10**6
|
||||||
|
thousandsNumber = (number - (millionsNumber * 10**6))/10**3
|
||||||
|
hundredsNumber = number - (millionsNumber * 10**6) - (thousandsNumber * 10**3)
|
||||||
|
if self.verbose:
|
||||||
|
print "Converting %s %s %s" % (millionsNumber, thousandsNumber, hundredsNumber)
|
||||||
|
|
||||||
|
# Convert hundredsNumber
|
||||||
|
if hundredsNumber :
|
||||||
|
hundredsString = self.stringFromInt(hundredsNumber)
|
||||||
|
|
||||||
|
# Convert thousandsNumber
|
||||||
|
if thousandsNumber:
|
||||||
|
if number > 1099 and number < 2000:
|
||||||
|
resultString = '%s %s' % (self.lessThanTwenty[number/100],
|
||||||
|
self.stringFromInt(number % 100))
|
||||||
|
self.text = resultString.strip().capitalize()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
thousandsString = self.stringFromInt(thousandsNumber)
|
||||||
|
|
||||||
|
# Convert millionsNumber
|
||||||
|
if millionsNumber:
|
||||||
|
millionsString = self.stringFromInt(millionsNumber)
|
||||||
|
|
||||||
|
# Concatenate the strings
|
||||||
|
resultString = ''
|
||||||
|
if millionsNumber:
|
||||||
|
resultString += "%s million " % millionsString
|
||||||
|
|
||||||
|
if thousandsNumber:
|
||||||
|
resultString += "%s thousand " % thousandsString
|
||||||
|
|
||||||
|
if hundredsNumber:
|
||||||
|
resultString += "%s" % hundredsString
|
||||||
|
|
||||||
|
if not millionsNumber and not thousandsNumber and not hundredsNumber:
|
||||||
|
resultString = "zero"
|
||||||
|
|
||||||
|
if self.verbose:
|
||||||
|
self.log(u'resultString: %s' % resultString)
|
||||||
|
self.text = resultString.strip().capitalize()
|
||||||
|
# }}}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user