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