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

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. from __future__ import with_statement
  5. __license__ = 'GPL v3'
  6. __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
  7. __docformat__ = 'restructuredtext en'
  8. import os
  9. import re
  10. import uuid
  11. import logging
  12. from mimetypes import types_map
  13. from collections import defaultdict
  14. from itertools import count
  15. from urlparse import urldefrag, urlparse, urlunparse
  16. from urllib import unquote as urlunquote
  17. from urlparse import urljoin
  18. from lxml import etree, html
  19. from cssutils import CSSParser
  20. from cssutils.css import CSSRule
  21. import calibre
  22. from calibre.constants import filesystem_encoding
  23. from calibre.translations.dynamic import translate
  24. from calibre.ebooks.chardet import xml_to_unicode
  25. from calibre.ebooks.oeb.entitydefs import ENTITYDEFS
  26. from calibre.ebooks.conversion.preprocess import CSSPreProcessor
  27. RECOVER_PARSER = etree.XMLParser(recover = True, no_network = True)
  28. XML_NS = 'http://www.w3.org/XML/1998/namespace'
  29. XHTML_NS = 'http://www.w3.org/1999/xhtml'
  30. OEB_DOC_NS = 'http://openebook.org/namespaces/oeb-document/1.0/'
  31. OPF1_NS = 'http://openebook.org/namespaces/oeb-package/1.0/'
  32. OPF2_NS = 'http://www.idpf.org/2007/opf'
  33. OPF_NSES = set([
  34.     OPF1_NS,
  35.     OPF2_NS])
  36. DC09_NS = 'http://purl.org/metadata/dublin_core'
  37. DC10_NS = 'http://purl.org/dc/elements/1.0/'
  38. DC11_NS = 'http://purl.org/dc/elements/1.1/'
  39. DC_NSES = set([
  40.     DC09_NS,
  41.     DC10_NS,
  42.     DC11_NS])
  43. XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance'
  44. DCTERMS_NS = 'http://purl.org/dc/terms/'
  45. NCX_NS = 'http://www.daisy.org/z3986/2005/ncx/'
  46. SVG_NS = 'http://www.w3.org/2000/svg'
  47. XLINK_NS = 'http://www.w3.org/1999/xlink'
  48. CALIBRE_NS = 'http://calibre.kovidgoyal.net/2009/metadata'
  49. RE_NS = 'http://exslt.org/regular-expressions'
  50. MBP_NS = 'http://www.mobipocket.com'
  51. XPNSMAP = {
  52.     'h': XHTML_NS,
  53.     'o1': OPF1_NS,
  54.     'o2': OPF2_NS,
  55.     'd09': DC09_NS,
  56.     'd10': DC10_NS,
  57.     'd11': DC11_NS,
  58.     'xsi': XSI_NS,
  59.     'dt': DCTERMS_NS,
  60.     'ncx': NCX_NS,
  61.     'svg': SVG_NS,
  62.     'xl': XLINK_NS,
  63.     're': RE_NS,
  64.     'mbp': MBP_NS,
  65.     'calibre': CALIBRE_NS }
  66. OPF1_NSMAP = {
  67.     'dc': DC11_NS,
  68.     'oebpackage': OPF1_NS }
  69. OPF2_NSMAP = {
  70.     'opf': OPF2_NS,
  71.     'dc': DC11_NS,
  72.     'dcterms': DCTERMS_NS,
  73.     'xsi': XSI_NS,
  74.     'calibre': CALIBRE_NS }
  75.  
  76. def XML(name):
  77.     return '{%s}%s' % (XML_NS, name)
  78.  
  79.  
  80. def XHTML(name):
  81.     return '{%s}%s' % (XHTML_NS, name)
  82.  
  83.  
  84. def OPF(name):
  85.     return '{%s}%s' % (OPF2_NS, name)
  86.  
  87.  
  88. def DC(name):
  89.     return '{%s}%s' % (DC11_NS, name)
  90.  
  91.  
  92. def XSI(name):
  93.     return '{%s}%s' % (XSI_NS, name)
  94.  
  95.  
  96. def DCTERMS(name):
  97.     return '{%s}%s' % (DCTERMS_NS, name)
  98.  
  99.  
  100. def NCX(name):
  101.     return '{%s}%s' % (NCX_NS, name)
  102.  
  103.  
  104. def SVG(name):
  105.     return '{%s}%s' % (SVG_NS, name)
  106.  
  107.  
  108. def XLINK(name):
  109.     return '{%s}%s' % (XLINK_NS, name)
  110.  
  111.  
  112. def CALIBRE(name):
  113.     return '{%s}%s' % (CALIBRE_NS, name)
  114.  
  115. _css_url_re = re.compile('url\\((.*?)\\)', re.I)
  116. _css_import_re = re.compile('@import "(.*?)"')
  117. _archive_re = re.compile('[^ ]+')
  118.  
  119. def iterlinks(root):
  120.     link_attrs = set(html.defs.link_attrs)
  121.     link_attrs.add(XLINK('href'))
  122.     for el in root.iter():
  123.         attribs = el.attrib
  124.         
  125.         try:
  126.             tag = el.tag
  127.         except UnicodeDecodeError:
  128.             continue
  129.  
  130.         if tag == XHTML('object'):
  131.             codebase = None
  132.             if 'codebase' in attribs:
  133.                 codebase = el.get('codebase')
  134.                 yield (el, 'codebase', codebase, 0)
  135.             
  136.             for attrib in ('classid', 'data'):
  137.                 if attrib in attribs:
  138.                     value = el.get(attrib)
  139.                     if codebase is not None:
  140.                         value = urljoin(codebase, value)
  141.                     
  142.                     yield (el, attrib, value, 0)
  143.                     continue
  144.             
  145.             if 'archive' in attribs:
  146.                 for match in _archive_re.finditer(el.get('archive')):
  147.                     value = match.group(0)
  148.                     if codebase is not None:
  149.                         value = urljoin(codebase, value)
  150.                     
  151.                     yield (el, 'archive', value, match.start())
  152.                 
  153.             
  154.         else:
  155.             for attr in attribs:
  156.                 if attr in link_attrs:
  157.                     yield (el, attr, attribs[attr], 0)
  158.                     continue
  159.             
  160.         if tag == XHTML('style') and el.text:
  161.             for match in _css_url_re.finditer(el.text):
  162.                 yield (el, None, match.group(1), match.start(1))
  163.             
  164.             for match in _css_import_re.finditer(el.text):
  165.                 yield (el, None, match.group(1), match.start(1))
  166.             
  167.         
  168.         if 'style' in attribs:
  169.             for match in _css_url_re.finditer(attribs['style']):
  170.                 yield (el, 'style', match.group(1), match.start(1))
  171.             
  172.     
  173.  
  174.  
  175. def make_links_absolute(root, base_url):
  176.     
  177.     def link_repl(href):
  178.         return urljoin(base_url, href)
  179.  
  180.     rewrite_links(root, link_repl)
  181.  
  182.  
  183. def resolve_base_href(root):
  184.     base_href = None
  185.     basetags = root.xpath('//base[@href]|//h:base[@href]', namespaces = XPNSMAP)
  186.     for b in basetags:
  187.         base_href = b.get('href')
  188.         b.drop_tree()
  189.     
  190.     if not base_href:
  191.         return None
  192.     make_links_absolute(root, base_href, resolve_base_href = False)
  193.  
  194.  
  195. def rewrite_links(root, link_repl_func, resolve_base_href = False):
  196.     if resolve_base_href:
  197.         resolve_base_href(root)
  198.     
  199.     for el, attrib, link, pos in iterlinks(root):
  200.         new_link = link_repl_func(link.strip())
  201.         if new_link == link:
  202.             continue
  203.         
  204.         if new_link is None:
  205.             if attrib is None:
  206.                 el.text = ''
  207.                 continue
  208.             del el.attrib[attrib]
  209.             continue
  210.         
  211.         if attrib is None:
  212.             new = el.text[:pos] + new_link + el.text[pos + len(link):]
  213.             el.text = new
  214.             continue
  215.         cur = el.attrib[attrib]
  216.         if not pos and len(cur) == len(link):
  217.             el.attrib[attrib] = new_link
  218.             continue
  219.         new = cur[:pos] + new_link + cur[pos + len(link):]
  220.         el.attrib[attrib] = new
  221.     
  222.  
  223. EPUB_MIME = types_map['.epub']
  224. XHTML_MIME = types_map['.xhtml']
  225. CSS_MIME = types_map['.css']
  226. NCX_MIME = types_map['.ncx']
  227. OPF_MIME = types_map['.opf']
  228. PAGE_MAP_MIME = 'application/oebps-page-map+xml'
  229. OEB_DOC_MIME = 'text/x-oeb1-document'
  230. OEB_CSS_MIME = 'text/x-oeb1-css'
  231. OPENTYPE_MIME = 'application/x-font-opentype'
  232. GIF_MIME = types_map['.gif']
  233. JPEG_MIME = types_map['.jpeg']
  234. PNG_MIME = types_map['.png']
  235. SVG_MIME = types_map['.svg']
  236. BINARY_MIME = 'application/octet-stream'
  237. XHTML_CSS_NAMESPACE = u'@namespace "%s";\n' % XHTML_NS
  238. OEB_STYLES = set([
  239.     CSS_MIME,
  240.     OEB_CSS_MIME,
  241.     'text/x-oeb-css'])
  242. OEB_DOCS = set([
  243.     XHTML_MIME,
  244.     'text/html',
  245.     OEB_DOC_MIME,
  246.     'text/x-oeb-document'])
  247. OEB_RASTER_IMAGES = set([
  248.     GIF_MIME,
  249.     JPEG_MIME,
  250.     PNG_MIME])
  251. OEB_IMAGES = set([
  252.     GIF_MIME,
  253.     JPEG_MIME,
  254.     PNG_MIME,
  255.     SVG_MIME])
  256. MS_COVER_TYPE = 'other.ms-coverimage-standard'
  257. ENTITY_RE = re.compile('&([a-zA-Z_:][a-zA-Z0-9.-_:]+);')
  258. COLLAPSE_RE = re.compile('[ \\t\\r\\n\\v]+')
  259. QNAME_RE = re.compile('^[{][^{}]+[}][^{}]+$')
  260. PREFIXNAME_RE = re.compile('^[^:]+[:][^:]+')
  261. XMLDECL_RE = re.compile('^\\s*<[?]xml.*?[?]>')
  262. CSSURL_RE = re.compile('url[(](?P<q>["\']?)(?P<url>[^)]+)(?P=q)[)]')
  263.  
  264. def element(parent, *args, **kwargs):
  265.     if parent is not None:
  266.         return etree.SubElement(parent, *args, **kwargs)
  267.     return etree.Element(*args, **kwargs)
  268.  
  269.  
  270. def namespace(name):
  271.     if '}' in name:
  272.         return name.split('}', 1)[0][1:]
  273.     return ''
  274.  
  275.  
  276. def barename(name):
  277.     if '}' in name:
  278.         return name.split('}', 1)[1]
  279.     return name
  280.  
  281.  
  282. def prefixname(name, nsrmap):
  283.     if not isqname(name):
  284.         return name
  285.     ns = namespace(name)
  286.     if ns not in nsrmap:
  287.         return name
  288.     prefix = nsrmap[ns]
  289.     if not prefix:
  290.         return barename(name)
  291.     return ':'.join((prefix, barename(name)))
  292.  
  293.  
  294. def isprefixname(name):
  295.     if name:
  296.         pass
  297.     return PREFIXNAME_RE.match(name) is not None
  298.  
  299.  
  300. def qname(name, nsmap):
  301.     if not isprefixname(name):
  302.         return name
  303.     (prefix, local) = name.split(':', 1)
  304.     if prefix not in nsmap:
  305.         return name
  306.     return '{%s}%s' % (nsmap[prefix], local)
  307.  
  308.  
  309. def isqname(name):
  310.     if name:
  311.         pass
  312.     return QNAME_RE.match(name) is not None
  313.  
  314.  
  315. def XPath(expr):
  316.     return etree.XPath(expr, namespaces = XPNSMAP)
  317.  
  318.  
  319. def xpath(elem, expr):
  320.     return elem.xpath(expr, namespaces = XPNSMAP)
  321.  
  322.  
  323. def xml2str(root, pretty_print = False, strip_comments = False):
  324.     ans = etree.tostring(root, encoding = 'utf-8', xml_declaration = True, pretty_print = pretty_print)
  325.     if strip_comments:
  326.         ans = re.compile('<!--.*?-->', re.DOTALL).sub('', ans)
  327.     
  328.     return ans
  329.  
  330.  
  331. def xml2unicode(root, pretty_print = False):
  332.     return etree.tostring(root, pretty_print = pretty_print)
  333.  
  334.  
  335. def xml2text(elem):
  336.     return etree.tostring(elem, method = 'text', encoding = unicode, with_tail = False)
  337.  
  338. ASCII_CHARS = set((lambda .0: for x in .0:
  339. chr(x))(xrange(128)))
  340. UNIBYTE_CHARS = set((lambda .0: for x in .0:
  341. chr(x))(xrange(256)))
  342. URL_SAFE = set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-/~')
  343. URL_UNSAFE = [
  344.     ASCII_CHARS - URL_SAFE,
  345.     UNIBYTE_CHARS - URL_SAFE]
  346.  
  347. def urlquote(href):
  348.     result = []
  349.     unsafe = None if isinstance(href, unicode) else 1
  350.     unsafe = URL_UNSAFE[unsafe]
  351.     for char in href:
  352.         if char in unsafe:
  353.             char = '%%%02x' % ord(char)
  354.         
  355.         result.append(char)
  356.     
  357.     return ''.join(result)
  358.  
  359.  
  360. def urlnormalize(href):
  361.     parts = urlparse(href)
  362.     if not (parts.scheme) or parts.scheme == 'file':
  363.         (path, frag) = urldefrag(href)
  364.         parts = ('', '', path, '', '', frag)
  365.     
  366.     parts = (lambda .0: for part in .0:
  367. part.replace('\\', '/'))(parts)
  368.     parts = (lambda .0: for part in .0:
  369. urlunquote(part))(parts)
  370.     parts = (lambda .0: for part in .0:
  371. urlquote(part))(parts)
  372.     return urlunparse(parts)
  373.  
  374.  
  375. def merge_multiple_html_heads_and_bodies(root, log = None):
  376.     heads = xpath(root, '//h:head')
  377.     bodies = xpath(root, '//h:body')
  378.     if not len(heads) > 1 or len(bodies) > 1:
  379.         return root
  380.     for child in root:
  381.         root.remove(child)
  382.     
  383.     head = root.makeelement(XHTML('head'))
  384.     body = root.makeelement(XHTML('body'))
  385.     for h in heads:
  386.         for x in h:
  387.             head.append(x)
  388.         
  389.     
  390.     for b in bodies:
  391.         for x in b:
  392.             body.append(x)
  393.         
  394.     
  395.     map(root.append, (head, body))
  396.     if log is not None:
  397.         log.warn('Merging multiple <head> and <body> sections')
  398.     
  399.     return root
  400.  
  401.  
  402. class DummyHandler(logging.Handler):
  403.     
  404.     def __init__(self):
  405.         logging.Handler.__init__(self, logging.WARNING)
  406.         self.setFormatter(logging.Formatter('%(message)s'))
  407.         self.log = None
  408.  
  409.     
  410.     def emit(self, record):
  411.         if self.log is not None:
  412.             msg = self.format(record)
  413.             f = None if record.levelno >= logging.ERROR else self.log.warn
  414.             f(msg)
  415.         
  416.  
  417.  
  418. _css_logger = logging.getLogger('calibre.css')
  419. _css_logger.setLevel(logging.WARNING)
  420. _css_log_handler = DummyHandler()
  421. _css_logger.addHandler(_css_log_handler)
  422.  
  423. class OEBError(Exception):
  424.     pass
  425.  
  426.  
  427. class NotHTML(OEBError):
  428.     pass
  429.  
  430.  
  431. class NullContainer(object):
  432.     
  433.     def __init__(self, log):
  434.         self.log = log
  435.  
  436.     
  437.     def read(self, path):
  438.         raise OEBError('Attempt to read from NullContainer')
  439.  
  440.     
  441.     def write(self, path):
  442.         raise OEBError('Attempt to write to NullContainer')
  443.  
  444.     
  445.     def exists(self, path):
  446.         return False
  447.  
  448.     
  449.     def namelist(self):
  450.         return []
  451.  
  452.  
  453.  
  454. class DirContainer(object):
  455.     
  456.     def __init__(self, path, log):
  457.         self.log = log
  458.         path = unicode(path)
  459.         ext = os.path.splitext(path)[1].lower()
  460.         if ext == '.opf':
  461.             self.opfname = os.path.basename(path)
  462.             self.rootdir = os.path.dirname(path)
  463.             return None
  464.         self.rootdir = path
  465.         for path in self.namelist():
  466.             ext = os.path.splitext(path)[1].lower()
  467.             if ext == '.opf':
  468.                 self.opfname = path
  469.                 return None
  470.         
  471.         self.opfname = None
  472.  
  473.     
  474.     def read(self, path):
  475.         if path is None:
  476.             path = self.opfname
  477.         
  478.         path = os.path.join(self.rootdir, path)
  479.         
  480.         try:
  481.             f = _[1]
  482.             return f.read()
  483.         finally:
  484.             pass
  485.  
  486.  
  487.     
  488.     def write(self, path, data):
  489.         path = os.path.join(self.rootdir, urlunquote(path))
  490.         dir = os.path.dirname(path)
  491.         if not os.path.isdir(dir):
  492.             os.makedirs(dir)
  493.         
  494.         
  495.         try:
  496.             f = _[1]
  497.             return f.write(data)
  498.         finally:
  499.             pass
  500.  
  501.  
  502.     
  503.     def exists(self, path):
  504.         
  505.         try:
  506.             path = os.path.join(self.rootdir, urlunquote(path))
  507.         except ValueError:
  508.             return False
  509.  
  510.         return os.path.isfile(path)
  511.  
  512.     
  513.     def namelist(self):
  514.         names = []
  515.         base = self.rootdir
  516.         if isinstance(base, unicode):
  517.             base = base.encode(filesystem_encoding)
  518.         
  519.         for root, dirs, files in os.walk(base):
  520.             for fname in files:
  521.                 fname = os.path.join(root, fname)
  522.                 fname = fname.replace('\\', '/')
  523.                 if not isinstance(fname, unicode):
  524.                     
  525.                     try:
  526.                         fname = fname.decode(filesystem_encoding)
  527.                     continue
  528.  
  529.                 
  530.                 names.append(fname)
  531.             
  532.         
  533.         return names
  534.  
  535.  
  536.  
  537. class Metadata(object):
  538.     DC_TERMS = set([
  539.         'contributor',
  540.         'coverage',
  541.         'creator',
  542.         'date',
  543.         'description',
  544.         'format',
  545.         'identifier',
  546.         'language',
  547.         'publisher',
  548.         'relation',
  549.         'rights',
  550.         'source',
  551.         'subject',
  552.         'title',
  553.         'type'])
  554.     CALIBRE_TERMS = set([
  555.         'series',
  556.         'series_index',
  557.         'rating',
  558.         'timestamp',
  559.         'publication_type'])
  560.     OPF_ATTRS = {
  561.         'role': OPF('role'),
  562.         'file-as': OPF('file-as'),
  563.         'scheme': OPF('scheme'),
  564.         'event': OPF('event'),
  565.         'type': XSI('type'),
  566.         'lang': XML('lang'),
  567.         'id': 'id' }
  568.     OPF1_NSMAP = {
  569.         'dc': DC11_NS,
  570.         'oebpackage': OPF1_NS }
  571.     OPF2_NSMAP = {
  572.         'opf': OPF2_NS,
  573.         'dc': DC11_NS,
  574.         'dcterms': DCTERMS_NS,
  575.         'xsi': XSI_NS,
  576.         'calibre': CALIBRE_NS }
  577.     
  578.     class Item(object):
  579.         
  580.         class Attribute(object):
  581.             
  582.             def __init__(self, attr, allowed = None):
  583.                 if not callable(attr):
  584.                     attr_ = (attr,)
  585.                     
  586.                     attr = lambda term: attr_
  587.                 
  588.                 self.attr = attr
  589.                 self.allowed = allowed
  590.  
  591.             
  592.             def term_attr(self, obj):
  593.                 term = obj.term
  594.                 if namespace(term) != DC11_NS:
  595.                     term = OPF('meta')
  596.                 
  597.                 allowed = self.allowed
  598.                 if allowed is not None and term not in allowed:
  599.                     raise AttributeError('attribute %r not valid for metadata term %r' % (self.attr(term), barename(obj.term)))
  600.                 term not in allowed
  601.                 return self.attr(term)
  602.  
  603.             
  604.             def __get__(self, obj, cls):
  605.                 if obj is None:
  606.                     return None
  607.                 return obj.attrib.get(self.term_attr(obj), '')
  608.  
  609.             
  610.             def __set__(self, obj, value):
  611.                 obj.attrib[self.term_attr(obj)] = value
  612.  
  613.  
  614.         
  615.         def __init__(self, term, value, attrib = { }, nsmap = { }, **kwargs):
  616.             self.attrib = attrib = dict(attrib)
  617.             self.nsmap = nsmap = dict(nsmap)
  618.             attrib.update(kwargs)
  619.             if namespace(term) == OPF2_NS:
  620.                 term = barename(term)
  621.             
  622.             ns = namespace(term)
  623.             local = barename(term).lower()
  624.             if local in Metadata.DC_TERMS:
  625.                 if not ns or ns in DC_NSES:
  626.                     term = DC(local)
  627.                 elif local in Metadata.CALIBRE_TERMS and ns in (CALIBRE_NS, ''):
  628.                     term = CALIBRE(local)
  629.                 
  630.             self.term = term
  631.             self.value = value
  632.             for attr, value in attrib.items():
  633.                 if isprefixname(value):
  634.                     attrib[attr] = qname(value, nsmap)
  635.                 
  636.                 nsattr = Metadata.OPF_ATTRS.get(attr, attr)
  637.                 if nsattr == OPF('scheme') and namespace(term) != DC11_NS:
  638.                     nsattr = 'scheme'
  639.                 
  640.                 if attr != nsattr:
  641.                     attrib[nsattr] = attrib.pop(attr)
  642.                     continue
  643.             
  644.  
  645.         
  646.         def name(self):
  647.             
  648.             def fget(self):
  649.                 return self.term
  650.  
  651.             return property(fget = fget)
  652.  
  653.         name = dynamic_property(name)
  654.         
  655.         def content(self):
  656.             
  657.             def fget(self):
  658.                 return self.value
  659.  
  660.             
  661.             def fset(self, value):
  662.                 self.value = value
  663.  
  664.             return property(fget = fget, fset = fset)
  665.  
  666.         content = dynamic_property(content)
  667.         scheme = Attribute((lambda term: if term == OPF('meta'):
  668. 'scheme'OPF('scheme')), [
  669.             DC('identifier'),
  670.             OPF('meta')])
  671.         file_as = Attribute(OPF('file-as'), [
  672.             DC('creator'),
  673.             DC('contributor'),
  674.             DC('title')])
  675.         role = Attribute(OPF('role'), [
  676.             DC('creator'),
  677.             DC('contributor')])
  678.         event = Attribute(OPF('event'), [
  679.             DC('date')])
  680.         id = Attribute('id')
  681.         type = Attribute(XSI('type'), [
  682.             DC('date'),
  683.             DC('format'),
  684.             DC('type')])
  685.         lang = Attribute(XML('lang'), [
  686.             DC('contributor'),
  687.             DC('coverage'),
  688.             DC('creator'),
  689.             DC('publisher'),
  690.             DC('relation'),
  691.             DC('rights'),
  692.             DC('source'),
  693.             DC('subject'),
  694.             OPF('meta')])
  695.         
  696.         def __getitem__(self, key):
  697.             return self.attrib[key]
  698.  
  699.         
  700.         def __setitem__(self, key, value):
  701.             self.attrib[key] = value
  702.  
  703.         
  704.         def __contains__(self, key):
  705.             return key in self.attrib
  706.  
  707.         
  708.         def get(self, key, default = None):
  709.             return self.attrib.get(key, default)
  710.  
  711.         
  712.         def __repr__(self):
  713.             return 'Item(term=%r, value=%r, attrib=%r)' % (barename(self.term), self.value, self.attrib)
  714.  
  715.         
  716.         def __str__(self):
  717.             return unicode(self.value).encode('ascii', 'xmlcharrefreplace')
  718.  
  719.         
  720.         def __unicode__(self):
  721.             return unicode(self.value)
  722.  
  723.         
  724.         def to_opf1(self, dcmeta = None, xmeta = None, nsrmap = { }):
  725.             attrib = { }
  726.             for key, value in self.attrib.items():
  727.                 if namespace(key) == OPF2_NS:
  728.                     key = barename(key)
  729.                 
  730.                 attrib[key] = prefixname(value, nsrmap)
  731.             
  732.             if namespace(self.term) == DC11_NS:
  733.                 name = DC(barename(self.term).title())
  734.                 elem = element(dcmeta, name, attrib = attrib)
  735.                 elem.text = self.value
  736.             else:
  737.                 elem = element(xmeta, 'meta', attrib = attrib)
  738.                 elem.attrib['name'] = prefixname(self.term, nsrmap)
  739.                 elem.attrib['content'] = prefixname(self.value, nsrmap)
  740.             return elem
  741.  
  742.         
  743.         def to_opf2(self, parent = None, nsrmap = { }):
  744.             attrib = { }
  745.             for key, value in self.attrib.items():
  746.                 attrib[key] = prefixname(value, nsrmap)
  747.             
  748.             if namespace(self.term) == DC11_NS:
  749.                 elem = element(parent, self.term, attrib = attrib)
  750.                 elem.text = self.value
  751.             else:
  752.                 elem = element(parent, OPF('meta'), attrib = attrib)
  753.                 elem.attrib['name'] = prefixname(self.term, nsrmap)
  754.                 elem.attrib['content'] = prefixname(self.value, nsrmap)
  755.             return elem
  756.  
  757.  
  758.     
  759.     def __init__(self, oeb):
  760.         self.oeb = oeb
  761.         self.items = defaultdict(list)
  762.  
  763.     
  764.     def add(self, term, value, attrib = { }, nsmap = { }, **kwargs):
  765.         item = self.Item(term, value, attrib, nsmap, **kwargs)
  766.         items = self.items[barename(item.term)]
  767.         items.append(item)
  768.         return item
  769.  
  770.     
  771.     def iterkeys(self):
  772.         for key in self.items:
  773.             yield key
  774.         
  775.  
  776.     __iter__ = iterkeys
  777.     
  778.     def clear(self, key):
  779.         l = self.items[key]
  780.         for x in list(l):
  781.             l.remove(x)
  782.         
  783.  
  784.     
  785.     def filter(self, key, predicate):
  786.         l = self.items[key]
  787.         for x in list(l):
  788.             if predicate(x):
  789.                 l.remove(x)
  790.                 continue
  791.         
  792.  
  793.     
  794.     def __getitem__(self, key):
  795.         return self.items[key]
  796.  
  797.     
  798.     def __contains__(self, key):
  799.         return key in self.items
  800.  
  801.     
  802.     def __getattr__(self, term):
  803.         return self.items[term]
  804.  
  805.     
  806.     def _nsmap(self):
  807.         
  808.         def fget(self):
  809.             nsmap = { }
  810.             for term in self.items:
  811.                 for item in self.items[term]:
  812.                     nsmap.update(item.nsmap)
  813.                 
  814.             
  815.             return nsmap
  816.  
  817.         return property(fget = fget)
  818.  
  819.     _nsmap = dynamic_property(_nsmap)
  820.     
  821.     def _opf1_nsmap(self):
  822.         
  823.         def fget(self):
  824.             nsmap = self._nsmap
  825.             for key, value in nsmap.items():
  826.                 if value in OPF_NSES or value in DC_NSES:
  827.                     del nsmap[key]
  828.                     continue
  829.             
  830.             return nsmap
  831.  
  832.         return property(fget = fget)
  833.  
  834.     _opf1_nsmap = dynamic_property(_opf1_nsmap)
  835.     
  836.     def _opf2_nsmap(self):
  837.         
  838.         def fget(self):
  839.             nsmap = self._nsmap
  840.             nsmap.update(OPF2_NSMAP)
  841.             return nsmap
  842.  
  843.         return property(fget = fget)
  844.  
  845.     _opf2_nsmap = dynamic_property(_opf2_nsmap)
  846.     
  847.     def to_opf1(self, parent = None):
  848.         nsmap = self._opf1_nsmap
  849.         nsrmap = dict((lambda .0: for key, value in .0:
  850. (value, key))(nsmap.items()))
  851.         elem = element(parent, 'metadata', nsmap = nsmap)
  852.         dcmeta = element(elem, 'dc-metadata', nsmap = OPF1_NSMAP)
  853.         xmeta = element(elem, 'x-metadata')
  854.         for term in self.items:
  855.             for item in self.items[term]:
  856.                 item.to_opf1(dcmeta, xmeta, nsrmap = nsrmap)
  857.             
  858.         
  859.         if 'ms-chaptertour' not in self.items:
  860.             chaptertour = self.Item('ms-chaptertour', 'chaptertour')
  861.             chaptertour.to_opf1(dcmeta, xmeta, nsrmap = nsrmap)
  862.         
  863.         return elem
  864.  
  865.     
  866.     def to_opf2(self, parent = None):
  867.         nsmap = self._opf2_nsmap
  868.         nsrmap = dict((lambda .0: for key, value in .0:
  869. (value, key))(nsmap.items()))
  870.         elem = element(parent, OPF('metadata'), nsmap = nsmap)
  871.         for term in self.items:
  872.             for item in self.items[term]:
  873.                 item.to_opf2(elem, nsrmap = nsrmap)
  874.             
  875.         
  876.         return elem
  877.  
  878.  
  879.  
  880. class Manifest(object):
  881.     
  882.     class Item(object):
  883.         NUM_RE = re.compile('^(.*)([0-9][0-9.]*)(?=[.]|$)')
  884.         META_XP = XPath('/h:html/h:head/h:meta[@http-equiv="Content-Type"]')
  885.         
  886.         def __init__(self, oeb, id, href, media_type, fallback = None, loader = str, data = None):
  887.             self.oeb = oeb
  888.             self.id = id
  889.             self.href = self.path = urlnormalize(href)
  890.             self.media_type = media_type
  891.             self.fallback = fallback
  892.             self.override_css_fetch = None
  893.             self.spine_position = None
  894.             self.linear = True
  895.             if loader is None and data is None:
  896.                 loader = oeb.container.read
  897.             
  898.             self._loader = loader
  899.             self._data = data
  900.  
  901.         
  902.         def __repr__(self):
  903.             return u'Item(id=%r, href=%r, media_type=%r)' % (self.id, self.href, self.media_type)
  904.  
  905.         
  906.         def _parse_xml(self, data):
  907.             data = xml_to_unicode(data, strip_encoding_pats = True, assume_utf8 = True, resolve_entities = True)[0]
  908.             if not data:
  909.                 return None
  910.             return etree.fromstring(data, parser = RECOVER_PARSER)
  911.  
  912.         
  913.         def _parse_xhtml(self, data):
  914.             self.oeb.log.debug('Parsing', self.href, '...')
  915.             data = self.oeb.decode(data)
  916.             data = self.oeb.html_preprocessor(data)
  917.             idx = data.find('<html')
  918.             if idx == -1:
  919.                 idx = data.find('<HTML')
  920.             
  921.             if idx > -1:
  922.                 pre = data[:idx]
  923.                 data = data[idx:]
  924.                 if '<!DOCTYPE' in pre:
  925.                     user_entities = { }
  926.                     for match in re.finditer('<!ENTITY\\s+(\\S+)\\s+([^>]+)', pre):
  927.                         val = match.group(2)
  928.                         if val.startswith('"') and val.endswith('"'):
  929.                             val = val[1:-1]
  930.                         
  931.                         user_entities[match.group(1)] = val
  932.                     
  933.                     if user_entities:
  934.                         pat = re.compile('&(%s);' % '|'.join(user_entities.keys()))
  935.                         data = (pat.sub,)((lambda m: user_entities[m.group(1)]), data)
  936.                     
  937.                 
  938.             
  939.             parser = etree.XMLParser(no_network = True)
  940.             
  941.             def first_pass(data):
  942.                 
  943.                 try:
  944.                     data = etree.fromstring(data, parser = parser)
  945.                 except etree.XMLSyntaxError:
  946.                     err = None
  947.                     self.oeb.log.exception('Initial parse failed:')
  948.                     
  949.                     repl = lambda m: ENTITYDEFS.get(m.group(1), m.group(0))
  950.                     data = ENTITY_RE.sub(repl, data)
  951.                     
  952.                     try:
  953.                         data = etree.fromstring(data, parser = parser)
  954.                     except etree.XMLSyntaxError:
  955.                         err = None
  956.                         self.oeb.logger.warn('Parsing file %r as HTML' % self.href)
  957.                         if err.args and err.args[0].startswith('Excessive depth'):
  958.                             soupparser = soupparser
  959.                             import lxml.html
  960.                             data = soupparser.fromstring(data)
  961.                         else:
  962.                             data = html.fromstring(data)
  963.                         data.attrib.pop('xmlns', None)
  964.                         for elem in data.iter(tag = etree.Comment):
  965.                             if elem.text:
  966.                                 elem.text = elem.text.strip('-')
  967.                                 continue
  968.                         
  969.                         data = etree.tostring(data, encoding = unicode)
  970.                         
  971.                         try:
  972.                             data = etree.fromstring(data, parser = parser)
  973.                         except etree.XMLSyntaxError:
  974.                             data = etree.fromstring(data, parser = RECOVER_PARSER)
  975.                         except:
  976.                             None<EXCEPTION MATCH>etree.XMLSyntaxError
  977.                         
  978.  
  979.                         None<EXCEPTION MATCH>etree.XMLSyntaxError
  980.                     
  981.  
  982.                     None<EXCEPTION MATCH>etree.XMLSyntaxError
  983.  
  984.                 return data
  985.  
  986.             data = first_pass(data)
  987.             if not namespace(data.tag):
  988.                 self.oeb.log.warn('Forcing', self.href, 'into XHTML namespace')
  989.                 data.attrib['xmlns'] = XHTML_NS
  990.                 data = etree.tostring(data, encoding = unicode)
  991.                 
  992.                 try:
  993.                     data = etree.fromstring(data, parser = parser)
  994.                 data = data.replace(':=', '=').replace(':>', '>')
  995.                 data = data.replace('<http:/>', '')
  996.                 try:
  997.                     data = etree.fromstring(data, parser = parser)
  998.                 except etree.XMLSyntaxError:
  999.                     self.oeb.logger.warn('Stripping comments and meta tags from %s' % self.href)
  1000.                     data = re.compile('<!--.*?-->', re.DOTALL).sub('', data)
  1001.                     data = re.sub('<meta\\s+[^>]+?>', '', data)
  1002.                     data = data.replace("<?xml version='1.0' encoding='utf-8'?><o:p></o:p>", '')
  1003.                     data = data.replace("<?xml version='1.0' encoding='utf-8'??>", '')
  1004.                     data = etree.fromstring(data, parser = RECOVER_PARSER)
  1005.                 except:
  1006.                     None<EXCEPTION MATCH>etree.XMLSyntaxError
  1007.                 
  1008.  
  1009.             elif namespace(data.tag) != XHTML_NS:
  1010.                 ns = namespace(data.tag)
  1011.                 attrib = dict(data.attrib)
  1012.                 nroot = etree.Element(XHTML('html'), nsmap = {
  1013.                     None: XHTML_NS }, attrib = attrib)
  1014.                 for elem in data.iterdescendants():
  1015.                     if isinstance(elem.tag, basestring) and namespace(elem.tag) == ns:
  1016.                         elem.tag = XHTML(barename(elem.tag))
  1017.                         continue
  1018.                 
  1019.                 for elem in data:
  1020.                     nroot.append(elem)
  1021.                 
  1022.                 data = nroot
  1023.             
  1024.             data = merge_multiple_html_heads_and_bodies(data, self.oeb.logger)
  1025.             head = xpath(data, '/h:html/h:head')
  1026.             head = None if head else None
  1027.             if head is None:
  1028.                 self.oeb.logger.warn('File %r missing <head/> element' % self.href)
  1029.                 head = etree.Element(XHTML('head'))
  1030.                 data.insert(0, head)
  1031.                 title = etree.SubElement(head, XHTML('title'))
  1032.                 title.text = self.oeb.translate(__('Unknown'))
  1033.             elif not xpath(data, '/h:html/h:head/h:title'):
  1034.                 self.oeb.logger.warn('File %r missing <title/> element' % self.href)
  1035.                 title = etree.SubElement(head, XHTML('title'))
  1036.                 title.text = self.oeb.translate(__('Unknown'))
  1037.             
  1038.             for meta in self.META_XP(data):
  1039.                 meta.getparent().remove(meta)
  1040.             
  1041.             etree.SubElement(head, XHTML('meta'), attrib = {
  1042.                 'http-equiv': 'Content-Type',
  1043.                 'content': '%s; charset=utf-8' % XHTML_NS })
  1044.             if not xpath(data, '/h:html/h:body'):
  1045.                 body = xpath(data, '//h:body')
  1046.                 if body:
  1047.                     body = body[0]
  1048.                     body.getparent().remove(body)
  1049.                     data.append(body)
  1050.                 else:
  1051.                     self.oeb.logger.warn('File %r missing <body/> element' % self.href)
  1052.                     etree.SubElement(data, XHTML('body'))
  1053.             
  1054.             r = _[1]
  1055.             for x in r:
  1056.                 x.tag = XHTML('span')
  1057.             
  1058.             body = xpath(data, '/h:html/h:body')[0]
  1059.             for key in list(body.attrib.keys()):
  1060.                 if key == 'lang' or key.endswith('}lang'):
  1061.                     body.attrib.pop(key)
  1062.                     continue
  1063.                 []
  1064.             
  1065.             
  1066.             def remove_elem(a):
  1067.                 p = a.getparent()
  1068.                 idx = p.index(a) - 1
  1069.                 p.remove(a)
  1070.                 if a.tail:
  1071.                     if idx <= 0:
  1072.                         if p.text is None:
  1073.                             p.text = ''
  1074.                         
  1075.                         p.text += a.tail
  1076.                     elif p[idx].tail is None:
  1077.                         p[idx].tail = ''
  1078.                     
  1079.                     p[idx].tail += a.tail
  1080.                 
  1081.  
  1082.             for a in xpath(data, '//h:a[@href]|//h:i|//h:b'):
  1083.                 if a.get('id', None) is None and a.get('name', None) is None and len(a) == 0 and not (a.text):
  1084.                     remove_elem(a)
  1085.                     continue
  1086.                 []
  1087.             
  1088.             return data
  1089.  
  1090.         
  1091.         def _parse_txt(self, data):
  1092.             if '<html>' in data:
  1093.                 return self._parse_xhtml(data)
  1094.             self.oeb.log.debug('Converting', self.href, '...')
  1095.             convert_markdown = convert_markdown
  1096.             import calibre.ebooks.txt.processor
  1097.             title = self.oeb.metadata.title
  1098.             if title:
  1099.                 title = unicode(title[0])
  1100.             else:
  1101.                 title = _('Unknown')
  1102.             return self._parse_xhtml(convert_markdown(data, title = title))
  1103.  
  1104.         
  1105.         def _parse_css(self, data):
  1106.             
  1107.             def get_style_rules_from_import(import_rule):
  1108.                 ans = []
  1109.                 if not import_rule.styleSheet:
  1110.                     return ans
  1111.                 rules = import_rule.styleSheet.cssRules
  1112.                 for rule in rules:
  1113.                     if rule.type == CSSRule.IMPORT_RULE:
  1114.                         ans.extend(get_style_rules_from_import(rule))
  1115.                         continue
  1116.                     import_rule.styleSheet
  1117.                     if rule.type in (CSSRule.FONT_FACE_RULE, CSSRule.STYLE_RULE):
  1118.                         ans.append(rule)
  1119.                         continue
  1120.                 
  1121.                 return ans
  1122.  
  1123.             self.oeb.log.debug('Parsing', self.href, '...')
  1124.             data = self.oeb.decode(data)
  1125.             data = self.oeb.css_preprocessor(data, add_namespace = True)
  1126.             if not self.override_css_fetch:
  1127.                 pass
  1128.             parser = CSSParser(loglevel = logging.WARNING, fetcher = self._fetch_css, log = _css_logger)
  1129.             data = parser.parseString(data, href = self.href)
  1130.             data.namespaces['h'] = XHTML_NS
  1131.             import_rules = list(data.cssRules.rulesOfType(CSSRule.IMPORT_RULE))
  1132.             rules_to_append = []
  1133.             insert_index = None
  1134.             for r in data.cssRules.rulesOfType(CSSRule.STYLE_RULE):
  1135.                 insert_index = data.cssRules.index(r)
  1136.             
  1137.             for rule in import_rules:
  1138.                 rules_to_append.extend(get_style_rules_from_import(rule))
  1139.             
  1140.             for r in reversed(rules_to_append):
  1141.                 data.insertRule(r, index = insert_index)
  1142.             
  1143.             for rule in import_rules:
  1144.                 data.deleteRule(rule)
  1145.             
  1146.             return data
  1147.  
  1148.         
  1149.         def _fetch_css(self, path):
  1150.             hrefs = self.oeb.manifest.hrefs
  1151.             if path not in hrefs:
  1152.                 self.oeb.logger.warn('CSS import of missing file %r' % path)
  1153.                 return (None, None)
  1154.             item = hrefs[path]
  1155.             if item.media_type not in OEB_STYLES:
  1156.                 self.oeb.logger.warn('CSS import of non-CSS file %r' % path)
  1157.                 return (None, None)
  1158.             data = item.data.cssText
  1159.             return ('utf-8', data)
  1160.  
  1161.         
  1162.         def data(self):
  1163.             doc = "Provides MIME type sensitive access to the manifest\n            entry's associated content.\n\n            - XHTML, HTML, and variant content is parsed as necessary to\n              convert and and return as an lxml.etree element in the XHTML\n              namespace.\n            - XML content is parsed and returned as an lxml.etree element.\n            - CSS and CSS-variant content is parsed and returned as a cssutils\n              CSS DOM stylesheet.\n            - All other content is returned as a :class:`str` object with no\n              special parsing.\n            "
  1164.             
  1165.             def fget(self):
  1166.                 data = self._data
  1167.                 if data is None:
  1168.                     if self._loader is None:
  1169.                         return None
  1170.                     data = self._loader(getattr(self, 'html_input_href', self.href))
  1171.                 
  1172.                 if not isinstance(data, basestring):
  1173.                     pass
  1174.                 elif self.media_type.lower() in OEB_DOCS:
  1175.                     data = self._parse_xhtml(data)
  1176.                 elif self.media_type.lower()[-4:] in ('+xml', '/xml'):
  1177.                     data = self._parse_xml(data)
  1178.                 elif self.media_type.lower() in OEB_STYLES:
  1179.                     data = self._parse_css(data)
  1180.                 elif 'text' in self.media_type.lower():
  1181.                     self.oeb.log.warn('%s contains data in TXT format' % self.href, 'converting to HTML')
  1182.                     data = self._parse_txt(data)
  1183.                     self.media_type = XHTML_MIME
  1184.                 
  1185.                 self._data = data
  1186.                 return data
  1187.  
  1188.             
  1189.             def fset(self, value):
  1190.                 self._data = value
  1191.  
  1192.             
  1193.             def fdel(self):
  1194.                 self._data = None
  1195.  
  1196.             return property(fget, fset, fdel, doc = doc)
  1197.  
  1198.         data = dynamic_property(data)
  1199.         
  1200.         def unload_data_from_memory(self, memory = None):
  1201.             if isinstance(self._data, (str, bytes)):
  1202.                 self._data = None
  1203.             
  1204.  
  1205.         
  1206.         def __str__(self):
  1207.             data = self.data
  1208.             if isinstance(data, etree._Element):
  1209.                 ans = xml2str(data, pretty_print = self.oeb.pretty_print)
  1210.                 if self.media_type in OEB_DOCS:
  1211.                     ans = re.sub('<(div|a|span)([^>]*)/>', '<\\1\\2></\\1>', ans)
  1212.                 
  1213.                 return ans
  1214.             if isinstance(data, unicode):
  1215.                 return data.encode('utf-8')
  1216.             if hasattr(data, 'cssText'):
  1217.                 data = data.cssText
  1218.                 return data
  1219.             return str(data)
  1220.  
  1221.         
  1222.         def __unicode__(self):
  1223.             data = self.data
  1224.             if isinstance(data, etree._Element):
  1225.                 return xml2unicode(data, pretty_print = self.oeb.pretty_print)
  1226.             if isinstance(data, unicode):
  1227.                 return data
  1228.             if hasattr(data, 'cssText'):
  1229.                 return data.cssText
  1230.             return unicode(data)
  1231.  
  1232.         
  1233.         def __eq__(self, other):
  1234.             return id(self) == id(other)
  1235.  
  1236.         
  1237.         def __ne__(self, other):
  1238.             return not self.__eq__(other)
  1239.  
  1240.         
  1241.         def __cmp__(self, other):
  1242.             result = cmp(self.spine_position, other.spine_position)
  1243.             if result != 0:
  1244.                 return result
  1245.             smatch = self.NUM_RE.search(self.href)
  1246.             sref = result != 0 if smatch else self.href
  1247.             snum = None if smatch else 0
  1248.             skey = (sref, snum, self.id)
  1249.             omatch = self.NUM_RE.search(other.href)
  1250.             oref = None if omatch else other.href
  1251.             onum = None if omatch else 0
  1252.             okey = (oref, onum, other.id)
  1253.             return cmp(skey, okey)
  1254.  
  1255.         
  1256.         def relhref(self, href):
  1257.             if urlparse(href).scheme:
  1258.                 return href
  1259.             if '/' not in self.href:
  1260.                 return href
  1261.             base = os.path.dirname(self.href).split('/')
  1262.             (target, frag) = urldefrag(href)
  1263.             target = target.split('/')
  1264.             for index in xrange(min(len(base), len(target))):
  1265.                 if base[index] != target[index]:
  1266.                     break
  1267.                     continue
  1268.                 '/' not in self.href
  1269.             else:
  1270.                 index += 1
  1271.             relhref = [
  1272.                 '..'] * (len(base) - index) + target[index:]
  1273.             relhref = '/'.join(relhref)
  1274.             if frag:
  1275.                 relhref = '#'.join((relhref, frag))
  1276.             
  1277.             return relhref
  1278.  
  1279.         
  1280.         def abshref(self, href):
  1281.             purl = urlparse(href)
  1282.             scheme = purl.scheme
  1283.             if scheme and scheme != 'file':
  1284.                 return href
  1285.             purl = list(purl)
  1286.             purl[0] = ''
  1287.             href = urlunparse(purl)
  1288.             (path, frag) = urldefrag(href)
  1289.             if not path:
  1290.                 if frag:
  1291.                     return '#'.join((self.href, frag))
  1292.                 return self.href
  1293.             path
  1294.             if '/' not in self.href:
  1295.                 return href
  1296.             dirname = os.path.dirname(self.href)
  1297.             href = os.path.join(dirname, href)
  1298.             href = os.path.normpath(href).replace('\\', '/')
  1299.             return href
  1300.  
  1301.  
  1302.     
  1303.     def __init__(self, oeb):
  1304.         self.oeb = oeb
  1305.         self.items = set()
  1306.         self.ids = { }
  1307.         self.hrefs = { }
  1308.  
  1309.     
  1310.     def add(self, id, href, media_type, fallback = None, loader = None, data = None):
  1311.         item = self.Item(self.oeb, id, href, media_type, fallback, loader, data)
  1312.         self.items.add(item)
  1313.         self.ids[item.id] = item
  1314.         self.hrefs[item.href] = item
  1315.         return item
  1316.  
  1317.     
  1318.     def remove(self, item):
  1319.         if item in self.ids:
  1320.             item = self.ids[item]
  1321.         
  1322.         del self.ids[item.id]
  1323.         if item.href in self.hrefs:
  1324.             del self.hrefs[item.href]
  1325.         
  1326.         self.items.remove(item)
  1327.         if item in self.oeb.spine:
  1328.             self.oeb.spine.remove(item)
  1329.         
  1330.  
  1331.     
  1332.     def generate(self, id = None, href = None):
  1333.         if id is not None:
  1334.             base = id
  1335.             index = 1
  1336.             while id in self.ids:
  1337.                 id = base + str(index)
  1338.                 index += 1
  1339.         
  1340.         if href is not None:
  1341.             href = urlnormalize(href)
  1342.             (base, ext) = os.path.splitext(href)
  1343.             index = 1
  1344.             lhrefs = []([ x.lower() for x in self.hrefs ])
  1345.             while href.lower() in lhrefs:
  1346.                 href = base + str(index) + ext
  1347.                 index += 1
  1348.                 continue
  1349.                 []
  1350.         
  1351.         return (id, href)
  1352.  
  1353.     
  1354.     def __iter__(self):
  1355.         for item in self.items:
  1356.             yield item
  1357.         
  1358.  
  1359.     
  1360.     def __len__(self):
  1361.         return len(self.items)
  1362.  
  1363.     
  1364.     def values(self):
  1365.         return list(self.items)
  1366.  
  1367.     
  1368.     def __contains__(self, item):
  1369.         return item in self.items
  1370.  
  1371.     
  1372.     def to_opf1(self, parent = None):
  1373.         elem = element(parent, 'manifest')
  1374.         for item in self.items:
  1375.             media_type = item.media_type
  1376.             if media_type in OEB_DOCS:
  1377.                 media_type = OEB_DOC_MIME
  1378.             elif media_type in OEB_STYLES:
  1379.                 media_type = OEB_CSS_MIME
  1380.             
  1381.             attrib = {
  1382.                 'id': item.id,
  1383.                 'href': urlunquote(item.href),
  1384.                 'media-type': media_type }
  1385.             if item.fallback:
  1386.                 attrib['fallback'] = item.fallback
  1387.             
  1388.             element(elem, 'item', attrib = attrib)
  1389.         
  1390.         return elem
  1391.  
  1392.     
  1393.     def to_opf2(self, parent = None):
  1394.         
  1395.         def sort(x, y):
  1396.             return cmp(x.href, y.href)
  1397.  
  1398.         elem = element(parent, OPF('manifest'))
  1399.         for item in sorted(self.items, cmp = sort):
  1400.             media_type = item.media_type
  1401.             if media_type in OEB_DOCS:
  1402.                 media_type = XHTML_MIME
  1403.             elif media_type in OEB_STYLES:
  1404.                 media_type = CSS_MIME
  1405.             
  1406.             attrib = {
  1407.                 'id': item.id,
  1408.                 'href': urlunquote(item.href),
  1409.                 'media-type': media_type }
  1410.             if item.fallback:
  1411.                 attrib['fallback'] = item.fallback
  1412.             
  1413.             element(elem, OPF('item'), attrib = attrib)
  1414.         
  1415.         return elem
  1416.  
  1417.  
  1418.  
  1419. class Spine(object):
  1420.     
  1421.     def __init__(self, oeb):
  1422.         self.oeb = oeb
  1423.         self.items = []
  1424.  
  1425.     
  1426.     def _linear(self, linear):
  1427.         if isinstance(linear, basestring):
  1428.             linear = linear.lower()
  1429.         
  1430.         if linear is None or linear in ('yes', 'true'):
  1431.             linear = True
  1432.         elif linear in ('no', 'false'):
  1433.             linear = False
  1434.         
  1435.         return linear
  1436.  
  1437.     
  1438.     def add(self, item, linear = None):
  1439.         item.linear = self._linear(linear)
  1440.         item.spine_position = len(self.items)
  1441.         self.items.append(item)
  1442.         return item
  1443.  
  1444.     
  1445.     def insert(self, index, item, linear):
  1446.         item.linear = self._linear(linear)
  1447.         item.spine_position = index
  1448.         self.items.insert(index, item)
  1449.         for i in xrange(index, len(self.items)):
  1450.             self.items[i].spine_position = i
  1451.         
  1452.         return item
  1453.  
  1454.     
  1455.     def remove(self, item):
  1456.         index = item.spine_position
  1457.         self.items.pop(index)
  1458.         for i in xrange(index, len(self.items)):
  1459.             self.items[i].spine_position = i
  1460.         
  1461.         item.spine_position = None
  1462.  
  1463.     
  1464.     def index(self, item):
  1465.         for i, x in enumerate(self):
  1466.             if item == x:
  1467.                 return i
  1468.         
  1469.         return -1
  1470.  
  1471.     
  1472.     def __iter__(self):
  1473.         for item in self.items:
  1474.             yield item
  1475.         
  1476.  
  1477.     
  1478.     def __getitem__(self, index):
  1479.         return self.items[index]
  1480.  
  1481.     
  1482.     def __len__(self):
  1483.         return len(self.items)
  1484.  
  1485.     
  1486.     def __contains__(self, item):
  1487.         return item in self.items
  1488.  
  1489.     
  1490.     def to_opf1(self, parent = None):
  1491.         elem = element(parent, 'spine')
  1492.         for item in self.items:
  1493.             if item.linear:
  1494.                 element(elem, 'itemref', attrib = {
  1495.                     'idref': item.id })
  1496.                 continue
  1497.         
  1498.         return elem
  1499.  
  1500.     
  1501.     def to_opf2(self, parent = None):
  1502.         elem = element(parent, OPF('spine'))
  1503.         for item in self.items:
  1504.             attrib = {
  1505.                 'idref': item.id }
  1506.             if not item.linear:
  1507.                 attrib['linear'] = 'no'
  1508.             
  1509.             element(elem, OPF('itemref'), attrib = attrib)
  1510.         
  1511.         return elem
  1512.  
  1513.  
  1514.  
  1515. class Guide(object):
  1516.     
  1517.     class Reference(object):
  1518.         _TYPES_TITLES = [
  1519.             ('cover', __('Cover')),
  1520.             ('title-page', __('Title Page')),
  1521.             ('toc', __('Table of Contents')),
  1522.             ('index', __('Index')),
  1523.             ('glossary', __('Glossary')),
  1524.             ('acknowledgements', __('Acknowledgements')),
  1525.             ('bibliography', __('Bibliography')),
  1526.             ('colophon', __('Colophon')),
  1527.             ('copyright-page', __('Copyright')),
  1528.             ('dedication', __('Dedication')),
  1529.             ('epigraph', __('Epigraph')),
  1530.             ('foreword', __('Foreword')),
  1531.             ('loi', __('List of Illustrations')),
  1532.             ('lot', __('List of Tables')),
  1533.             ('notes', __('Notes')),
  1534.             ('preface', __('Preface')),
  1535.             ('text', __('Main Text'))]
  1536.         TYPES = set((lambda .0: for t, _ in .0:
  1537. t)(_TYPES_TITLES))
  1538.         TITLES = dict(_TYPES_TITLES)
  1539.         ORDER = dict((lambda .0: for t, _ in .0:
  1540. (t, i))(enumerate(_TYPES_TITLES)))
  1541.         
  1542.         def __init__(self, oeb, type, title, href):
  1543.             self.oeb = oeb
  1544.             if type.lower() in self.TYPES:
  1545.                 type = type.lower()
  1546.             elif type not in self.TYPES and not type.startswith('other.'):
  1547.                 type = 'other.' + type
  1548.             
  1549.             if not title and type in self.TITLES:
  1550.                 title = oeb.translate(self.TITLES[type])
  1551.             
  1552.             self.type = type
  1553.             self.title = title
  1554.             self.href = urlnormalize(href)
  1555.  
  1556.         
  1557.         def __repr__(self):
  1558.             return 'Reference(type=%r, title=%r, href=%r)' % (self.type, self.title, self.href)
  1559.  
  1560.         
  1561.         def _order(self):
  1562.             
  1563.             def fget(self):
  1564.                 return self.ORDER.get(self.type, self.type)
  1565.  
  1566.             return property(fget = fget)
  1567.  
  1568.         _order = dynamic_property(_order)
  1569.         
  1570.         def __cmp__(self, other):
  1571.             if not isinstance(other, Guide.Reference):
  1572.                 return NotImplemented
  1573.             return cmp(self._order, other._order)
  1574.  
  1575.         
  1576.         def item(self):
  1577.             doc = 'The manifest item associated with this reference.'
  1578.             
  1579.             def fget(self):
  1580.                 path = urldefrag(self.href)[0]
  1581.                 hrefs = self.oeb.manifest.hrefs
  1582.                 return hrefs.get(path, None)
  1583.  
  1584.             return property(fget = fget, doc = doc)
  1585.  
  1586.         item = dynamic_property(item)
  1587.  
  1588.     
  1589.     def __init__(self, oeb):
  1590.         self.oeb = oeb
  1591.         self.refs = { }
  1592.  
  1593.     
  1594.     def add(self, type, title, href):
  1595.         ref = self.Reference(self.oeb, type, title, href)
  1596.         self.refs[type] = ref
  1597.         return ref
  1598.  
  1599.     
  1600.     def remove(self, type):
  1601.         return self.refs.pop(type, None)
  1602.  
  1603.     
  1604.     def iterkeys(self):
  1605.         for type in self.refs:
  1606.             yield type
  1607.         
  1608.  
  1609.     __iter__ = iterkeys
  1610.     
  1611.     def values(self):
  1612.         return sorted(self.refs.values())
  1613.  
  1614.     
  1615.     def items(self):
  1616.         for type, ref in self.refs.items():
  1617.             yield (type, ref)
  1618.         
  1619.  
  1620.     
  1621.     def __getitem__(self, key):
  1622.         return self.refs[key]
  1623.  
  1624.     
  1625.     def __delitem__(self, key):
  1626.         del self.refs[key]
  1627.  
  1628.     
  1629.     def __contains__(self, key):
  1630.         return key in self.refs
  1631.  
  1632.     
  1633.     def __len__(self):
  1634.         return len(self.refs)
  1635.  
  1636.     
  1637.     def to_opf1(self, parent = None):
  1638.         elem = element(parent, 'guide')
  1639.         for ref in self.refs.values():
  1640.             attrib = {
  1641.                 'type': ref.type,
  1642.                 'href': urlunquote(ref.href) }
  1643.             if ref.title:
  1644.                 attrib['title'] = ref.title
  1645.             
  1646.             element(elem, 'reference', attrib = attrib)
  1647.         
  1648.         return elem
  1649.  
  1650.     
  1651.     def to_opf2(self, parent = None):
  1652.         elem = element(parent, OPF('guide'))
  1653.         for ref in self.refs.values():
  1654.             attrib = {
  1655.                 'type': ref.type,
  1656.                 'href': urlunquote(ref.href) }
  1657.             if ref.title:
  1658.                 attrib['title'] = ref.title
  1659.             
  1660.             element(elem, OPF('reference'), attrib = attrib)
  1661.         
  1662.         return elem
  1663.  
  1664.  
  1665.  
  1666. class TOC(object):
  1667.     
  1668.     def __init__(self, title = None, href = None, klass = None, id = None, play_order = None, author = None, description = None):
  1669.         self.title = title
  1670.         self.href = None if href else href
  1671.         self.klass = klass
  1672.         self.id = id
  1673.         self.nodes = []
  1674.         self.play_order = 0
  1675.         if play_order is None:
  1676.             play_order = self.next_play_order()
  1677.         
  1678.         self.play_order = play_order
  1679.         self.author = author
  1680.         self.description = description
  1681.  
  1682.     
  1683.     def add(self, title, href, klass = None, id = None, play_order = 0, author = None, description = None):
  1684.         node = TOC(title, href, klass, id, play_order, author, description)
  1685.         self.nodes.append(node)
  1686.         return node
  1687.  
  1688.     
  1689.     def remove(self, node):
  1690.         for child in self.nodes:
  1691.             if child is node:
  1692.                 self.nodes.remove(child)
  1693.                 return True
  1694.             if child.remove(node):
  1695.                 return True
  1696.         
  1697.         return False
  1698.  
  1699.     
  1700.     def iter(self):
  1701.         yield self
  1702.         for child in self.nodes:
  1703.             for node in child.iter():
  1704.                 yield node
  1705.             
  1706.         
  1707.  
  1708.     
  1709.     def count(self):
  1710.         return len(list(self.iter())) - 1
  1711.  
  1712.     
  1713.     def next_play_order(self):
  1714.         entries = [ x.play_order for x in self.iter() ]
  1715.         base = [] if entries else 0
  1716.         return base + 1
  1717.  
  1718.     
  1719.     def has_href(self, href):
  1720.         for x in self.iter():
  1721.             if x.href == href:
  1722.                 return True
  1723.         
  1724.         return False
  1725.  
  1726.     
  1727.     def has_text(self, text):
  1728.         for x in self.iter():
  1729.             if x.title and x.title.lower() == text.lower():
  1730.                 return True
  1731.         
  1732.         return False
  1733.  
  1734.     
  1735.     def iterdescendants(self):
  1736.         for child in self.nodes:
  1737.             for node in child.iter():
  1738.                 yield node
  1739.             
  1740.         
  1741.  
  1742.     
  1743.     def __iter__(self):
  1744.         for node in self.nodes:
  1745.             yield node
  1746.         
  1747.  
  1748.     
  1749.     def __getitem__(self, index):
  1750.         return self.nodes[index]
  1751.  
  1752.     
  1753.     def autolayer(self):
  1754.         prev = None
  1755.         for node in list(self.nodes):
  1756.             if prev and urldefrag(prev.href)[0] == urldefrag(node.href)[0]:
  1757.                 self.nodes.remove(node)
  1758.                 prev.nodes.append(node)
  1759.                 continue
  1760.             prev = node
  1761.         
  1762.  
  1763.     
  1764.     def depth(self):
  1765.         
  1766.         try:
  1767.             return max((lambda .0: for node in .0:
  1768. node.depth())(self.nodes)) + 1
  1769.         except ValueError:
  1770.             return 1
  1771.  
  1772.  
  1773.     
  1774.     def __str__(self):
  1775.         return 'TOC: %s --> %s' % (self.title, self.href)
  1776.  
  1777.     
  1778.     def to_opf1(self, tour):
  1779.         for node in self.nodes:
  1780.             element(tour, 'site', attrib = {
  1781.                 'title': node.title,
  1782.                 'href': urlunquote(node.href) })
  1783.             node.to_opf1(tour)
  1784.         
  1785.         return tour
  1786.  
  1787.     
  1788.     def to_ncx(self, parent = None):
  1789.         if parent is None:
  1790.             parent = etree.Element(NCX('navMap'))
  1791.         
  1792.         for node in self.nodes:
  1793.             if not node.id:
  1794.                 pass
  1795.             id = unicode(uuid.uuid4())
  1796.             po = node.play_order
  1797.             if po == 0:
  1798.                 po = 1
  1799.             
  1800.             attrib = {
  1801.                 'id': id,
  1802.                 'playOrder': str(po) }
  1803.             if node.klass:
  1804.                 attrib['class'] = node.klass
  1805.             
  1806.             point = element(parent, NCX('navPoint'), attrib = attrib)
  1807.             label = etree.SubElement(point, NCX('navLabel'))
  1808.             title = node.title
  1809.             if title:
  1810.                 title = re.sub('\\s+', ' ', title)
  1811.             
  1812.             element(label, NCX('text')).text = title
  1813.             element(point, NCX('content'), src = urlunquote(node.href))
  1814.             node.to_ncx(point)
  1815.         
  1816.         return parent
  1817.  
  1818.     
  1819.     def rationalize_play_orders(self):
  1820.         
  1821.         def po_node(n):
  1822.             for x in self.iter():
  1823.                 if x is n:
  1824.                     return None
  1825.                 if x.play_order == n.play_order:
  1826.                     return x
  1827.             
  1828.  
  1829.         
  1830.         def href_node(n):
  1831.             for x in self.iter():
  1832.                 if x is n:
  1833.                     return None
  1834.                 if x.href == n.href:
  1835.                     return x
  1836.             
  1837.  
  1838.         for x in self.iter():
  1839.             y = po_node(x)
  1840.             if y is not None:
  1841.                 if x.href != y.href:
  1842.                     x.play_order = getattr(href_node(x), 'play_order', self.next_play_order())
  1843.                 
  1844.             
  1845.             y = href_node(x)
  1846.             if y is not None:
  1847.                 x.play_order = y.play_order
  1848.                 continue
  1849.         
  1850.  
  1851.  
  1852.  
  1853. class PageList(object):
  1854.     
  1855.     class Page(object):
  1856.         TYPES = set([
  1857.             'front',
  1858.             'normal',
  1859.             'special'])
  1860.         
  1861.         def __init__(self, name, href, type = 'normal', klass = None, id = None):
  1862.             self.name = unicode(name)
  1863.             self.href = urlnormalize(href)
  1864.             self.type = None if type in self.TYPES else 'normal'
  1865.             self.id = id
  1866.             self.klass = klass
  1867.  
  1868.  
  1869.     
  1870.     def __init__(self):
  1871.         self.pages = []
  1872.  
  1873.     
  1874.     def add(self, name, href, type = 'normal', klass = None, id = None):
  1875.         page = self.Page(name, href, type, klass, id)
  1876.         self.pages.append(page)
  1877.         return page
  1878.  
  1879.     
  1880.     def __len__(self):
  1881.         return len(self.pages)
  1882.  
  1883.     
  1884.     def __iter__(self):
  1885.         for page in self.pages:
  1886.             yield page
  1887.         
  1888.  
  1889.     
  1890.     def __getitem__(self, index):
  1891.         return self.pages[index]
  1892.  
  1893.     
  1894.     def pop(self, index = -1):
  1895.         return self.pages.pop(index)
  1896.  
  1897.     
  1898.     def remove(self, page):
  1899.         return self.pages.remove(page)
  1900.  
  1901.     
  1902.     def to_ncx(self, parent = None):
  1903.         plist = element(parent, NCX('pageList'), id = str(uuid.uuid4()))
  1904.         values = dict((lambda .0: for t in .0:
  1905. (t, count(1)))(('front', 'normal', 'special')))
  1906.         for page in self.pages:
  1907.             if not page.id:
  1908.                 pass
  1909.             id = unicode(uuid.uuid4())
  1910.             type = page.type
  1911.             value = str(values[type].next())
  1912.             attrib = {
  1913.                 'id': id,
  1914.                 'value': value,
  1915.                 'type': type,
  1916.                 'playOrder': '0' }
  1917.             if page.klass:
  1918.                 attrib['class'] = page.klass
  1919.             
  1920.             ptarget = element(plist, NCX('pageTarget'), attrib = attrib)
  1921.             label = element(ptarget, NCX('navLabel'))
  1922.             element(label, NCX('text')).text = page.name
  1923.             element(ptarget, NCX('content'), src = page.href)
  1924.         
  1925.         return plist
  1926.  
  1927.     
  1928.     def to_page_map(self):
  1929.         pmap = etree.Element(OPF('page-map'), nsmap = {
  1930.             None: OPF2_NS })
  1931.         for page in self.pages:
  1932.             element(pmap, OPF('page'), name = page.name, href = page.href)
  1933.         
  1934.         return pmap
  1935.  
  1936.  
  1937.  
  1938. class OEBBook(object):
  1939.     COVER_SVG_XP = XPath('h:body//svg:svg[position() = 1]')
  1940.     COVER_OBJECT_XP = XPath('h:body//h:object[@data][position() = 1]')
  1941.     
  1942.     def __init__(self, logger, html_preprocessor, css_preprocessor = CSSPreProcessor(), encoding = 'utf-8', pretty_print = False, input_encoding = 'utf-8'):
  1943.         _css_log_handler.log = logger
  1944.         self.encoding = encoding
  1945.         self.input_encoding = input_encoding
  1946.         self.html_preprocessor = html_preprocessor
  1947.         self.css_preprocessor = css_preprocessor
  1948.         self.pretty_print = pretty_print
  1949.         self.logger = self.log = logger
  1950.         self.version = '2.0'
  1951.         self.container = NullContainer(self.log)
  1952.         self.metadata = Metadata(self)
  1953.         self.uid = None
  1954.         self.manifest = Manifest(self)
  1955.         self.spine = Spine(self)
  1956.         self.guide = Guide(self)
  1957.         self.toc = TOC()
  1958.         self.pages = PageList()
  1959.         self.auto_generated_toc = True
  1960.  
  1961.     
  1962.     def generate(cls, opts):
  1963.         encoding = opts.encoding
  1964.         pretty_print = opts.pretty_print
  1965.         return cls(encoding = encoding, pretty_print = pretty_print)
  1966.  
  1967.     generate = classmethod(generate)
  1968.     
  1969.     def translate(self, text):
  1970.         lang = str(self.metadata.language[0])
  1971.         lang = lang.split('-', 1)[0].lower()
  1972.         return translate(lang, text)
  1973.  
  1974.     
  1975.     def decode(self, data):
  1976.         
  1977.         def fix_data(d):
  1978.             return d.replace('\r\n', '\n').replace('\r', '\n')
  1979.  
  1980.         if isinstance(data, unicode):
  1981.             return fix_data(data)
  1982.         bom_enc = None
  1983.         if data[:4] in ('\x00\x00\xfe\xff', '\xff\xfe\x00\x00'):
  1984.             bom_enc = {
  1985.                 '\x00\x00\xfe\xff': 'utf-32-be',
  1986.                 '\xff\xfe\x00\x00': 'utf-32-le' }[data[:4]]
  1987.             data = data[4:]
  1988.         elif data[:2] in ('\xff\xfe', '\xfe\xff'):
  1989.             bom_enc = {
  1990.                 '\xff\xfe': 'utf-16-le',
  1991.                 '\xfe\xff': 'utf-16-be' }[data[:2]]
  1992.             data = data[2:]
  1993.         elif data[:3] == '\xef\xbb\xbf':
  1994.             bom_enc = 'utf-8'
  1995.             data = data[3:]
  1996.         
  1997.         if bom_enc is not None:
  1998.             
  1999.             try:
  2000.                 return fix_data(data.decode(bom_enc))
  2001.             except UnicodeDecodeError:
  2002.                 pass
  2003.             except:
  2004.                 None<EXCEPTION MATCH>UnicodeDecodeError
  2005.             
  2006.  
  2007.         None<EXCEPTION MATCH>UnicodeDecodeError
  2008.         if self.input_encoding is not None:
  2009.             
  2010.             try:
  2011.                 return fix_data(data.decode(self.input_encoding, 'replace'))
  2012.             except UnicodeDecodeError:
  2013.                 pass
  2014.             except:
  2015.                 None<EXCEPTION MATCH>UnicodeDecodeError
  2016.             
  2017.  
  2018.         None<EXCEPTION MATCH>UnicodeDecodeError
  2019.         
  2020.         try:
  2021.             return fix_data(data.decode('utf-8'))
  2022.         except UnicodeDecodeError:
  2023.             pass
  2024.  
  2025.         (data, _) = xml_to_unicode(data)
  2026.         return fix_data(data)
  2027.  
  2028.     
  2029.     def to_opf1(self):
  2030.         package = etree.Element('package', attrib = {
  2031.             'unique-identifier': self.uid.id })
  2032.         self.metadata.to_opf1(package)
  2033.         self.manifest.to_opf1(package)
  2034.         self.spine.to_opf1(package)
  2035.         tours = element(package, 'tours')
  2036.         tour = element(tours, 'tour', attrib = {
  2037.             'id': 'chaptertour',
  2038.             'title': 'Chapter Tour' })
  2039.         self.toc.to_opf1(tour)
  2040.         self.guide.to_opf1(package)
  2041.         return {
  2042.             OPF_MIME: ('content.opf', package) }
  2043.  
  2044.     
  2045.     def _update_playorder(self, ncx):
  2046.         hrefs = set(map(urlnormalize, xpath(ncx, '//ncx:content/@src')))
  2047.         playorder = { }
  2048.         next = 1
  2049.         selector = XPath('h:body//*[@id or @name]')
  2050.         for item in self.spine:
  2051.             base = item.href
  2052.             if base in hrefs:
  2053.                 playorder[base] = next
  2054.                 next += 1
  2055.             
  2056.             for elem in selector(item.data):
  2057.                 added = False
  2058.                 for attr in ('id', 'name'):
  2059.                     id = elem.get(attr)
  2060.                     if not id:
  2061.                         continue
  2062.                     
  2063.                     href = '#'.join([
  2064.                         base,
  2065.                         id])
  2066.                     if href in hrefs:
  2067.                         playorder[href] = next
  2068.                         added = True
  2069.                         continue
  2070.                 
  2071.                 if added:
  2072.                     next += 1
  2073.                     continue
  2074.             
  2075.         
  2076.         selector = XPath('ncx:content/@src')
  2077.         for i, elem in enumerate(xpath(ncx, '//*[@playOrder and ./ncx:content[@src]]')):
  2078.             href = urlnormalize(selector(elem)[0])
  2079.             order = playorder.get(href, i)
  2080.             elem.attrib['playOrder'] = str(order)
  2081.         
  2082.  
  2083.     
  2084.     def _to_ncx(self):
  2085.         lang = unicode(self.metadata.language[0])
  2086.         ncx = etree.Element(NCX('ncx'), attrib = {
  2087.             'version': '2005-1',
  2088.             XML('lang'): lang }, nsmap = {
  2089.             None: NCX_NS })
  2090.         head = etree.SubElement(ncx, NCX('head'))
  2091.         etree.SubElement(head, NCX('meta'), name = 'dtb:uid', content = unicode(self.uid))
  2092.         etree.SubElement(head, NCX('meta'), name = 'dtb:depth', content = str(self.toc.depth()))
  2093.         generator = ''.join([
  2094.             'calibre (',
  2095.             calibre.__version__,
  2096.             ')'])
  2097.         etree.SubElement(head, NCX('meta'), name = 'dtb:generator', content = generator)
  2098.         etree.SubElement(head, NCX('meta'), name = 'dtb:totalPageCount', content = str(len(self.pages)))
  2099.         maxpnum = etree.SubElement(head, NCX('meta'), name = 'dtb:maxPageNumber', content = '0')
  2100.         title = etree.SubElement(ncx, NCX('docTitle'))
  2101.         text = etree.SubElement(title, NCX('text'))
  2102.         text.text = unicode(self.metadata.title[0])
  2103.         navmap = etree.SubElement(ncx, NCX('navMap'))
  2104.         self.toc.to_ncx(navmap)
  2105.         if len(self.pages) > 0:
  2106.             plist = self.pages.to_ncx(ncx)
  2107.             value = max((lambda .0: for x in .0:
  2108. int(x))(xpath(plist, '//@value')))
  2109.             maxpnum.attrib['content'] = str(value)
  2110.         
  2111.         self._update_playorder(ncx)
  2112.         return ncx
  2113.  
  2114.     
  2115.     def to_opf2(self, page_map = False):
  2116.         results = { }
  2117.         package = etree.Element(OPF('package'), attrib = {
  2118.             'version': '2.0',
  2119.             'unique-identifier': self.uid.id }, nsmap = {
  2120.             None: OPF2_NS })
  2121.         self.metadata.to_opf2(package)
  2122.         manifest = self.manifest.to_opf2(package)
  2123.         spine = self.spine.to_opf2(package)
  2124.         self.guide.to_opf2(package)
  2125.         results[OPF_MIME] = ('content.opf', package)
  2126.         (id, href) = self.manifest.generate('ncx', 'toc.ncx')
  2127.         etree.SubElement(manifest, OPF('item'), id = id, href = href, attrib = {
  2128.             'media-type': NCX_MIME })
  2129.         spine.attrib['toc'] = id
  2130.         results[NCX_MIME] = (href, self._to_ncx())
  2131.         if page_map and len(self.pages) > 0:
  2132.             (id, href) = self.manifest.generate('page-map', 'page-map.xml')
  2133.             etree.SubElement(manifest, OPF('item'), id = id, href = href, attrib = {
  2134.                 'media-type': PAGE_MAP_MIME })
  2135.             spine.attrib['page-map'] = id
  2136.             results[PAGE_MAP_MIME] = (href, self.pages.to_page_map())
  2137.         
  2138.         return results
  2139.  
  2140.  
  2141.