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

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. __license__ = 'GPL v3'
  5. __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
  6. __docformat__ = 'restructuredtext en'
  7. import re
  8. import sys
  9. import unittest
  10. import functools
  11. import os
  12. import mimetypes
  13. import uuid
  14. import glob
  15. import cStringIO
  16. from urllib import unquote
  17. from urlparse import urlparse
  18. from lxml import etree
  19. from calibre.ebooks.chardet import xml_to_unicode
  20. from calibre.constants import __appname__, __version__, filesystem_encoding
  21. from calibre.ebooks.metadata.toc import TOC
  22. from calibre.ebooks.metadata import MetaInformation, string_to_authors
  23. from calibre.utils.date import parse_date, isoformat
  24. from calibre.utils.localization import get_lang
  25.  
  26. class Resource(object):
  27.     
  28.     def __init__(self, href_or_path, basedir = os.getcwd(), is_path = True):
  29.         self.orig = href_or_path
  30.         self._href = None
  31.         self._basedir = basedir
  32.         self.path = None
  33.         self.fragment = ''
  34.         
  35.         try:
  36.             self.mime_type = mimetypes.guess_type(href_or_path)[0]
  37.         except:
  38.             self.mime_type = None
  39.  
  40.         if self.mime_type is None:
  41.             self.mime_type = 'application/octet-stream'
  42.         
  43.         if is_path:
  44.             path = href_or_path
  45.             if not os.path.isabs(path):
  46.                 path = os.path.abspath(os.path.join(basedir, path))
  47.             
  48.             if isinstance(path, str):
  49.                 path = path.decode(sys.getfilesystemencoding())
  50.             
  51.             self.path = path
  52.         else:
  53.             href_or_path = href_or_path
  54.             url = urlparse(href_or_path)
  55.             if url[0] not in ('', 'file'):
  56.                 self._href = href_or_path
  57.             else:
  58.                 pc = url[2]
  59.                 if isinstance(pc, unicode):
  60.                     pc = pc.encode('utf-8')
  61.                 
  62.                 pc = pc.decode('utf-8')
  63.                 self.path = os.path.abspath(os.path.join(basedir, pc.replace('/', os.sep)))
  64.                 self.fragment = url[-1]
  65.  
  66.     
  67.     def href(self, basedir = None):
  68.         if basedir is None:
  69.             if self._basedir:
  70.                 basedir = self._basedir
  71.             else:
  72.                 basedir = os.getcwd()
  73.         
  74.         if self.path is None:
  75.             return self._href
  76.         f = self.path is None if isinstance(self.fragment, unicode) else self.fragment
  77.         frag = None if self.fragment else ''
  78.         if self.path == basedir:
  79.             return '' + frag
  80.         
  81.         try:
  82.             rpath = os.path.relpath(self.path, basedir)
  83.         except ValueError:
  84.             self.path == basedir
  85.             self.path == basedir
  86.             rpath = self.path
  87.         except:
  88.             self.path == basedir
  89.  
  90.         if isinstance(rpath, unicode):
  91.             rpath = rpath.encode('utf-8')
  92.         
  93.         return rpath.replace(os.sep, '/') + frag
  94.  
  95.     
  96.     def set_basedir(self, path):
  97.         self._basedir = path
  98.  
  99.     
  100.     def basedir(self):
  101.         return self._basedir
  102.  
  103.     
  104.     def __repr__(self):
  105.         return 'Resource(%s, %s)' % (repr(self.path), repr(self.href()))
  106.  
  107.  
  108.  
  109. class ResourceCollection(object):
  110.     
  111.     def __init__(self):
  112.         self._resources = []
  113.  
  114.     
  115.     def __iter__(self):
  116.         for r in self._resources:
  117.             yield r
  118.         
  119.  
  120.     
  121.     def __len__(self):
  122.         return len(self._resources)
  123.  
  124.     
  125.     def __getitem__(self, index):
  126.         return self._resources[index]
  127.  
  128.     
  129.     def __bool__(self):
  130.         return len(self._resources) > 0
  131.  
  132.     
  133.     def __str__(self):
  134.         resources = map(repr, self)
  135.         return '[%s]' % ', '.join(resources)
  136.  
  137.     
  138.     def __repr__(self):
  139.         return str(self)
  140.  
  141.     
  142.     def append(self, resource):
  143.         if not isinstance(resource, Resource):
  144.             raise ValueError('Can only append objects of type Resource')
  145.         isinstance(resource, Resource)
  146.         self._resources.append(resource)
  147.  
  148.     
  149.     def remove(self, resource):
  150.         self._resources.remove(resource)
  151.  
  152.     
  153.     def replace(self, start, end, items):
  154.         self._resources[start:end] = items
  155.  
  156.     
  157.     def from_directory_contents(top, topdown = True):
  158.         collection = ResourceCollection()
  159.         for spec in os.walk(top, topdown = topdown):
  160.             path = os.path.abspath(os.path.join(spec[0], spec[1]))
  161.             res = Resource.from_path(path)
  162.             res.set_basedir(top)
  163.             collection.append(res)
  164.         
  165.         return collection
  166.  
  167.     from_directory_contents = staticmethod(from_directory_contents)
  168.     
  169.     def set_basedir(self, path):
  170.         for res in self:
  171.             res.set_basedir(path)
  172.         
  173.  
  174.  
  175.  
  176. class ManifestItem(Resource):
  177.     
  178.     def from_opf_manifest_item(item, basedir):
  179.         href = item.get('href', None)
  180.         if href:
  181.             res = ManifestItem(href, basedir = basedir, is_path = True)
  182.             mt = item.get('media-type', '').strip()
  183.             if mt:
  184.                 res.mime_type = mt
  185.             
  186.             return res
  187.  
  188.     from_opf_manifest_item = staticmethod(from_opf_manifest_item)
  189.     
  190.     def media_type(self):
  191.         
  192.         def fget(self):
  193.             return self.mime_type
  194.  
  195.         
  196.         def fset(self, val):
  197.             self.mime_type = val
  198.  
  199.         return property(fget = fget, fset = fset)
  200.  
  201.     media_type = dynamic_property(media_type)
  202.     
  203.     def __unicode__(self):
  204.         return u'<item id="%s" href="%s" media-type="%s" />' % (self.id, self.href(), self.media_type)
  205.  
  206.     
  207.     def __str__(self):
  208.         return unicode(self).encode('utf-8')
  209.  
  210.     
  211.     def __repr__(self):
  212.         return unicode(self)
  213.  
  214.     
  215.     def __getitem__(self, index):
  216.         if index == 0:
  217.             return self.href()
  218.         if index == 1:
  219.             return self.media_type
  220.         raise IndexError('%d out of bounds.' % index)
  221.  
  222.  
  223.  
  224. class Manifest(ResourceCollection):
  225.     
  226.     def from_opf_manifest_element(items, dir):
  227.         m = Manifest()
  228.         for item in items:
  229.             
  230.             try:
  231.                 m.append(ManifestItem.from_opf_manifest_item(item, dir))
  232.                 id = item.get('id', '')
  233.                 if not id:
  234.                     id = 'id%d' % m.next_id
  235.                 
  236.                 m[-1].id = id
  237.                 m.next_id += 1
  238.             continue
  239.             except ValueError:
  240.                 continue
  241.                 continue
  242.             
  243.  
  244.         
  245.         return m
  246.  
  247.     from_opf_manifest_element = staticmethod(from_opf_manifest_element)
  248.     
  249.     def from_paths(entries):
  250.         m = Manifest()
  251.         for path, mt in entries:
  252.             mi = ManifestItem(path, is_path = True)
  253.             if mt:
  254.                 mi.mime_type = mt
  255.             
  256.             mi.id = 'id%d' % m.next_id
  257.             m.next_id += 1
  258.             m.append(mi)
  259.         
  260.         return m
  261.  
  262.     from_paths = staticmethod(from_paths)
  263.     
  264.     def add_item(self, path, mime_type = None):
  265.         mi = ManifestItem(path, is_path = True)
  266.         if mime_type:
  267.             mi.mime_type = mime_type
  268.         
  269.         mi.id = 'id%d' % self.next_id
  270.         self.next_id += 1
  271.         self.append(mi)
  272.         return mi.id
  273.  
  274.     
  275.     def __init__(self):
  276.         ResourceCollection.__init__(self)
  277.         self.next_id = 1
  278.  
  279.     
  280.     def item(self, id):
  281.         for i in self:
  282.             if i.id == id:
  283.                 return i
  284.         
  285.  
  286.     
  287.     def id_for_path(self, path):
  288.         path = os.path.normpath(os.path.abspath(path))
  289.         for i in self:
  290.             if i.path and os.path.normpath(i.path) == path:
  291.                 return i.id
  292.         
  293.  
  294.     
  295.     def path_for_id(self, id):
  296.         for i in self:
  297.             if i.id == id:
  298.                 return i.path
  299.         
  300.  
  301.     
  302.     def type_for_id(self, id):
  303.         for i in self:
  304.             if i.id == id:
  305.                 return i.mime_type
  306.         
  307.  
  308.  
  309.  
  310. class Spine(ResourceCollection):
  311.     
  312.     class Item(Resource):
  313.         
  314.         def __init__(self, idfunc, *args, **kwargs):
  315.             Resource.__init__(self, *args, **kwargs)
  316.             self.is_linear = True
  317.             self.id = idfunc(self.path)
  318.             self.idref = None
  319.  
  320.         
  321.         def __repr__(self):
  322.             return 'Spine.Item(path=%r, id=%s, is_linear=%s)' % (self.path, self.id, self.is_linear)
  323.  
  324.  
  325.     
  326.     def from_opf_spine_element(itemrefs, manifest):
  327.         s = Spine(manifest)
  328.         for itemref in itemrefs:
  329.             idref = itemref.get('idref', None)
  330.             if idref is not None:
  331.                 path = s.manifest.path_for_id(idref)
  332.                 if path:
  333.                     r = (Spine.Item,)((lambda x: idref), path, is_path = True)
  334.                     r.is_linear = itemref.get('linear', 'yes') == 'yes'
  335.                     r.idref = idref
  336.                     s.append(r)
  337.                 
  338.             path
  339.         
  340.         return s
  341.  
  342.     from_opf_spine_element = staticmethod(from_opf_spine_element)
  343.     
  344.     def from_paths(paths, manifest):
  345.         s = Spine(manifest)
  346.         for path in paths:
  347.             
  348.             try:
  349.                 s.append(Spine.Item(s.manifest.id_for_path, path, is_path = True))
  350.             continue
  351.             continue
  352.             continue
  353.  
  354.         
  355.         return s
  356.  
  357.     from_paths = staticmethod(from_paths)
  358.     
  359.     def __init__(self, manifest):
  360.         ResourceCollection.__init__(self)
  361.         self.manifest = manifest
  362.  
  363.     
  364.     def replace(self, start, end, ids):
  365.         items = []
  366.         for path in ids:
  367.             id = None
  368.             if path is None:
  369.                 raise ValueError('id %s not in manifest')
  370.             path is None
  371.             items.append((Spine.Item,)((lambda x: id), path, is_path = True))
  372.         
  373.         ResourceCollection.replace(start, end, items)
  374.  
  375.     
  376.     def linear_items(self):
  377.         for r in self:
  378.             if r.is_linear:
  379.                 yield r.path
  380.                 continue
  381.         
  382.  
  383.     
  384.     def nonlinear_items(self):
  385.         for r in self:
  386.             if not r.is_linear:
  387.                 yield r.path
  388.                 continue
  389.         
  390.  
  391.     
  392.     def items(self):
  393.         for i in self:
  394.             yield i.path
  395.         
  396.  
  397.  
  398.  
  399. class Guide(ResourceCollection):
  400.     
  401.     class Reference(Resource):
  402.         
  403.         def from_opf_resource_item(ref, basedir):
  404.             title = ref.get('title', '')
  405.             href = ref.get('href')
  406.             type = ref.get('type')
  407.             res = Guide.Reference(href, basedir, is_path = True)
  408.             res.title = title
  409.             res.type = type
  410.             return res
  411.  
  412.         from_opf_resource_item = staticmethod(from_opf_resource_item)
  413.         
  414.         def __repr__(self):
  415.             ans = '<reference type="%s" href="%s" ' % (self.type, self.href())
  416.             if self.title:
  417.                 ans += 'title="%s" ' % self.title
  418.             
  419.             return ans + '/>'
  420.  
  421.  
  422.     
  423.     def from_opf_guide(references, base_dir = os.getcwdu()):
  424.         coll = Guide()
  425.         for ref in references:
  426.             
  427.             try:
  428.                 ref = Guide.Reference.from_opf_resource_item(ref, base_dir)
  429.                 coll.append(ref)
  430.             continue
  431.             continue
  432.             continue
  433.  
  434.         
  435.         return coll
  436.  
  437.     from_opf_guide = staticmethod(from_opf_guide)
  438.     
  439.     def set_cover(self, path):
  440.         []([], _[1])
  441.         for type in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
  442.             self.append(Guide.Reference(path, is_path = True))
  443.             self[-1].type = type
  444.             self[-1].title = ''
  445.         
  446.  
  447.  
  448.  
  449. class MetadataField(object):
  450.     
  451.     def __init__(self, name, is_dc = True, formatter = None, none_is = None):
  452.         self.name = name
  453.         self.is_dc = is_dc
  454.         self.formatter = formatter
  455.         self.none_is = none_is
  456.  
  457.     
  458.     def __real_get__(self, obj, type = None):
  459.         ans = obj.get_metadata_element(self.name)
  460.         if ans is None:
  461.             return None
  462.         ans = obj.get_text(ans)
  463.         if ans is None:
  464.             return ans
  465.         if hasattr(ans, 'strip'):
  466.             ans = ans.strip()
  467.         
  468.         return ans
  469.  
  470.     
  471.     def __get__(self, obj, type = None):
  472.         ans = self.__real_get__(obj, type)
  473.         if ans is None:
  474.             ans = self.none_is
  475.         
  476.         return ans
  477.  
  478.     
  479.     def __set__(self, obj, val):
  480.         elem = obj.get_metadata_element(self.name)
  481.         if val is None:
  482.             if elem is not None:
  483.                 elem.getparent().remove(elem)
  484.             
  485.             return None
  486.         if elem is None:
  487.             elem = obj.create_metadata_element(self.name, is_dc = self.is_dc)
  488.         
  489.         obj.set_text(elem, unicode(val))
  490.  
  491.  
  492.  
  493. class OPF(object):
  494.     MIMETYPE = 'application/oebps-package+xml'
  495.     PARSER = etree.XMLParser(recover = True)
  496.     NAMESPACES = {
  497.         None: 'http://www.idpf.org/2007/opf',
  498.         'dc': 'http://purl.org/dc/elements/1.1/',
  499.         'opf': 'http://www.idpf.org/2007/opf' }
  500.     META = '{%s}meta' % NAMESPACES['opf']
  501.     xpn = NAMESPACES.copy()
  502.     xpn.pop(None)
  503.     xpn['re'] = 'http://exslt.org/regular-expressions'
  504.     XPath = functools.partial(etree.XPath, namespaces = xpn)
  505.     CONTENT = XPath('self::*[re:match(name(), "meta$", "i")]/@content')
  506.     TEXT = XPath('string()')
  507.     metadata_path = XPath('descendant::*[re:match(name(), "metadata", "i")]')
  508.     metadata_elem_path = XPath('descendant::*[re:match(name(), concat($name, "$"), "i") or (re:match(name(), "meta$", "i") and re:match(@name, concat("^calibre:", $name, "$"), "i"))]')
  509.     title_path = XPath('descendant::*[re:match(name(), "title", "i")]')
  510.     authors_path = XPath('descendant::*[re:match(name(), "creator", "i") and (@role="aut" or @opf:role="aut" or (not(@role) and not(@opf:role)))]')
  511.     bkp_path = XPath('descendant::*[re:match(name(), "contributor", "i") and (@role="bkp" or @opf:role="bkp")]')
  512.     tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]')
  513.     isbn_path = XPath('descendant::*[re:match(name(), "identifier", "i") and ' + '(re:match(@scheme, "isbn", "i") or re:match(@opf:scheme, "isbn", "i"))]')
  514.     raster_cover_path = XPath('descendant::*[re:match(name(), "meta", "i") and ' + 're:match(@name, "cover", "i") and @content]')
  515.     identifier_path = XPath('descendant::*[re:match(name(), "identifier", "i")]')
  516.     application_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and ' + '(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]')
  517.     uuid_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and ' + '(re:match(@opf:scheme, "uuid", "i") or re:match(@scheme, "uuid", "i"))]')
  518.     manifest_path = XPath('descendant::*[re:match(name(), "manifest", "i")]/*[re:match(name(), "item", "i")]')
  519.     manifest_ppath = XPath('descendant::*[re:match(name(), "manifest", "i")]')
  520.     spine_path = XPath('descendant::*[re:match(name(), "spine", "i")]/*[re:match(name(), "itemref", "i")]')
  521.     guide_path = XPath('descendant::*[re:match(name(), "guide", "i")]/*[re:match(name(), "reference", "i")]')
  522.     title = MetadataField('title', formatter = (lambda x: re.sub('\\s+', ' ', x)))
  523.     publisher = MetadataField('publisher')
  524.     language = MetadataField('language')
  525.     comments = MetadataField('description')
  526.     category = MetadataField('type')
  527.     rights = MetadataField('rights')
  528.     series = MetadataField('series', is_dc = False)
  529.     series_index = MetadataField('series_index', is_dc = False, formatter = float, none_is = 1)
  530.     rating = MetadataField('rating', is_dc = False, formatter = int)
  531.     pubdate = MetadataField('date', formatter = parse_date)
  532.     publication_type = MetadataField('publication_type', is_dc = False)
  533.     timestamp = MetadataField('timestamp', is_dc = False, formatter = parse_date)
  534.     
  535.     def __init__(self, stream, basedir = os.getcwdu(), unquote_urls = True, populate_spine = True):
  536.         if not hasattr(stream, 'read'):
  537.             stream = open(stream, 'rb')
  538.         
  539.         raw = stream.read()
  540.         if not raw:
  541.             raise ValueError('Empty file: ' + getattr(stream, 'name', 'stream'))
  542.         raw
  543.         self.basedir = self.base_dir = basedir
  544.         self.path_to_html_toc = None
  545.         self.html_toc_fragment = None
  546.         (raw, self.encoding) = xml_to_unicode(raw, strip_encoding_pats = True, resolve_entities = True, assume_utf8 = True)
  547.         raw = raw[raw.find('<'):]
  548.         self.root = etree.fromstring(raw, self.PARSER)
  549.         self.metadata = self.metadata_path(self.root)
  550.         if not self.metadata:
  551.             raise ValueError('Malformed OPF file: No <metadata> element')
  552.         self.metadata
  553.         self.metadata = self.metadata[0]
  554.         if unquote_urls:
  555.             self.unquote_urls()
  556.         
  557.         self.manifest = Manifest()
  558.         m = self.manifest_path(self.root)
  559.         if m:
  560.             self.manifest = Manifest.from_opf_manifest_element(m, basedir)
  561.         
  562.         self.spine = None
  563.         s = self.spine_path(self.root)
  564.         if populate_spine and s:
  565.             self.spine = Spine.from_opf_spine_element(s, self.manifest)
  566.         
  567.         self.guide = None
  568.         guide = self.guide_path(self.root)
  569.         self.guide = None if guide else None
  570.         self.cover_data = (None, None)
  571.         self.find_toc()
  572.  
  573.     
  574.     def find_toc(self):
  575.         self.toc = None
  576.         
  577.         try:
  578.             spine = self.XPath('descendant::*[re:match(name(), "spine", "i")]')(self.root)
  579.             toc = None
  580.             if spine:
  581.                 spine = spine[0]
  582.                 toc = spine.get('toc', None)
  583.             
  584.             if toc is None and self.guide:
  585.                 for item in self.guide:
  586.                     if item.type and item.type.lower() == 'toc':
  587.                         toc = item.path
  588.                         continue
  589.                 
  590.             
  591.             if toc is None:
  592.                 for item in self.manifest:
  593.                     if 'toc' in item.href().lower():
  594.                         toc = item.path
  595.                         continue
  596.                 
  597.             
  598.             if toc is None:
  599.                 return None
  600.             self.toc = TOC(base_path = self.base_dir)
  601.             if getattr(self, 'manifest', None) is not None and self.manifest.type_for_id(toc) is not None:
  602.                 pass
  603.             is_ncx = 'dtbncx' in self.manifest.type_for_id(toc)
  604.             if is_ncx or toc.lower() in ('ncx', 'ncxtoc'):
  605.                 path = self.manifest.path_for_id(toc)
  606.                 if path:
  607.                     self.toc.read_ncx_toc(path)
  608.                 else:
  609.                     f = glob.glob(os.path.join(self.base_dir, '*.ncx'))
  610.                     if f:
  611.                         self.toc.read_ncx_toc(f[0])
  612.                     
  613.             else:
  614.                 self.path_to_html_toc = toc.partition('#')[0]
  615.                 self.html_toc_fragment = toc.partition('#')[-1]
  616.                 if not os.access(self.path_to_html_toc, os.R_OK) or not os.path.isfile(self.path_to_html_toc):
  617.                     self.path_to_html_toc = None
  618.                 
  619.                 self.toc.read_html_toc(toc)
  620.         except:
  621.             pass
  622.  
  623.  
  624.     
  625.     def get_text(self, elem):
  626.         if not self.CONTENT(elem):
  627.             pass
  628.         return u''.join(self.TEXT(elem))
  629.  
  630.     
  631.     def set_text(self, elem, content):
  632.         if elem.tag == self.META:
  633.             elem.attrib['content'] = content
  634.         else:
  635.             elem.text = content
  636.  
  637.     
  638.     def itermanifest(self):
  639.         return self.manifest_path(self.root)
  640.  
  641.     
  642.     def create_manifest_item(self, href, media_type):
  643.         ids = [ i.get('id', None) for i in self.itermanifest() ]
  644.         id = None
  645.         for c in xrange(1, sys.maxint):
  646.             id = 'id%d' % c
  647.             if id not in ids:
  648.                 break
  649.                 continue
  650.             []
  651.         
  652.         if not media_type:
  653.             media_type = 'application/xhtml+xml'
  654.         
  655.         ans = etree.Element('{%s}item' % self.NAMESPACES['opf'], attrib = {
  656.             'id': id,
  657.             'href': href,
  658.             'media-type': media_type })
  659.         ans.tail = '\n\t\t'
  660.         return ans
  661.  
  662.     
  663.     def replace_manifest_item(self, item, items):
  664.         items = [ self.create_manifest_item(*i) for i in items ]
  665.         for i, item2 in enumerate(items):
  666.             item2.set('id', item.get('id') + '.%d' % (i + 1))
  667.         
  668.         manifest = item.getparent()
  669.         index = manifest.index(item)
  670.         manifest[index:index + 1] = items
  671.         return [ i.get('id') for i in items ]
  672.  
  673.     
  674.     def add_path_to_manifest(self, path, media_type):
  675.         has_path = False
  676.         path = os.path.abspath(path)
  677.         for i in self.itermanifest():
  678.             xpath = os.path.join(self.base_dir, *i.get('href', '').split('/'))
  679.             if os.path.abspath(xpath) == path:
  680.                 has_path = True
  681.                 break
  682.                 continue
  683.         
  684.         if not has_path:
  685.             href = os.path.relpath(path, self.base_dir).replace(os.sep, '/')
  686.             item = self.create_manifest_item(href, media_type)
  687.             manifest = self.manifest_ppath(self.root)[0]
  688.             manifest.append(item)
  689.         
  690.  
  691.     
  692.     def iterspine(self):
  693.         return self.spine_path(self.root)
  694.  
  695.     
  696.     def spine_items(self):
  697.         for item in self.iterspine():
  698.             idref = item.get('idref', '')
  699.             for x in self.itermanifest():
  700.                 if x.get('id', None) == idref:
  701.                     yield x.get('href', '')
  702.                     continue
  703.             
  704.         
  705.  
  706.     
  707.     def first_spine_item(self):
  708.         items = self.iterspine()
  709.         if not items:
  710.             return None
  711.         idref = items[0].get('idref', '')
  712.         for x in self.itermanifest():
  713.             if x.get('id', None) == idref:
  714.                 return x.get('href', None)
  715.         
  716.  
  717.     
  718.     def create_spine_item(self, idref):
  719.         ans = etree.Element('{%s}itemref' % self.NAMESPACES['opf'], idref = idref)
  720.         ans.tail = '\n\t\t'
  721.         return ans
  722.  
  723.     
  724.     def replace_spine_items_by_idref(self, idref, new_idrefs):
  725.         items = list(map(self.create_spine_item, new_idrefs))
  726.         spine = self.XPath('/opf:package/*[re:match(name(), "spine", "i")]')(self.root)[0]
  727.         old = _[1]
  728.         for x in old:
  729.             i = spine.index(x)
  730.             spine[i:i + 1] = items
  731.         
  732.  
  733.     
  734.     def create_guide_element(self):
  735.         e = etree.SubElement(self.root, '{%s}guide' % self.NAMESPACES['opf'])
  736.         e.text = '\n        '
  737.         e.tail = '\n'
  738.         return e
  739.  
  740.     
  741.     def remove_guide(self):
  742.         self.guide = None
  743.         for g in self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces = {
  744.             're': 'http://exslt.org/regular-expressions' }):
  745.             self.root.remove(g)
  746.         
  747.  
  748.     
  749.     def create_guide_item(self, type, title, href):
  750.         e = etree.Element('{%s}reference' % self.NAMESPACES['opf'], type = type, title = title, href = href)
  751.         e.tail = '\n'
  752.         return e
  753.  
  754.     
  755.     def add_guide_item(self, type, title, href):
  756.         g = self.root.xpath('./*[re:match(name(), "guide", "i")]', namespaces = {
  757.             're': 'http://exslt.org/regular-expressions' })[0]
  758.         g.append(self.create_guide_item(type, title, href))
  759.  
  760.     
  761.     def iterguide(self):
  762.         return self.guide_path(self.root)
  763.  
  764.     
  765.     def unquote_urls(self):
  766.         
  767.         def get_href(item):
  768.             raw = unquote(item.get('href', ''))
  769.             if not isinstance(raw, unicode):
  770.                 raw = raw.decode('utf-8')
  771.             
  772.             return raw
  773.  
  774.         for item in self.itermanifest():
  775.             item.set('href', get_href(item))
  776.         
  777.         for item in self.iterguide():
  778.             item.set('href', get_href(item))
  779.         
  780.  
  781.     
  782.     def authors(self):
  783.         
  784.         def fget(self):
  785.             ans = []
  786.             for elem in self.authors_path(self.metadata):
  787.                 ans.extend(string_to_authors(self.get_text(elem)))
  788.             
  789.             return ans
  790.  
  791.         
  792.         def fset(self, val):
  793.             remove = list(self.authors_path(self.metadata))
  794.             for elem in remove:
  795.                 elem.getparent().remove(elem)
  796.             
  797.             elems = []
  798.             for author in val:
  799.                 attrib = {
  800.                     '{%s}role' % self.NAMESPACES['opf']: 'aut' }
  801.                 elem = self.create_metadata_element('creator', attrib = attrib)
  802.                 self.set_text(elem, author.strip())
  803.                 elems.append(elem)
  804.             
  805.             for elem in reversed(elems):
  806.                 parent = elem.getparent()
  807.                 parent.remove(elem)
  808.                 parent.insert(0, elem)
  809.             
  810.  
  811.         return property(fget = fget, fset = fset)
  812.  
  813.     authors = dynamic_property(authors)
  814.     
  815.     def author_sort(self):
  816.         
  817.         def fget(self):
  818.             matches = self.authors_path(self.metadata)
  819.             if matches:
  820.                 for match in matches:
  821.                     ans = match.get('{%s}file-as' % self.NAMESPACES['opf'], None)
  822.                     if not ans:
  823.                         ans = match.get('file-as', None)
  824.                     
  825.                     if ans:
  826.                         return ans
  827.                 
  828.             
  829.  
  830.         
  831.         def fset(self, val):
  832.             matches = self.authors_path(self.metadata)
  833.             if matches:
  834.                 for key in matches[0].attrib:
  835.                     if key.endswith('file-as'):
  836.                         matches[0].attrib.pop(key)
  837.                         continue
  838.                 
  839.                 matches[0].set('{%s}file-as' % self.NAMESPACES['opf'], unicode(val))
  840.             
  841.  
  842.         return property(fget = fget, fset = fset)
  843.  
  844.     author_sort = dynamic_property(author_sort)
  845.     
  846.     def title_sort(self):
  847.         
  848.         def fget(self):
  849.             matches = self.title_path(self.metadata)
  850.             if matches:
  851.                 for match in matches:
  852.                     ans = match.get('{%s}file-as' % self.NAMESPACES['opf'], None)
  853.                     if not ans:
  854.                         ans = match.get('file-as', None)
  855.                     
  856.                     if ans:
  857.                         return ans
  858.                 
  859.             
  860.  
  861.         
  862.         def fset(self, val):
  863.             matches = self.title_path(self.metadata)
  864.             if matches:
  865.                 for key in matches[0].attrib:
  866.                     if key.endswith('file-as'):
  867.                         matches[0].attrib.pop(key)
  868.                         continue
  869.                 
  870.                 matches[0].set('{%s}file-as' % self.NAMESPACES['opf'], unicode(val))
  871.             
  872.  
  873.         return property(fget = fget, fset = fset)
  874.  
  875.     title_sort = dynamic_property(title_sort)
  876.     
  877.     def tags(self):
  878.         
  879.         def fget(self):
  880.             ans = []
  881.             for tag in self.tags_path(self.metadata):
  882.                 text = self.get_text(tag)
  883.                 if text and text.strip():
  884.                     []([ x.strip() for x in text.split(',') ])
  885.                     continue
  886.                 []
  887.             
  888.             return ans
  889.  
  890.         
  891.         def fset(self, val):
  892.             for tag in list(self.tags_path(self.metadata)):
  893.                 tag.getparent().remove(tag)
  894.             
  895.             for tag in val:
  896.                 elem = self.create_metadata_element('subject')
  897.                 self.set_text(elem, unicode(tag))
  898.             
  899.  
  900.         return property(fget = fget, fset = fset)
  901.  
  902.     tags = dynamic_property(tags)
  903.     
  904.     def isbn(self):
  905.         
  906.         def fget(self):
  907.             for match in self.isbn_path(self.metadata):
  908.                 if not self.get_text(match):
  909.                     pass
  910.                 return None
  911.             
  912.  
  913.         
  914.         def fset(self, val):
  915.             matches = self.isbn_path(self.metadata)
  916.             if val is None:
  917.                 if matches:
  918.                     for x in matches:
  919.                         x.getparent().remove(x)
  920.                     
  921.                     return None
  922.             
  923.             if not matches:
  924.                 attrib = {
  925.                     '{%s}scheme' % self.NAMESPACES['opf']: 'ISBN' }
  926.                 matches = [
  927.                     self.create_metadata_element('identifier', attrib = attrib)]
  928.             
  929.             self.set_text(matches[0], unicode(val))
  930.  
  931.         return property(fget = fget, fset = fset)
  932.  
  933.     isbn = dynamic_property(isbn)
  934.     
  935.     def application_id(self):
  936.         
  937.         def fget(self):
  938.             for match in self.application_id_path(self.metadata):
  939.                 if not self.get_text(match):
  940.                     pass
  941.                 return None
  942.             
  943.  
  944.         
  945.         def fset(self, val):
  946.             matches = self.application_id_path(self.metadata)
  947.             if not matches:
  948.                 attrib = {
  949.                     '{%s}scheme' % self.NAMESPACES['opf']: 'calibre' }
  950.                 matches = [
  951.                     self.create_metadata_element('identifier', attrib = attrib)]
  952.             
  953.             self.set_text(matches[0], unicode(val))
  954.  
  955.         return property(fget = fget, fset = fset)
  956.  
  957.     application_id = dynamic_property(application_id)
  958.     
  959.     def uuid(self):
  960.         
  961.         def fget(self):
  962.             for match in self.uuid_id_path(self.metadata):
  963.                 if not self.get_text(match):
  964.                     pass
  965.                 return None
  966.             
  967.  
  968.         
  969.         def fset(self, val):
  970.             matches = self.uuid_id_path(self.metadata)
  971.             if not matches:
  972.                 attrib = {
  973.                     '{%s}scheme' % self.NAMESPACES['opf']: 'uuid' }
  974.                 matches = [
  975.                     self.create_metadata_element('identifier', attrib = attrib)]
  976.             
  977.             self.set_text(matches[0], unicode(val))
  978.  
  979.         return property(fget = fget, fset = fset)
  980.  
  981.     uuid = dynamic_property(uuid)
  982.     
  983.     def book_producer(self):
  984.         
  985.         def fget(self):
  986.             for match in self.bkp_path(self.metadata):
  987.                 if not self.get_text(match):
  988.                     pass
  989.                 return None
  990.             
  991.  
  992.         
  993.         def fset(self, val):
  994.             matches = self.bkp_path(self.metadata)
  995.             if not matches:
  996.                 attrib = {
  997.                     '{%s}role' % self.NAMESPACES['opf']: 'bkp' }
  998.                 matches = [
  999.                     self.create_metadata_element('contributor', attrib = attrib)]
  1000.             
  1001.             self.set_text(matches[0], unicode(val))
  1002.  
  1003.         return property(fget = fget, fset = fset)
  1004.  
  1005.     book_producer = dynamic_property(book_producer)
  1006.     
  1007.     def identifier_iter(self):
  1008.         for item in self.identifier_path(self.metadata):
  1009.             yield item
  1010.         
  1011.  
  1012.     
  1013.     def guess_cover(self):
  1014.         if self.base_dir and os.path.exists(self.base_dir):
  1015.             for item in self.identifier_path(self.metadata):
  1016.                 scheme = None
  1017.                 for key in item.attrib.keys():
  1018.                     if key.endswith('scheme'):
  1019.                         scheme = item.get(key)
  1020.                         break
  1021.                         continue
  1022.                 
  1023.                 if scheme is None:
  1024.                     continue
  1025.                 
  1026.                 if item.text:
  1027.                     prefix = item.text.replace('-', '')
  1028.                     for suffix in [
  1029.                         '.jpg',
  1030.                         '.jpeg',
  1031.                         '.gif',
  1032.                         '.png',
  1033.                         '.bmp']:
  1034.                         cpath = os.access(os.path.join(self.base_dir, prefix + suffix), os.R_OK)
  1035.                         if os.access(os.path.join(self.base_dir, prefix + suffix), os.R_OK):
  1036.                             return cpath
  1037.                     
  1038.                 os.access(os.path.join(self.base_dir, prefix + suffix), os.R_OK)
  1039.             
  1040.         
  1041.  
  1042.     
  1043.     def raster_cover(self):
  1044.         covers = self.raster_cover_path(self.metadata)
  1045.         if covers:
  1046.             cover_id = covers[0].get('content')
  1047.             for item in self.itermanifest():
  1048.                 if item.get('id', None) == cover_id:
  1049.                     return item.get('href', None)
  1050.             
  1051.         
  1052.  
  1053.     raster_cover = property(raster_cover)
  1054.     
  1055.     def cover(self):
  1056.         
  1057.         def fget(self):
  1058.             if self.guide is not None:
  1059.                 for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
  1060.                     for item in self.guide:
  1061.                         if item.type.lower() == t:
  1062.                             return item.path
  1063.                     
  1064.                 
  1065.             
  1066.             
  1067.             try:
  1068.                 return self.guess_cover()
  1069.             except:
  1070.                 pass
  1071.  
  1072.  
  1073.         
  1074.         def fset(self, path):
  1075.             if self.guide is not None:
  1076.                 self.guide.set_cover(path)
  1077.                 for item in list(self.iterguide()):
  1078.                     if 'cover' in item.get('type', ''):
  1079.                         item.getparent().remove(item)
  1080.                         continue
  1081.                 
  1082.             else:
  1083.                 g = self.create_guide_element()
  1084.                 self.guide = Guide()
  1085.                 self.guide.set_cover(path)
  1086.                 etree.SubElement(g, 'opf:reference', nsmap = self.NAMESPACES, attrib = {
  1087.                     'type': 'cover',
  1088.                     'href': self.guide[-1].href() })
  1089.             id = self.manifest.id_for_path(self.cover)
  1090.             if id is None:
  1091.                 for t in ('cover', 'other.ms-coverimage-standard', 'other.ms-coverimage'):
  1092.                     for item in self.guide:
  1093.                         if item.type.lower() == t:
  1094.                             self.create_manifest_item(item.href(), mimetypes.guess_type(path)[0])
  1095.                             continue
  1096.                     
  1097.                 
  1098.             
  1099.  
  1100.         return property(fget = fget, fset = fset)
  1101.  
  1102.     cover = dynamic_property(cover)
  1103.     
  1104.     def get_metadata_element(self, name):
  1105.         matches = self.metadata_elem_path(self.metadata, name = name)
  1106.         if matches:
  1107.             return matches[-1]
  1108.  
  1109.     
  1110.     def create_metadata_element(self, name, attrib = None, is_dc = True):
  1111.         if is_dc:
  1112.             name = '{%s}%s' % (self.NAMESPACES['dc'], name)
  1113.         elif not attrib:
  1114.             pass
  1115.         attrib = { }
  1116.         attrib['name'] = 'calibre:' + name
  1117.         name = '{%s}%s' % (self.NAMESPACES['opf'], 'meta')
  1118.         elem = etree.SubElement(self.metadata, name, attrib = attrib, nsmap = self.NAMESPACES)
  1119.         elem.tail = '\n'
  1120.         return elem
  1121.  
  1122.     
  1123.     def render(self, encoding = 'utf-8'):
  1124.         raw = etree.tostring(self.root, encoding = encoding, pretty_print = True)
  1125.         if not raw.lstrip().startswith('<?xml '):
  1126.             raw = '<?xml version="1.0"  encoding="%s"?>\n' % encoding.upper() + raw
  1127.         
  1128.         return raw
  1129.  
  1130.     
  1131.     def smart_update(self, mi, replace_metadata = False):
  1132.         for attr in ('title', 'authors', 'author_sort', 'title_sort', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'language', 'tags', 'category', 'comments', 'pubdate'):
  1133.             val = getattr(mi, attr, None)
  1134.             if val is not None and val != [] and val != (None, None):
  1135.                 setattr(self, attr, val)
  1136.                 continue
  1137.         
  1138.  
  1139.  
  1140.  
  1141. class OPFCreator(MetaInformation):
  1142.     
  1143.     def __init__(self, base_path, *args, **kwargs):
  1144.         MetaInformation.__init__(self, *args, **kwargs)
  1145.         self.base_path = os.path.abspath(base_path)
  1146.         if self.application_id is None:
  1147.             self.application_id = str(uuid.uuid4())
  1148.         
  1149.         if not isinstance(self.toc, TOC):
  1150.             self.toc = None
  1151.         
  1152.         if not self.authors:
  1153.             self.authors = [
  1154.                 _('Unknown')]
  1155.         
  1156.         if self.guide is None:
  1157.             self.guide = Guide()
  1158.         
  1159.         if self.cover:
  1160.             self.guide.set_cover(self.cover)
  1161.         
  1162.  
  1163.     
  1164.     def create_manifest(self, entries):
  1165.         entries = (map,)((lambda x: if os.path.isabs(x[0]):
  1166. x(os.path.abspath(os.path.join(self.base_path, x[0])), x[1])), entries)
  1167.         self.manifest = Manifest.from_paths(entries)
  1168.         self.manifest.set_basedir(self.base_path)
  1169.  
  1170.     
  1171.     def create_manifest_from_files_in(self, files_and_dirs):
  1172.         entries = []
  1173.         
  1174.         def dodir(dir):
  1175.             for spec in os.walk(dir):
  1176.                 root = spec[0]
  1177.                 files = spec[-1]
  1178.                 for name in files:
  1179.                     path = os.path.join(root, name)
  1180.                     if os.path.isfile(path):
  1181.                         entries.append((path, None))
  1182.                         continue
  1183.                 
  1184.             
  1185.  
  1186.         for i in files_and_dirs:
  1187.             if os.path.isdir(i):
  1188.                 dodir(i)
  1189.                 continue
  1190.             (None,)
  1191.             entries.append((i, None))
  1192.         
  1193.         self.create_manifest(entries)
  1194.  
  1195.     
  1196.     def create_spine(self, entries):
  1197.         entries = (map,)((lambda x: if os.path.isabs(x):
  1198. xos.path.abspath(os.path.join(self.base_path, x))), entries)
  1199.         self.spine = Spine.from_paths(entries, self.manifest)
  1200.  
  1201.     
  1202.     def set_toc(self, toc):
  1203.         self.toc = toc
  1204.  
  1205.     
  1206.     def create_guide(self, guide_element):
  1207.         self.guide = Guide.from_opf_guide(guide_element, self.base_path)
  1208.         self.guide.set_basedir(self.base_path)
  1209.  
  1210.     
  1211.     def render(self, opf_stream = sys.stdout, ncx_stream = None, ncx_manifest_entry = None, encoding = None):
  1212.         if encoding is None:
  1213.             encoding = 'utf-8'
  1214.         
  1215.         toc = getattr(self, 'toc', None)
  1216.         if self.manifest:
  1217.             self.manifest.set_basedir(self.base_path)
  1218.             if ncx_manifest_entry is not None and toc is not None:
  1219.                 if not os.path.isabs(ncx_manifest_entry):
  1220.                     ncx_manifest_entry = os.path.join(self.base_path, ncx_manifest_entry)
  1221.                 
  1222.                 remove = _[1]
  1223.                 for item in remove:
  1224.                     self.manifest.remove(item)
  1225.                 
  1226.                 self.manifest.append(ManifestItem(ncx_manifest_entry, self.base_path))
  1227.                 self.manifest[-1].id = 'ncx'
  1228.                 self.manifest[-1].mime_type = 'application/x-dtbncx+xml'
  1229.             
  1230.         
  1231.         if self.guide is None:
  1232.             self.guide = Guide()
  1233.         
  1234.         if self.cover:
  1235.             cover = self.cover
  1236.             if not os.path.isabs(cover):
  1237.                 cover = os.path.abspath(os.path.join(self.base_path, cover))
  1238.             
  1239.             self.guide.set_cover(cover)
  1240.         
  1241.         self.guide.set_basedir(self.base_path)
  1242.         ElementMaker = ElementMaker
  1243.         import lxml.builder
  1244.         OPF2_NS = OPF2_NS
  1245.         DC11_NS = DC11_NS
  1246.         CALIBRE_NS = CALIBRE_NS
  1247.         import calibre.ebooks.oeb.base
  1248.         DNS = OPF2_NS + '___xx___'
  1249.         E = ElementMaker(namespace = DNS, nsmap = {
  1250.             None: DNS })
  1251.         M = ElementMaker(namespace = DNS, nsmap = {
  1252.             'dc': DC11_NS,
  1253.             'calibre': CALIBRE_NS,
  1254.             'opf': OPF2_NS })
  1255.         DC = ElementMaker(namespace = DC11_NS)
  1256.         
  1257.         def DC_ELEM(tag, text, dc_attrs = None, opf_attrs = ({ }, { })):
  1258.             if text:
  1259.                 elem = getattr(DC, tag)(text, **dc_attrs)
  1260.             else:
  1261.                 elem = getattr(DC, tag)(**dc_attrs)
  1262.             for k, v in opf_attrs.items():
  1263.                 elem.set('{%s}%s' % (OPF2_NS, k), v)
  1264.             
  1265.             return elem
  1266.  
  1267.         
  1268.         def CAL_ELEM(name, content):
  1269.             return M.meta(name = name, content = content)
  1270.  
  1271.         metadata = M.metadata()
  1272.         a = metadata.append
  1273.         role = { }
  1274.         if self.title_sort:
  1275.             role = {
  1276.                 'file-as': self.title_sort }
  1277.         
  1278.         None(a(DC_ELEM, 'title' if self.title else _('Unknown'), opf_attrs = role))
  1279.         for i, author in enumerate(self.authors):
  1280.             fa = {
  1281.                 'role': 'aut' }
  1282.             if i == 0 and self.author_sort:
  1283.                 fa['file-as'] = self.author_sort
  1284.             
  1285.             a(DC_ELEM('creator', author, opf_attrs = fa))
  1286.         
  1287.         a(DC_ELEM('contributor', '%s (%s) [%s]' % (__appname__, __version__, 'http://calibre-ebook.com'), opf_attrs = {
  1288.             'role': 'bkp',
  1289.             'file-as': __appname__ }))
  1290.         a(DC_ELEM('identifier', str(self.application_id), opf_attrs = {
  1291.             'scheme': __appname__ }, dc_attrs = {
  1292.             'id': __appname__ + '_id' }))
  1293.         if getattr(self, 'pubdate', None) is not None:
  1294.             a(DC_ELEM('date', self.pubdate.isoformat()))
  1295.         
  1296.         lang = self.language
  1297.         if not lang or lang.lower() == 'und':
  1298.             lang = get_lang().replace('_', '-')
  1299.         
  1300.         a(DC_ELEM('language', lang))
  1301.         if self.comments:
  1302.             a(DC_ELEM('description', self.comments))
  1303.         
  1304.         if self.publisher:
  1305.             a(DC_ELEM('publisher', self.publisher))
  1306.         
  1307.         if self.isbn:
  1308.             a(DC_ELEM('identifier', self.isbn, opf_attrs = {
  1309.                 'scheme': 'ISBN' }))
  1310.         
  1311.         if self.rights:
  1312.             a(DC_ELEM('rights', self.rights))
  1313.         
  1314.         if self.tags:
  1315.             for tag in self.tags:
  1316.                 a(DC_ELEM('subject', tag))
  1317.             
  1318.         
  1319.         if self.series:
  1320.             a(CAL_ELEM('calibre:series', self.series))
  1321.             if self.series_index is not None:
  1322.                 a(CAL_ELEM('calibre:series_index', self.format_series_index()))
  1323.             
  1324.         
  1325.         if self.rating is not None:
  1326.             a(CAL_ELEM('calibre:rating', str(self.rating)))
  1327.         
  1328.         if self.timestamp is not None:
  1329.             a(CAL_ELEM('calibre:timestamp', self.timestamp.isoformat()))
  1330.         
  1331.         if self.publication_type is not None:
  1332.             a(CAL_ELEM('calibre:publication_type', self.publication_type))
  1333.         
  1334.         manifest = E.manifest()
  1335.         if self.manifest is not None:
  1336.             for ref in self.manifest:
  1337.                 item = E.item(id = str(ref.id), href = ref.href())
  1338.                 item.set('media-type', ref.mime_type)
  1339.                 manifest.append(item)
  1340.             
  1341.         
  1342.         spine = E.spine()
  1343.         if self.toc is not None:
  1344.             spine.set('toc', 'ncx')
  1345.         
  1346.         if self.spine is not None:
  1347.             for ref in self.spine:
  1348.                 spine.append(E.itemref(idref = ref.id))
  1349.             
  1350.         
  1351.         guide = E.guide()
  1352.         if self.guide is not None:
  1353.             for ref in self.guide:
  1354.                 item = E.reference(type = ref.type, href = ref.href())
  1355.                 if ref.title:
  1356.                     item.set('title', ref.title)
  1357.                 
  1358.                 guide.append(item)
  1359.             
  1360.         
  1361.         root = E.package(metadata, manifest, spine, guide)
  1362.         root.set('unique-identifier', __appname__ + '_id')
  1363.         raw = etree.tostring(root, pretty_print = True, xml_declaration = True, encoding = encoding)
  1364.         raw = raw.replace(DNS, OPF2_NS)
  1365.         opf_stream.write(raw)
  1366.         opf_stream.flush()
  1367.         if toc is not None and ncx_stream is not None:
  1368.             toc.render(ncx_stream, self.application_id)
  1369.             ncx_stream.flush()
  1370.         
  1371.  
  1372.  
  1373.  
  1374. def metadata_to_opf(mi, as_string = True):
  1375.     etree = etree
  1376.     import lxml
  1377.     import textwrap
  1378.     OPF = OPF
  1379.     DC = DC
  1380.     import calibre.ebooks.oeb.base
  1381.     if not mi.application_id:
  1382.         mi.application_id = str(uuid.uuid4())
  1383.     
  1384.     if not mi.uuid:
  1385.         mi.uuid = str(uuid.uuid4())
  1386.     
  1387.     if not mi.book_producer:
  1388.         mi.book_producer = __appname__ + ' (%s) ' % __version__ + '[http://calibre-ebook.com]'
  1389.     
  1390.     if not mi.language:
  1391.         mi.language = 'UND'
  1392.     
  1393.     root = etree.fromstring(textwrap.dedent('\n    <package xmlns="http://www.idpf.org/2007/opf" unique-identifier="uuid_id">\n        <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n            <dc:identifier opf:scheme="%(a)s" id="%(a)s_id">%(id)s</dc:identifier>\n            <dc:identifier opf:scheme="uuid" id="uuid_id">%(uuid)s</dc:identifier>\n        </metadata>\n        <guide/>\n    </package>\n    ' % dict(a = __appname__, id = mi.application_id, uuid = mi.uuid)))
  1394.     metadata = root[0]
  1395.     guide = root[1]
  1396.     metadata[0].tail = '\n' + '        '
  1397.     
  1398.     def factory(tag, text = None, sort = None, role = None, scheme = None, name = None, content = (None, None)):
  1399.         attrib = { }
  1400.         if sort:
  1401.             attrib[OPF('file-as')] = sort
  1402.         
  1403.         if role:
  1404.             attrib[OPF('role')] = role
  1405.         
  1406.         if scheme:
  1407.             attrib[OPF('scheme')] = scheme
  1408.         
  1409.         if name:
  1410.             attrib['name'] = name
  1411.         
  1412.         if content:
  1413.             attrib['content'] = content
  1414.         
  1415.         elem = metadata.makeelement(tag, attrib = attrib)
  1416.         elem.tail = '\n' + '        '
  1417.         if text:
  1418.             elem.text = text.strip()
  1419.         
  1420.         metadata.append(elem)
  1421.  
  1422.     factory(DC('title'), mi.title)
  1423.     for au in mi.authors:
  1424.         factory(DC('creator'), au, mi.author_sort, 'aut')
  1425.     
  1426.     factory(DC('contributor'), mi.book_producer, __appname__, 'bkp')
  1427.     if hasattr(mi.pubdate, 'isoformat'):
  1428.         factory(DC('date'), isoformat(mi.pubdate))
  1429.     
  1430.     if mi.category:
  1431.         factory(DC('type'), mi.category)
  1432.     
  1433.     if mi.comments:
  1434.         factory(DC('description'), mi.comments)
  1435.     
  1436.     if mi.publisher:
  1437.         factory(DC('publisher'), mi.publisher)
  1438.     
  1439.     if mi.isbn:
  1440.         factory(DC('identifier'), mi.isbn, scheme = 'ISBN')
  1441.     
  1442.     if mi.rights:
  1443.         factory(DC('rights'), mi.rights)
  1444.     
  1445.     None(factory, DC('language') if mi.language and mi.language.lower() != 'und' else get_lang().replace('_', '-'))
  1446.     if mi.tags:
  1447.         for tag in mi.tags:
  1448.             factory(DC('subject'), tag)
  1449.         
  1450.     
  1451.     
  1452.     meta = lambda n, c: factory('meta', name = 'calibre:' + n, content = c)
  1453.     if mi.series:
  1454.         meta('series', mi.series)
  1455.     
  1456.     if mi.series_index is not None:
  1457.         meta('series_index', mi.format_series_index())
  1458.     
  1459.     if mi.rating is not None:
  1460.         meta('rating', str(mi.rating))
  1461.     
  1462.     if hasattr(mi.timestamp, 'isoformat'):
  1463.         meta('timestamp', isoformat(mi.timestamp))
  1464.     
  1465.     if mi.publication_type:
  1466.         meta('publication_type', mi.publication_type)
  1467.     
  1468.     if mi.title_sort:
  1469.         meta('title_sort', mi.title_sort)
  1470.     
  1471.     metadata[-1].tail = '\n' + '    '
  1472.     if mi.cover:
  1473.         if not isinstance(mi.cover, unicode):
  1474.             mi.cover = mi.cover.decode(filesystem_encoding)
  1475.         
  1476.         guide.text = '\n' + '        '
  1477.         r = guide.makeelement(OPF('reference'), attrib = {
  1478.             'type': 'cover',
  1479.             'title': _('Cover'),
  1480.             'href': mi.cover })
  1481.         r.tail = '\n' + '    '
  1482.         guide.append(r)
  1483.     
  1484.     if as_string:
  1485.         return etree.tostring(root, pretty_print = True, encoding = 'utf-8', xml_declaration = True)
  1486.     return root
  1487.  
  1488.  
  1489. def test_m2o():
  1490.     nowf = now
  1491.     import calibre.utils.date
  1492.     StringIO = StringIO
  1493.     import cStringIO
  1494.     mi = MetaInformation('test & title', [
  1495.         'a"1',
  1496.         "a'2"])
  1497.     mi.title_sort = 'a\'"b'
  1498.     mi.author_sort = 'author sort'
  1499.     mi.pubdate = nowf()
  1500.     mi.language = 'en'
  1501.     mi.category = 'test'
  1502.     mi.comments = 'what a fun book\n\n'
  1503.     mi.publisher = 'publisher'
  1504.     mi.isbn = 'boooo'
  1505.     mi.tags = [
  1506.         'a',
  1507.         'b']
  1508.     mi.series = 's"c\'l&<>'
  1509.     mi.series_index = 3.34
  1510.     mi.rating = 3
  1511.     mi.timestamp = nowf()
  1512.     mi.publication_type = 'ooooo'
  1513.     mi.rights = 'yes'
  1514.     mi.cover = 'asd.jpg'
  1515.     opf = metadata_to_opf(mi)
  1516.     print opf
  1517.     newmi = MetaInformation(OPF(StringIO(opf)))
  1518.     for attr in ('author_sort', 'title_sort', 'comments', 'category', 'publisher', 'series', 'series_index', 'rating', 'isbn', 'tags', 'cover_data', 'application_id', 'language', 'cover', 'book_producer', 'timestamp', 'lccn', 'lcc', 'ddc', 'pubdate', 'rights', 'publication_type'):
  1519.         o = getattr(mi, attr)
  1520.         n = getattr(newmi, attr)
  1521.         if o != n and o.strip() != n.strip():
  1522.             print 'FAILED:', attr, getattr(mi, attr), '!=', getattr(newmi, attr)
  1523.             continue
  1524.     
  1525.  
  1526.  
  1527. class OPFTest(unittest.TestCase):
  1528.     
  1529.     def setUp(self):
  1530.         self.stream = cStringIO.StringIO('<?xml version="1.0"  encoding="UTF-8"?>\n<package version="2.0" xmlns="http://www.idpf.org/2007/opf" >\n<metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">\n    <dc:title opf:file-as="Wow">A Cool & © ß Title</dc:title>\n    <creator opf:role="aut" file-as="Monkey">Monkey Kitchen</creator>\n    <creator opf:role="aut">Next</creator>\n    <dc:subject>One</dc:subject><dc:subject>Two</dc:subject>\n    <dc:identifier scheme="ISBN">123456789</dc:identifier>\n    <meta name="calibre:series" content="A one book series" />\n    <meta name="calibre:rating" content="4"/>\n    <meta name="calibre:publication_type" content="test"/>\n    <meta name="calibre:series_index" content="2.5" />\n</metadata>\n<manifest>\n    <item id="1" href="a%20%7E%20b" media-type="text/txt" />\n</manifest>\n</package>\n')
  1531.         self.opf = OPF(self.stream, os.getcwd())
  1532.  
  1533.     
  1534.     def testReading(self, opf = None):
  1535.         if opf is None:
  1536.             opf = self.opf
  1537.         
  1538.         self.assertEqual(opf.title, u'A Cool & ┬⌐ ├ƒ Title')
  1539.         self.assertEqual(opf.authors, u'Monkey Kitchen,Next'.split(','))
  1540.         self.assertEqual(opf.author_sort, 'Monkey')
  1541.         self.assertEqual(opf.title_sort, 'Wow')
  1542.         self.assertEqual(opf.tags, [
  1543.             'One',
  1544.             'Two'])
  1545.         self.assertEqual(opf.isbn, '123456789')
  1546.         self.assertEqual(opf.series, 'A one book series')
  1547.         self.assertEqual(opf.series_index, 2.5)
  1548.         self.assertEqual(opf.rating, 4)
  1549.         self.assertEqual(opf.publication_type, 'test')
  1550.         self.assertEqual(list(opf.itermanifest())[0].get('href'), 'a ~ b')
  1551.  
  1552.     
  1553.     def testWriting(self):
  1554.         for test in [
  1555.             ('title', 'New & Title'),
  1556.             ('authors', [
  1557.                 'One',
  1558.                 'Two']),
  1559.             ('author_sort', 'Kitchen'),
  1560.             ('tags', [
  1561.                 'Three']),
  1562.             ('isbn', 'a'),
  1563.             ('rating', 3),
  1564.             ('series_index', 1),
  1565.             ('title_sort', 'ts')]:
  1566.             setattr(self.opf, *test)
  1567.             (attr, val) = test
  1568.             self.assertEqual(getattr(self.opf, attr), val)
  1569.         
  1570.         self.opf.render()
  1571.  
  1572.     
  1573.     def testCreator(self):
  1574.         opf = OPFCreator(os.getcwd(), self.opf)
  1575.         buf = cStringIO.StringIO()
  1576.         opf.render(buf)
  1577.         raw = buf.getvalue()
  1578.         self.testReading(opf = OPF(cStringIO.StringIO(raw), os.getcwd()))
  1579.  
  1580.     
  1581.     def testSmartUpdate(self):
  1582.         self.opf.smart_update(MetaInformation(self.opf))
  1583.         self.testReading()
  1584.  
  1585.  
  1586.  
  1587. def suite():
  1588.     return unittest.TestLoader().loadTestsFromTestCase(OPFTest)
  1589.  
  1590.  
  1591. def test():
  1592.     unittest.TextTestRunner(verbosity = 2).run(suite())
  1593.  
  1594. if __name__ == '__main__':
  1595.     test()
  1596.  
  1597.