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