pax_global_header00006660000000000000000000000064150775047340014526gustar00rootroot0000000000000052 comment=7058a9e577b3529f1a494b7166a8019ef4c556f4 ebooklib-0.20/000077500000000000000000000000001507750473400132355ustar00rootroot00000000000000ebooklib-0.20/.coveragerc000066400000000000000000000007451507750473400153640ustar00rootroot00000000000000[run] # Specify directories/files to include for coverage measurement source = ebooklib/ # Specify directories/files to exclude from coverage omit = samples/*, tests/* [report] # Fail if total coverage is below this percentage fail_under = 65 # Exclude lines matching these regular expressions exclude_lines = pragma: no cover if TYPE_CHECKING: raise NotImplementedError if __name__ == .__main__.: [html] # Directory for the HTML report directory = htmlcov_reports ebooklib-0.20/.github/000077500000000000000000000000001507750473400145755ustar00rootroot00000000000000ebooklib-0.20/.github/workflows/000077500000000000000000000000001507750473400166325ustar00rootroot00000000000000ebooklib-0.20/.github/workflows/ci-tests.yml000066400000000000000000000023371507750473400211150ustar00rootroot00000000000000name: Test on Pull Request on: pull_request: jobs: verify: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Run ruff format check uses: astral-sh/ruff-action@v3 with: args: 'format --check --diff' - name: Run ruff lint check uses: astral-sh/ruff-action@v3 with: args: 'check --show-fixes' test: needs: [verify] strategy: matrix: python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v5 - name: Make test script executable run: chmod +x ./test.sh - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Test with python ${{ matrix.python-version }} run: | python -m pip install --upgrade pip pip install -e ".[test]" pip install pygments ./test.sh - name: Upload coverage reports uses: actions/upload-artifact@v4 with: name: coverage-report-python-${{ matrix.python-version }} path: test_reports/coverage/ ebooklib-0.20/.gitignore000066400000000000000000000024721507750473400152320ustar00rootroot00000000000000#ignore these in git listings *~ .emacs* .DS_Store .swp *.swp \#*# .#* \#* ### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log .static_storage/ .media/ local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ ### JetBrains template .idea/ .vscode ebooklib-0.20/.readthedocs.yaml000066400000000000000000000011421507750473400164620ustar00rootroot00000000000000# Read the Docs configuration file for Sphinx projects # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the OS, Python version and other tools you might need build: os: ubuntu-22.04 tools: python: "3.12" # Build documentation in the "docs/" directory with Sphinx sphinx: configuration: docs/conf.py # Optional but recommended, declare the Python requirements required # to build your documentation # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html # python: # install: # - requirements: docs/requirements.txt ebooklib-0.20/AUTHORS.txt000066400000000000000000000033551507750473400151310ustar00rootroot00000000000000Listed in in chronological order: Aleksandar Erkalovic Borko Jandras the-happy-hippo Daniel James Mikhail Gusarov Raymond Yee Chris Grice Fernando Wime Andy Roberts Tom McLean Travis L Helmy Giacoman clach04 Edward Betts Kennyl Oleg Pshenichniy clach04 Olivier Le Thanh Duong Michael Storm Sergey Markelov Laurent Laporte Deborah Kaplan Willem van der Walt Peter <82152909+repeterat@users.noreply.github.com> u8slvn tugot17 张悦 Morphus Kian-Meng, Ang Jared Wong Michael Timblin Hsiu-Ming Chang Bono Lv Tom Ritchford Juan Pablo Delgado Tom Kuson Samuel Clay plu5 Youssif Shaaban Alsager Soroush Javadi Milad Tyler Whipple João Seckler Yuan-Yi Chang Nicolas Ivan Milicevic 林檎 ebooklib-0.20/CHANGES.txt000066400000000000000000000214071507750473400150520ustar00rootroot00000000000000EbookLib Changelog ================== All notable changes to this project will be documented in this file. Version 0.20 (2025-10-26) ------------------ Added ~~~~~ * Add Read the docs configuration file (#333) * Add pytest support and GitHub workflow (#349) * Add extension method for to EpubHtml object (#298) Changed ~~~~~~~ * Reformat all code with ruff, fix docs * Update the list of software using ebooklib * Increase the priority of inserting into * Return bytes in EpubHtml's content getter methods (#319) Fixed ~~~~~ * Fix the version in Sphinx documentation (#335) * Bug fix: Prevent None join error when HREF is not found on a link_node (#328) * Fix item.get_type() does not recognize webm (#312, #338) * Add more informative text for future Contributors (#333) Version 0.19 (2025-05-02) ------------------------- Added ~~~~~ * Add option to set toc page direction (#234) * Add compresslevel parameter for zip compression (#287) Changed ~~~~~~~ * Update README.md with refreshing the project issue * Use posix module instead of os.path when managing EPUB references (#305) * Change deprecation check for ignore_ncx * Using lxml text_content() on all nodes for better ToC parsing Fixed ~~~~~ * Throwing errors while writing EPUB is optional now and behavior will be changed in the future (#316) * Fix lxml root warning (#305) * Toning down metadata failure to an info log * Check truth value of ignore_ncx deprecation, not just truthiness (#283) * Add direction to the epub Nav page only if it was set Version 0.18 (2022-11-30) ------------------------- Added ~~~~~ * Add an example for embedding images into paragraph * Add default option ignore_ncx to EpubReader and document it (#183) * Add option to set custom title in toc page (#243) * Add `scripted` to EpubHtml properties if a script is added (#176) * Read un-zipped epub directories (#183) * Add direction for HTML tags (#174) Changed ~~~~~~~ * Update AUTHORS.txt file * Improved header in README.md * Updated README.md; Added python linting, updated file links * Use custom XMLParser for security * Use double quotes for XML declaration in xml container Fixed ~~~~~ * Define format for the long description * Fix dublin core typo in tutorial documentation (#256) * Fix EpubImage lacks parameters (#231) * Fix the issue toc can not loaded from epub file * Fixed #218 - Use correct variable for static item ids * Ignore NCX if Navigation Document exists * Try hard to parse through broken XML, improve the compatibility Version 0.17.1 (2019-01-03) --------------------------- Added ~~~~~ * Initial SMIL support (#08_SMIL sample) * Adding support for the EPUB3 pagebreak/page-list semantics * Page lists are now attached to each EpubHtml item as well as to the book Fixed ~~~~~ * Increase version number to 0.17.1 * Update README.md * Update AUTHORS.txt file * Fixed #168 - Improve Sphinx documentation a bit * Fixed #166 - Readfile method fails on MS Windows * Fixed #165 - Sample file 05_plugins_create files on Python 3+ * Fixed #164 - Parsing files fails because of page-list * Fixed #163 - epub:type is not properly extracted Version 0.17 (2018-07-04) ------------------------- Added ~~~~~ * Add WAI-ARIA role to the nav element for accessibility * Add ability to include playOrder into navPoint (#146) * Add get_type method to EpubCover class (#147) Changed ~~~~~~~ * Improve playOrder logic to handle subchapters correctly (#148) * Improved `.gitignore` (#144) * Make `samples/04_markdown_parse` Python 2+3 compatible (#143) * Dynamically change the long-description in project configuration * Change double quotes into single quotes Fixed ~~~~~ * Fixed #161 - Provide default value for metadata * Fixed #159 - Add doc-toc role programmatically for EPUB accessibility * Fixed #137 - Create a PyPi compatible README.md file * Fixed #139 - Correct MANIFEST.in file and rename CHANGES.TXT to CHANGES.txt * Fixed #132 - Read the README.md file using utf-8 encoding * Fixed #125 - Refactor writing of OPF file * Fixed #123 - Remove unused test files * Fixed #121 - Fix relative path resolving * Don't cut off first character of body in EpubHtml.get_body_content() * Fix for pop-up footnotes in EPUB 3 Version 0.16 (2017-04-03) ------------------------- Added ~~~~~ * Add ability to set custom prefixes and namespaces to content.opf file * Add bindings support (#70) Changed ~~~~~~~ * Remove Booktype version number * Reconstruct options for legacy tidy * Control attribute order of meta tag * Update Readme with new project that uses ebooklib Fixed ~~~~~ * Fixed #119 - Plugin samples file uses obsolete API * Fixed #112 - Get body content calls get content for no reason * Fixed #111 - Initialize mimetype library only when it is being used * Fixed #110 - Add .ttf extension to the list of font files * Fixed #109 - spine: text orientation attribute * Fixed #101 - Update list of authors * Fixed #100 - Fix docstrings * Fixed #99 - Change README.md to have ASCII encoding * Fixed #98 - Underline tag gets deleted * Fixed #97 - Sections are not pointing to any specific content * Fixed #92 - set.cover not working in Python 3.4 * Fixed #85 - Epub2 guide types do not map completely to epub3 landmark types * Fixed #84 - Set unique metadata function * Fixed #83 - In the sample file read cover image in binary mode * Fixed #82 - Ignore vim swapfiles * Fixed #81 - Mimetypes library isn't aware of xhtml * Fixed #72 - example for reading epub didn't work, wrong module level for ITEM_IMAGE * Fixed #71 - Handling sections with content * Fixed #69 - Correct rendition URL in OPF file * Fixed #64 - embed _lnk.text = "" else: _lnk = etree.SubElement(_head, "link", lnk) # this should not be like this # head = html_root.find('head') # if head is not None: # for i in head.getchildren(): # if i.tag == 'title' and self.title != '': # continue # _head.append(i) # create and populate body _body = etree.SubElement(tree_root, "body") if self.direction: _body.set("dir", self.direction) tree_root.set("dir", self.direction) body = html_tree.find("body") if body is not None: for i in body.getchildren(): _body.append(i) tree_str = etree.tostring(tree, pretty_print=True, encoding="utf-8", xml_declaration=True) return tree_str def __str__(self): return "".format(id=self.id, file_name=self.file_name) # noqa: UP032 class EpubCoverHtml(EpubHtml): """ Represents Cover page in the EPUB file. """ def __init__(self, uid="cover", file_name="cover.xhtml", image_name="", title="Cover"): super(EpubCoverHtml, self).__init__(uid=uid, file_name=file_name, title=title) # noqa: UP008 self.image_name = image_name self.is_linear = False def is_chapter(self): """ Returns if this document is chapter or not. :Returns: Returns book value. """ return False def get_content(self): """ Returns content for cover page as HTML string. Content will be of type 'str' (Python 2) or 'bytes' (Python 3). :Returns: Returns content of this document. """ self.content = self.book.get_template("cover") tree = parse_string(super(EpubCoverHtml, self).get_content()) # noqa: UP008 tree_root = tree.getroot() images = tree_root.xpath("//xhtml:img", namespaces={"xhtml": NAMESPACES["XHTML"]}) images[0].set("src", self.image_name) images[0].set("alt", self.title) tree_str = etree.tostring(tree, pretty_print=True, encoding="utf-8", xml_declaration=True) return tree_str def __str__(self): return "".format(id=self.id, file_name=self.file_name) # noqa: UP032 class EpubNav(EpubHtml): """ Represents Navigation Document in the EPUB file. """ def __init__(self, uid="nav", file_name="nav.xhtml", media_type="application/xhtml+xml", title="", direction=None): super(EpubNav, self).__init__( # noqa: UP008 uid=uid, file_name=file_name, media_type=media_type, title=title, direction=direction ) def is_chapter(self): """ Returns if this document is chapter or not. :Returns: Returns book value. """ return False def __str__(self): return "".format(id=self.id, file_name=self.file_name) # noqa: UP032 class EpubImage(EpubItem): """ Represents Image in the EPUB file. """ def __init__(self, *args, **kwargs): super(EpubImage, self).__init__(*args, **kwargs) # noqa: UP008 def get_type(self): return ebooklib.ITEM_IMAGE def __str__(self): return "".format(id=self.id, file_name=self.file_name) # noqa: UP032 class EpubSMIL(EpubItem): def __init__(self, uid=None, file_name="", content=None): super(EpubSMIL, self).__init__(uid=uid, file_name=file_name, media_type="application/smil+xml", content=content) # noqa: UP008 def get_type(self): return ebooklib.ITEM_SMIL def __str__(self): return "".format(id=self.id, file_name=self.file_name) # noqa: UP032 # EpubBook class EpubBook(object): # noqa: UP004 def __init__(self): self.EPUB_VERSION = None self.reset() # we should have options here def reset(self): "Initialises all needed variables to default values" self.metadata = {} self.items = [] self.spine = [] self.guide = [] self.pages = [] self.toc = [] self.bindings = [] self.IDENTIFIER_ID = "id" self.FOLDER_NAME = "EPUB" self._id_html = 0 self._id_image = 0 self._id_static = 0 self.title = "" self.language = "en" self.direction = None self.templates = {"ncx": NCX_XML, "nav": NAV_XML, "chapter": CHAPTER_XML, "cover": COVER_XML} self.add_metadata( "OPF", "generator", "", {"name": "generator", "content": "Ebook-lib {}".format(".".join([str(s) for s in VERSION]))}, ) # default to using a randomly-unique identifier if one is not specified manually self.set_identifier(str(uuid.uuid4())) # custom prefixes and namespaces to be set to the content.opf doc self.prefixes = [] self.namespaces = {} def set_identifier(self, uid): """ Sets unique id for this epub :Args: - uid: Value of unique identifier for this book """ self.uid = uid self.set_unique_metadata("DC", "identifier", self.uid, {"id": self.IDENTIFIER_ID}) def set_title(self, title): """ Set title. You can set multiple titles. :Args: - title: Title value """ self.title = title self.add_metadata("DC", "title", self.title) def set_language(self, lang): """ Set language for this epub. You can set multiple languages. Specific items in the book can have different language settings. :Args: - lang: Language code """ self.language = lang self.add_metadata("DC", "language", lang) def set_direction(self, direction): """ :Args: - direction: Options are "ltr", "rtl" and "default" """ self.direction = direction def set_cover(self, file_name, content, create_page=True): """ Set cover and create cover document if needed. :Args: - file_name: file name of the cover page - content: Content for the cover image - create_page: Should cover page be defined. Defined as bool value (optional). Default value is True. """ # as it is now, it can only be called once c0 = EpubCover(file_name=file_name) c0.content = content self.add_item(c0) if create_page: c1 = EpubCoverHtml(image_name=file_name) self.add_item(c1) self.add_metadata(None, "meta", "", OrderedDict([("name", "cover"), ("content", "cover-img")])) def add_author(self, author, file_as=None, role=None, uid="creator"): "Add author for this document" self.add_metadata("DC", "creator", author, {"id": uid}) if file_as: self.add_metadata( None, "meta", file_as, {"refines": "#" + uid, "property": "file-as", "scheme": "marc:relators"} ) if role: self.add_metadata(None, "meta", role, {"refines": "#" + uid, "property": "role", "scheme": "marc:relators"}) def add_metadata(self, namespace, name, value, others=None): "Add metadata" if namespace in NAMESPACES: namespace = NAMESPACES[namespace] if namespace not in self.metadata: self.metadata[namespace] = {} if name not in self.metadata[namespace]: self.metadata[namespace][name] = [] self.metadata[namespace][name].append((value, others)) def get_metadata(self, namespace, name): "Retrieve metadata" if namespace in NAMESPACES: namespace = NAMESPACES[namespace] return self.metadata[namespace].get(name, []) def set_unique_metadata(self, namespace, name, value, others=None): "Add metadata if metadata with this identifier does not already exist, otherwise update existing metadata." if namespace in NAMESPACES: namespace = NAMESPACES[namespace] if namespace in self.metadata and name in self.metadata[namespace]: self.metadata[namespace][name] = [(value, others)] else: self.add_metadata(namespace, name, value, others) def add_item(self, item): """ Add additional item to the book. If not defined, media type and chapter id will be defined for the item. :Args: - item: Item instance """ if item.media_type == "": (has_guessed, media_type) = guess_type(item.get_name().lower()) if has_guessed: if media_type is not None: item.media_type = media_type else: item.media_type = has_guessed else: item.media_type = "application/octet-stream" if not item.get_id(): # make chapter_, image_ and static_ configurable if isinstance(item, EpubHtml): item.id = "chapter_{id_html}".format(id_html=self._id_html) # noqa: UP032 self._id_html += 1 # If there's a page list, append it to the book's page list self.pages += item.pages elif isinstance(item, EpubImage): item.id = "image_{id_image}".format(id_image=self._id_image) # noqa: UP032 self._id_image += 1 else: item.id = "static_{id_static}".format(id_static=self._id_static) # noqa: UP032 self._id_static += 1 item.book = self self.items.append(item) return item def get_item_with_id(self, uid): """ Returns item for defined UID. >>> book.get_item_with_id('image_001') :Args: - uid: UID for the item :Returns: Returns item object. Returns None if nothing was found. """ for item in self.get_items(): if item.id == uid: return item return None def get_item_with_href(self, href): """ Returns item for defined HREF. >>> book.get_item_with_href('EPUB/document.xhtml') :Args: - href: HREF for the item we are searching for :Returns: Returns item object. Returns None if nothing was found. """ for item in self.get_items(): if item.get_name() == href: return item return None def get_items(self): """ Returns all items attached to this book. :Returns: Returns all items as tuple. """ return (item for item in self.items) def get_items_of_type(self, item_type): """ Returns all items of specified type. >>> book.get_items_of_type(epub.ITEM_IMAGE) :Args: - item_type: Type for items we are searching for :Returns: Returns found items as tuple. """ return (item for item in self.items if item.get_type() == item_type) def get_items_of_media_type(self, media_type): """ Returns all items of specified media type. :Args: - media_type: Media type for items we are searching for :Returns: Returns found items as tuple. """ return (item for item in self.items if item.media_type == media_type) def set_template(self, name, value): """ Defines templates which are used to generate certain types of pages. When defining new value for the template we have to use content of type 'str' (Python 2) or 'bytes' (Python 3). At the moment we use these templates: - ncx - nav - chapter - cover :Args: - name: Name for the template - value: Content for the template """ self.templates[name] = value def get_template(self, name): """ Returns value for the template. :Args: - name: template name :Returns: Value of the template. """ return self.templates.get(name) def add_prefix(self, name, uri): """ Appends custom prefix to be added to the content.opf document >>> epub_book.add_prefix('bkterms', 'http://booktype.org/') :Args: - name: namespave name - uri: URI for the namespace """ self.prefixes.append("{name}: {uri}".format(name=name, uri=uri)) # noqa: UP032 class EpubWriter(object): # noqa: UP004 DEFAULT_OPTIONS = { "epub2_guide": True, "epub3_landmark": True, "epub3_pages": True, "landmark_title": "Guide", "pages_title": "Pages", "spine_direction": True, "package_direction": False, "play_order": {"enabled": False, "start_from": 1}, "raise_exceptions": False, "compresslevel": 6, } def __init__(self, name, book, options=None): self.file_name = name self.book = book self.options = dict(self.DEFAULT_OPTIONS) if options: self.options.update(options) self._init_play_order() def _init_play_order(self): self._play_order = {"enabled": False, "start_from": 1} try: self._play_order["enabled"] = self.options["play_order"]["enabled"] self._play_order["start_from"] = self.options["play_order"]["start_from"] except KeyError: pass def process(self): # should cache this html parsing so we don't do it for every plugin for plg in self.options.get("plugins", []): if hasattr(plg, "before_write"): plg.before_write(self.book) for item in self.book.get_items(): if isinstance(item, EpubHtml): for plg in self.options.get("plugins", []): if hasattr(plg, "html_before_write"): plg.html_before_write(self.book, item) def _write_container(self): container_xml = CONTAINER_XML % {"folder_name": self.book.FOLDER_NAME} self.out.writestr(CONTAINER_PATH, container_xml) def _write_opf_metadata(self, root): # This is really not needed # problem is uppercase/lowercase # for ns_name, values in six.iteritems(self.book.metadata): # if ns_name: # for n_id, ns_url in six.iteritems(NAMESPACES): # if ns_name == ns_url: # nsmap[n_id.lower()] = NAMESPACES[n_id] nsmap = {"dc": NAMESPACES["DC"], "opf": NAMESPACES["OPF"]} nsmap.update(self.book.namespaces) metadata = etree.SubElement(root, "metadata", nsmap=nsmap) el = etree.SubElement(metadata, "meta", {"property": "dcterms:modified"}) if "mtime" in self.options: mtime = self.options["mtime"] else: import datetime mtime = datetime.datetime.now() el.text = mtime.strftime("%Y-%m-%dT%H:%M:%SZ") for ns_name, values in six.iteritems(self.book.metadata): if ns_name == NAMESPACES["OPF"]: for values2 in values.values(): for v in values2: if "property" in v[1] and v[1]["property"] == "dcterms:modified": continue try: el = etree.SubElement(metadata, "meta", v[1]) if v[0]: el.text = v[0] except ValueError: logging.error("Could not create metadata.") else: for name, values2 in six.iteritems(values): for v in values2: try: if ns_name: el = etree.SubElement(metadata, "{%s}%s" % (ns_name, name), v[1]) # noqa: UP031 else: el = etree.SubElement(metadata, "{name}".format(name=name), v[1]) # noqa: UP032 el.text = v[0] except ValueError: logging.info('Could not create metadata "{name}".'.format(name=name)) # noqa: UP032 def _write_opf_manifest(self, root): manifest = etree.SubElement(root, "manifest") _ncx_id = None # mathml, scripted, svg, remote-resources, and switch # nav # cover-image for item in self.book.get_items(): if not item.manifest: continue if isinstance(item, EpubNav): etree.SubElement( manifest, "item", {"href": item.get_name(), "id": item.id, "media-type": item.media_type, "properties": "nav"}, ) elif isinstance(item, EpubNcx): _ncx_id = item.id etree.SubElement( manifest, "item", {"href": item.file_name, "id": item.id, "media-type": item.media_type} ) elif isinstance(item, EpubCover): etree.SubElement( manifest, "item", {"href": item.file_name, "id": item.id, "media-type": item.media_type, "properties": "cover-image"}, ) else: opts = {"href": item.file_name, "id": item.id, "media-type": item.media_type} if hasattr(item, "properties") and len(item.properties) > 0: opts["properties"] = " ".join(item.properties) if hasattr(item, "media_overlay") and item.media_overlay is not None: opts["media-overlay"] = item.media_overlay if hasattr(item, "media_duration") and item.media_duration is not None: opts["duration"] = item.media_duration etree.SubElement(manifest, "item", opts) return _ncx_id def _write_opf_spine(self, root, ncx_id): spine_attributes = {"toc": ncx_id or "ncx"} if self.book.direction and self.options["spine_direction"]: spine_attributes["page-progression-direction"] = self.book.direction spine = etree.SubElement(root, "spine", spine_attributes) for _item in self.book.spine: # this is for now # later we should be able to fetch things from tuple is_linear = True if isinstance(_item, tuple): item = _item[0] if len(_item) > 1: if _item[1] == "no": is_linear = False else: item = _item if isinstance(item, EpubHtml): opts = {"idref": item.get_id()} if not item.is_linear or not is_linear: opts["linear"] = "no" elif isinstance(item, EpubItem): opts = {"idref": item.get_id()} if not item.is_linear or not is_linear: opts["linear"] = "no" else: opts = {"idref": item} try: itm = self.book.get_item_with_id(item) if not itm.is_linear or not is_linear: opts["linear"] = "no" except Exception: pass etree.SubElement(spine, "itemref", opts) def _write_opf_guide(self, root): # - http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.6 if len(self.book.guide) > 0 and self.options.get("epub2_guide"): guide = etree.SubElement(root, "guide", {}) for item in self.book.guide: if "item" in item: chap = item.get("item") if chap: _href = chap.file_name _title = chap.title else: _href = item.get("href", "") _title = item.get("title", "") if _title is None: _title = "" _ref = etree.SubElement( guide, "reference", {"type": item.get("type", ""), "title": _title, "href": _href} ) def _write_opf_bindings(self, root): if len(self.book.bindings) > 0: bindings = etree.SubElement(root, "bindings", {}) for item in self.book.bindings: etree.SubElement(bindings, "mediaType", item) def _write_opf_file(self, root): tree_str = etree.tostring(root, pretty_print=True, encoding="utf-8", xml_declaration=True) self.out.writestr("{FOLDER_NAME}/content.opf".format(FOLDER_NAME=self.book.FOLDER_NAME), tree_str) # noqa: UP032 def _write_opf(self): package_attributes = { "xmlns": NAMESPACES["OPF"], "unique-identifier": self.book.IDENTIFIER_ID, "version": "3.0", } if self.book.direction and self.options["package_direction"]: package_attributes["dir"] = self.book.direction root = etree.Element("package", package_attributes) prefixes = ["rendition: http://www.idpf.org/vocab/rendition/#"] + self.book.prefixes root.attrib["prefix"] = " ".join(prefixes) # METADATA self._write_opf_metadata(root) # MANIFEST _ncx_id = self._write_opf_manifest(root) # SPINE self._write_opf_spine(root, _ncx_id) # GUIDE self._write_opf_guide(root) # BINDINGS self._write_opf_bindings(root) # WRITE FILE self._write_opf_file(root) def _get_nav(self, item): # just a basic navigation for now nav_xml = parse_string(self.book.get_template("nav")) root = nav_xml.getroot() root.set("lang", self.book.language) root.attrib["{%s}lang" % NAMESPACES["XML"]] = self.book.language # noqa nav_dir_name = os.path.dirname(item.file_name) head = etree.SubElement(root, "head") title = etree.SubElement(head, "title") title.text = item.title or self.book.title # for now this just handles css files and ignores others for _link in item.links: _lnk = etree.SubElement( head, "link", {"href": _link.get("href", ""), "rel": "stylesheet", "type": "text/css"} ) body = etree.SubElement(root, "body") if item.direction: body.set("dir", item.direction) nav = etree.SubElement( body, "nav", { "{%s}type" % NAMESPACES["EPUB"]: "toc", # noqa "id": "id", "role": "doc-toc", }, ) content_title = etree.SubElement(nav, "h2") content_title.text = item.title or self.book.title def _create_section(itm, items): ol = etree.SubElement(itm, "ol") for item in items: if isinstance(item, tuple) or isinstance(item, list): li = etree.SubElement(ol, "li") if isinstance(item[0], EpubHtml): a = etree.SubElement(li, "a", {"href": zip_path.relpath(item[0].file_name, nav_dir_name)}) elif isinstance(item[0], Section) and item[0].href != "": a = etree.SubElement(li, "a", {"href": zip_path.relpath(item[0].href, nav_dir_name)}) elif isinstance(item[0], Link): a = etree.SubElement(li, "a", {"href": zip_path.relpath(item[0].href, nav_dir_name)}) else: a = etree.SubElement(li, "span") a.text = item[0].title _create_section(li, item[1]) elif isinstance(item, Link): li = etree.SubElement(ol, "li") a = etree.SubElement(li, "a", {"href": zip_path.relpath(item.href, nav_dir_name)}) a.text = item.title elif isinstance(item, EpubHtml): li = etree.SubElement(ol, "li") a = etree.SubElement(li, "a", {"href": zip_path.relpath(item.file_name, nav_dir_name)}) a.text = item.title _create_section(nav, self.book.toc) # LANDMARKS / GUIDE # - http://www.idpf.org/epub/30/spec/epub30-contentdocs.html#sec-xhtml-nav-def-types-landmarks if len(self.book.guide) > 0 and self.options.get("epub3_landmark"): # Epub2 guide types do not map completely to epub3 landmark types. guide_to_landscape_map = {"notes": "rearnotes", "text": "bodymatter"} guide_nav = etree.SubElement(body, "nav", {"{%s}type" % NAMESPACES["EPUB"]: "landmarks"}) # noqa: UP031 guide_content_title = etree.SubElement(guide_nav, "h2") guide_content_title.text = self.options.get("landmark_title", "Guide") guild_ol = etree.SubElement(guide_nav, "ol") for elem in self.book.guide: li_item = etree.SubElement(guild_ol, "li") if "item" in elem: chap = elem.get("item", None) if chap: _href = chap.file_name _title = chap.title else: _href = elem.get("href", "") _title = elem.get("title", "") guide_type = elem.get("type", "") a_item = etree.SubElement( li_item, "a", { "{%s}type" % NAMESPACES["EPUB"]: guide_to_landscape_map.get(guide_type, guide_type), # noqa: UP031 "href": zip_path.relpath(_href, nav_dir_name), }, ) a_item.text = _title # PAGE-LIST if self.options.get("epub3_pages"): inserted_pages = get_pages_for_items( [item for item in self.book.get_items_of_type(ebooklib.ITEM_DOCUMENT) if not isinstance(item, EpubNav)] ) if len(inserted_pages) > 0: pagelist_nav = etree.SubElement( body, "nav", { "{%s}type" % NAMESPACES["EPUB"]: "page-list", # noqa: UP031 "id": "pages", "hidden": "hidden", }, ) pagelist_content_title = etree.SubElement(pagelist_nav, "h2") pagelist_content_title.text = self.options.get("pages_title", "Pages") pages_ol = etree.SubElement(pagelist_nav, "ol") for filename, pageref, label in inserted_pages: li_item = etree.SubElement(pages_ol, "li") _href = "{filename}#{pageref}".format(filename=filename, pageref=pageref) # noqa: UP032 _title = label a_item = etree.SubElement( li_item, "a", { "href": zip_path.relpath(_href, nav_dir_name), }, ) a_item.text = _title tree_str = etree.tostring(nav_xml, pretty_print=True, encoding="utf-8", xml_declaration=True) return tree_str def _get_ncx(self): # we should be able to setup language for NCX as also ncx = parse_string(self.book.get_template("ncx")) root = ncx.getroot() head = etree.SubElement(root, "head") # get this id _uid = etree.SubElement(head, "meta", {"content": self.book.uid, "name": "dtb:uid"}) _uid = etree.SubElement(head, "meta", {"content": "0", "name": "dtb:depth"}) _uid = etree.SubElement(head, "meta", {"content": "0", "name": "dtb:totalPageCount"}) _uid = etree.SubElement(head, "meta", {"content": "0", "name": "dtb:maxPageNumber"}) doc_title = etree.SubElement(root, "docTitle") title = etree.SubElement(doc_title, "text") title.text = self.book.title # doc_author = etree.SubElement(root, 'docAuthor') # author = etree.SubElement(doc_author, 'text') # author.text = 'Name of the person' # For now just make a very simple navMap nav_map = etree.SubElement(root, "navMap") def _add_play_order(nav_point): nav_point.set("playOrder", str(self._play_order["start_from"])) self._play_order["start_from"] += 1 def _create_section(itm, items, uid): for item in items: if isinstance(item, tuple) or isinstance(item, list): section, subsection = item[0], item[1] np = etree.SubElement( itm, "navPoint", { "id": section.get_id() if isinstance(section, EpubHtml) else "sep_{uid}".format(uid=uid) # noqa: UP032 }, ) if self._play_order["enabled"]: _add_play_order(np) nl = etree.SubElement(np, "navLabel") nt = etree.SubElement(nl, "text") nt.text = section.title # CAN NOT HAVE EMPTY SRC HERE href = "" if isinstance(section, EpubHtml): href = section.file_name elif isinstance(section, Section) and section.href != "": href = section.href elif isinstance(section, Link): href = section.href _nc = etree.SubElement(np, "content", {"src": href}) uid = _create_section(np, subsection, uid + 1) elif isinstance(item, Link): _parent = itm _content = _parent.find("content") if _content is not None: if _content.get("src") == "": _content.set("src", item.href) np = etree.SubElement(itm, "navPoint", {"id": item.uid}) if self._play_order["enabled"]: _add_play_order(np) nl = etree.SubElement(np, "navLabel") nt = etree.SubElement(nl, "text") nt.text = item.title _nc = etree.SubElement(np, "content", {"src": item.href}) elif isinstance(item, EpubHtml): _parent = itm _content = _parent.find("content") if _content is not None: if _content.get("src") == "": _content.set("src", item.file_name) np = etree.SubElement(itm, "navPoint", {"id": item.get_id()}) if self._play_order["enabled"]: _add_play_order(np) nl = etree.SubElement(np, "navLabel") nt = etree.SubElement(nl, "text") nt.text = item.title _nc = etree.SubElement(np, "content", {"src": item.file_name}) return uid _create_section(nav_map, self.book.toc, 0) tree_str = etree.tostring(root, pretty_print=True, encoding="utf-8", xml_declaration=True) return tree_str def _write_items(self): for item in self.book.get_items(): if isinstance(item, EpubNcx): self.out.writestr( "{FOLDER_NAME}/{file_name}".format(FOLDER_NAME=self.book.FOLDER_NAME, file_name=item.file_name), # noqa: UP032 self._get_ncx(), ) elif isinstance(item, EpubNav): self.out.writestr( "{FOLDER_NAME}/{file_name}".format(FOLDER_NAME=self.book.FOLDER_NAME, file_name=item.file_name), # noqa: UP032 self._get_nav(item), ) elif item.manifest: self.out.writestr( "{FOLDER_NAME}/{file_name}".format(FOLDER_NAME=self.book.FOLDER_NAME, file_name=item.file_name), # noqa: UP032 item.get_content(), ) else: self.out.writestr("{file_name}".format(file_name=item.file_name), item.get_content()) # noqa: UP032 def write(self): if six.PY2: self.out = zipfile.ZipFile(self.file_name, "w", zipfile.ZIP_DEFLATED) else: self.out = zipfile.ZipFile( self.file_name, "w", zipfile.ZIP_DEFLATED, compresslevel=self.options["compresslevel"] ) self.out.writestr("mimetype", "application/epub+zip", compress_type=zipfile.ZIP_STORED) self._write_container() self._write_opf() self._write_items() self.out.close() class EpubReader(object): # noqa: UP004 DEFAULT_OPTIONS = {"ignore_ncx": True} def __init__(self, epub_file_name, options=None): self.file_name = epub_file_name self.book = EpubBook() self.zf = None self.opf_file = "" self.opf_dir = "" self.options = dict(self.DEFAULT_OPTIONS) if options: self.options.update(options) self._check_deprecated() def _check_deprecated(self): if self.options.get("ignore_ncx") is None: warnings.warn("In the future version we will turn default option ignore_ncx to True.", stacklevel=2) def process(self): # should cache this html parsing so we don't do it for every plugin for plg in self.options.get("plugins", []): if hasattr(plg, "after_read"): plg.after_read(self.book) for item in self.book.get_items(): if isinstance(item, EpubHtml): for plg in self.options.get("plugins", []): if hasattr(plg, "html_after_read"): plg.html_after_read(self.book, item) def load(self): self._load() return self.book def read_file(self, name): # Raises KeyError name = zip_path.normpath(name) return self.zf.read(name) def _load_container(self): meta_inf = self.read_file("META-INF/container.xml") tree = parse_string(meta_inf) for root_file in tree.findall( ".//xmlns:rootfile[@media-type]", namespaces={"xmlns": NAMESPACES["CONTAINERNS"]} ): if root_file.get("media-type") == "application/oebps-package+xml": self.opf_file = root_file.get("full-path") self.opf_dir = zip_path.dirname(self.opf_file) def _load_metadata(self): container_root = self.container.getroot() # get epub version self.book.version = container_root.get("version", None) # get unique-identifier if container_root.get("unique-identifier", None): self.book.IDENTIFIER_ID = container_root.get("unique-identifier") # get xml:lang # get metadata metadata = self.container.find("{%s}%s" % (NAMESPACES["OPF"], "metadata")) # noqa: UP031 nsmap = metadata.nsmap nstags = dict(six.iteritems(nsmap)) default_ns = nstags.get(None, "") nsdict = {v: {} for v in nsmap.values()} def add_item(ns, tag, value, extra): if ns not in nsdict: nsdict[ns] = {} values = nsdict[ns].setdefault(tag, []) values.append((value, extra)) for t in metadata: if not etree.iselement(t) or t.tag is etree.Comment: continue if t.tag == default_ns + "meta": name = t.get("name") others = dict(t.items()) if name and ":" in name: prefix, name = name.split(":", 1) else: prefix = None add_item(t.nsmap.get(prefix, prefix), name, t.text, others) else: tag = t.tag[t.tag.rfind("}") + 1 :] if (t.prefix and t.prefix.lower() == "dc") and tag == "identifier": _id = t.get("id", None) if _id: self.book.IDENTIFIER_ID = _id others = dict(t.items()) add_item(t.nsmap[t.prefix], tag, t.text, others) self.book.metadata = nsdict titles = self.book.get_metadata("DC", "title") if len(titles) > 0: self.book.title = titles[0][0] for value, others in self.book.get_metadata("DC", "identifier"): if others.get("id") == self.book.IDENTIFIER_ID: self.book.uid = value def _load_manifest(self): for r in self.container.find("{%s}%s" % (NAMESPACES["OPF"], "manifest")): # noqa: UP031 if r is not None and r.tag != "{%s}item" % NAMESPACES["OPF"]: # noqa: UP031 continue media_type = r.get("media-type") _properties = r.get("properties", "") if _properties: properties = _properties.split(" ") else: properties = [] # people use wrong content types if media_type == "image/jpg": media_type = "image/jpeg" if media_type == "application/x-dtbncx+xml": ei = EpubNcx(uid=r.get("id"), file_name=unquote(r.get("href"))) ei.content = self.read_file(zip_path.join(self.opf_dir, ei.file_name)) elif media_type == "application/smil+xml": ei = EpubSMIL(uid=r.get("id"), file_name=unquote(r.get("href"))) ei.content = self.read_file(zip_path.join(self.opf_dir, ei.file_name)) elif media_type == "application/xhtml+xml": if "nav" in properties: ei = EpubNav(uid=r.get("id"), file_name=unquote(r.get("href"))) ei.content = self.read_file(zip_path.join(self.opf_dir, r.get("href"))) elif "cover" in properties: ei = EpubCoverHtml() ei.content = self.read_file(zip_path.join(self.opf_dir, unquote(r.get("href")))) else: ei = EpubHtml() ei.id = r.get("id") ei.file_name = unquote(r.get("href")) ei.media_type = media_type ei.media_overlay = r.get("media-overlay", None) ei.media_duration = r.get("duration", None) ei.content = self.read_file(zip_path.join(self.opf_dir, ei.get_name())) ei.properties = properties elif media_type in IMAGE_MEDIA_TYPES: if "cover-image" in properties: ei = EpubCover(uid=r.get("id"), file_name=unquote(r.get("href"))) ei.media_type = media_type ei.content = self.read_file(zip_path.join(self.opf_dir, ei.get_name())) else: ei = EpubImage() ei.id = r.get("id") ei.file_name = unquote(r.get("href")) ei.media_type = media_type ei.content = self.read_file(zip_path.join(self.opf_dir, ei.get_name())) else: # different types ei = EpubItem() ei.id = r.get("id") ei.file_name = unquote(r.get("href")) ei.media_type = media_type ei.content = self.read_file(zip_path.join(self.opf_dir, ei.get_name())) self.book.add_item(ei) def _parse_ncx(self, data): tree = parse_string(data) tree_root = tree.getroot() nav_map = tree_root.find("{%s}navMap" % NAMESPACES["DAISY"]) # noqa: UP031 def _get_children(elems, n, nid): label, content = "", "" children = [] for a in elems.getchildren(): if a.tag == "{%s}navLabel" % NAMESPACES["DAISY"]: # noqa: UP031 label = a.getchildren()[0].text if a.tag == "{%s}content" % NAMESPACES["DAISY"]: # noqa: UP031 content = a.get("src", "") if a.tag == "{%s}navPoint" % NAMESPACES["DAISY"]: # noqa: UP031 children.append(_get_children(a, n + 1, a.get("id", ""))) if len(children) > 0: if n == 0: return children return (Section(label, href=content), children) else: return Link(content, label, nid) self.book.toc = _get_children(nav_map, 0, "") def _parse_nav(self, data, base_path, navtype="toc"): html_node = parse_html_string(data) if navtype == "toc": # parsing the table of contents nav_node = html_node.xpath("//nav[@*='toc']")[0] else: # parsing the list of pages _page_list = html_node.xpath("//nav[@*='page-list']") if len(_page_list) == 0: return nav_node = _page_list[0] def parse_list(list_node): items = [] for item_node in list_node.findall("li"): sublist_node = item_node.find("ol") link_node = item_node.find("a") if sublist_node is not None: title = item_node[0].text_content() children = parse_list(sublist_node) if link_node is not None and link_node.get("href"): href = zip_path.normpath(zip_path.join(base_path, link_node.get("href"))) items.append((Section(title, href=href), children)) else: items.append((Section(title), children)) elif link_node is not None and link_node.get("href"): title = link_node.text_content() href = zip_path.normpath(zip_path.join(base_path, link_node.get("href"))) items.append(Link(href, title)) return items if navtype == "toc": self.book.toc = parse_list(nav_node.find("ol")) elif nav_node is not None: # generate the pages list if there is one self.book.pages = parse_list(nav_node.find("ol")) # generate the per-file pages lists # because of the order of parsing the files, this can't be done # when building the EpubHtml objects htmlfiles = {} for htmlfile in self.book.items: if isinstance(htmlfile, EpubHtml): htmlfiles[htmlfile.file_name] = htmlfile for page in self.book.pages: try: (filename, idref) = page.href.split("#") except ValueError: filename = page.href if filename in htmlfiles: htmlfiles[filename].pages.append(page) def _load_spine(self): spine = self.container.find("{%s}%s" % (NAMESPACES["OPF"], "spine")) # noqa: UP031 self.book.spine = [(t.get("idref"), t.get("linear", "yes")) for t in spine] toc = spine.get("toc", "") self.book.set_direction(spine.get("page-progression-direction", None)) # should read ncx or nav file nav_item = next((item for item in self.book.items if isinstance(item, EpubNav)), None) if toc: if not self.options.get("ignore_ncx") or not nav_item: try: ncxFile = self.read_file(zip_path.join(self.opf_dir, self.book.get_item_with_id(toc).get_name())) except KeyError: raise EpubException(-1, "Can not find ncx file.") # noqa: B904 self._parse_ncx(ncxFile) def _load_guide(self): guide = self.container.find("{%s}%s" % (NAMESPACES["OPF"], "guide")) # noqa: UP031 if guide is not None: self.book.guide = [{"href": t.get("href"), "title": t.get("title"), "type": t.get("type")} for t in guide] def _load_opf_file(self): try: s = self.read_file(self.opf_file) except KeyError: raise EpubException(-1, "Can not find container file") # noqa: B904 self.container = parse_string(s) self._load_metadata() self._load_manifest() self._load_spine() self._load_guide() # read nav file if found # nav_item = next((item for item in self.book.items if isinstance(item, EpubNav)), None) if nav_item: if self.options.get("ignore_ncx") or not self.book.toc: self._parse_nav(nav_item.content, zip_path.dirname(nav_item.file_name), navtype="toc") self._parse_nav(nav_item.content, zip_path.dirname(nav_item.file_name), navtype="pages") def _load(self): self.zf = None if (six.PY3 and isinstance(self.file_name, (str, bytes, os.PathLike))) or ( six.PY2 and isinstance(self.file_name, (six.string_types)) ): if os.path.isdir(self.file_name): self.zf = Directory(self.file_name) if self.zf is None: try: self.zf = zipfile.ZipFile(self.file_name, "r", compression=zipfile.ZIP_DEFLATED, allowZip64=True) except zipfile.BadZipfile: raise EpubException(0, "Bad Zip file") # noqa: B904 except zipfile.LargeZipFile: raise EpubException(1, "Large Zip file") # noqa: B904 # 1st check metadata self._load_container() self._load_opf_file() self.zf.close() # WRITE def write_epub(name, book, options=None): """ Creates epub file with the content defined in EpubBook. >>> ebooklib.write_epub('book.epub', book) :Args: - name: file name for the output file - book: instance of EpubBook - options: extra opions as dictionary (optional) """ epub = EpubWriter(name, book, options) epub.process() try: epub.write() except OSError: warnings.warn("In the future throwing exceptions while writing will be default behavior.", stacklevel=2) t, v, tb = sys.exc_info() if options and options.get("raise_exceptions"): six.reraise(t, v, tb) else: return False return True # READ def read_epub(name, options=None): """ Creates new instance of EpubBook with the content defined in the input file. >>> book = ebooklib.read_epub('book.epub') :Args: - name: full path to the input file - options: extra options as dictionary (optional) :Returns: Instance of EpubBook. """ reader = EpubReader(name, options) book = reader.load() reader.process() return book ebooklib-0.20/ebooklib/plugins/000077500000000000000000000000001507750473400165045ustar00rootroot00000000000000ebooklib-0.20/ebooklib/plugins/__init__.py000066400000000000000000000000001507750473400206030ustar00rootroot00000000000000ebooklib-0.20/ebooklib/plugins/base.py000066400000000000000000000030311507750473400177650ustar00rootroot00000000000000# This file is part of EbookLib. # Copyright (c) 2013 Aleksandar Erkalovic # # EbookLib is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # EbookLib is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with EbookLib. If not, see . class BasePlugin(object): # noqa: UP004 def before_write(self, book): "Processing before save" return True def after_write(self, book): "Processing after save" return True def before_read(self, book): "Processing before save" return True def after_read(self, book): "Processing after save" return True def item_after_read(self, book, item): "Process general item after read." return True def item_before_write(self, book, item): "Process general item before write." return True def html_after_read(self, book, chapter): "Processing HTML before read." return True def html_before_write(self, book, chapter): "Processing HTML before save." return True ebooklib-0.20/ebooklib/plugins/booktype.py000066400000000000000000000110561507750473400207150ustar00rootroot00000000000000# This file is part of EbookLib. # Copyright (c) 2013 Aleksandar Erkalovic # # EbookLib is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # EbookLib is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with EbookLib. If not, see . from ebooklib.plugins.base import BasePlugin from ebooklib.utils import parse_html_string class BooktypeLinks(BasePlugin): NAME = "Booktype Links" def __init__(self, booktype_book): self.booktype_book = booktype_book def html_before_write(self, book, chapter): try: from urlparse import urljoin, urlparse except ImportError: from urllib.parse import urljoin, urlparse from lxml import etree try: tree = parse_html_string(chapter.content) except Exception: return root = tree.getroottree() if len(root.find("body")) != 0: body = tree.find("body") # should also be aware to handle # ../chapter/ # ../chapter/#reference # ../chapter#reference for _link in body.xpath("//a"): # This is just temporary for the footnotes if _link.get("href", "").find("InsertNoteID") != -1: _ln = _link.get("href", "") i = _ln.find("#") _link.set("href", _ln[i:]) continue _u = urlparse(_link.get("href", "")) # Let us care only for internal links at the moment if _u.scheme == "": if _u.path != "": _link.set("href", "{path}.xhtml".format(path=_u.path)) # noqa: UP032 if _u.fragment != "": _link.set("href", urljoin(_link.get("href"), "#{fragment}".format(fragment=_u.fragment))) # noqa: UP032 if _link.get("name") is not None: _link.set("id", _link.get("name")) etree.strip_attributes(_link, "name") chapter.content = etree.tostring(tree, pretty_print=True, encoding="utf-8") class BooktypeFootnotes(BasePlugin): NAME = "Booktype Footnotes" def __init__(self, booktype_book): self.booktype_book = booktype_book def html_before_write(self, book, chapter): from lxml import etree from ebooklib import epub try: tree = parse_html_string(chapter.content) except Exception: return root = tree.getroottree() if len(root.find("body")) != 0: body = tree.find("body") # # 1 # #
    #
  1. prvi footnote # # ^ # #
  2. #
# 1

# for footnote in body.xpath('//span[@class="InsertNoteMarker"]'): footnote_id = footnote.get("id")[:-8] a = footnote.getchildren()[0].getchildren()[0] footnote_text = body.xpath('//li[@id="{footnote_id}"]'.format(footnote_id=footnote_id))[0] # noqa: UP032 a.attrib["{%s}type" % epub.NAMESPACES["EPUB"]] = "noteref" # noqa ftn = etree.SubElement(body, "aside", {"id": footnote_id}) ftn.attrib["{%s}type" % epub.NAMESPACES["EPUB"]] = "footnote" # noqa ftn_p = etree.SubElement(ftn, "p") ftn_p.text = footnote_text.text old_footnote = body.xpath('//ol[@id="InsertNote_NoteList"]') if len(old_footnote) > 0: body.remove(old_footnote[0]) chapter.content = etree.tostring(tree, pretty_print=True, encoding="utf-8") ebooklib-0.20/ebooklib/plugins/sourcecode.py000066400000000000000000000046351507750473400212210ustar00rootroot00000000000000# This file is part of EbookLib. # Copyright (c) 2013 Aleksandar Erkalovic # # EbookLib is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # EbookLib is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with EbookLib. If not, see . from ebooklib.plugins.base import BasePlugin from ebooklib.utils import parse_html_string class SourceHighlighter(BasePlugin): def __init__(self): pass def html_before_write(self, book, chapter): from lxml import etree, html from pygments import highlight from pygments.formatters import HtmlFormatter try: tree = parse_html_string(chapter.content) except Exception: return root = tree.getroottree() had_source = False if len(root.find("body")) != 0: body = tree.find("body") # check for embeded source for source in body.xpath('//pre[contains(@class,"source-")]'): css_class = source.get("class") source_text = (source.text or "") + "".join([html.tostring(child) for child in source.iterchildren()]) if "source-python" in css_class: from pygments.lexers import PythonLexer # _text = highlight(source_text, PythonLexer(), HtmlFormatter(linenos="inline")) _text = highlight(source_text, PythonLexer(), HtmlFormatter()) if "source-css" in css_class: from pygments.lexers import CssLexer _text = highlight(source_text, CssLexer(), HtmlFormatter()) _parent = source.getparent() _parent.replace(source, etree.XML(_text)) had_source = True if had_source: chapter.add_link(href="style/code.css", rel="stylesheet", type="text/css") chapter.content = etree.tostring(tree, pretty_print=True, encoding="utf-8") ebooklib-0.20/ebooklib/plugins/standard.py000066400000000000000000000360021507750473400206570ustar00rootroot00000000000000# This file is part of EbookLib. # Copyright (c) 2013 Aleksandar Erkalovic # # EbookLib is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # EbookLib is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with EbookLib. If not, see . import six from ebooklib.plugins.base import BasePlugin from ebooklib.utils import parse_html_string # TODO: # - should also look for the _required_ elements # http://www.w3.org/html/wg/drafts/html/master/tabular-data.html#the-table-element ATTRIBUTES_GLOBAL = [ "accesskey", "class", "contenteditable", "contextmenu", "dir", "draggable", "dropzone", "hidden", "id", "inert", "itemid", "itemprop", "itemref", "itemscope", "itemtype", "lang", "spellcheck", "style", "tabindex", "title", "translate", "epub:type", ] # Remove for now from here DEPRECATED_TAGS = [ "acronym", "applet", "basefont", "big", "center", "dir", "font", "frame", "frameset", "isindex", "noframes", "s", "strike", "tt", ] def leave_only(item, tag_list): for _attr in six.iterkeys(item.attrib): if _attr not in tag_list: del item.attrib[_attr] class SyntaxPlugin(BasePlugin): NAME = "Check HTML syntax" def html_before_write(self, book, chapter): from lxml import etree try: tree = parse_html_string(chapter.content) except Exception: return root = tree.getroottree() # delete deprecated tags # i should really have a list of allowed tags for tag in DEPRECATED_TAGS: etree.strip_tags(root, tag) head = tree.find("head") if head is not None and len(head) != 0: for _item in head: if _item.tag == "base": leave_only(_item, ATTRIBUTES_GLOBAL + ["href", "target"]) elif _item.tag == "link": leave_only( _item, ATTRIBUTES_GLOBAL + ["href", "crossorigin", "rel", "media", "hreflang", "type", "sizes"] ) elif _item.tag == "title": if _item.text == "": head.remove(_item) elif _item.tag == "meta": leave_only(_item, ATTRIBUTES_GLOBAL + ["name", "http-equiv", "content", "charset"]) # just remove for now, but really should not be like this head.remove(_item) elif _item.tag == "script": leave_only(_item, ATTRIBUTES_GLOBAL + ["src", "type", "charset", "async", "defer", "crossorigin"]) elif _item.tag == "source": leave_only(_item, ATTRIBUTES_GLOBAL + ["src", "type", "media"]) elif _item.tag == "style": leave_only(_item, ATTRIBUTES_GLOBAL + ["media", "type", "scoped"]) else: leave_only(_item, ATTRIBUTES_GLOBAL) if len(root.find("body")) != 0: body = tree.find("body") for _item in body.iter(): # it is not # if _item.tag == "a": leave_only(_item, ATTRIBUTES_GLOBAL + ["href", "target", "download", "rel", "hreflang", "type"]) elif _item.tag == "area": leave_only( _item, ATTRIBUTES_GLOBAL + ["alt", "coords", "shape", "href", "target", "download", "rel", "hreflang", "type"], ) elif _item.tag == "audio": leave_only( _item, ATTRIBUTES_GLOBAL + ["src", "crossorigin", "preload", "autoplay", "mediagroup", "loop", "muted", "controls"], ) elif _item.tag == "blockquote": leave_only(_item, ATTRIBUTES_GLOBAL + ["cite"]) elif _item.tag == "button": leave_only( _item, ATTRIBUTES_GLOBAL + [ "autofocus", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget", "name", "type", "value", "menu", ], ) elif _item.tag == "canvas": leave_only(_item, ATTRIBUTES_GLOBAL + ["width", "height"]) elif _item.tag == "canvas": leave_only(_item, ATTRIBUTES_GLOBAL + ["width", "height"]) elif _item.tag == "del": leave_only(_item, ATTRIBUTES_GLOBAL + ["cite", "datetime"]) elif _item.tag == "details": leave_only(_item, ATTRIBUTES_GLOBAL + ["open"]) elif _item.tag == "embed": leave_only(_item, ATTRIBUTES_GLOBAL + ["src", "type", "width", "height"]) elif _item.tag == "fieldset": leave_only(_item, ATTRIBUTES_GLOBAL + ["disable", "form", "name"]) elif _item.tag == "details": leave_only( _item, ATTRIBUTES_GLOBAL + [ "accept-charset", "action", "autocomplete", "enctype", "method", "name", "novalidate", "target", ], ) elif _item.tag == "iframe": leave_only( _item, ATTRIBUTES_GLOBAL + ["src", "srcdoc", "name", "sandbox", "seamless", "allowfullscreen", "width", "height"], ) elif _item.tag == "img": _src = _item.get("src", "").lower() if _src.startswith("http://") or _src.startswith("https://"): if "remote-resources" not in chapter.properties: chapter.properties.append("remote-resources") # THIS DOES NOT WORK, ONLY VIDEO AND AUDIO FILES CAN BE REMOTE RESOURCES # THAT MEANS I SHOULD ALSO CATCH # # EbookLib is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # EbookLib is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with EbookLib. If not, see . import subprocess import six from ebooklib.plugins.base import BasePlugin # Recommend usage of # - https://github.com/w3c/tidy-html5 def tidy_cleanup(content, **extra): cmd = [] for k, v in six.iteritems(extra): if v: cmd.append("--{k}".format(k=k)) # noqa: UP032 cmd.append(v) else: cmd.append("-{k}".format(k=k)) # noqa: UP032 # must parse all other extra arguments try: p = subprocess.Popen( ["tidy"] + cmd, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, ) except OSError: return (3, None) p.stdin.write(content) (cont, p_err) = p.communicate() # 0 - all ok # 1 - there were warnings # 2 - there were errors # 3 - exception return (p.returncode, cont) class TidyPlugin(BasePlugin): NAME = "Tidy HTML" OPTIONS = {"char-encoding": "utf8", "tidy-mark": "no"} def __init__(self, extra=None): self.options = dict(self.OPTIONS) if extra is not None: self.options.update(extra) def html_before_write(self, book, chapter): if not chapter.content: return None (_, chapter.content) = tidy_cleanup(chapter.content, **self.options) return chapter.content def html_after_read(self, book, chapter): if not chapter.content: return None (_, chapter.content) = tidy_cleanup(chapter.content, **self.options) return chapter.content ebooklib-0.20/ebooklib/utils.py000066400000000000000000000070021507750473400165340ustar00rootroot00000000000000# This file is part of EbookLib. # Copyright (c) 2013 Aleksandar Erkalovic # # EbookLib is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # EbookLib is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with EbookLib. If not, see . import io import mimetypes import os from lxml import etree mimetype_initialised = False def debug(obj): import pprint pp = pprint.PrettyPrinter(indent=4) pp.pprint(obj) def parse_string(s): parser = etree.XMLParser(recover=True, resolve_entities=False) try: tree = etree.parse(io.BytesIO(s.encode("utf-8")), parser=parser) except Exception: tree = etree.parse(io.BytesIO(s), parser=parser) return tree def parse_html_string(s): from lxml import html utf8_parser = html.HTMLParser(encoding="utf-8") html_tree = html.document_fromstring(s, parser=utf8_parser) return html_tree def guess_type(extenstion): global mimetype_initialised if not mimetype_initialised: mimetypes.init() mimetypes.add_type("application/xhtml+xml", ".xhtml") mimetype_initialised = True return mimetypes.guess_type(extenstion) def create_pagebreak(pageref, label=None, html=True): from ebooklib.epub import NAMESPACES pageref_attributes = { "{%s}type" % NAMESPACES["EPUB"]: "pagebreak", # noqa "title": "{pageref}".format(pageref=pageref), # noqa: UP032 "id": "{pageref}".format(pageref=pageref), # noqa: UP032 } pageref_elem = etree.Element("span", pageref_attributes, nsmap={"epub": NAMESPACES["EPUB"]}) if label: pageref_elem.text = label if html: return etree.tostring(pageref_elem, encoding="unicode") return pageref_elem def get_headers(elem): for n in range(1, 7): headers = elem.xpath("./h{n}".format(n=n)) # noqa: UP032 if len(headers) > 0: text = headers[0].text_content().strip() if len(text) > 0: return text return None def get_pages(item): body = parse_html_string(item.get_body_content()) pages = [] for elem in body.iter(): if "epub:type" in elem.attrib: if elem.get("id") is not None: _text = None if elem.text is not None and elem.text.strip() != "": _text = elem.text.strip() if _text is None: _text = elem.get("aria-label") if _text is None: _text = get_headers(elem) pages.append((item.get_name(), elem.get("id"), _text or elem.get("id"))) return pages def get_pages_for_items(items): pages_from_docs = [get_pages(item) for item in items] return [item for pages in pages_from_docs for item in pages] class Directory(object): # noqa: UP004 def __init__(self, directory_path): self.directory_path = directory_path def read(self, subname): with open(os.path.join(self.directory_path, subname), "rb") as fp: return fp.read() def close(self): pass ebooklib-0.20/ruff.toml000066400000000000000000000017711507750473400151020ustar00rootroot00000000000000exclude = [ ".bzr", ".direnv", ".eggs", ".git", ".git-rewrite", ".hg", ".ipynb_checkpoints", ".mypy_cache", ".nox", ".pants.d", ".pyenv", ".pytest_cache", ".pytype", ".ruff_cache", ".svn", ".tox", ".venv", ".vscode", "__pypackages__", "_build", "buck-out", "build", "dist", "node_modules", "site-packages", "venv", "docs", ] line-length = 120 indent-width = 4 include = ["ebooklib/**/*.py", "tests/**/*.py", "samples/**/*.py"] [lint] extend-select = ["E501"] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # Pyflakes (unused imports, etc.) "I", # isort (import sorting) "B", # flake8-bugbear (potential bugs) "UP", # pyupgrade (modernize Python syntax) "C4", # flake8-comprehensions ] ignore = [] [format] quote-style = "double" indent-style = "space" skip-magic-trailing-comma = false line-ending = "auto" docstring-code-format = false ebooklib-0.20/samples/000077500000000000000000000000001507750473400147015ustar00rootroot00000000000000ebooklib-0.20/samples/01_basic_create/000077500000000000000000000000001507750473400176055ustar00rootroot00000000000000ebooklib-0.20/samples/01_basic_create/README.md000066400000000000000000000001211507750473400210560ustar00rootroot00000000000000Basic create ============ Simple EPUB3 creation. ## Start python create.pyebooklib-0.20/samples/01_basic_create/create.py000066400000000000000000000036631507750473400214320ustar00rootroot00000000000000# coding=utf-8 # noqa: UP009 from ebooklib import epub if __name__ == "__main__": book = epub.EpubBook() # add metadata book.set_identifier("sample123456") book.set_title("Sample book") book.set_language("en") book.add_author("Aleksandar Erkalovic") # intro chapter c1 = epub.EpubHtml(title="Introduction", file_name="intro.xhtml", lang="en") c1.content = ( """

Introduction

""" """

Introduction paragraph where i explain what is """ """happening.

""" ) # about chapter c2 = epub.EpubHtml(title="About this book", file_name="about.xhtml") c2.content = "

About this book

Helou, this is my book! There are many books, but this one is mine.

" # add chapters to the book book.add_item(c1) book.add_item(c2) # create table of contents # - add section # - add auto created links to chapters book.toc = (epub.Link("intro.xhtml", "Introduction", "intro"), (epub.Section("Languages"), (c1, c2))) # add navigation files book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # define css style style = """ @namespace epub "http://www.idpf.org/2007/ops"; body { font-family: Cambria, Liberation Serif, Bitstream Vera Serif, Georgia, Times, Times New Roman, serif; } h2 { text-align: left; text-transform: uppercase; font-weight: 200; } ol { list-style-type: none; } ol > li:first-child { margin-top: 0.3em; } nav[epub|type~='toc'] > ol > li > ol { list-style-type:square; } nav[epub|type~='toc'] > ol > li > ol > li { margin-top: 0.3em; } """ # add css file nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style) book.add_item(nav_css) # create spine book.spine = ["nav", c1, c2] # create epub file epub.write_epub("test.epub", book, {}) ebooklib-0.20/samples/02_cover_create/000077500000000000000000000000001507750473400176435ustar00rootroot00000000000000ebooklib-0.20/samples/02_cover_create/README.md000066400000000000000000000001411507750473400211160ustar00rootroot00000000000000Cover support ============= Create simple EPUB3 with cover page. ## Start python create.pyebooklib-0.20/samples/02_cover_create/cover.jpg000066400000000000000000002530541507750473400214740ustar00rootroot00000000000000*ExifMM*bj(1 r2i ' 'Adobe Photoshop CS6 (Macintosh)2013:02:09 13:28:28N"*(2HH XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km Adobe_CMAdobed            ~"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?TT&$״piΙ jݝ#m`H5R7"}cߥ#vN!;Xm`';R\f|Xrm5$nGwDk44!?H֊9@CE]NOǭ,vkADJ{k7hL;MR'Ye$BHo١2uJI)#i7ԏp3M.vd8CAjJCQ.(hnG6FI$TI%<,\F۩eCUh.a_7s>LU3ԱvִUO?z~n龻"}\}se]@Z朻w[[GF-6- 2>)J eQʴFپ{j.wJk[^Lzczz_YjgQ=Ӄ˽eCku/kMVz,XS>Up[68ۛQuV>ͭuRf< o Onju6o԰sVCq{#5rO\ܗ6N$l{G;מu31"rא_ZS=*XqztYi鹛ѽ3?H1}e揣Oq*:}1el/n0Ĕ]UKca{W#oַ[dR*WY{]vGf]?JZZ\45W"eigj8gxk޴;yqku~='eo{H- 5Mr=t< zu.~=-7u{?-??a\2Qa]c! 2xGSyjDb?Vzsqj X]tn36_P+ƽwUU52Xꭡֵk7ֵ\COU.lI%_TI%8[03Ldcc5[]k}l7Y?Vz>+XXUeIzSjgGHL\9_M5zw5eU]3m{ie{+bVݭ=w {?vq#q>$?N+[r}F-=C3B\w[sW TT~·g}4)X,M=o%sF^=OBCz5/wty[e#sh?jT+I~ӾR9"L?U8ٟTzO֪fUkS}JY=\OMվוּ`/bZUim~{mg޳zm]/Qݕnm]/.ԯNA_ *Q<0D7T㾸^3cZgWMnԯ3?Y}'o_o77;:sne{(Wmj]H}/DŽccCUSIe?99Uh ѓYE1rp2q^C@-V;վu^EꏯP+GwYV}7އrK$8G_G~ꪶk/Wo+Ey}?H޾̻"$.C$KyHI1OTI%)$IJI$RL5.q hԓVt̛=<|.cev1Θ.۵?ԔI$RI$I%)$IOTI%)$IJI$Ss;}'c< 529տd?EWk쎏I e%h4Ss6nغ>8CϜ ,ߵcuQ]Z %C cZ 3~%7~  '-+k>+YeM `C[GnŤI$$I)I$JTI%)$IL-V \}`s1FNAoYC2wM~lЇuZN]./+thFݻcl+r*mQLnl`wwV,l2K} q5v?+;Xs)Yǵlſ!ζRm8/m/[__ëiy2DOYpEu6Sىsqye[GlS.`5w ^׊ "5KWMKl[ɧvVVKs&ouǵ5huj!SNkzL9}? u?~"7/&CJEF{[[Yؽ ^{quW($IJI$STI%)$IM, jy1oZB>adzOk_ӁTכëN+)}.vW_}E 7XZ5 ׁs j㱮sxo%svD2ݍhѕ>vwk}yog[{o\^\-gG?W &ZZzUkqkWv;q~KrC[I$I$TI%)$IJT:t>Ŏ @]FtVc%<eĂƀL( uOP@!L GaL:r's̡II,%^ y^qiѕy J{4]O2׿zjdEmd]]?D Dfy8&ZIUt;ڱz?Mn&6Jo0:atI)I$JRI$TI%)$IJY_Z4Xַ3 ߵj8fHr1/t>J|tdlqm&V]6۶"\[~oekr:}o0= ;m0~S:zu^jw{u%=sS6j`X a;u\dg])hX/ezmi~\f9AkZ^ӡ}߼׵j( 3\_k]nsMv=/QO$N6k/ǺVCK1oܵ)GCϩ8V1v)I$JRI$'Photoshop 3.08BIMZ%GZ%G8BIM%]tn۾9y\8BIM: printOutputPstSboolInteenumInteClrmprintSixteenBitbool printerNameTEXTprintProofSetupObjc Proof Setup proofSetupBltnenum builtinProof proofCMYK8BIM;-printOutputOptionsCptnboolClbrboolRgsMboolCrnCboolCntCboolLblsboolNgtvboolEmlDboolIntrboolBckgObjcRGBCRd doub@oGrn doub@oBl doub@oBrdTUntF#RltBld UntF#RltRsltUntF#Pxl@R vectorDataboolPgPsenumPgPsPgPCLeftUntF#RltTop UntF#RltScl UntF#Prc@YcropWhenPrintingboolcropRectBottomlong cropRectLeftlong cropRectRightlong cropRectToplong8BIMHH8BIM&?8BIM x8BIM8BIM 8BIM' 8BIMH/fflff/ff2Z5-8BIMp8BIM@@8BIM8BIM]NThe-binary-chronicesNnullboundsObjcRct1Top longLeftlongBtomlongRghtlongNslicesVlLsObjcslicesliceIDlonggroupIDlongoriginenum ESliceOrigin autoGeneratedTypeenum ESliceTypeImg boundsObjcRct1Top longLeftlongBtomlongRghtlongNurlTEXTnullTEXTMsgeTEXTaltTagTEXTcellTextIsHTMLboolcellTextTEXT horzAlignenumESliceHorzAligndefault vertAlignenumESliceVertAligndefault bgColorTypeenumESliceBGColorTypeNone topOutsetlong leftOutsetlong bottomOutsetlong rightOutsetlong8BIM( ?8BIM8BIM  ~| XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km Adobe_CMAdobed            ~"?   3!1AQa"q2B#$Rb34rC%Scs5&DTdE£t6UeuF'Vfv7GWgw5!1AQaq"2B#R3$brCScs4%&5DTdEU6teuFVfv'7GWgw ?TT&$״piΙ jݝ#m`H5R7"}cߥ#vN!;Xm`';R\f|Xrm5$nGwDk44!?H֊9@CE]NOǭ,vkADJ{k7hL;MR'Ye$BHo١2uJI)#i7ԏp3M.vd8CAjJCQ.(hnG6FI$TI%<,\F۩eCUh.a_7s>LU3ԱvִUO?z~n龻"}\}se]@Z朻w[[GF-6- 2>)J eQʴFپ{j.wJk[^Lzczz_YjgQ=Ӄ˽eCku/kMVz,XS>Up[68ۛQuV>ͭuRf< o Onju6o԰sVCq{#5rO\ܗ6N$l{G;מu31"rא_ZS=*XqztYi鹛ѽ3?H1}e揣Oq*:}1el/n0Ĕ]UKca{W#oַ[dR*WY{]vGf]?JZZ\45W"eigj8gxk޴;yqku~='eo{H- 5Mr=t< zu.~=-7u{?-??a\2Qa]c! 2xGSyjDb?Vzsqj X]tn36_P+ƽwUU52Xꭡֵk7ֵ\COU.lI%_TI%8[03Ldcc5[]k}l7Y?Vz>+XXUeIzSjgGHL\9_M5zw5eU]3m{ie{+bVݭ=w {?vq#q>$?N+[r}F-=C3B\w[sW TT~·g}4)X,M=o%sF^=OBCz5/wty[e#sh?jT+I~ӾR9"L?U8ٟTzO֪fUkS}JY=\OMվוּ`/bZUim~{mg޳zm]/Qݕnm]/.ԯNA_ *Q<0D7T㾸^3cZgWMnԯ3?Y}'o_o77;:sne{(Wmj]H}/DŽccCUSIe?99Uh ѓYE1rp2q^C@-V;վu^EꏯP+GwYV}7އrK$8G_G~ꪶk/Wo+Ey}?H޾̻"$.C$KyHI1OTI%)$IJI$RL5.q hԓVt̛=<|.cev1Θ.۵?ԔI$RI$I%)$IOTI%)$IJI$Ss;}'c< 529տd?EWk쎏I e%h4Ss6nغ>8CϜ ,ߵcuQ]Z %C cZ 3~%7~  '-+k>+YeM `C[GnŤI$$I)I$JTI%)$IL-V \}`s1FNAoYC2wM~lЇuZN]./+thFݻcl+r*mQLnl`wwV,l2K} q5v?+;Xs)Yǵlſ!ζRm8/m/[__ëiy2DOYpEu6Sىsqye[GlS.`5w ^׊ "5KWMKl[ɧvVVKs&ouǵ5huj!SNkzL9}? u?~"7/&CJEF{[[Yؽ ^{quW($IJI$STI%)$IM, jy1oZB>adzOk_ӁTכëN+)}.vW_}E 7XZ5 ׁs j㱮sxo%svD2ݍhѕ>vwk}yog[{o\^\-gG?W &ZZzUkqkWv;q~KrC[I$I$TI%)$IJT:t>Ŏ @]FtVc%<eĂƀL( uOP@!L GaL:r's̡II,%^ y^qiѕy J{4]O2׿zjdEmd]]?D Dfy8&ZIUt;ڱz?Mn&6Jo0:atI)I$JRI$TI%)$IJY_Z4Xַ3 ߵj8fHr1/t>J|tdlqm&V]6۶"\[~oekr:}o0= ;m0~S:zu^jw{u%=sS6j`X a;u\dg])hX/ezmi~\f9AkZ^ӡ}߼׵j( 3\_k]nsMv=/QO$N6k/ǺVCK1oܵ)GCϩ8V1v)I$JRI$8BIM!UAdobe PhotoshopAdobe Photoshop CS68BIMhttp://ns.adobe.com/xap/1.0/ B45851E6EC9E130BE35D35FB857AB355 XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)KmAdobed         NJ  s!1AQa"q2B#R3b$r%C4Scs5D'6Tdt& EFVU(eufv7GWgw8HXhx)9IYiy*:JZjzm!1AQa"q2#BRbr3$4CS%cs5DT &6E'dtU7()󄔤euFVfvGWgw8HXhx9IYiy*:JZjz ?N*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UN*UثV#1 4TU,~^P/rMqƣ*SnZ9*,إUVUEOlUBO0H-O$?ZbiY<3>*/R5VI T"}4UU;m帙 45 4=*]sJm6=IgRcR4wIs=^́p(~v5qT=ܭ ܪquON-Vw?kR_3hh.$riDUU,Aìq"-WIȋBhVJ6~,Umavш,4r5FAh(G~X1 W+8+dtXTRTv*suok\8]A@MIبI"s4J$v<1TKv(ց4$9bþyKg#Ħ) V2(t4e䑲}Tv*m4NR6ux.抡P3O*Uy#KHGE ДQ*[.X`&"F'e -?lUk\w&!È9H + Uͮ.-ĀKh+T#E=V쯭ًYyG *<W]m2u;1GTe 'X`D@[7mq(V4-lS`VTTwq4WsT3g5f cV>=Zf&u;C4rBI 1ZV[kze3w"b'N?[6*oLh%B+v2dI":qUukD׷Ee yO|U "Ҿ.Uك4v%bܒ28qW'f/*B&xЙMF'}U%wٴ3G4JcDL6n_⩟Ն05բ'Hq/qV?i%Ռ$ZSR[r(l2@~+qY*U'/zw/H[z U{ m`jpWkzNv'?LR60E+<_Sfbv*HtA !9}AB@+QKQ̋=LT1*d}27"?o=?yoХ~=Oxb&P~zY~=kU C|֫-}K-yUAY[E-+[#j}*}_w}_BONyp4qToOO犻GTmG[6wCO%G+,[Y6*U!/z$TW lU-7r%Ɲt/4*3FiKqoS*7~ѧIVzWֿk<:Î*s_ݐ=[xdA"I^ER=ogZki-\WePvbl,խ{8EhPP^ JE)QO`b- S|:C mefI(Cr##ETmG[6wCכIpVXcqZ l+I(O/OV^ a(@h)4LUhV:.lch3,0NaBS}GQ0LUj֫m )ڎ[,ڔpy[pyK"+BaN)SLUq$ӮQJGC^#Vb_[[s=FoZee0ci[oQyG ߗ #]R[تmv*UثN*UثWb"^-SJLqUiQ t^cəBzrFlm):ǒ:#bKHc12ʲ$SڕJتż76̼9UXPB\ZUͥdq_F⫮]:S,ѷ7P :,6Q _*goq%1#֜" W#F*>G$MŮ>J"7Әe`8pzdK8Y$FBHwlUmahb0X@nZ0BUPhh_LG\8hK1v,<ѯ\UP${VU;[{(z I H2<$c df2l9b:Ns}m}4<9}^Z+c 7,Uتf<1Wbk:PAtWYj , I^hyWY$ByɯUmZγAW@DA# HR*j.MKsqz;Dg劫ekH"'ANG*ՍONK,|,jx'쏇DbP6&m: %]9GzqVYZ1iw MxJU}jD-Yᄫ#vHN6bqW\heO,$iT;r&HՄrRekܷQ%z:6qU;m*K8bm)rrb?y *8"^1D#ZEQ@*w銬N5yuvTqT#yM3:IpލAr+SP*BIc5'oQ=Kyj>jATmtKYD=e %īCD2vTqT5ևL--=eY$DX$mbhuH%G謐4'ӯ.?tڸD-1dh3}+ *ߵ|Xidnr;3;RܳX⨌UتVV]OuR{g<`ؚ r=1U .fYhcIbXX#LTbǣؤI-Yie*/5}U~m߯pJR\J^ثvvS piܚP>t)gMDeԓ^b^zኢ (7Nd,&QPgZ?eX۴s"9^H|*&%d5FiEp($g2&تigSR ;5:ry 9j*]v*UN*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UN*UثWb]v*Uث?>8ZMK%Ēί*dHEsqx%#? &-:c%ᵙRRduU$FͿ+gC4 XEUwc<6͗6? >d:y hd[+eEg *KOª 9 (8c䑻էX9e%+?×Iy⿢Uk? 6~Ty!u=u Fař>(eTͦg'ɉ ~e jזt%O ɖ'vSVmB|o\y :Ls4۴^Aj)\ҭoy5<Y3桛Wb]v*U%󥶧wmFL/R/X ;Jw1 Py1o2y&Bsm2_i]̶Oj`fGdfPxz|?4A>;xx_A!?-|{zyyWUseQ?6ɓg81><(X"Ͷw͟.ieuDK3z.=N3!Ŋ2OC7b]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWN*UثWb]v*UثWba]z% uH6f O4Gϋ 9JBr'?u5+?77ʬXVe;cdZ/4h0 }:OKPQ"RD%8~<~u݉2d8kTy9vh3;1j z˓<&b?JJR[cJs0aun13@U)?43鍦kviwjǒdqtu#{˰j'\P<%[̇אתou#._Wa?ʟf窨}L|0+C˾VӿGVIgnO) ՞FGj5:NL,(KWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*N*UثWb]v*UثWb?:?4|?z)@h{%ͅfn?wPSfgo79|9-m\C.a,6rzڬju$5}:?gY:9r|ٯ况]aZ "5>^4>o5}~~?cot.I-t6vq0D;" 9dg#)}Rov*ӺrTUXKk_ʰE ,fVhF.fGpKz ʳ .]]Ysw4vR+E3J124I<'yC"0M9+2kQ\>. b$Ad%l\hҼ7BZԎ`}7ggɼa/?Gԝy C%l#R?^0˴}{D֭_62[H|xc9p$UثW$gVdh(O̲H>i6vegH2 'N|?O֕>Fg(#'?5J-.n_[%ocs661rHdyb]_kZ8RvEN}epO!"iwãyJUѮ~B92ԩ*u 8Q6V4|ɬj /@ |5_˳HQ/g 2^r&[yj4SP.$xG Ewx~5]OU{J[۹>HڬNپǎ0>yMO_jr{ɞF}67̪ޞSDG[`vxw'&}rK1JX#)FvI_\:i>+b-Kqm>&okIȇز'9hD@l;JҿJJ`i2Ɗ\ ͷ/7ii81!0Pjw`xzY16?&6O]Rjm#xpWY*5s _bgsWæ}_ %tz>z6p:^4 XHUO,9JFMlο_:qo[2K3+ k7Xpc16e0I/>\i_Q:g)Z}_[afn]v*UثWb]N*UثWb]v*UثWb:iybG\'Z.l'6,^ lhQO}kn)T泶IYŔ9Ώc:7Y-p~SDu#R G?zmM,i a+<1vc.1?WyλgHhRxl~Lc-3Նo'Zy;#BUԂ #61z "$moHpƬis_3M\Ywf&C)BV\ ]NFr/?QQmӌ,c_>WV[s.RXrwS^+^]Nnԃ|i}=u,Z}ڙl.IJ#i#OV';^׍L.e-201dgڶ?ԟǛ!h~\YAL즶ӢQ'J:rJ7fV4KF"y5ud[GS@M<(~%IxqGi"e!^{{8g!#_g1y}%h^Qa4un(=I$Չ??esεZ癔u{>PJMObQ K͏&E<_i6ŏ#V*׃r;D<{/ާ<z {` Qi?A0pu3>qη^SՌ}"Y:HǺƏ?NfcGgv.K31$I';)/&Q2W2A̼=M=9VM41;w+>+gW ǜOKVGJ{cJMwQ?IޚG?C,[;|â}veS=ưNc@FQ?QIJol BbS B/j:u}=-oyj8+;N3MĴjcmSzlk;[aɿg9|is#*QV5PݷЗ犷 ϫ懴Yx<[<0aRY6`5t<EQfhq|?Qᗼy :tAJ]K(dJSS9OQ>)|?':giwzez<HM=10dzFiNAOZ:2LrqKr{_μ-<ߨh7-T 9%?s /lPU柘@Aӷah`Fa;ł1<שǑ_ o&wucX svH2+ >#l?ߎvƳC6Pz^jYv*UثWb]v*UثN*UثWb]v*UثWb9?>bƋ_4K ̓?_mqeosov*մ [L5WqS OIBBQQRc~Z>iԴ)&Hzd\6(qȢBגHD^2A4ך?o5ݧD~FT_i+(e!T8WYFMk#'#/1jd_?0mkLq?\ܦΏϮk:֦Es ֱ]wcVyːUhCk&uLEܥC'ILy:rXɅ&0NNo3_sIp8b$I4rN*/;@y~Zң JôqQsolw#ÏXCoKQmGGOccXXɥn'&|yCCǎ{OqfTyO.OR"Uzɸp?츿Q(͛~B~mh\$Y,fpM_,TDge'7fc6;h\}g۝v*Ŀ5h˧VLd,>!鞆*_Qg? =Lr)yh=/ש&?(<3yrrR.ng!?15/X?cMs SXywQFTa"#i;cWf }dfWb]v*UثWb]N*UثWb]v*UثWb6ߟ_J?M?zfxUǗ6#zsAlwP'86b_.T+Ltf<}˝̏ކ-b?$пVg?Yjf|[ɜf?+u /$|GgEk:Es ֱ]wcVyːUhCk&uL yP9#㑏^d~|9z\3ы_6泶?guP;8&b?*-?0Mv^p'7fc6;h\}g۝v*Uw_z1' +:t9%(GO4+ &kVZio9H󓒈ȫ+u kCO_۝K#|뚙٧b3]?)7y.CWW/kɣ''̙׵2u/=/?M'o- ٢\<|Vgj}+T6R[a8Q\v>!=Lo}_X;ƄȓE<<bBz[&Fck8f%#!2b{7SY_򦔺pmEQ&G@g\0&DO,yLt,i+Os"x**%OcvvL1 5IY Eyc:[Et88$EN9.&rDғO6_]yb^q 彍CS/Oew ^mNCW۠/VHKP4Q?e?sypO`ȓkX5=jp,/Xqe!Sż{9?7Ÿdaic}szPA;7&{Z wc2\B[uTl6!'(/FwoZ&o;՚>A U3fM8t9=Y?8|K[.Zl1êL NS5J6ffl3?K> =rOd4umAŝc,Yۏ9VX0'F͓iE_&y Z7H.%%uSOi|&#&9Rikq4g+o(q"IȆ2EFjHfv*Ui6Ƒ{^/+[dw"${ba!!'H|iWZF{]763o(9FIiQ !C,w9izG+VlM_KYDY}( b]v*UثWb]v*UثN*UثWb]v*Uث4o;hjOs=*Hn"ӋVY=l/\zڐo/56?Qy?U/o'K-|0V{[>??t>R_ (K8_KWSÙTkfGt?я -UT9uL,eaߍ땱>ˤoؾ>qKKOxbI&W/h?/&8[bXM~k"ҹ2P4CѭX[C-H<@~y˚y ș2ثהAjsVDDh 漤n6:>˧8h/SR4gQ-q4̃ڏGTk= cU qȍ_S?W(?0"mI5CD䇴|u!s#g;XKkgTp(ݕ8~kjȋaQ2Yp?T\G+ *Ư ]7U qɅ^U8G*MY"\uHM!'OV[5^FoyX4:Ćm맧$½_SbQ3~YOjǷJBaJz?7U qɆޯ{Qo%H;obxU'0$M4jNi"3#X9zڜ^T-᳙ "UߊI])Bk($: GrWa8`5 j{us:Gc#jZeL}$&uezVj5].qS!P C7qDԼv4{ؼ֌홎RO4T\vx1f qSέqUcFPă62 m^k+$7UR֊: "!Ww/hQ6F4~\WMsFnϒ9@B<5/ڃP8dx[E?G|ү_BaJzuL˺^?,H+Uy?-ͨM~b"_ZLғl`Wb]Nm,?1ݗi@K"k˞viI䡪PJ5Fl-/mL#;Yll3v*UثWb]v*UثWb]N*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UثWb]hqDv4 *I>h+Xo󙘆q?7==:ڀZE4ے4ZOW-,Z~(45 (@ n]]v*UثWb]v*UثWbN*UثWb]v*UثW=R;;l:oÝ9S% _O+C@ta:*y?.|xe?9o,&JwQK-C3[!+^8bhﺼqu|-kSVbc"ᗊ?|s|# q02ҳv*UثWb]v*UثWb]v*UثWb]v*Uy?AZMG]-W.nIe?N?gd/?yuhoQFOb'>١fUثWb]v*UثWb]v*N*UثWb]v*UتQo(:}ԖD Mr\Qy0sh,A}O^6]Y9=[4hљYJb74oGrޛ}AbYcۏDjKoKu^.WL%+yU=~iZ[ $t,r%y0edtxj"qTݛVuyv׷hǑ;NQ~.kr&1dy<|tW˚Mһ J*'deY?wYY~f6M,R8=_lA$2y|ߪk~t;d"I5U1vHS!M,8|i=Ħ }SW:eS3Ewޡ%FHa1x4<|6ipc@ף?I!G#qu,EY\0 CK$^1#m'KCŒ3$OLb_/>hm.OO^C6Ja$'r'|d8^B;]bT򽶱][ܹUVB7d ba3ˆ\?elg{aFkwK HHbZ6fvC_DM4S\֡Gy-˛x{ArNebi8>|WIpXd FQqO\K)j6~pNSH?1)oL_LJCX0X8T9Y"Oɩ/]-ݓ*f4[/-z]㴸-BFD}`M*>Q rà ǒ9x|Nc)oK9$[O_ >PfvO7'F@׿av/?q$ϓ+?#0Sъ?f"rI.">_Α]=OL23C\=~<\\|].)G?2Ģ;\_O_VtDGd&bX'+*aYC1FRYJ14ʽ.I XkZeMKB!jƹv'/c%Fe̟/esɱdk2L/B^q6REoD5k"Tt4`w9f<$KvnO엘"_{[SU~_S̬l,-c(!Eʪ9&&5YCW1wmu&.~.c >pw3Ao-ռk@.a!4HB @͕y0XR-g cD]%VOUb}oW2qx<<<_ǐs浏F k;qidXjF&ol#/яHc˺&!w-n`D7NQȤ:*ǚpV)V۟^Գ]A5(_yɨcPXzV>b򟘯-*wpnC,h<. DFu1[ZG5& #S{>i BUyly14$>OCV^F햆]CB"[i z EOq$xx<<2ZRs%΋m5iTӓr񴆟>.QS QGUb(E!rxZ6jpf8c/O~Jb ak]JJ[;+FugFfNe~sVU-D!"x?c%g_tLv0_4ӍBrS0i+2VvidžF>)8BU6垴?do*dqKm_b2܄kV1UϥD~CSyȧb 1WO~ayϦG\Ogu~UH銻v*UثWb]v*UثWb]v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثWb]v*UثW$'|9mG49Ğ]䬳nb0Sߓ&*(䓉Q@ݶ?F*6~h+O}TB򾧪Z@ұہU䯘f=])0ynD􅷤JEk]1TkI,G߮M324UtK-<(eb-uJ>L®;bF*{P(*kS5[G[pŨYCx$"EԵ-A׸^WyV]>oJP!TuHfJ@?U7R}/T-z'22է%>WWb]v*UثWb]v*UثWb]v*UثWb]v*UثWbN*UثWb]v*UثWb]v*UثWb]v*UثWƿjӵ̑cfE템_w2HElUQ€cO|k{QK+aVoUN*KHe5#3{@{mvqU9.95w?~KBN!LUYG@c Wyw#"'Zj*# oGxf;BZNthB?(yʾ{Ӽ-VmPg;(zۉS1WxWb]v*UثWb]v*UثWb]v*UثWb]v*UثWbN*UثWb]v*UثWb]v*UثWb]v*UثWpX~m-= /^j{Y迳tRiV/}Mwuv )zbU;/R1O׻pe;?s Z}1(dA\YElԍƽB⨸bBQqV=2]RȱV?6zv*yyUMeK?{zMs3H"oImU\'㗏1WO 9*#8UثWb]v*UثWb]v*UثWb]v*UثWb]N*UثWb]v*UثWb]v*UثWb%PE%ǫU~Wy^o k~\@ӯ>I1ut^A+U<劲mV]>йe^ CUyOEU }6"ej$l%?S[RƧ[\Uv*Uث g#mũ$*JJm>U*E*O֏>YkU`AUf=4 uaN*c`7֮\ekSFϱ*!Zvߵ)\c tUZȱsi1WZ7%F*|=H"%y#Փ-*=`4meȋ H|s;.*tу#j;[]v*UثWb]v*UثWb]v*UثWb]v*UثN*UثWb]v*UثWb]v*UثWbv?湙"ˑ^=KbnoZWT78@#ԑI6*g}qNaD Tee=*iQV YF<$nNzSPJBӯm1T5Vt튾z_0~iZ_n?sW:dORh?ҡATWh^kֿFȬ^̟m.*]v*UثWb]v*UثWb]v*UثWb]N*UثWb]v*UثWb]v*UثV쟖*u_5,bBnnFޔdPKc>hnF*gqRⲳ%f$Ҧo`oAE⬓J5nQ O*l// [ID6ƛ> qTwݩU_P,tY.#rycEf bh'Z7/gc yFf_Q~bƲkKcm17^\UMPf ܑXN۳Џ;'.*ȼhaNl0*LJD)޷3[bCuhE uo>ޤ[iIO^o]Dթت!|jaTLmHT(튫r~xQ]WZ|l(qW ䷄Los; K _b S3,:͛T-}[8I ɏp.~'*yu=S E) [bգ,16C}5/繰ӭQlU :mܲBY[3#O}Fr1UyYS4SzbAyŭ%ο # ⬿\[ٝ{I1=Zrط 4_nK#yQ~*m+::\U4]v*UثWb]v*UثWb]v*UثWb_N*UثWb]v*UثWb]v*UثV0_OmRO/q*:3X|Q#ġhI0ͿZ]Vmu8)_q=>\oe>PgKY-FCTJ$n4:⇠2m9Ձ$±+ѽswt,K3_Y'v :vvs\U'sUMGLU~vrP}s(EI*o*} i+E rd)q2/J&SN*ke[#*(̂$>OO;). v$+RZ0ҋ7Gz6xʁh*!=j{Oġ5ZX[ast%aŸE76><7_*C*~j7-AeLioLlt+7ت}:im2)qaonh~վ(QqU[.Xu74K$eqhVEJ2jWY;5?Pm?nvtJCアK숮~{R,i,L9du )Uv*UثWb]v*UثWb]v*UثWb]N*UثWb]v*UثWb]v*Uث`Rmfx1_84OFSb!wھ8%JtTJ~#R0@?׊w*M67d:)|8μUy{>`qAq:9ѣܡ|UyZ=Z񯡶6UIPF~=&xoX#[cwnFJțYF*ƵW&WJUfsP:Yʎ*j7vGa`QV_ <~xkPq]$ԛ|l8r/bl5Ӣ$zE5+&ziqUa 5d@I/5%YLĬ$q>?z6k{4V-]tdjtXߜ<4WEgC{biSwz~'WrIe>i4+Qg2 ȪX> |1BiZmzˬi+cITn7eJR=(.ulHZ*[7UDwhK0KqOjs娖AHi)4ؙo5'qV îEc+C^j-pWullvZuKwufXʻuc M?IN4sJ-/RrbO{ dO+w {jiҞ%? CRqUl:c Wکi[ X.mdJ'b[woRBu+Jg J[z^obn]v*UثWb]v*UثWb]v*UثWN*UثWb]v*UثWb]v*Ur;XvڵTd IS]g3Xk]SO~5r U }_"(%GIqTqIQxWF* @*:nz֞>=1UZ%ޢ8*銳>8֔;U~c^B-t4q}k$s XI7̒:!W ,%[K]a*^mm2~8TϦ]qed努y#N{ve-ZQǧZ(vWM+zB4 7#ŊT^!FB()lP,cHHPw?oj];{m-[w}~IR3ڶSѲ0 mc$t%䶵klUui{m-K&TI.nI_Mg"ƛYFRVIذ_U N% R$}ث}k;_5 1D¶?-I3\8^g*)_88yB&lcǵOӊ6z>n|mO¿v*?'$m"ztm]KH}^CyVuRյ F[9ftJGD^gx/Պ3ėiM{yRk[?`QzMC04c7'PskTzvW=N7Sj~;A+݌#!7Q}f6f r'noݫU|P$6!1U3i$z+A{nM{M{c%D:)BekVO^4Jjxj IXJ}J7-Jg$zKU-tr?%z/1jRT &.?\UzY:WrQQxt;0Q:ڢ@-eueb=+ UV?^)5ҊuIסSAN=as<;׵טC*!M>+֏.~CAj,M\9Z^[Fv?HMhx`g v܏Dˊ`4[[H̱{6T ~la۷u+w}+6*"Gz` qEO+X*3~Ү4h¦苕?ﯬ*A]v*UثWb]v*UثWb]v*UثN*UثWb]v*UثWb]v*UتKJt}WRҘ,sTQ*zby V|qT4:l9$ƒ`ߙVT$MĮ#^)*B/E^Aky^XζIԼ0t,^@y)$9~W>*Fֻv3'4rߵ7gh-OJY_roXf }Bܿ{#ӁIԥU$Ks\U~dLoo0}Wː6۔څ첾l; Λ&81}@HjRkqgr-qT Ip sAKy? uU+vi#PG6cAӗi:z}Yܱ+im0 +uϲˬ l{xa? ;}WˮWwl[^mN?ЭSbrH-ZMY6`OZuˉTx[ct e'tR?N*%a[Y%}M5Sax*;ǣXs_MC4sCѰhPáV_v*UثWb]v*UثWb]v*UثN*UثWb]v*UثWb]v*UثT$4T-TwԳ#eW`l*Tf|UUu:/e{{hFbIn-a_ٵvUӪɣaj(roEԶ/g?Q^HI'ߚkb[˗PSWQp[֢CnoU0#V#u1. &3.{7*ӮQçR s߷<PQlPZgNջM{wo'SSg1U>_<jc,P˨yZ"/hֽnN*hmtԝ+}|r[ifUplWWX'PI7qm_TL!n/+%*ոjm4mvk7 Uٝn4DQΒjP?bLjz9,/.?[WSeeV"|4chn#цi( )c'RnثYKfՊ/.tEuzb:}9VR-P9,)n/4 Xd1,QF bGev?̅ld?g:\w2-^Ӹoun8 >(B? :YX]Ȳy[/p6W1w+ _eY6*UثWb]v*UثWb]v*UثWbN*UثWb]v*UثWb]v*UثT#)Y$8tV4 ߊK?WvC%Crfbz(|jc̞_i&m$\OE?,U,!]P,𡧧 <{M=?⩏픰\ T4lB@{ XӼ ۱ՒP bHWL1;qT=;[K~[5ԟ=Y%*͢0"\@GJS 8bW e=]k&*x7fk\\UOC-%ȚKvWMP&OS=fY1U'R+G,>)'o )!xز2bJAzZaٟ#DkS[)؝RVXNW&<801 cFg}&V;\F&bm=4BKn=F*j[@ 6y}ݾ.*~/p"'B|Pw-Tn?[ѣ<.n光Lb [7stOi0zx2+J5ERUy4Fn22V^4`@raG=i\mqT&3muύ 5K*,`ki 5< n$C PZOCJت$!W:v֣dU($UԕH QQ^6zbӴWTZwn阪WEנ)l&HV HSлo$^{hVW>QY]fVTM)g ON蓜W\U[j"V)-PFRk]GLD_7vښ".#Ti"kUvp#i`Ѣ,IKtlU/V59e13Gmxbƍ,z7 2oۈ.".U1Zs̋"|WU]v*UثWb]v*UثWb]v*N*UثWb]v*UثWb]v*UثWb;FrOxGT&q5JS6XO-ZMRXW/ceB'^^⯙/R!%fm!dS|[动ջef 7nq\:iQʵ;5CRӛգO0]7\UGMUKkOU V2 -?Oy}kUH5dtv6VkjiBu+ڒg:{;nELт MsuVȈH_Ot!.4r)O߹_8[\,X}gt^9{y)bQyZU$Uw;"vroE~^]Er3F<#soK%Tykyǜ1-VXvӚ]<k{ٕOqW66[e' #?mjO&ѷت)"tQ x⨛yip8ڻտgH˧j/8.9n3oqAug#$BI-,XcYN*UثWb]v*UثWb]v*UثWN*UثWb]v*UثWb]v*UثT>0bh"޿8ҁcoM1VA@* ;vXe\UfUߗ[nK\@@y$6>?qY,3vC7 eki5.m=fQHO8XObڲ4 <##OZI/5?*?$%@фm],U._X(nԨqﱿ[IH]4zmfS+t+Π}zPjڄ_ePvMZ*{ gjixA{kzC?8MaZG}I=K4=¡Rٶ.,4čɾךR>|U63 ?ޖQ勆IfMq5{|Wڌ] v6rü*%ZKS6o!n/oQ/?RV_#.kuE2]V:PŊii`*;OhqUkxg"7zXT9$ yBBE *zm6)YOI>߹xPc\HfH~,:qo/*p.L"Ͷk)`(~V*[fͤC=S+ƪس,IY1bTn;UVE, WRtXR3۩y4*rrc[X]$Y[G+"*v?g_:E3"}MHeiV^v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثTO88GRLU$D]ȡ/c:SrV1|XCYDKk'25?O!~/S>=vqU+HP"1TY@ BR;4O-?bd.Tq3l>ghc˩b&Ju#`?hqWξdLɴsܻ-;|)ty#/8Va乍3925Ѷ=kX]wU<6zoj5 ?].?Zܟ ?#HǍ.RQHWBnVqO檒Rܶ|3犪~B[[L5}TqTmunaXVHRp_{R9o^ ME:aPGъ]v*UثWb]v*UثWb]N*UثWb]v*UثWb]v*UثTB]1z!>ЃJxb1+^Ku$%I8/ȍYn'H! Ŭ1c*ُ)",ꋨyv[KxͽYe(RIZ{sok'/䘪*R H&I9 0I 3fpea,}[L-KXڀIc'[ah"oێ0vp犣v6֋|Io]OSUh! P (XC;MfҖ{y$"h)UZxq,݆0\wKxOnm@-[OJ40<C 5~6eW[k(X-۸tnz {b9an(ɈR5ۥ#|K[Oy.1T[5kRKgKK!$ ⫭0 \{l~*֍356jWYH*\$QYI>?۟1Wi \P;Rk?UtG/FŏNiwW'uR-&11)DrhB6P7Bm$2)g[i$a oڲ1TOuui"0[@Cs"?!!o|˧KDx> wG\U^OEE{~-{sZr.\Uڼfȣ >/eyKAG:M̰7z͞fE*f*zv(1T ,b'zx-_홱W_L%3\LcjۙA"kWGB {1B7Q54jzrɹݞ:[ۭzUnh,cFT<ޢ "$nƹkmQ2qBjVCj:XTѤdtÄwPkYtHI1W[ײVH"0ЎNMGSb{[F6Zv=lURXn-U'?z}>HHB~,+?ض*26.xpk=cy@w,yr;b =ѕdvxWo/O=*s^=Ii+}^CQ6T*Ͽ/=es5/f/qVsRkNB[$oۛG8&q*%0 iNJkg%ycF|?das_R'_HXN'(a`~ҭ~U>FlqU@E(6P y7IkxZFe.$|}"(fxfY\qT{Qw+R'.50'3V}[Ѵt^op-[1+Đ\eFʜӏ}ecU^t4iR5x#:qH7֮i6*fKb*%ma04?Xy-$aіS_bqTZQԎ:5X&=wmޛl@vdzk?b[}dYB62'%\P}\f&ޛukhO╶Y-֦}VaҮ*ôVSqok,;dƥƎt$',[GWmL6tc$j1ml~.&1ԝНʛYw8nA#z} 8>V+[t@S]@)z:U*Q)#oQ>+ PkRBvTI!zlCJa\,iⵉ`I Z}5;2xG_M?*4-y;/=Fyf_/qW~|iȚq7o(_Guϊ׮KIUSlUv #Gn> aZyCSNoM4Y/df L6ڿT2*j,hm9J@{RYqT ΠSR[Ɨ :XP\UqMo<" m-<@ZBm_J⬳z.=+YOx#aq,2*&œ("^يv*UثWb]v*UثWb]v*N*UثWb]v*UثWb]v*UثWb>ӐḌU=R譶b38>CISWwACCXTv b12rhMF) A(U"ΩO+y N)wy KQ_FoR(Դ<ɧX$-ȁ@(zbk˱ eiC7v+Ul+^Gnl6Ci[wv%nO=>qSy(q4VIѼ^-?,p?^İݥO1^*\"U wWd3mJƊ(~*Zq6k[m,zNL:}7^to8%rzOKw~bVUB!4iK#bn8jav?UR K!8 ?`X)"nkK1_bj"FmK<*q޾QT{my}buӒm dLUmKo,ToMXԧ_1 R,B4D^EGJ GF.*6 !n%;/oZ"(K?:V',.4EM=HрݤX&3ppǁ(>* q#VDI1(܃~늲 ĠG/02> IKo"ኧYniY+HӛkbO  x~?8MVmz^uP֕^S![j3ƿ'⯣qWb]v*UثWb]v*UثWb]N*UثWb]v*UثWb]v*UثWbۅM%Ġ|?ajlU$ 銨IV'~!Cr7wpXD qH>-*x`+<'_R,UP\VK){y-:;Ae֩iy4xK~i=?Kf-,w_7ڣזyU⪒e eҮA&g0 "W($|Ws[2A T)~*,y-FэhXۼkھmfpth:Ԛ^/'= kө&"1TNoj% jֶ,U iVW {0b2:9_%fܲƘŧ tUjW3*K`گK]ݶ%*+k7 ~[mY^gSD,BOn+C_i:Ahu+Ů/o6 8DFF+bxYTwCNH#GqmKsE %F\Zay~Ti,$*K!YXw/)$,ڬ)qUe[dݶ6*| (­5h)sUir\ Z1F~GXYlHv;R^^Db4,riEG#vi*KD(ǧ}౺n*manTkhc=CV= k {Z-2t** ]]4M0 ,:ю]fI?s5.!VC;TkӋ)o9f 59>µduStFr,.ik* %-'uEmW^xZ>C@kȿYI*`8Ԥ5*$׺W⬷v*UثWb]v*UثWb]v*UثN*UثWb]v*UثWb]v*UثWbRG'wezʵG?6BqWY[˖nMI0?5[SLUثT< s(>{/CKxىZQnzvQE(9Qv1MOb?lU :wF4uy=Xq_w?ݱTR4m9'rOF' m I`,7ܲIj\Rn ޛ8W1 5H7R6 -ɼU,Dohy/yRy9% D1ﮉ߷"*F64}9lUhmq%LQYiQgIP=>Aa)[(U[TȚcbR%_sXx\ȠgᆧyY6*F=}arz! FXncTlE>Wvl_@Aͧ:3 gzVjTP 髱FYVU@AE@b>*Isdܨ+;c O䖄U#5U ѦޡICb$^?*[ZۋM*t+^)8\+LUtט1(kaGXrW1ZKJ+YZ AUWԍݡ'?FVԄ-BߕmWqT=B4~І+fj7ɼUV;Tʕոf?D3bn&K߬kPbhlEȎ Z7>G1MZc銢-cyjA훥wتIRm1Kq* VIV1Z _/v/xxI2eRA1oWvP#EZ<gV*յPAQ]=b$X bn^MOa\U@Fl+;eQ6%\8st:@;Q&<b`Ԙnt:tت=\Jg۴5Į(fb3^eӕar]iF⨭BI. Hn*HBzL4KSz4īb!˥E>G(Ed>9$JQ2 U$U~tWG5581cڣ*Ij~[_0c9JϬ2h#n$ %Q31+> miUIIFUbEtVj,OuJMBux]GkJ>6>U{IK}e/_8"A5?JĊbv]AߗGp]KbCPVѠ";qBȮ)":G~J⪪( CSO һ-k*bMM(Qz?6)ygtêG"wJ?⸏𩊾cS1T4 \拸>j_bA]v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثWb]v*Uث?=I9i\HcvH|U,"y$SY.X ,Bc$Y3pIҴ$⭥]%nc]8FdVI6;`Q>Bgjc㐑—vwv 3:|PC@kVwxOLUUfIn i\uK co$v⮾21}ZdԮg>qTlʺYJu![Ԟvsv &vኬ:NJrn'SUثWb]v*UثWb]v*UثWb_N*UثWb]v*UثWb]v*UثWb]v*Uث?ڧaʍA%X$*ǸX|n'ۚP-mꍀFeš'hnI Yx O&NYidlPŷ1ǺPnªb+Jj:Ү~R$M:5n3=Pކ~Sbި^d Ӗ*V5\Z7?ZޯR[N9hIK#SּV$@FƗ5R'mZ' HjquɊPRii10\wǤ'Z}9PѶ(d߭JoM A]r݉ݸݸzJAk9bUvXC=-&#V Al&J ]cRUt-]_\j`ѡ#1TSodʁHn2UE;SNˊ lĘԈ#BhLeWz\fKxX% -4!DjBj3qi1U"X^jҏnz+*1Wb]v*UثWb]v*UثWb]v*N*UثWb]v*UثWb]v*UثWb]v*Uث?io 2lbee?ݖsyΛV 8*0b gxм㐊!xHŊe^ ?/ cVE Iw !D$+Ə(.L۪Ziu(Z e5ܽ+-PZnx֠, $*V}B $z=$"m{Fg,W,My(܆>_S}%y4K"ƍITC+Gr<*w0̿qTXu5@]X0]MyH[1y'v;⨜UثWb]v*UثWb]v*UثWb]N*UثWb]v*UثWb]v*UثWb]v*Uث?! /v.EҞ늼g a*JդoɿqB<:s3}mOO'*EG2QJj1TN GmQ5S_c%sqZ҇/ЈZ_Nm4ZǷy[U5 {Qd,.A-kfYo>{ T㊫@~]gſWVjHf1^JPVQ>ت[J.Ĉ-?ZQN-əZb'&z廌qe=7:6bm z1Xz &EO;إMĖRagq'^4lPI:rV 2jEZ8,)hm^9X$PP=7|*#]d[NHIPM_c- .)CN [D*?ӤO"b42Y,jZ GZqa@ݤ֥IrO4C4Nsn>'Q6-_F{Awí#.øYhE%zׯ)>*EMWb]v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثWb]v*Uث=&y#_J?$a FD.V?8Jִ8ULw<O6~劦:enf9TWK)4uAoت6fh 3b8kX8˩U;f@[,>NET,V0W:Iƕ5|8 U1%/kcK1=Z^@~𸪌ܘPk닖ϧ<hS-n{_gC?wݷkP֯(//$UZaAP~vf, U]*xzԵKk"9G$G*Wb]v*UثWb]v*UثWb]v*UN*UثWb]v*UثWb]v*UثWb]v*UثϤV(X, ӚJ1S6<?U1Po-e9s^fKL4icР$'sdٱKF\PPs{_J]w{LUe2w2Q흝T©"K#W ?.IqT[E(zvѸbӭ}YJ,%[71#ҦӕBԆ]B):u踫'EO/TP/oaZPVr,ߧO\.y, =mnklݿtVՙi{)AZiQjQVVZp81ZUDf ֖vCt֒ pyJ-eQv]Crս~'}Vj:[nZOȁ-/\Vgcfe!D((^Pz\Uk*ER=&sZ+R?\}DQSݿ\Ub]v*UثWb]v*UثWb]v*UثWN*UثWb]v*UثWb]v*UثWb]v*Uث!X<{Yc kȒÈ/b$suHvbO(Gi []HG[N*&P9:YO +Kؾ)F wQ+N'=Rh?|A S"H2$uR?eSc&Boe+IO䘮*QxߠјJVXwFif=CA-H%u[wlU ehɧY/#Zڨ$c nDRGk Iҧj=+:o:2Mz-=]G4=uJoZm&]Tqq,V~aP15EVZd|JbŵEKz jm_Xib~w:{Urp}>k7t5WTaS]Mvɿ\UGTvkJVb$$~֝<1V-yhYm &'*|écBDq% ܀Hx&O9/$ .SS5t{kwt0KO#/QğX>;k}&lUr޳BX]܊xtVKtb(oSKJؓ!UWb]v*UثWb]v*UثWb]v*UثN*UثWb]v*UثWb]v*UثWb]v*Uث91tx4`׸*hFSD\m?DEWK)?a2PΠ΋^[H+?/ŸM3]е٤uOQq%Qn/.8d}aԆPyWSkE6ؒD#wvMxKذ\[j#iqT,Hz𴸆?6zv{c4>qh{(.x[JSKu5w?] UKH4@"ӫ{!gU ?G 3hRQUuh̿oڷSتY pkj)*阬p܉S\J5[}5buݡJ.s:<96*tk}'K[r!B(e+?k{//_gHTX*91٦=F>/^J52IPgSIx.m%nRYf^1//WyͿ^K]QWO҃.7xYI?^)edXۈS,cP=mFX6/͟6,f#Y#Ho0h\Uk7hmq= ]?懜nn8VZ!UTn<+G`[8Z>WŊk3s!4 !$O+UUqVa8h:~ʷfh$+sUw1xc,z.tB4(b&Zpm4;E/sz恘);q?ib3>dht . soD(\W^Xտ<٪˞`qX)=Fz"xa]v*UثWb]v*UثWb]v*UثWb_N*UثWb]v*UثWb]v*UثWb]v*Uث/(M-O\K_*=9X@2Ҿ4*8tcVwwEPv?{ILڋ" Z~biiwZ=M#_Jum[\JJ6z=qJ\!hh(5fN,U>(5he3/eKbfHnbz;'eVSǑZ[byT℻#hٹM Js@8d(jԟ#6(TԜ4JIӁ )o˜+{0K0-o9&h,9U9.0^*\XwpܼcF p Xq6Ȇd~U^el-.<+DžMDt3XU7տ8$LҋKJqvN8󎽯4y4|1!®*bYb }?*'q~hf$gM.*'v*UثWb]v*UثWb]v*UثWb]N*UثWb]v*UثWb]v*UثWb]v*UثxV^ r&qW鳬RG*@biQoY;ҡRips߳&(Mm!wzV-v*(ϔxOwY1yۨTeTS 1T ң_NK4R}6nV+cK[o@؎J*]LUN7YVX~"+U1TEn]17=O*\y  WJ*wS;h"DG7⦿È[eP1 M?Uc[Gx~EU{)MԞxvhPTӯ v:>< DC9':JtlU=-#Y%y3z8\U40^IVqYT1Vd0TTU.d.yR;?)=K2n9TN ~^8tWo]2' 0_Rb]v*UثWb]v*UثWb]v*UثWbN*UثWb]v*UثWb]v*UثWb]v*Uث ou |1]ڱ |U󭀡zLUi|b9>$еJ`v ֞u(b %@OiUoWڥ=(DdDCoYU/Pi"ӯ._qTK=[ YoR45\U~ԉTqu&nGb̷3Gm u$|GU8+bZ[=KdrzTEv=qJK;ȤQ"N木z bxhUWNlqTQbv/_J MtM=T֬ ?,U-gp}\UPM=}65I ]v*UثWb]v*UثWb]v*UثWb_N*UثWb]v*UثWb]v*UثWb]v*UتYOupW>b> |UX__T>>b8bMX 5ܞcx  ncC iT,ғljVaqLU/ԹV9sSMKAxҋrk%W,UoIc2}^jHP|P<)u)bKV'@68ij'cN7Qm&TH^D@G'q_\V%bX"F1 uSRB9J!4_^E45,޲kh~x=5,kdQM=CɌL#ys,bƼCi]y6jÓzV>إiǜ l$弄sWM[%eԘJ„zvX=/JN];*&~p?D[6_f]v*UثWb]v*UثWb]v*UثWbebooklib-0.20/samples/02_cover_create/create.py000066400000000000000000000042511507750473400214620ustar00rootroot00000000000000# coding=utf-8 # noqa: UP009 from ebooklib import epub if __name__ == "__main__": book = epub.EpubBook() # add metadata book.set_identifier("sample123456") book.set_title("Sample book") book.set_language("en") book.add_author("Aleksandar Erkalovic") # add cover image book.set_cover("image.jpg", open("cover.jpg", "rb").read()) # intro chapter c1 = epub.EpubHtml(title="Introduction", file_name="intro.xhtml", lang="hr") c1.content = ( """

Introduction

""" """

Introduction paragraph where i explain what is """ """happening.

""" ) # about chapter c2 = epub.EpubHtml(title="About this book", file_name="about.xhtml") c2.content = ( """

About this book

""" """

Helou, this is my book! There are many books, but this one is mine.

""" """

Cover Image

""" ) # add chapters to the book book.add_item(c1) book.add_item(c2) # create table of contents # - add manual link # - add section # - add auto created links to chapters book.toc = (epub.Link("intro.xhtml", "Introduction", "intro"), (epub.Section("Languages"), (c1, c2))) # add navigation files book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # define css style style = """ @namespace epub "http://www.idpf.org/2007/ops"; body { font-family: Cambria, Liberation Serif, Bitstream Vera Serif, Georgia, Times, Times New Roman, serif; } h2 { text-align: left; text-transform: uppercase; font-weight: 200; } ol { list-style-type: none; } ol > li:first-child { margin-top: 0.3em; } nav[epub|type~='toc'] > ol > li > ol { list-style-type:square; } nav[epub|type~='toc'] > ol > li > ol > li { margin-top: 0.3em; } """ # add css file nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style) book.add_item(nav_css) # create spin, add cover page as first page book.spine = ["cover", "nav", c1, c2] # create epub file epub.write_epub("test.epub", book, {}) ebooklib-0.20/samples/03_advanced_create/000077500000000000000000000000001507750473400202735ustar00rootroot00000000000000ebooklib-0.20/samples/03_advanced_create/README.md000066400000000000000000000002011507750473400215430ustar00rootroot00000000000000Advanced create =============== Create simple EPUB3 using some of advanced features of EbookLib. ## Start python create.pyebooklib-0.20/samples/03_advanced_create/create.py000066400000000000000000000045361507750473400221200ustar00rootroot00000000000000# coding=utf-8 # noqa: UP009 from ebooklib import epub if __name__ == "__main__": book = epub.EpubBook() # add metadata book.set_identifier("sample123456") book.set_title("Sample book") book.set_language("en") book.add_author("Aleksandar Erkalovic") # intro chapter c1 = epub.EpubHtml(title="Introduction", file_name="intro.xhtml", lang="hr") c1.content = ( """

Introduction

""" """

Introduction paragraph where i explain what is """ """happening.

""" ) # defube style style = """BODY { text-align: justify;}""" default_css = epub.EpubItem( uid="style_default", file_name="style/default.css", media_type="text/css", content=style ) book.add_item(default_css) # about chapter c2 = epub.EpubHtml(title="About this book", file_name="about.xhtml") c2.content = "

About this book

Helou, this is my book! There are many books, but this one is mine.

" c2.set_language("hr") c2.properties.append("rendition:layout-pre-paginated rendition:orientation-landscape rendition:spread-none") c2.add_item(default_css) # add chapters to the book book.add_item(c1) book.add_item(c2) # create table of contents # - add manual link # - add section # - add auto created links to chapters book.toc = (epub.Link("intro.xhtml", "Introduction", "intro"), (epub.Section("Languages"), (c1, c2))) # add navigation files book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # define css style style = """ @namespace epub "http://www.idpf.org/2007/ops"; body { font-family: Cambria, Liberation Serif, Bitstream Vera Serif, Georgia, Times, Times New Roman, serif; } h2 { text-align: left; text-transform: uppercase; font-weight: 200; } ol { list-style-type: none; } ol > li:first-child { margin-top: 0.3em; } nav[epub|type~='toc'] > ol > li > ol { list-style-type:square; } nav[epub|type~='toc'] > ol > li > ol > li { margin-top: 0.3em; } """ # add css file nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style) book.add_item(nav_css) # create spine book.spine = ["nav", c1, c2] # create epub file epub.write_epub("test.epub", book, {}) ebooklib-0.20/samples/04_markdown_parse/000077500000000000000000000000001507750473400202205ustar00rootroot00000000000000ebooklib-0.20/samples/04_markdown_parse/README.md000066400000000000000000000004101507750473400214720ustar00rootroot00000000000000# epub2markdown This is just a simple example and not utility script you should use for converting your ebooks. ## Usage epub2markdown my_ebook.epub ## Dependencies You need to install [Pandoc](http://johnmacfarlane.net/pandoc/) for this script to work. ebooklib-0.20/samples/04_markdown_parse/epub2markdown.py000077500000000000000000000023441507750473400233600ustar00rootroot00000000000000#!/usr/bin/env python import os.path import subprocess import sys from ebooklib import epub # This is just a basic example which can easily break in real world. if __name__ == "__main__": # read epub book = epub.read_epub(sys.argv[1]) # get base filename from the epub base_name = os.path.basename(os.path.splitext(sys.argv[1])[0]) for item in book.items: # convert into markdown if this is html if isinstance(item, epub.EpubHtml): proc = subprocess.Popen( ["pandoc", "-f", "html", "-t", "markdown", "-"], stdin=subprocess.PIPE, stdout=subprocess.PIPE ) content, error = proc.communicate(item.content) file_name = os.path.splitext(item.file_name)[0] + ".md" else: file_name = item.file_name content = item.content # create needed directories dir_name = "{}/{}".format(base_name, os.path.dirname(file_name)) # noqa: UP032 if not os.path.exists(dir_name): os.makedirs(dir_name) print(">> {}".format(file_name)) # noqa: UP032 # write content to file with open("{}/{}".format(base_name, file_name), "w") as f: # noqa: UP032 f.write(content) ebooklib-0.20/samples/05_plugins_create/000077500000000000000000000000001507750473400202115ustar00rootroot00000000000000ebooklib-0.20/samples/05_plugins_create/README.md000066400000000000000000000001211507750473400214620ustar00rootroot00000000000000Plugins ======= How to make your useless plugin. ## Start python create.pyebooklib-0.20/samples/05_plugins_create/create.py000066400000000000000000000055121507750473400220310ustar00rootroot00000000000000# coding=utf-8 # noqa: UP009 from lxml import etree from ebooklib import epub from ebooklib.plugins.base import BasePlugin from ebooklib.utils import parse_html_string class SamplePlugin(BasePlugin): NAME = "Sample Plugin" # Very useless but example of what can be done def html_before_write(self, book, chapter): if chapter.content is None: return try: tree = parse_html_string(chapter.get_content()) except Exception: return root = tree.getroottree() if len(root.find("body")) != 0: body = tree.find("body") for _link in body.xpath("//a[@class='test']"): _link.set("href", "http://www.binarni.net/") chapter.content = etree.tostring(tree, pretty_print=True, encoding="utf-8") if __name__ == "__main__": book = epub.EpubBook() # add metadata book.set_identifier("sample123456") book.set_title("Sample book") book.set_language("en") book.add_author("Aleksandar Erkalovic") # intro chapter c1 = epub.EpubHtml(title="Introduction", file_name="intro.xhtml", lang="en") c1.content = ( """

Introduction

""" """

Introduction paragraph with a link where i explain what is """ """happening.

""" ) # about chapter c2 = epub.EpubHtml(title="About this book", file_name="about.xhtml") c2.content = ( """

About this book

""" """

Helou, this is my book! There are many books, but this one is mine.

""" ) # add chapters to the book book.add_item(c1) book.add_item(c2) # create table of contents # - add section # - add auto created links to chapters book.toc = (epub.Link("intro.xhtml", "Introduction", "intro"), (epub.Section("Languages"), (c1, c2))) # add navigation files book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # define css style style = """ @namespace epub "http://www.idpf.org/2007/ops"; body { font-family: Cambria, Liberation Serif, Bitstream Vera Serif, Georgia, Times, Times New Roman, serif; } h2 { text-align: left; text-transform: uppercase; font-weight: 200; } ol { list-style-type: none; } ol > li:first-child { margin-top: 0.3em; } nav[epub|type~='toc'] > ol > li > ol { list-style-type:square; } nav[epub|type~='toc'] > ol > li > ol > li { margin-top: 0.3em; } """ # add css file nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style) book.add_item(nav_css) # create spine book.spine = ["nav", c1, c2] opts = {"plugins": [SamplePlugin()]} # create epub file epub.write_epub("test.epub", book, opts) ebooklib-0.20/samples/06_parse/000077500000000000000000000000001507750473400163205ustar00rootroot00000000000000ebooklib-0.20/samples/06_parse/README.md000066400000000000000000000001251507750473400175750ustar00rootroot00000000000000Plugins ======= How to parse EPUB file. ## Start python parse.py my-book.epub ebooklib-0.20/samples/06_parse/parse.py000066400000000000000000000004761507750473400200130ustar00rootroot00000000000000import sys import ebooklib from ebooklib import epub from ebooklib.utils import debug book = epub.read_epub(sys.argv[1]) debug(book.metadata) debug(book.spine) debug(book.toc) for x in book.get_items_of_type(ebooklib.ITEM_IMAGE): debug(x) for x in book.get_items_of_type(ebooklib.ITEM_DOCUMENT): debug(x) ebooklib-0.20/samples/07_pagebreaks/000077500000000000000000000000001507750473400173135ustar00rootroot00000000000000ebooklib-0.20/samples/07_pagebreaks/README.md000066400000000000000000000002001507750473400205620ustar00rootroot00000000000000Advanced create =============== Create simple EPUB3 with both visible and invisible pagebreaks ## Start python create.py ebooklib-0.20/samples/07_pagebreaks/create.py000066400000000000000000000037721507750473400211410ustar00rootroot00000000000000# coding=utf-8 # noqa: UP009 from ebooklib import epub from ebooklib.plugins import standard from ebooklib.utils import create_pagebreak if __name__ == "__main__": book = epub.EpubBook() # add metadata book.set_identifier("sample123456") book.set_title("Sample book") book.set_language("en") book.add_author("Aleksandar Erkalovic") # build the chapter HTML and add the page break c1 = epub.EpubHtml(title="Introduction", file_name="intro.xhtml", lang="en") c1.content = ( """

Introduction

1""" """This chapter has a visible page number.

2Something else now.

""" ) c2 = epub.EpubHtml(title="Chapter the Second", file_name="chap02.xhtml", lang="en") c2.content = ( """

Chapter the Second

This chapter has two page """ """breaks, both with invisible page numbers.

""" ) # Add invisible page numbers that match the printed text, for accessibility c2.content += create_pagebreak("2") # You can add more content after the page break c2.content += "

This is the second page in the second chapter, after the invisible page break.

" # Add invisible page numbers that match the printed text, for accessibility c2.content += create_pagebreak("3", label="Page 3") # close the chapter c2.content += "" # add chapters to the book book.add_item(c1) book.add_item(c2) # create table of contents # - add manual link # - add section # - add auto created links to chapters book.toc = ( c1, c2, ) # add navigation files book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # create spine book.spine = [ "nav", c1, c2, ] # create epub file opts = {"plugins": [standard.SyntaxPlugin()]} epub.write_epub("test.epub", book, opts) ebooklib-0.20/samples/08_SMIL/000077500000000000000000000000001507750473400157545ustar00rootroot00000000000000ebooklib-0.20/samples/08_SMIL/chapter1_audio.mp3000066400000000000000000002737661507750473400213120ustar00rootroot00000000000000ID3TIT21 Minute of SilenceTPE1Anar Software LLCTALB Blank AudioAPICimage/jpegPNG  IHDR88lbKGD pHYs==UtIME 1ov IDATx{Ue0 7/(ꀉW,S42oV*5MQ3iy-54 CYy)dra[ sΞ9g<3Zzkg?{߳pηg]NJN$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$O$OAlbĉ1tPh*"^h {g|ѿ?`47hA TVVO~=ztA:ꨘ1c,SNq 7ĨQ fΜ91bĈXz[T( /0>L'>dfEu'_ܬm׬Y#F7|S!kb)?&NtҢ(v}{-K_R+ @Ŷj;nDD7NQ ZSNq}^{U>3L̟?_(~zQÍ8A#DUUU=3,Yeҭ[Kn @A4o}I1j( 27J=#dRUU^zi]ti29c>g}shѢ9E /ѣGyE#<2/^SNm~Ȑ!v)>9 8(7l͊3<ƍ5kDDɓ[ ӧO;h-]4Ǝuuu][oŬY1b 'ַ{.Zgyf; ? }]$ I'TnFiӦ5XVUU' Mpرc[nEwߍ/fjtfǞ{i hHN1jԨӟ4>m! W% +_x㍘2eJvÆ 34IAN#G,Z_?яbݺuy=ӱjժ m&%Iqg}6{챂ڮ^:^|?O%I{wlE뢋.W^il=0(4JA9䐢#s=i_~%Q;w:[..56c204JAg۷ox73okE}} ``hFぞ+VYۮ^: u޽( 폀F-㦛nŋ7{K6X6#QC ik׮_-cٲe M8hO>-zXdIh,08h`-7"">4FAkf͊{E4FA-}SɤIroye@c4В˖-x(DZf5XֳgO@ӧOХKݻw;w6@4 ࠁfmW__wygQa+о 8h`ʕn1gΜrp@sb?ȼܹs',j?:p@sfpL<9>âjr[ThpEѣG?(8hܹs555-ϸqr4FA 8^cO?=6@c4[T/_ޢ}\yѭ[Lۘ@c4j޼yy۴$8bĈpИJ@c lٲf=p 2of͚x Ъ***,kB_n]|g=N;ʅ[[aZymmm[Hv}ܹs}UTTĘ1cGgq'\e\j)]Ju5\筭Ǫ\ϵc޾8WE|]\>OG}4"&L&LP)q)q5ϫ@lߡ:8 ƍN}:d"4o޼X|y 89>|xQw䝚xhv*B^fq|48cرEUbԩpNE+s88W]uU{GMM:|6Y 'WvEeeet=nٳg;i$:$38Spt%㪫mӟW^Q|Cpd"5wܜ\~qu7xf"z饗\uO=TL6M;,yGÚ(uJ1[.?|:4d"$׫boٳg+8С 8S 38/^?٩y7cŊ%ǥ^a#;/\_|Ÿ{ 86$@qז>Ə Eo㪪o1:蠒c޼yq'Fmme޽{ǔ)SJnDD|d@3l&9 8t /cV_k=Tݻw{ア뮻>$NEڹnݺwKW|-ґS(G;ֹs馛ӟt{vM7 4٩H;UQQ?bk<T +>fΜ555Mw} @38Svhq)t=\x׿6pTVV*G8ڙ/~qgtj{챱rxl[ǠA @F28p`?/>w8c /l9ٹE%;i'uzkl%ǢE⨣ wYm* 8Sv/!C8ꨣbΜ9,w㭷jr})ݢ"h&J?>8−_[[|꫍5_~% ^ګ\8D Ҷ{_\׭[zjnT/Wa|Cu%ڨ*IqgO<]7Dx(@s5c 5Qt?>>qm7k֬Xn]k0U]]]? kivmc%?yvm]jUkM޽{ :ԠdJGҥKI_~ymݦ9xG6 =_J\7|3ƍqÇǏ㈈dMb=]v%}'V\K,ŋŋΝ3f̈e˖v[Tp$wq> oDMMMm_|Ŝwm~ԧ znHϞ=c7ot]]]]<Ǵi7pa펇f#H̹niI>8gqF~ٱbŊٳgtGydQ2kkϏ7|3{챸c….]0#IȠAc)I\sM<-Q*nm~1s?~|ɞMК٨HBN9唒\OmQs91cƌ8]0@٨H"6x=zt}7[V懊.>`\}ձ[Ev[{ア;x$yJ6DwqѣGX8f=TW^q~6lXYlcG!٨HO.z7|s[po~>|=wx>#ܢ~EoΜ9qeӟt<#۷_2uʖfY [gM|ߍUVdL:][\uUq駻kj*y1g^|qt8=]m1!(ṕlMQ?oq 0h85jT5*38v=CvV9Vb-cl0oL[(6G##;)d IDAT^m„ ^' @[pa 38W_s6X=SMGÆ +0ϟ/Bcs7b3vk׮<-[Vq+eۦZ|Ԡ\ǹus3͹ߕŹN[O?ު>ĠA\@pd"e_XjUvx`SSS{ntAβxsƉ''NlbTTTıD#BѠJP~ZzO?M~bn>~g?٘4iR5frʜQqM7!k=zWmBj($oY_{q 'ĢErٳgl%_|1/@7d8\@,Bݐy_nmڵkc̘1qM#m* 8"}{̘1c.yMgF5ŋ&oY_߾}3}f5A[ouѣ{gs=Wc=\@pdehɒ%7(]>}2{ƤI2O1xf5kf,\dN;E]@pde%?e-7x yJsoS 'k֬)xt9vm7&Ъ٨Fj-*8,Y'|r^Yo|MK!L!ܦ6FQ &oݢdp#{}ID|Gsߌ^ 8Psgp|1w܂;oy?)՛TsM7dLv}w&Ъܢj7k+Vĺu jtҼmL71,D:vZ͜93^~墏Iև#(C/rAG-[˹~ݺuq=|:uۮh+,.]0V%F5"~3o|%Oի&=;uL*++*FQf̘y,Ggu-ڹ,X mp]6nY@k2#(S 8yʂ z>&К٨F׿gδM]]]mݢR[<̙s}1ﰧ%mL PG6QƲׯ_mgϞ+-*%fm)z yG?@+pdͦ'>KQSSY -9ڜǑGo}Q$\26hСCOy9_@R>Q7@;zDD2ۄ b„ @o_VgGKo4XSozn#F(!K,ӧg:+K9Ɩ瞱;4y555CfvXٳ{=Ċ+Rj۔r=,gAJ=rnSb,sks˲e]'pBGIW[nd_m7.XlY}1} 77~ǿØ1c .hr}׮]~?.9믿w\@lT#>`Sg?^{-{C7t56|G-cƌ.@?/#FW_`I'T*bIeѽ{#B]]] O=Tv_f͊ɓ'olC=h?mw߽}Y&:d袋^?Y},_M bŊXdI6pUVV'F5+}ݗ駟ݺuw7X[gQ7o^lMUwm38Q]qQ[[o{lg̘~˖.]ZcpkGcvkQ/pm38Q-X ƍ]vø{z뭢{!_WZ;#֬YD[T***i=D]wu-g1Ӭф bҤIf͚h|V[mU#lk֬;ӅF5nݺwc7nsύ!C /PwJUUUl-ǠAbjQwumFj$^+2gW_}|zs-ѣ[4Ukڵq 76%F5_"lǩZǻ`Xvm6-yJ^u[~ٳ]\@pd?3bժU9۝{EymjK}GKfpL0!6tfo?{x(|?C 7gΜ8s_ѻw6?|hTvq8餓Z㬳;5xMl6NL2%&O6lS=c/Ν;7&M= (  86?K.M?b2r>|x饗^.E dڵkfכtIq7F.]8ݢfeow6\̙sL\E d;cƌul7rȘ:uj<# ~Ajbԩ7X.\GydPQQQHpC3Jڂgpdɓsnw~8vaV;X|y6nS޽{y1xfÊ+NW_}%F5ڱ_ 71f̘jckɃF SLC6ks̉|+?E-F5ڹI&r+.1cF%?|GS38=И>}z|ӟn~g̘x`.jt&M; I&ů~e]JvLϹ38z7pCp ~0M7{lcʁ# n8 n>Ĵi'?I׶" Oơڬ}cĉyx G6ۢ".袂w)>׿O=Tw'x"-Zcp <8N;8ꨣbo~x rI[ߏ.(zU6]v/o>MӦM^{Y\Y ?ώ3gt Il=O=T\q/})Ӷ1t:ths91gΜx㥗^9sܹsΖ]V^^{m\f $KZ`A}-Euuu4hPr),[re,X VZWڨڪ诣Sƍ7o/ਨP8:?|9rd7. >{m]MMML4)nXd 38puuu<SOÇG{y,\0n喸;cŊhw8<0Fg"XbEL>=}xG=chrplHA{x m˹G@AڜJAYvmm"Pm>֬Yz38(6z&י@) MEE@\38w@DApTUUEUU"Wg%h?b]ww=>G޽K.ѵkרsƼygW^y,9WѥKG;0x8_ztڵm<rK,_͎=38""jkk 0yE%a[lE~1cƌ8 7"">Yg3gΌ?! 5kD]] /GFOp$#¡Z}=%"⥗^2yxLwWb]Z/c]UE֢uoTmA"%I~1Kfq]*=sΙs9u p@F +UBBBܺ {Jpx^xAŊ{".Q[o/HIIÇ9!p˗bvwa#+66Ė]dIοTR6hp+Z[vl:{ }.]fفpx8w ܾ}mPXXղh۷p{m3\ycl͚5JJJbFY"5ǎsۼu0/N!p~"˽{l)S7ojΝxS8<ܶm۞r7oެxW 4ZvZݿp ;s.^]b]fM<y ]ޑ#Gj*ͿUVVhp8 `Μ9JIIɰ i$ݿ H30NuI|._(U-[ wV&M2tӇÀyfy'OTDD\lڴƌL2Y-Wjtyv* ]hb@С4TÆ nkر4ipCƎKp jpol۶MmڴQRR[֩D}|||~Ϟ={*22e3gN}j׮]k.EDD(!!p IC ui]zU5k֔C)X5kg̙3Z~``:tٳg_VOVDDܹ 585kԔ)S-[6wQM@wf܊O`_vJvȑ#o>v Cp<^|E 6Lۖm۶iΜ9ڲe 8!%JPƍոqc)R$KJJΝ;|r^Z111ldAzWԸqc-[V Thhf,))):w>ÇСC:|0#p@`ʞ=n߾۷o+66VoVLLݻdžx$`xl`t8p#G 0<`x8p#G 0<`x8p#G 0<`x8p#G 0<`x8p#G 0<`x8p#G 0<`x8p#G 0<`x| IDAT8p#G ϗMY*88X~bcc†Cp8~~~j޼^yݽ{W{ʕ+AAAʖ-5kVir @_WjTJ5ݻvZmٲEoNu͚5ӻ+I9svԺeϞ]-[TҥuE\R'Npjʖ-fj:^w,Y̦oݺU˖-x4/I1m۶f}  >%$$DS2e̦o۶M۷׽{8xsUŊ n믿.Iʕ+oq*UԩS|ݻBCCzobb.]fUD ]V޽{k/tҚ;w (`v= 8Сs믿Q~~~N}ɓ'kԨQ F <d͝;3_ƎknH;CO?T#G} ˗P߿L2tcx75n8YݼyS/_֍7ɓGr咗H˖-ոqc͞=[&M2iӦ"GkfFÇuaq:(8JJJŋͦ)O(((M7vFL`ծ]fyF8ڳgΝ;˗/KŊӻPYgϞUbb˧B jժرcN*TYcjӦFavٳgkӦM:qfrYjժjժ[ѣի]BTRTŋ>^sIMѣGpCt}wjѢ6ljg!8sp)-[6Vڵ5a}?^?/VÍe˖F?~%Ke'OT}vvl,ܸz6liӦ9}Ȉ#[%Lj :S?hĉ0`x۷n \_VXa5Bq-`ҥO,Y}jw^5l0KΞ=BAM~iv_5kT5n8v/_^G6VZ ԨQ#]t4V۷o׭[l\2]?Z4H8ƺtyѣ Ԟx?-[L_n&Māw,>}Z۷WBBCJF3OǏow {58b]y{k„ f['Ot?Fv_Lׯ?6yԌ3zt70 III۷^{5 34q7nSٲeu]ٳǥ5fd:߿]*&&扭ۀoZÑnRBB(LM4s=g{ZvKqeuE/K.gA*UUVzu[Nvr:[ O0 >Ӝ9ș? MT]xql;wN8D-11QC ܹs-!~b G|ٴ-ڽ{K :j3MT|rMxD@֭h,xYq-}W~6lЎ;,;RP\9)RwBBn͚5lhV 1o.pΪo I`$jp,Zڤٳ-9ZZjf_t)~#99YC Zl`VZe184x8NIIѼy:\,MTqqqVw]rʐڵ]D dbŊ9M <  8>z1ӿ 8# ԩS3d87M \@eʔq=f͚YfYtNѣ5 <ɓ'M!>>^W\Z;hΜ9ʝ;[р/8UVu=wzfj,Y(k֬ZpT ty8qB))).&^^^ʔ)2gl_rEk׮ur˕+:u(<<\yQHH|}}ׯҥKڲe~7ݻwׅDJJJҩSկ_jYjoiZhYGϮry }:]z>|lٲiʕtY`AIu9rD UN(QByQhhgϮݸqCǎӞ={믿lmoJ**Tͫק~5klK7nܹs3g-iӦ:teй1**J_,N:رx 󈉉͛5a TΝդIvz\%I*URΝ hڴiN (O?Tuֵ9[M۷o͟?_ӧO7`8y)GDznݺ)gΜ#$$Dm۶Uf4zhvjgϞ!=4iA)44Ԣsww̘1Nm hƌzwճgOoRe˖c_߿m۶ĉTsϩz-/N6mhfz<5tP}{={JHHo_U;wԉ'oeΜY>C!%%E{W*SL SZLp 6LZgϞT%K3%KpnZ}Q޼y:{jРf̘#Gcƍ5h SdS͗9r(44Դԭ[75kL}ц ^wﮞ={ZDo>۷OW\)5j(O+vܩ  2eʤm:.6mEtR A?դIҞ={Ծ}{qV%ԳgOm޼YWV۶mTٖYW7|cn۷ϩpCzb1ccs׀ipc֬YZ,Ybu;whѢEVNEճg4mÑ#GZ;nHLkW^vg:ԩS˗իW/C Qxx8'' Uҥ{lAF5^֬Yս{w5lP)))ׯ&OГΝSm3o޼^̅5*TP>},oڴ)MhȑӋ+͸8M4jY\[o{=/z1c(sJIIQϞ=5gw-ӈ>ִh|{lQ<>>qɓ'[ShQuJ۷oWjմ}v-\r3fvڥ;<*-ڹsgURb35=jgt33glVѶm[uM4a <ءԪU+ݽ{jy-ԤIGqTl ׯo͛[3lذ4 UemڴlZL4i$y{s;xk_ZNeaZ`S=x'3gh'Kcǎz7a„4_~ѣG-7mTCt e}QۣOJ׬Y .E L8!U/\`I[hhAG/_jS9M>KBvlI\͛CX ըQdSi]_OZLߵkC}bώ*UqwQ\\ղ/hܸqNŋmvT#Gyrrӵ_4i̙3[- T|,iӏ?͛7yYq|]6'6f,Y"E襗^R۶m`tܹ4w|6w\7OORխV=q8pBeU}(>>fӡʕ+륗^Jg"ߑ8C ,0YQn`O׮]N_ti>Og}81~zըQC[ZkZnKaOԩS'5{mݺ$KK7nЀ۬̾n߾Վd7mdv-uֵ[nm6mX]~zݻDԩ''JNNVtt6mڤ}rnSLIssA]xjfݻ[eXSJÉfSk8*{즀ȑ#ڻw/_H7ߤy(ћ7o ^|EQpa7<ڽ{w>ۉ'>zRRReuA*TutP-]T*Tx̙3]vVǞ8}Ԯ]e5̙Z߭(Z`?-M,Q׹ș ickyJ*9U 8'V6mUnݺt:tRX1Emۖ_tf֭[;4͟?jY1m˖-MM Ο?7{gkǵmjǏ:ܣ#-̙jFpU?QDD^xuEK,o$4c 4jyҥKuVokիW/~Gs7nl3H[V^mժUj8 }l p@{G͛N۷oW|||akG/ڰ8ӾzƌV{{{:sڷo/˗@۶mKw`:((ȡ:N]5Iʕ3/^zW^o޽{kѢEO7o^M2#:v~c/r}a]eԹZǬ҃(-ѣv~zu7ړ++I`nrwwJO'o llȞ|McccvSNY^hQ+zͶ-Zp*Uj~<_`+?I˚5ͦ,9ֱ4޼_Zx>p} IDAT>UTIʕS׮]5gwyتEc;ύkإbŊViZϫnݺڲe޽۷okݺuYy\rOMx&O.oÝVǎK|x7S֬Y } _m8w΍:vE $/nEz_,Y4vX!|@9xjԨyҵOo_f};w\{O}z8e#Z]v:ujh׮dt.l ߑCo߾io``C7&9r_ 4޽ambb[YFSbŬ[wjҤI.V}}UP!o~pf]Q"=yi~QBRi$;)@ => EB=/\Q5!{aЎ;vڹsg͜9MZj%Ax'Rت_rcGLK/iϫ~Zb͚j`'}nw}]~Μ9 {Dd6 5]Yibo [y9:ţLb5ȓ'7onwF)W\ݼ$&&Vͮ2kaR۶mi&eɒŢ_ѢEzj;vT#БWV-ގ;jܸqڿڴiÏ4t#<رcvFa[JJOnѡvӪjժV:tH[nug9s!__Zjyl+ O?kBL5]R2s{> 2ez" VZAVhbҥK/^\J.^l=]4iڵkUZUJLLԼypڽ{j[o'ٲeرcM>jpi4o<Νmț7:wl6mܸq>2e |eh„ ~!&M摬<իWm]EUV5p{#GСC`S Ǐ7 >s4cnݺi׮]:}N8ٳg{PXX.\'O?Ν;լY3/K;wΝ;ɓ3gBBB8сMx:<ݻlTnFiW^io ThQ;w[7-[%'',_֓Oޞ}jm0n8ʕ+]ϒ%fϞyJV\UVf.\X?E)9rNz0Ȓ%Kڵkmv<`tגyn+WNt͘1%ǝ7:u>Ks=ziXqY}Ν;mٳ^~to/Ro$?~|7m4}*R-[6խ[Wk׮uk/֯_*U(K,RXX&Nh7]&M_ fͪڵkk}$)\}!ChȑkmbsWgo 'ŋkٲek֬7xe͚U~5uM))).]_~YN碎4fdϞfжʞ=󈋋S׮]V ĉM;+GZx… ۷ᎫRJi͚5z7]:N:ԱcG%%%95[2xp;/=.%%E~nݺe\7mڴ4i9s)۷ս{wN<7f˖ϟנAlΜ9ij*W_ٳg֯_o={իWn)S̴,'NTN$=A?LÛf͚$2d[~#4yde͚jy߾}>qƍ=.44TC,ٜVfʹ}vu5;D-k[իWZ!Yӣ]4m454rCIII֭ܲ8p@ {gD ]Ͳ%K;̱+\>{l9p fzZlS{l2͛7ձcGX:w9W\Zh>t#F߻wO:u˗_Ŋm y=e˦_|jYݺu /]^zY _x^D|4|SHt=uE(Q;{nlw套^z,X@-gΜ1>>>0`Zh!>}}Ott>Cֆk͚5No͞=[M65G?#?~ZpZrostͫ#Fh׮]@bb:wlwҖW^yESNYAiEш#l>YaÆ90@6llܸQիWo֯_o6*g}gϦ{?T||EYhh~g {Æ z-͚5MWRRl٢ӧO˺~ϯpW_5EΝ;k޽65kV~~~ʗ/~/^j19fjժ4h|}}/_>e˖c֭[45Mjڴ<҃V.]lʕ+Zv/EFF*o޼zUti-["d5ژ`?ڏھ}?˗/ڵk Uxx7kuZ&-Zdw+W9X|ZrOw͛gfJTT~%l<J.dʔɡ$Uoϑ9sf'.4G#I.)O7nPӦMջwouts5kVO}ٳgΝ;*X+fbu6lvc8pT Y6[l.>+VrҢDN7h S(\pwy5kZd={V׿~.;99Y/֐!Ctmg#G43fhʕѣ"""L7 TǎձcGݻwOW^յkL sV޼y4gM8ѡ߲ãK,X0򲨅Z۷oWݺu5tPM8|||TF oذAOw'͟1 黵Z-ٳgOsa6O{d :TҠAvɗ/_3`4Q &-7O7oy9R+W֊+zs=jժ~*W oJJvڥ {n=X}75l0MIIѦMTV4]tI]vUz믿˚]:tP]nHҜ9sl֨>^ ApJJulvyMgMTx[MTƏʚ5UڵkhѢʓ'iRtt] .hڸq:MM|k.kҥfmw*Rj֬ʕ+@ Q\XEFF5k֘5tSNn߾?j%[ .~[*URMԂhEGGիڽ{~7;v#-= ɣ5jZj*Tɣ`y{{+..Nׯ_סC~YF.]b=xRٲeUzum#Gݿ_111:}vء+W._\r_XXi|}}umݸqCցvZWϞ=ՠA+22R7o֗_~8^zI}ʖ-L2?~K=zQF*Z"##uV >P!3@Я_?bz>}Mq$=zڵk'IS:'駶 z1~::&MRAX 8~g󇇇iӦh@*hXzӨ#DSNbˠAL.1p ?~jJ ݻn:ݺu5*]ٴǏk݆j, ~Z}]I҆ sN C+Z`gnvխ[׬W_}U+V0{o׮]rJC}PرC3g6~eJLLxnݪ0%$$F:s v>>> IzաCio+V0\!IE!=Rxqׯ$="pp s=gqƦgΜYk׮i̅ r͛7W%I;v̙39p'pp|,+WJ.-oooG!!!G۷͛7 /eW^?ݻ&L IpႺvd t2 5_]tI?$)11Q;v ݫyyyYC{Q%ajruo^7nD'2ĦMTTT_w%C{5g:ttkϜ96mح6IC m޽jܸ'&&jɒ%ԩΞ=T|;v(gΜz饗mE{4m4SQQQ(@Q@ RΝUlY*THwCiź~SK*Mx Sll.]m۶i҉#G 0<`x8p#G 0<`x8p#G 0<`x8p#G 0<`x8p#G 0<`x8p#G 0<`x8p#G 0<`x8p#G ׎`Op{'8= `OIDATp{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{'8= `Op{ؗpIENDB`TCMP0TPE2Anar Software LLC ZGI/1?]BBJÁyooԸhw: b KEN3*UYTAFirܦ/Y2aN=  T" 3w") C^ GH/,FdESTǪ޾oRI2.? bMIbnGE(V0 /||.١0J!] VN #f L׭y=c)01{Ȯ$ZXt?ɧK%S!9Kd cj SN$HʻHc/6{6o';#]merL^X=@qy f(Nh_O*w:5! QG%/?B |~tO<_ď #b M_ٹ&Z=rYf ֒(RF>'?Rۿ5̪ġ j MJDmi1 j9˥7SLaգ8 ,T0,1-hoykb$SIΦDL.N Y*@PH饨8Ҙ` *QmE^½p+bSIiRu6=)œ4lL2 b SI ): nQ]l/G f$0a2K\V}D!+|)M {j MH9LI,ߕq)xq+je!wQ=:g<܊۹KGcjۣ.Ktzҙ-ESSQLˎN ׷KT[9 aKG b8MIw*"Ŝ׫MD3g5or s GIDh3.Hm I7j$SHoSTLS0.[ׯFrL-΃\NMjm7"E}cI(ciL@rVWaVє fM,%/b1HE!晄N7 ?5L* a[ :cfSHLh%&TOY^_jjWHJjHB2 dH aJjj4}eV[f HCob״YS"Rr#)&NιU2wDbI?+ޱH`&н4qKQhV-n|9&TozQjEca>vQ)!pR6 bMIr!vHIpoUV\_ jR #PQn46قk{? pP#b -ȣ?$7yuUwKE8 kj(MHx0)CTAutB髧ɚl#%'Tk_Vp Uf SI?>L<ޢB,|ʣn|3ė7Z:=5)ſO|]Xɢ jSH>Cj6mh"`B" 4HHf$IPɈ*)fdH:&$| sj$QHϘr~l j29H :eAU|әRDovj xs#;ݯ^QeYͼwls ((J4XRbX [b MIeUZ2u1Έ{2zt7.Q?dKe2vg5Dn@ҬZ ^$MHT`}W? e/e 갦8Dh#QU*)K[V說jHK[0 T؂G5 dd0BB}kY_yJf.Zwkf 6NAlTҫShVlLu%k<Uw"9͔hVA^jUϩ]+b ID2Jnb[}o*˚O&jFN( ebAqH#fks*ՑdjDwfHKYcf{t i22IL7L4DR4)ǮT=wd}ԃu~s j SHo:sVvr4bWğ5*,5aP8@*[O-fBQ3sx9 j(SHl_}%"aFeb(F:Mj~*umDZ1Q>bIJ"GWKSYl9|_p(>G$6x*rK\=11x.v #b SI42?Lh T hA A8q)))mS;4c D4ɀ+j HژyN3)%iWrCTKQi [&cD&ky(37R! jSHSu)k<>KU +0) ,ƪo[~]?XhJ;/j"v#=E}QCNre$hx&t@PKB䒺b j)qoe6r9h1H3db b(I6޿I|Nz@ID=1\4D1[Q"93kԏ؛ެ3jHm[_r_[]Ҋ3LOdy2ʨ-a%3|fzQRQ f MH؜׷9U8ԧ JW&kaNl&)!Y 1Yr۟:Ԍٵ b8QIZg$#-ݮnbeˢRISs-N$0!βb V[U1]=[j SH^EM.x9K![rSSg^ Iyā;92xeC GL*[^I"1Sߵof9qGN1tRPVgh)EupاsjS;s_ﭿ`AY;iufa|PѴK'J'RGS.jiLdv k^8QIUUi͘t5ؾ_ȤɎxC wbDDZ)D_niF~$Xb#\#ï 9 Ga:Bl# c115̸rWs"0e2%W/&O f H+g)LDEh%IS2㓂*)D.[I~u9¦T8 ^$SHJ,ufrE6[J>\>%B#iœe&BChl|~#j?켵z b0Iʍ\v.EmSVi^.JA&RlRk< IUN2Yk} 3f$MH?HS,X<{|nj.`8" Fu3/2LzNFM7ݓeF #f4MHk]m+ڳזbIvqp֑BXiDKeYt14SBe$U+~˯" N4DbI L< Ve_$ͰFqkPеIP)ƽduz"Rƹh)f,_2J[,U-_Hv mq{#H(Q0m5@9(B b$MI;O;%T]vq#@!6-vG ?r\xkτFr#b(I9k_a A Srn4ADoM&ALAMEUUUEGѕ13*  Cf SHS ,alTDhlR?[2>ۊDf$HS4R&5ꪭ[YuOZRWR0 ePNg{F}ejM,#FoԥPo:8zx9ņ8$@YggQWd4 b4MI֔cDLjRFE^_ܯҊ.= F@qva%,0㭚j6SjSHZ{)Qdlt۽?W O§6_Y$$%Fh]_Gh$#fXf}e+~5qQ,ɗ Ru_%UKԧ2Έ cbMHggbiUE)Wv+Q.s{ŢSKRtg6̊躋X#aBG{bS˦ގ׵w[ROZ w -8Mr)Ȫ; bS-av xFf,w+#o*Jk,gdA$i"I'ƍ Z I=HǦe̩ZzzKA{d &Sjjy粟ϽWfQ a !/̘UU}Φ]S:c+ 3F(Me[9 !ֵ}TaQ.UxS8lU|χV"Bj$H# QE[+Zgb`ahod0@$PYŤʃK/7o][O3b -SU]iYAW_%,V~#FYDKE ?@ {b@GIo!K$H, k/9&!s_dOYaeM CjZK_I9ۡ$#bIJ8NJU;vUz-A;;UQ0) .Sekw>_cfS,u#EY_ov.R!Nb˜cK:JA+(us޿^NB[_Nv滼&2AFQg2b j)qqUb  bSI($ ΚU#5P/S7ngeX]LeEpP j,1ӷճ j0kpr{! :ʿ[uuQCjy"&=Nt@y/9 b$MI)e"퍞ZuDn&Nt jGրdEJ]4eUSgWg bHMIr2՝Q{U\Σ gc=b@ A@L<*7<f]x HzD r*|wict]:5%ͥ:"fUoF bS-"M{9fPrWfUEY瓩rbMu (6^Ŋ蝋"\ kfMH= {Oz1%uI__f3[tZ`p@ihJ4[b } dg9JʟOq[6L"s9 me-X-NGpv jlb 0 fM,&$ϢUfOr \[YU5td "BU%ȄU?2 ^$SHV3-͓銜JsBHSDQMeY4.]>ڙb ;5*0ugjYW~LɎ(9$=&MFE 30 PCY*< b$MIȹe~XRGXY7BHX)'kM7'jzQ슍2VV*S^$SIeRf8Dz<#imf5QIxA%cV2"$z:P0  bM- bF/CIӬ!D] gEf E)H0Z+j*o"Df,)d b d!mp˅OO/uGur0AW8XmߔT…d҇nhy{%]+  f MHkݙrn5f]Uzlaէ-PHj' #M '^ kx 2Ż@/#bIG/\n5^wfOkӞ+6y1j(^&V%dZ[F &&^SHXDtUǏ0OVj!{50yc1 SFH3SSQLˎNeQ|]%hN Cj0MH9NSOBާ ]h{-=Hx/LcMJadr4 ;b -O5ܾk΅&I䎻r$Rw}hS56TB b(MIr1 GSSTTѝXK_g?.Y!0qK4әĄ z{ j MHRwZ]Kww9ټQd",sMG=\ESկu;Ԃs&+sjyv[ +K"p& ˗#$pW {PYe<,#5/ bMI!~,x\0i4 aQԙ)e'I;ZtqLH+b [z';p)e'UU6F7"} . bQIC ( h>rm;'{@rY\@iBܠeL`8ܕ[t֊DYe2"3#jM,u.GuGkR؞cgP^`Ȁ.n'2bDG/9^ H."ڭ|/h51$xOHI'bZT >6:eQ fdMflfs,}V7LP$eYT 44eG IUoMG2bM-UNT37V3 H-L F4Ǜ*qDiƌc:rQfH8$$Ցh؉>w6~HЩ n 'Q6*Ix4TbLU)Ϟ j$MHV7^^yn=;ʪhT je#ӱG2ȱQnv'W+UTftŪfSHD\HDYZR2tƲ~nDQ.$2FPБBakKb Iؒ!y# .0J.MD)ԅ*\֨\ZXs6b S-",`uDSJHlA5LLAME3.98.2zef!2[JIcV j(SHwLOmtZbX_/,gCčiVYJ9y??skj <ʔgg{/SDHI8Mͤ"MUN0ekt?KRޮz c^$SHe07ETcۭ?Gv)շR$aa98\DzX Wٷ:=fdGfS"V4yݪgJA"!)53A-TDM7yy39򺐮_bkj vBgbԎ\JiyOKRTcuUO " @PiCpsg'PB VS-kJny4a.RiJvHPn,CP>SUHѹ!V-!TDIh$F0:L3%Z9|a,PDj* bIDʦWe0;6g~aQTFǤ@|V"3:WKV@z+b vPkY2'6lR9i5P$uDqN+ȕTdIʴ[!SOVѨ fM,J Rn?ܕ7rɘ6 D₀d&z'{)IJtЍN`mj S!$]R5BӃ$D%PHXK- ou[dy bMH4W( #(!Lqha15̸UUUUUUUUIV荚}466ٙb-y:ӍeRwf&TP`jdY` LȮ0 {f SHBw2F!4r2߿?3{ĶNjnITDr0{sH0DhFZSCf SHlGDթ脺0<6e5R~ HouJknafdj=/|MhS{`f5b1b F-mĵ/+"^$SI՜Kfuʕvn'12ͰD`Rj \Ѐ\hNmfgoGLb90zqlI!G~ 8cdL?UEgAbRY?QLȡe1pX Kb MIn ZaAjd1sЪs)f:SQjozۻR0WWnL +^$MHewR;Z<Ƨpil`m|9fewKzIHe?MJj,}?y'j'^_؊aynO'c3B15i>{]deDv +f$SHSJ{%23۶_^\n&c"^#aThnشfŎm>s\ j4MH}gvH3XBox:uV@t@'+$(XR+XNb#f I9_Y*'уmEI4iPj6 <Bo-+9B7Q f(SH9i_qR#|xdm=]Dj$X!!r䌆,# PBqIǢUcUܖ +j9o]PXR)n_K!I\wjxVEEq *9zbAqH NL  ]) +bM,= i|9~>qc@" AGFԞĐIouSJveeH b$SIU\J#VGW<,eUaԳIeSm[ac*#۳:2TC^I*#n(Qsf?aV3K_jUdT%*BB 6Zuf+b Ig5/.?&=!ỏ/H$hŪ)U4:9Q 3+YB ^(MHi-PЏcm!s*a1,h. M&>D*2vо *J|z,.j HY>R?f65R(#4LeZ4 Fu[2Gmk+%U 3^ y]C:3\S+|I4 7| nI&@Q(*F6d'$Ⱥ f$MHV <ﺋ?RMv$ݼZhL )ӣH;j .,Pͣ䅺Y?c)M߲iOp(1~hT@3U}_fڜ?M7kz {^ SIq 6fڝ__㰛dw2D8(@YB!UIY?e?ffSb -(3,=~3Wq[B=+(Zy/]kzyB"freR bS-9X)nFMV?a}#FɒYٍwdLLZ][jH]߻ʶ-I9sͶfGԲsPe[ w&)q~XvDqJ h q {^ MIjus ,jg6{Կ}iqq' )f$} i*oMS|.E[b(IdR)9{' 489fW^?!n2 lҵSJՔ\c~Sb SIr4]oKպ.zދ w!g]v(#˔}5X, hjok[j 5Gb::,+]?mŖ|W>b$6i2| bMIDExH!jZ4Kxϭoz " B(TS2'%@i& #*noϳ [f$SH,pA8[4ο~n6iTIfő;TDLBj[@.w0t^%yNY9ʿڼ6{5AZNh {lDUMEFGWzʒ b I"j~ޫnM)OW3eXBЕ!:1]ʽ|X "?SbM|/qlLyJ 2bPIB_t䌕28P<') b$SIݜyͦxlA|0qwS Lɑ{:j)ƤKye J Kj SHb̿|8$u3i0ѢynlR]h!.m%R bMI~PWqF#_^z,Fc*X͚{֭9K f0MH:A٘ݖiP泥&}W]a^ GICL9A'$C(!j{b";0"H[Bˤ_ߖiDCZn^t;{bQ-]jN@(Uu]z7lZNJHΕ& TBg$t[Zwmw jS,F)[={澤^!u~2Ғ*_'IC'|[b i΍:PV*]_O$ԥ`Ҫ BQMHQ.O% b$MIFC76dE?e?Cxd*9'yUIU5JŽF[Y9{j$JcfUrA_RGQaX),4g"fqUV)U0%7}Ms-*圈 b,Kqu5E^K{ 8LAME3.98.2rI^(@_ϟk^KWr vu!ԐaW°5dS2㓅kg EŒ2$ fSHm{J̼ͮW_2mB汾f(-kjj[a f SH%-H#PY+3?e?+Sv4UH|DzŰT-Y_9_ j0MHaG[qkUE Q)9]& */)"J/t b0QIIdRbdn§jwy;|OtDL*RM[&,L("GL".0*ªjHOSyn2~ \&}d R?Ҿ̈Kj쥑BbH1؎uw<7l_,mY@_*Tm"⊊NI-{+kk[ "`GKXvEʎu"-"QVk N53 )n*҂쏇BfM+ifbHdCQs:x& *OPQ6HQ#gW b@Me fu9ژde 60 |˩Aʅ"+b$8-!46ݖ2Ymל+E-N6j'7PF2BFAd*-6f vg=ɭVw3.rt/)&i(N0 0_+j<}zn b IގH+Jf=\ pМJMjdea0k8^S-BL߶ 3L~_Z@hnn݃J'H)J-˛drK f ,+clں{! ;3|vI$,̚3PBt6QU3QUf,ՙ7*+ 1J"rYWm?;k!HF ]tzr*ksCb HY<߫=E}TYi#)fC6 *%M; .52 fMHcx\foPi? U;Zd J "91! hK[z0Ȳ4kj,NO{*C3nY&nt) 8AiDO.EɽG[S|/<,.^ SoPU3MZjHXpL15UUUVy:)- cb$MIOSpOzBRer=FB^w^f2Ȫ $!u#h2*bM-@׵f/nMw7.I=$Dᇢ`$*UGf  #b SI#s+oyq~Q<+\@NXt*%#w1Vt 3b MIM("Pc$̊$]ގTNtsd}Л\#lkf$SHݵ$+NҭR x4Jd,[ )5%Ьʤm_BΞZkj =.),ḼG?_?_-QWsJل Eڇh`ћP1uvgb> p6G%Ow|gц/nBc@*% d=?cJJΕI f(MHe::nV5<3%#JfS0H䑒f k]gj,e2uuĘ9񷾕nLAXNlW !2mU)EγўB-1f ,u6][޾*J"aҜfƘYu$j~ ɮoDՌ͒Rd5x 36<MIj1N k/WHiRQegL0hO2)n5}5nt5Ʈ[j c)Υg~۱m~JR0,GXəiX> .? f(MHd,ހiYl?{OG3hI)[YYDO[Ꙩ@\Z IfSZ"=Ҵf5LD0qb) $ ZeK#9>Bb SI#E"r^c)9d+mZi D$ B%F FDZv*%]ȬCb  HQ v G*sІ.#sF{(l^F#33G HV(D&,(A b$MIU/??ϼ(۶M>۾5Ͻ{:bU!d-5WB^F uRdykjSH'W*rH$*)!AbLB^,72kwWkwMpId fSHdy!Zzvi I#Cb .PqrqXlQB!(k2 b̃Sb$Ef/"MGbHr.|Ezbwū.ATTJ;d勦 )a>9BMKg bMI:`aR"zu~t31¤9:Fxl4 8R [ sf(MH\ʯZԙe/NUT=z( ( ,)IŒFu2N~jS,xQ->^[C-v+% (&UĬdViԑ4oOuZ*1 b$MIT+&n ,Ej H@8hޫxmm䀅9ɓ>T:a& kO~ae[ j H yWŗcwKŚSd"i}\RU[ /:<+o?'g 6<f )#eoDlEцO&JtP5ˑl];kT~ bMIyJ;'ϼV#A@Y… P&P0A) eIĭ_\(khǒ)eVj SH|6:{g]97YQ:@,q而ɛ{ʹ2-=g;ig1 [b SIf1$dXۻ󩥱 "r &@XphPA]^uBCjH]ѓЇb! fY>צwKlcMhEW%Y7@ I #f$GH9U̧s[3Η_{X9%$ΰ6U$T5  [jS,4y׭&Y[tfNWnX[FK/ b+Ź[fS, Vʳm:7p|~c,T5"TJᖅD(8F@<ob~ c^$SIH{AGѿK9 W<ڛcA.ic #kesm)*H'n$YDݓbbq422Vf5ʿwV\w3M\g Va*'Zu)E'> b(SdZ2NhH."r~~9WAeo ma#0Tݧ,ZaZZd 2Y"-^ HR9NV_WgI۱T٬"X/HD&b Կ:H99,< bS-#єhzH df9F!Aj1 lNsTtR]2 n_W fs [fSH{I6\%hAH C(Rf %%$oۂQ?2&))CU\j ,C=XA%HH. 蘚] Rم+*_nc۩xVR +f$MHwBTa)yt}o6>)p/hu ۂ<(ċU2n( z.u، V$IKc3yQms?V]7._]mXݪb\YPNdu:Jsb(8S-dd`Q4RRS;k+ۏUx-%@D6d`NkM\ j$HhTۢufnƘQLyj<ےjceMur((yV>rV4,| BbR!|^y+M#f5*3#D7)J]GR$tz[jW,u;$dd";9V?MbzՉHDiJ(h8%(A*CjS_vM+.vmTS'z]΍yntb&duk)Q  {jMHD4u'?rH$ef\!$ Z]D_]ׯMԕ崲T jSHի,G*GIc6DT26Z_]B >66$j52sƉ"3Y'LH@a1JYfWO2-.1r  ] & )ڞB= ahneR [bM-/*UeT}VPm W 1 Ȧ(xMrCk:֋bǗOf^GΗ ;f$GHJC2{\[RʭSF(]d4Ly% BDAsÓ҈CjMWX7wce7=R>ddNI_o>LpCYMJ^IɍqgͿy-39^Υ*dKoRI"kJGEsN9 {fMH9#8ȱjrY$y$֙*& \MBr7o~Gsf4<"Qu>m5'8ϣWW.@b> GouWI8 V$MIiV#Ώ#Wilsf=GaHHVKO)E2q,+j,SH!Զue'e=Ej YHS2$[~mfl!c^$#5^zG3ˤr%>4 =Y&3rM 73PDme ̑ Z$SI㔬>%u@ }}.[7f׬oA'f`" $.ʰ;2*7* C^ SIp)EswҟnSBw#%-$9Qñu@^}r:">CZS, ߇#_xF3J("DāXiU"AWVQd ^MI#L|{Zdݴ?$'7䔪 hytG#B@ad5.YB[f$MHM;H:5JP{Nӓa|?L)@߶s3mWg [b<MIwtĪ==2&KfءIFd EIj^73J)Dy ebfSOߟfUhd";Q@j.dɘ2wʣ"$ĨMJ|vm"Aկb CheM]yΥ%n6ig*)'daa)j!2p,,*'#] bMI= k?%)%V/>SϦM V4T ;Hnegu)E/?34aj H^m"wv暽]i\$ZI`)N`Dag^RSjQjyo̲.7neG?ȈE PXh,.2 Uqk<{H bMIuo2Y_Zޚf0+QQ(#,F:D@a(U߬lk#+ffS,r喾UtrF(Y%$1`'iT̛n2Fm Ircfg!2Wyzֆׄe"v8Ϛ.3b-ΝfK8E|$#4C6OTˢv "e9 I2:P%ٰ Cf HC0O sHZ9rsjIcWM.F`BMN*'fpP&FUꞟV=bI=UсsױmVO۶Gt!1a(R4E(4k>2l3f_\uV6SdO D0THMzY#e#D bMI39H4-Rr"!U}{==L OTK\U b S%s"5IZ֨G3L1?P4YMBNvX~2fxKb1cac3 77?o+Mi˖Z8/lXj)ܑNBJoծ̕u KbSIW{&;w}W{5\xti$ĦNW#\kG=cb S?Qە'PI\ (,Fji.KI!)K:FAfEbSYs((SڔDFEuxͩ("YF)?kKKU{ bSI*]Dt:I|oor.jM8H-'.n"qeBaJ( b$IYy𬓵,_ww% zgL1Z1X8*Zs&b;L]z  b,SI"A!D:c{F:Zk>hAc1H*)E-ZdTv6@Bo)j SH|[37&QH3(pȀd%V܈A#LLIĔ$e$GIl5 j2H}C*p =)PLS LFA$'Fd()ß^=E"" f MH;WR tiIȶ8ZQ7qPj`e)ŬlRS\ bQIsI{yrWU!nn LB$((*^՗kkTU%eCj MHX&3;golK W]LBp:$9%i%a*k;NH% dKkb SI6Lrw\e_qnuӊu)%<]p!"gjo%Ir̒N{drcb$SI'ccH^}z}Z{ -cjHVH[oﻎϝ֪YעK^ H˕FbIM*R4Gf̽6LAME?"ϓD,G PSf HLe (Jf=|mD.lTl6ޛ~]&fRZ9hgjSH # N s>Yw;qm4)/ 9d ep*hU)ſ)e#Wӑ@CZSHb~[6!Nbm+SH1Vz""&S#vr: j SHQtW#Ԭ@wɿd*O'Pږ,U:/2"@²+A"LVK :+#jSEtTFsҮ+29oA[ ??w?;鑀 b,SI%sئ!=#sS1e1âLh3 x}RȗGor2 B#b=Yq)Z4TwդGw}6gVE2 *#wtMC&:  {VMI3!2$*avN#"Ir.|CdӐ4t.eE^@$ãGUjsjSHI"l2g?o^ۖ|^)PꖛL4(')0fA# u2d{>ns fMH]LDr2#]~kcט2r8)֢ti&b2F*&vqܙ.O{bSI?]kb2ܢʬ ԙs UnGҿLNj\4^ 3b$YKuڝ#|z{y͚NLQ{ʍ#B.U 8(mkt=*g%'4 baG%Z1P|IMFiҍ*>Tȏ$YopcY+ O bMI'JՉ3)6bv]x9VGE>98ǁ2/͗NEKڸy =YdC K^@MIM)O_aq[Qx‘2NY%` ?j-S*;j(Hscg{ۯocq8j&E{ZHb]4dm($~Qmϓ9VfH$W(jЪgW+ys96@=['0Vrlq5 @LErI [f MHK@**_5z&rfQ.W_Qۣ,6-9iF^f 0JKeksgN[Zpr' Cբ+B cb8MIoW>tu2/Te'w?w-J(;V 3BrgA0ޖ*M~{fS-墌rf_m%Nm|Z SݑJ )^e}!"ЌfȝQ[j=BݴPۜ~5EQy@0gaspLQ6T z,ouRN"I`;I j MH^{w^wH$Daz|DfUPRty֘U%mbDH%z f,hlk^d>lG?\( y?;M&@) P*osh +b0MIhݠ&\r7:1MgDG& =(bJ C[J+e3 f$SHTg+߯?S-tz,%r䩠; mH%[y!`,< {b SIgp|oמtE4DJ;& {Yص CfHa(R ~n n \# |-u6p'zb mVMi+{)cb(SIv"ٙQcyukudA ĈtIV !7!D (w/b-7XnTbS 7[YDs9CO*`a)( fMHk9"N^pd"Ttad6)GSa&єD}ۿ[T1$j qht(b j)qqUUUUUUUUUUUUUUUUUmIISp& bM-Y hFUmHV{ͻ\mE/60#B'e'0`*P* G7̹ kf$SH co_}m3M˂ϲ I0;Mrew_f%#Qb-Y=*W|somy(2hYrU-:+/ܕD!'bDN9OĹR"\A!UO""I`,MDw[jWojQ/09FY DYP>VuWʹ ^0ZG cbMI/NUnZm5`2]j>(0$MmiGM,S\S6bM- Z!U6[}z'8  f$SHc |:Zi?]۩NنH~U)Y) ^$SHLH6e>ez)vPÖZOQŵD 2K1\%r9bٲ#h&bYNW8}9m#0%*,a C(cttneJF(DQX Z MI ;;[e,uzKϽN iRWF& e^+tt bM-Y{|25Mp(*ƻd]bTʙ9jfTv ^M-@BЕ!;t%gtڝQQtAM|Wd4.u0 )j{jSHԿZ$䭘#u++%Q1,0͕%1(u$ [Se8j,ϹRSm-c;ΦYD$ (24NG/?Νu> f$Md yLFakZ"E筍}άGU㖍ꩰX q0bdH [(yMj3( 9 1ϯg.5ư Y @eÐFɊ>k@JSR> bS-;9E?.´QI閁\VEID 57Y~:PH3ABq  j MHfGkțOy%Wڞ贃a5 `ȄHR3PĜ(!")/}ƌfrBb(I'H T'{[ #j(ISfލU#1ME~vRg25 bMIW*B].VugS[߯ʻ:MJ$, I*rAKn DVey b$MI}erެV_wjgA: q֞o F6u!O'ɆUTgm{L5s1jM Nu'_?ٙZVۮ_ϼkafw7\YZ3f$SHT ZfL}wPnʥ&"]GEz& QDmwiAH bMIʪg9A@p( SSQLˎN B*/K4Hfb-4J$qlLy tM:n),u͖$&_Pܚ Ҥd6 UkZh j SHO;2-BWcݳ> b5bHVY͇s)tikd`,K%y b$MIˮUܥ2#n[UK3jA):X<Ӽnog>e4Awo72b|EfDfByc$FH%'ɨ!8;pjZg"̓ Y1U`&7`qJUe_O}ƀdhj HU2UfK5c>ta@"   @9xjoCHV=*#Էc^M. 85QkzyR_~~Z(G(dDk#bNByo}Jj(SH|s)*#|oߠ<(%>k](E*|Y%nl"oɯ" b$MI]i_>7rʽk ۧijP@IXgiff"fOesV {jMH&sKs(-mQ*OALsIiL>H&c ,e767=;jnsGdؓ\`R_ mm?{~0Ipi| Cb(MH90ˣ~r+'ap DD` c; %SvQvMYAbMEvC)Uu5[֤N ]wU57ʖޢ8mj DetqFUWnY3 jSHiSN4W?_i֧b[gFj8muNHvgЙflfh8̣z4tzUq-%U8 V{df Mj?R +bMIcϨ8bF[EFD:Y"i XM'f,ѕ]T5ĻT:X*g3t-&"d& ~z̃%q1{ Cf SH E;6 [?=+9r[A1W :[wG+j Sңu,: q%!i>2Dq+LAME3.98.2UUUUUk:$#L s^SI#'s^ S,x_$Zq8 {[&步15z;MBʌU2n$kj,H;0-RʏWM/.^WߴBkj"Y !R11jgfTj0SH,A!' w-eTqt9̐$y @ 8d0QAaI}jKj(HSB^ee+U? iT "{c.g#J  b SH[ \ufgww,5dhaDX&IP"7ȯ9{+%1 mj ɘa8Sd=Fks!jhKڤvDK(k0B[{Q/bZwX)bIs dG}OnwjWUvR88$\9uH" b'X]|X:j{b-w笿~V].=d4R364IG0(!IaU 1%FN +b MImDvmLnSZ 9PB@9sbA ('hG:SfSL#]Xr~P1)gvg#ךFdi`2 8fɇ Cb MI <"2O型3˟ ?QC^Rxxf+,7Ϣ͡df,bI*f$<םISHFp+*7o%! N$MIӣ5YTj F[%+J"M$߶Z\Ъ\;dDɒб! Lk!Yb^YWr :>ӺxԵ4&Q'C^ GE/.J f MH&}) #2P|Cj&l.]=&@M3,$@p"%>O b SID瑈R%f=5XᆠDWB7'!ڶHgW$*V_bwb3 bI k{-SۓHٺl$Kc\*:!(zJl.Hy"0]IG b(MIn!fCVaw,~.Sl-fX.(xi4\ ^|^re밒r^ZTgBE!QGDLT`ZS4Q ^MI*P18cj+,˨t1 "(()+EAQMDckjH&ISrGpM&${'`}xce& {+͌],}M h sf SHLew>3O;/Y|TrѿJ%FY d5KVMw*W b(ItvK ] }-ZXKZu͡ec$k6LBzl DO*Jw[~vRsnS NcΓ,nFNnLj՝*ȢZ,tP(FGdyZ9#fAlcӄ49~RcAM/XD`M|ř&#-  kb MIf7t{u:{!T!^s) :NE lPmDH ^־kb-?Frj 04eIp"ɖb j)qqUUUUU22+YVuwsBCj,:WpHÑ.ccQ&Y]Wͻj4 j+BBgJ!艌a} ZMImT."&4bysLAME3.98.2UUUUU)ӪjC͍.b(I[G>zil "@G#e+kfq87D[\ttS2Ԏ bXk8;gngoN9Zʶ؂@de)iV8#DCnYOT b7hyT_ߝXwv5?6VgI $L!XZdSfJP  b MIZ)3r]Y~GcɾBJ98cb#Fi9=H5'W{:Laũ{^$I(0 Dhf3IX"s+T=ܑƝ FegQ ,H +jj7Gp$ne<oOyݍ">-<$R)s3s b$SIMD$م FEW/[a듔YY$PWLU<[bSHg!r}7%^fҌi`8'tg*airYm͔$j SQ+7?qi|(F{'R{]e& )jF=b@d$l  Sf MH$R )y\zo&R&A2%DZQͦ*0#HRS7}ib_ 5[j S, ed2݇gFUz_kߘv1Z5~T$mr 3bMIuҋ"SR*\Do_-U41Bduk짝pT ^4MH% )0`f`#"'K2# D$Bz-ҫ@0#(!Y'k|}N;b SId?X3_y O]7Vd8\lv ih,rNr#UsE$3juՙmHj [.fI!I[HGVfm+TC(l&$'IShx%#9 f0>Ri-wћF]PIg$\W.B ] L;ffSH[2 Af!єP37w=P]Jl("4b[Oͭ4`[ Sb$MIEq)<^bt: bV[VĘ HRC9fMt43x#@'Q S0A SLAME3.98.2o_7F>g9$ bMIbJޫx+CSNKz'R`AF..JW+jM-QUHS(ŗ2mpJ8bK(uׁRB[\Hƅd6dTB&F* j MH~{erH`bK׿_UmK7q&('d*ȴ- fTGfgcřsP,7QR7f%*)"6E"&DսhjHyj{ 2 3 _ɾ9dZTEܒA"'Yg`*7?]b #3(V5h;ZR< L-'j:Zosg SfMH92@3N6c#HD>T 'c-B)2j)gͼ9Ieb-Q[ Z.BFS# i$a&c7LAMo_9i 1KfM,wd҆3m&"܎O0nj?&UUU+u:kv3 bM`}CHEn%ks5U*荒jE% cfMH15!VVr['1c DU(. z8VL8dHti]cj 9g,}Lx:bosA3 vtӱ2b j*o{!#GSr CbLGeiw*=Ժ|ݨ7-WI TQjbE  ̴,R284tT j HMHL2R 6J8w~R"eI:L.er( dlPLJ%s;^ S nEGw}2̍uO: 96 YTfŵZz21`2! +F@MIH%(/sWfΣ)14cƒn-#<[Ogav;{5kf,H#J 2bZ d!!ԙ BCRv"6*IL²lɠT.fS-hҀ$ ͛7|Cٹ 46H5)DHqbo%KJAD] fSHX.8}2eϻTmX]kt(3M4KB*2*oveؔve?} f(SJgrJ{O{:#]blZ.Y,SR`ınk$Dk|8 jSHVywSsUj*fʸ-NE 2Ќ^L_2#!Hf$SIĨH1;=WU9d?8RMQ-IfᄄBs2~ Sj$SH^mUguZ-ϖuSnn -yT1ȑ0;gd@('#5TY^ IxOvֱI<9F3G& *=;M#48V j SHXǻ%̛z~q3'mj$,zQh#RIe8ȭVt& 7[f "`&"-< -Hk!&yڳ&FҘꃡcJgڡ矍 fMH٘I yj0c CGؾXN٩dHytnŗ^[b KF<MI{%P2H[kJA6c?w^׍Bjr 'QG'`M͌d4SU+f,\g8d4#otwm> ii$o1]IǞp-CZg{;j SzsqbrŖJzS 0_P::%Ɉ*?.]Wg^b CbSIدr*9O#TrGmQVuU KND- K Eq}|HBԪ[gf 1?皓6]rxI-%8 b#Nb}|T yمotU& +j MHӢb6{JUQݔ!LLAv]yd#d2|e^H0v6jN|Cu j(MH~ C;v[JƷݵDZ֚D!Kdf56wj 5=e2Jf?ͭ*KȲW9сk$G_ʲKt((f%!Y)L' jЅ7o{Ӗtdښu< ZMKjP;lmz3Z*#Pe28HMk_:bS꧒ٝmYGkm "{(%G+6Xꆔ 3\r?m{7v dCb/ZB S^ SI+#.MѯQ~ȧ|? ˴?"g+e*ZsgR@CC@mk 9= [b MI j2 FI}b H] -C7GquI)Ћע fJM{j HE+eG\+g:Q@geZjxQL9Z_}us5jSHWeJSqEm2V._ RG[<6 @"w2GSdE*+bcj ,^amCT]rer勡A<0ܲ!Ug8B^6_\ b$GH2t~|VZY<>` Ff!:Z+UήSb IqtsH-D$%ɺu[v8/̲g$s,c'i FP@BtQcbIzLUUN(g l*H@M-f$H#§ٴ `SZTGXsm PPx,Qdd IšʘԄKw kbMI{ϕ׻>Ru59LKؘ,lWE>L f SHKj=m *IVyGG`%Dtj b$MI';mxֹk"\-r{Fit_E+@q *YU}۳%JrU: cjSH"ބ{}m͇ hSQxdnxfu$)} '!!1 s^$SHdbF|+̖GJRTy0@C((X6$+=Ș [~ +?vSb? "`GgDK?!I\_lj$hJ8)f .4D*7=O6F$zb$SI$cFs$ݓbǭ=J.aQ@8BN`j*¿OÚ'b-/Tr7:hJ&v$j'$EbIULAMEUB D)Cf SHeJ$i?=]DL10Ĕ% tz4X ZLAME3.98fSH.2LAME3.98.2;bQ fMHkmXQHTAG1 Minute of SilenceAnar Software LLCBlank Audioebooklib-0.20/samples/08_SMIL/create.py000066400000000000000000000032641507750473400175760ustar00rootroot00000000000000# coding=utf-8 # noqa: UP009 from ebooklib import epub if __name__ == "__main__": book = epub.EpubBook() # add metadata book.set_identifier("sample123456") book.set_title("Sample book") book.set_language("en") book.add_metadata(None, "meta", "Naro Narrator", {"property": "media:narrator"}) book.add_metadata(None, "meta", "0:10:10.500", {"property": "media:duration"}) book.add_metadata(None, "meta", "0:05:00.500", {"property": "media:duration", "refines": "#intro_overlay"}) book.add_metadata(None, "meta", "-epub-media-overlay-active", {"property": "media:active-class"}) book.add_author("Aleksandar Erkalovic") # intro chapter c1 = epub.EpubHtml(title="Introduction", file_name="intro.xhtml", lang="en", media_overlay="intro_overlay") c1.content = ( """

""" """Introduction

Introduction paragraph where i explain what is """ """happening.

""" ) s1 = epub.EpubSMIL(uid="intro_overlay", file_name="test.smil", content=open("test.smil").read()) a1 = epub.EpubItem( file_name="chapter1_audio.mp3", content=open("chapter1_audio.mp3", "rb").read(), media_type="audio/mpeg" ) # add chapters to the book book.add_item(c1) book.add_item(s1) book.add_item(a1) book.toc = [epub.Link("intro.xhtml", "Introduction", "intro")] # add navigation files book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # create spine book.spine = ["nav", c1] # create epub file epub.write_epub("test.epub", book, {}) ebooklib-0.20/samples/08_SMIL/parse.py000066400000000000000000000010711507750473400174370ustar00rootroot00000000000000import sys import ebooklib from ebooklib import epub from ebooklib.utils import debug book = epub.read_epub(sys.argv[1]) debug(book.metadata) debug(book.spine) debug(book.toc) debug("================================") debug("SMIL") for x in book.get_items_of_type(ebooklib.ITEM_SMIL): debug(x) debug("================================") debug("DOCUMENTS") for x in book.get_items_of_type(ebooklib.ITEM_DOCUMENT): if x.is_chapter(): debug("[{}] media_overlay={}".format(x, x.media_overlay)) # noqa: UP032 debug("================================") ebooklib-0.20/samples/08_SMIL/test.smil000066400000000000000000000007561507750473400176310ustar00rootroot00000000000000 ebooklib-0.20/samples/09_create_image/000077500000000000000000000000001507750473400176165ustar00rootroot00000000000000ebooklib-0.20/samples/09_create_image/README.md000066400000000000000000000001471507750473400210770ustar00rootroot00000000000000Basic create ============ Simple EPUB3 creation with attached images. ## Start python create.py ebooklib-0.20/samples/09_create_image/create.py000066400000000000000000000022551507750473400214370ustar00rootroot00000000000000# coding=utf-8 # noqa: UP009 from ebooklib import epub if __name__ == "__main__": book = epub.EpubBook() # add metadata book.set_identifier("image123") book.set_title("Simple book with image") book.set_language("en") book.add_author("Aleksandar Erkalovic") # chapter with image c1 = epub.EpubHtml(title="Chapter with image", file_name="chapter_image.xhtml", lang="en") c1.content = """

The world famous chapter

Yes, this is the world famous chapter with image!

""" image_content = open("ebooklib.gif", "rb").read() img = epub.EpubImage(uid="image_1", file_name="static/ebooklib.gif", media_type="image/gif", content=image_content) # add chapters to the book book.add_item(c1) book.add_item(img) # create table of contents # - add section # - add auto created links to chapters book.toc = (c1,) # add navigation files book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # create spine book.spine = ["nav", c1] # create epub file epub.write_epub("test.epub", book, {}) ebooklib-0.20/samples/09_create_image/ebooklib.gif000066400000000000000000000304541507750473400221010ustar00rootroot00000000000000GIF89a,,;]][11/YYWNNM//-zzy??>ttsKKIյ==;lljDDffe""33UUffwwLLJ--+YYX%%#RRPUUT##!))'hhgJJH886"" ! NETSCAPE2.0! XMP DataXMP ~}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#"!  !;,,,@pH,Ȥrl:ШtJZجvzxL.zn|N~ H*\ȰÇ#JHŋ3jdC Ird ,pܳ %cu |ɳ8lQTUљ@uˢ#(]Zfӧ$Pdr+ 2@0 va` bQ ~l IvRadY ZEd7@`fϐdCdB彧*Q[4ǁo Y*2$ge2w|dlX1X^u' |}40#W$?GmIwY{\W^yIA߄{}`D10FFu'iEWF1(^3p! ܤQIU؅>a#Q( @#I6A J.m`v, @,yM85q2P>l;\!qOGRFCR_SE9Fy[!=@w {' $$IGdr <߂^G2J&2-9i"w3YclȯN2$ q&] /srW(FRwpwRĩ#,Hxʓ@LHJ""tcG)Tx4b2iQMd ԁI}s % BK(4V$ up"$!уa>lP9Fa\>JX,)rh7%TTLD *A8$,*y6I@!G*q,@YЫBp<4a X]-:5&4 j-$`D0_**B$Dp)AE-BgIW^Ѳ<'*h 8|UG8;͎. jp ,95OUm@m ;DO\qe| +"1H}R1@"eKIȬJ&Y Ktԙ 9#0+K G %V {k61P"3sB$'6Q86׿> jGyT6Ǧ՗{@mF[d$mTЀMnt`׳HT $ُp4Xk+4rOA% Fk%@w,*_߃#T0T&`r`  LMo@L2'ńtPf%D_KaxkbVݒ2nhHIp \X$%$&Dgtq:;@p \=O:“@nB:  1VpRlG7ׇpp;!VABZMn4vK >nqT  74$ؓU=lixNϟrs zn1߁&j_|ꡰqdXoy&U=.XBpcC{F8ruPGAHQOKP}OIR`NoJ@`Jk'{O`H@뀂VG['#5(7HM?n3HF`ꀃϤpNM Mpwl"FH|gZpPvQlT(sHeQhN`r~G@v`8WZq'9GnÖ~AsZ5i~k$w8Zx(e(Vw|I@j wuo{fxekHZIVsGr'džC*7BB$CIPyq6}C{\EHBN]<'BqF|& l E00%l>2Ѷ\&]0Do8 p֨:y NjDWpP>u`Ej{A_J@~Xl|H~&{;k"树3&cQa"3aDYDyBp'ЉC@(c@y"JI*\ٕ^`b9dYfyhjlٖnpr9tYvyxz|ٗ~9Yy٘9Yyٙ9Yyٚ9Yyٛ9YٚA!;,)Pdb@pH,Ȥrl:ШtJZجvnp74u18P |N x> !vuy`[ U rxde` Kl Tv f!r xvRr!JuQ! NP:x Fԙ M qBӅB 癑M !{:,W*"ڱBY-hq1J"f10@Kj/l)&g%Uɲc&)&sh>)3ēƄc:%O;۞,0dKD-,ɂ)HC*zEc').]RM3d6$4f_tUص Ɛ06dʹٷȘK@yGPcm K· T;w3|泔={bW9 @DP!€pepmZ!i!Z5f _C>gKԬC,PY b؝#=71x"Ƽho0@EFmA/.m88A<+`Q A!;,)Td^@pH,Ȥrl:ШtJZجvzxL.zn@?~?ms9i hffedĺcʷϽbӱaثݲ᫡  L80A"D(p[, ~"p_)8\B r0E(T5 0!DZ3Ta C3ppAΝBH)IC$D[H :JMlڵQ.(Xʵ_` 5ԐQnikPCp͋h]sK6-CRHP\2]9WʰRhPAB< p!RqOH$.[h{H.Rא;ߥuiR H0EŅ]rCHA@hv9h D%X%܍e` H_ܕX"Ʌ;ɹ(``yY p%G(BLKaIHLUdb v@)@K6P@جfϪNغ6F+-^Fڢmf| .+F悁n^;D!;,*UCd@pH,䂑3XШtz䮘uRCwL>9Fy]Ơs|Ήxhݐ~S ui qBJn !E LD!: g:_{Qhh d~Vo OuWcyhwesurr`}sk`sW~pxl9} CA4| jF>(5agRo(VDMF< &PfUro"dl,т7M,LUxh7-FO얢3R &Z*Q7WTL0> 0āN('3՘sf~(]<Ђ`Á׮ mꉯ`;͆ |E8ǂ^4 fl pqD#Fyh0Ŏ,Po" P"!LpJJN){P84%zGQ\,B-`p VP!50@[LPJsCD]`V A#}\lS"^8RB,qoo2:L— [X\hWGms}u%D9I\JX]mD ])yDMg"eTvÍĜ ["! Bb0pq[2jVILOׁש2rhd*^%CBq)p}D~bIAWdiPP,Z06ʯeX-i1K*ߜͻb ox0ng/JMG!;,,&`@pH,ȤrY( IZ؅'톰 nU^ |'Z ujz}p Nt a ]TC_Cf\ B P\ ]m\Æ{i:X!!9K uH!]8OZ 㰢@Ň# A9 ‰1ϤEL+p)QRO`BaiJҴT [!v`9xFϩEF*Z5F0[/9=c;ܭd7Uk|V8[ TIƎ?.=322ˁ*}`*! 'i:f\SsnfJ2P=19d͂7%g^0I;%}<~WH-V4.%tV"̂Hl!Ѓ6 ( yLVgT1XvQSH YB!q5DKW]jEP7dE"LYDɬ`,ōy fLxi$p9fni*mmVJEZYȅA 0!|'@u@@2@ @ٙK@E\`|eXx u80D4@@".aAP9x(j(@qwaP (LzDvED2V D9JuP1GZs갮2ѐKqkq/KS`{qw"1Ju.4@Bó**<&Y F9ʅD4ET MtbW ECH @ ̄`#uąJ@S4p3U5u5d+vxD$<r WT L9c:\j('4}#2=7:H08#2W$JH*"a/P(-e1G[ & "}o&]\DKjECBwB~E Ī]C91DlEWľzj,%Dj`A.ׅUBX%FӅ DB&ie$Aa3.܊=%*uTnDCp'U`qPF }\a: 1=PB":$D`^x8>҂¹F  `H9+p @Q*wK*_("IB7Z8fneFkXB(Ґ3bOaɤ]lla$wj%&BHX1h@_ZH-H(X)7)*5@!$*c8'Pkx,7mZ_vt y*w@1AQ:X}:!B>_2NDÅ[ƃq{4^jC&pWP4@: #aF@Y\<ڊ!,AS@5BC K$(}u-6p V@_Be sU/WxjA,pհMZk:,2لuzT*oZV|);F!e{hX 1|=?de1/@#T&v (#GlY9QR{a;HwDrP  o'c$4 : y+1&Ze DhlcPɴŖ<!;,,&`@pH,Ȥrl:ШtJZجvzxL.zn|N~B!! 99 jj ʮjhigi 駶F pZ/ػ8 ( pf ,hiQc[G寀n>s`V!i)11[6;@2NJ+|UTTWU#]͡J` +%X60D(@/f^#paWxH /C \h@W)*n}) _pga>|~H_,5cltNv'/R r}l(!ʕmPJu"_Dʵ|RP8]5  )_]2<&E/Nqԃ.`Ѕu @~8iHɱ4PtƏmGH`+(6mUk5!,"a#+ 41]՘CkGPJMIdQD9Q$cᕮ܋9Ƅ9nua|LrxbdQF=&@Lj8!AZlV ( c%;NҪ$ W iR_\jKt̙X M|bA(`t)ny)Yw!^{^m~Uq[vTy}TED{Dtpr:`G!kCk jźjʹjϴ|i؞h ݘhh!a ja Gkr9,肰fsAFO! |AK@Eb.j\IGZtc!kP RLU0NR9<-@͠@OV5KM4S0c@PI()i@ RPXL l*ئ0l(6qx2&TJq/=Y {ae]?ଌtf,9\fEۏhToFȩh AFcc뒾پp\Ga{0=/Ae!^ze/uu|~~'DܥCW] H\q mZo~)rCā%n5 0-za bRiXHZ$)Evq׎~@XJ:[&Ya_ ԤweN; pƘe酚ia y[YQ7'\B(V:碚2"iU~ RQMJrJa/ji]ȕ˫1$)EZjl6lzR9 [D98.lyj $>kmߒr_9hEXȼ*W!;,*AOs@pH,Ȥrl:ШtJZجvzp1(ˠ`ȹ tk0jBz8e@a1카1Z`6bC٢ ]lX` \ , ݇o[i+`Q.),Ξu |)J(jlt2֬BK9Dٶ-Z EmLli" eN[ PP.tDz\w#/WUV+7q3FjJ˙ƣbF$6hGqV-FV>@ǝb *; O;IXer9iD4gowoa5gMu79Ze mH%M׷gw8[mc;98vG^y&ZM\K/Z)Zbo%= ׊Y&0.&ax`5.f@qJtn;Oj @87`/ HI`<Ept"̟7Їi~]!H"/iJ/bt.wc5\ҖCM!r @CRJF\H#PBRU UƈW@D'슔@1ډJi8Sx_J< (;ebooklib-0.20/samples/README.md000066400000000000000000000013121507750473400161550ustar00rootroot00000000000000EbookLib Samples ================ * 01_sample_create Create simple EPUB with two chapters and custom CSS for the navigation. * 02_cover_create Same as 01_sample_create but with cover page. * 03_advanced_create Creates EPUB but uses some advanced options from the library. * 04_markdown_parse Simple script which creates static markdown files + images from your EPUB. * 05_plugins_create How to create sample plugin. * 06_parse Basing parsing of EPUB file. * 07_pagebreaks Create simple EPUB3 with both visible and invisible pagebreaks. * 08_SMIL How to integrate Synchronized Multimedia Integration Language (SMIL) into your EPUB. * 09_create_image Creates chapter with image. ebooklib-0.20/setup.py000066400000000000000000000035711507750473400147550ustar00rootroot00000000000000import io from setuptools import setup def read(path): with io.open(path, mode="r", encoding="utf-8") as fd: content = fd.read() return content setup( name='EbookLib', version='0.20', author='Aleksandar Erkalovic', author_email='aerkalov@gmail.com', packages=['ebooklib', 'ebooklib.plugins'], url='https://github.com/aerkalov/ebooklib', project_urls={ 'Documentation': 'https://ebooklib.readthedocs.io', 'Source': 'https://github.com/aerkalov/ebooklib', 'Bug Tracker': 'https://github.com/aerkalov/ebooklib/issues' }, license='GNU Affero General Public License', description='Ebook library which can handle EPUB2/EPUB3 format', long_description=read('README.md'), long_description_content_type='text/markdown', keywords=['ebook', 'epub'], classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", "Operating System :: OS Independent", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries :: Python Modules" ], python_requires=">=2.7", install_requires=[ "lxml", "six", ], extras_require={ "dev": [ "pytest", "pytest-cov", "sphinx", ], "test": [ "pytest", "pytest-cov", ], "docs": [ "sphinx", ], }, ) ebooklib-0.20/test.sh000077500000000000000000000002201507750473400145450ustar00rootroot00000000000000#!/bin/sh pytest -o log_cli=true -o log_cli_level=debug --cov=ebooklib --cov-report=term-missing --cov-report=html:test_reports/coverage tests/ ebooklib-0.20/tests/000077500000000000000000000000001507750473400143775ustar00rootroot00000000000000ebooklib-0.20/tests/conftest.py000066400000000000000000000002101507750473400165670ustar00rootroot00000000000000import pytest @pytest.fixture(scope="session") def session_temp_dir(tmp_path_factory): return tmp_path_factory.mktemp("ebooklib") ebooklib-0.20/tests/resources/000077500000000000000000000000001507750473400164115ustar00rootroot00000000000000ebooklib-0.20/tests/resources/create_test01.py000066400000000000000000000045321507750473400214320ustar00rootroot00000000000000from ebooklib import epub if __name__ == "__main__": book = epub.EpubBook() # add metadata book.set_identifier("sample123456") book.set_title("Sample book") book.set_language("en") book.add_author("Aleksandar Erkalovic") # intro chapter c1 = epub.EpubHtml(title="Introduction", file_name="intro.xhtml", lang="hr") c1.content = ( "

Introduction

" "

Introduction paragraph where i explain what is happening.

" ) # defube style style = """BODY { text-align: justify;}""" default_css = epub.EpubItem( uid="style_default", file_name="style/default.css", media_type="text/css", content=style, ) book.add_item(default_css) # about chapter c2 = epub.EpubHtml(title="About this book", file_name="about.xhtml") c2.content = "

About this book

Helou, this is my book! There are many books, but this one is mine.

" c2.set_language("hr") c2.properties.append("rendition:layout-pre-paginated rendition:orientation-landscape rendition:spread-none") c2.add_item(default_css) # add chapters to the book book.add_item(c1) book.add_item(c2) # create table of contents # - add manual link # - add section # - add auto created links to chapters book.toc = ( epub.Link("intro.xhtml", "Introduction", "intro"), (epub.Section("Languages"), (c1, c2)), ) # add navigation files book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) # define css style style = """ @namespace epub "http://www.idpf.org/2007/ops"; body { font-family: Cambria, Liberation Serif, Bitstream Vera Serif, Georgia, Times, Times New Roman, serif; } h2 { text-align: left; text-transform: uppercase; font-weight: 200; } ol { list-style-type: none; } ol > li:first-child { margin-top: 0.3em; } nav[epub|type~='toc'] > ol > li > ol { list-style-type:square; } nav[epub|type~='toc'] > ol > li > ol > li { margin-top: 0.3em; } """ # add css file nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style) book.add_item(nav_css) # create spine book.spine = ["nav", c1, c2] # create epub file epub.write_epub("test01.epub", book, {}) ebooklib-0.20/tests/resources/test01.epub000066400000000000000000000063221507750473400204110ustar00rootroot00000000000000PKӋF[oa,mimetypeapplication/epub+zipPKӋF[}META-INF/container.xmlU=0 ཧ6?; ["R;JR'P{\wɉ7h8UGH{Kcs3tmQ-a9O1)FEz¨Qz6S[ i2f1aou;ktUڼ,f&= WEZnPKӋF[^75;EPUB/content.opfTr +z#!mh,ez^2kXٌP@EXƙ7o{oG˧}F׳2/f57Bu=C}=5wK | k$}M7!؊\ ƭټ(-%{̤@d+T zV:l徦!WpX3}!p4T ~d{R vуge^2:bFhXtPSW]NlJf^̿fe?ˇjQT_K6M4tX5jt>fJHq(l Hxr~ɮ0 G?!gc |>`6iߴpA pmA~Dd)cNe>2`G61pPЫsiK/5% Y88€ {&up&oB<|6R\sJrHПR"-_Ok#O7\!fq3 k! gd 4qe19XX"F-\nxh&*"k^nz(D]`Jr1 :~1Y?$;Yq7 wsJ: =&<84q)&Ae_PKӋF[}>4EPUB/about.xhtmlen >زUmn퐥/}bHV@H䇩0)(7ȖS {G._???^=w 'Z2cpHOJk3nq &9$teVJ.BW^o[h <:Yn0|aL%RjL [LC~C@@ȡlo { @43Gj:73nY31a<P|yn?Tqk=z0O_o}/PKӋF[qEPUB/style/nav.css=O0 [1 !X 6RH|NZz҉%_ R¾EdY}ֻ>v9-}HJxCyDžB m5FwpOQ\jN -4EPUB/about.xhtmlPKӋF[@4[ aEPUB/toc.ncxPKӋF[`&6EPUB/nav.xhtmlPKӋF[qH EPUB/style/nav.cssPK . ebooklib-0.20/tests/test_ebook.py000066400000000000000000000355041507750473400171160ustar00rootroot00000000000000import io import logging import os import zipfile import six if six.PY3: from pathlib import Path else: from pathlib2 import Path import pytest import ebooklib from ebooklib import epub from ebooklib.plugins import booktype, sourcecode, standard from ebooklib.utils import parse_html_string, parse_string logger = logging.getLogger(__name__) @pytest.mark.usefixtures("session_temp_dir") class TestEbook: def _create_basic_book(self, ncx=True): book = epub.EpubBook() book.set_identifier("test123456") book.set_title("Test book") book.set_language("en") book.add_author("Test Author") book.set_cover("image.jpg", b"fake image data") doc1 = epub.EpubHtml(uid="chap_1", file_name="test.xhtml") doc1.set_content( """

Title

""" ) doc2 = epub.EpubHtml(uid="chap_2", file_name="test2.xhtml") doc2.set_content( """"

Title2

lorum ipsum.1

""" ) css1 = epub.EpubItem( uid="style", file_name="style.css", media_type="text/css", content="BODY { color: black; }", ) book.add_item(css1) book.add_item(doc1) book.add_item(doc2) book.toc = ( doc1, (epub.Section("Section"), (doc2,)), ) if ncx: book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) book.spine = ["nav", doc1, doc2] return book def _test_basic_book(self, book): assert len(book.toc) == 2 assert type(book.toc[0]) is epub.Link assert type(book.toc[1]) is tuple assert type(book.toc[1][0]) is epub.Section assert type(book.toc[1][1]) is list assert book.spine == [("nav", "yes"), ("chap_1", "yes"), ("chap_2", "yes")] assert book.get_item_with_id("chap_1") is not None assert book.get_item_with_id("style") is not None assert book.get_item_with_href("test.xhtml") is not None assert book.get_item_with_href("style.css") is not None assert len(list(book.get_items_of_type(ebooklib.ITEM_DOCUMENT))) == 4 assert len(list(book.get_items_of_type(ebooklib.ITEM_NAVIGATION))) == 1 assert len(list(book.get_items_of_type(ebooklib.ITEM_STYLE))) == 1 assert len(list(book.get_items_of_type(ebooklib.ITEM_COVER))) == 1 assert book.get_metadata("DC", "title") == [("Test book", {})] assert book.get_metadata("DC", "identifier") == [("test123456", {"id": "id"})] assert book.get_metadata("DC", "language") == [("en", {})] def test_basic_bytes(self): book = self._create_basic_book() # Write to f = io.BytesIO() epub.write_epub(f, book, {}) f.seek(0) self._test_basic_book(epub.read_epub(f)) def test_basic_file(self, session_temp_dir): book = self._create_basic_book() epub.write_epub(os.path.join(str(session_temp_dir), "test_basic.epub"), book, {}) self._test_basic_book(epub.read_epub(os.path.join(str(session_temp_dir), "test_basic.epub"))) def test_basic_read_options(self): book = self._create_basic_book() book.toc = [] # Write to f = io.BytesIO() epub.write_epub(f, book) f.seek(0) bk = epub.read_epub(f, {"ignore_ncx": False}) assert type(bk.toc) is epub.Link bk = epub.read_epub(f, {"ignore_ncx": True}) assert len(bk.toc) == 0 def test_basic_file_raise_exceptions(self): book = self._create_basic_book() class FakeFile: def write(self, content): raise OSError def writestr(self, filename, content, compress_type): raise OSError def close(self): pass def tell(self): return 0 def seek(self, offset, whence=0): return 0 with pytest.raises(IOError): epub.write_epub(FakeFile(), book, {"raise_exceptions": True}) assert epub.write_epub(FakeFile(), book, {"raise_exceptions": False}) is False def test_basic_file_save_options(self, session_temp_dir): book = self._create_basic_book() book.guide = [{"href": "test.xhtml", "title": "Introduction", "type": "bodymatter"}] book.set_direction("ltr") epub.write_epub( os.path.join(str(session_temp_dir), "test_basic_on.epub"), book, { "epub2_guide": True, "epub3_landmark": True, "epub3_pages": True, "landmark_title": "My Guide", "pages_title": "My Pages", "spine_direction": True, "package_direction": True, "play_order": {"enabled": True, "start_from": 1}, }, ) epub.write_epub( os.path.join(str(session_temp_dir), "test_basic_off.epub"), book, { "epub2_guide": False, "epub3_landmark": False, "epub3_pages": False, "spine_direction": False, "package_direction": False, }, ) # Everything turned off zf_off = zipfile.ZipFile( os.path.join(str(session_temp_dir), "test_basic_off.epub"), "r", compression=zipfile.ZIP_DEFLATED, allowZip64=True, ) content_off = parse_string(zf_off.read("EPUB/content.opf")) nav_off = parse_string(zf_off.read("EPUB/nav.xhtml")) toc_off = parse_string(zf_off.read("EPUB/toc.ncx")) assert content_off.find("{%s}%s" % (epub.NAMESPACES["OPF"], "guide")) is None # noqa landmarks = nav_off.find( './/{%s}nav[@{%s}type="landmarks"]' % (epub.NAMESPACES["XHTML"], epub.NAMESPACES["EPUB"]) # noqa ) assert landmarks is None page_list = nav_off.find( './/{%s}nav[@{%s}type="page-list"]' % (epub.NAMESPACES["XHTML"], epub.NAMESPACES["EPUB"]) # noqa ) assert page_list is None assert content_off.find(".//{%s}spine" % epub.NAMESPACES["OPF"]).get("page-progression-direction") is None # noqa assert content_off.getroot().get("dir") is None nav_points = toc_off.findall(".//{%s}navPoint" % epub.NAMESPACES["DAISY"]) # noqa assert len(nav_points) > 0 for _n, nav_point in enumerate(nav_points, 1): assert "playOrder" not in nav_point.attrib zf_off.close() # Everything turned onn zf_on = zipfile.ZipFile( os.path.join(str(session_temp_dir), "test_basic_on.epub"), "r", compression=zipfile.ZIP_DEFLATED, allowZip64=True, ) content_on = parse_string(zf_on.read("EPUB/content.opf")) nav_on = parse_string(zf_on.read("EPUB/nav.xhtml")) toc_on = parse_string(zf_on.read("EPUB/toc.ncx")) guide = content_on.find("{%s}%s" % (epub.NAMESPACES["OPF"], "guide")) # noqa references = list(guide) print("\n\n\n\n\n", zf_on.read("EPUB/nav.xhtml"), "\n\n\n\n\n") assert guide is not None assert len(references) == 1 assert references[0] is not None assert references[0].get("href") == "test.xhtml" assert references[0].get("title") == "Introduction" assert references[0].get("type") == "bodymatter" landmarks = nav_on.find( './/{%s}nav[@{%s}type="landmarks"]' % (epub.NAMESPACES["XHTML"], epub.NAMESPACES["EPUB"]) # noqa ) assert landmarks is not None assert landmarks.find(".//{%s}h2" % epub.NAMESPACES["XHTML"]).text == "My Guide" # noqa page_list = nav_on.find( './/{%s}nav[@{%s}type="page-list"]' % (epub.NAMESPACES["XHTML"], epub.NAMESPACES["EPUB"]) # noqa ) assert page_list is not None assert page_list.find(".//{%s}h2" % epub.NAMESPACES["XHTML"]).text == "My Pages" # noqa assert ( page_list.find(".//{%s}ol" % epub.NAMESPACES["XHTML"]) # noqa .find(".//{%s}li" % epub.NAMESPACES["XHTML"]) # noqa .find(".//{%s}a" % epub.NAMESPACES["XHTML"]) # noqa .get("href") == "test.xhtml#fn01" ) assert ( page_list.find(".//{%s}ol" % epub.NAMESPACES["XHTML"]) # noqa .find(".//{%s}li" % epub.NAMESPACES["XHTML"]) # noqa .find(".//{%s}a" % epub.NAMESPACES["XHTML"]) # noqa .text == "fn01" ) assert content_on.find(".//{%s}spine" % epub.NAMESPACES["OPF"]).get("page-progression-direction") == "ltr" # noqa assert content_on.getroot().get("dir") == "ltr" nav_points = toc_on.findall(".//{%s}navPoint" % epub.NAMESPACES["DAISY"]) # noqa assert len(nav_points) > 0 for n, nav_point in enumerate(nav_points, 1): assert "playOrder" in nav_point.attrib assert nav_point.get("playOrder") == str(n) zf_on.close() def test_syntax_plugin(self): book = self._create_basic_book() doc1 = epub.EpubHtml(uid="chap_syntax", file_name="syntax.xhtml") doc1.set_content( "

Title

Wrong

Correct link

" ) book.add_item(doc1) # create epub file f = io.BytesIO() epub.write_epub(f, book, {"plugins": [standard.SyntaxPlugin()]}) f.seek(0) book2 = epub.read_epub(f) chapter1 = book2.get_item_with_id("chap_syntax") html_tree = parse_html_string(chapter1.get_content()) body = html_tree.find("body") link = body.xpath(".//a") assert len(link) == 1 # Make sure plugin erases unwanted attributes assert link[0].attrib.get("onclick") is None def test_booktype_plugin(self): book = self._create_basic_book() doc1 = epub.EpubHtml(uid="chap_syntax", file_name="syntax.xhtml") doc1.set_content( """

Title

Wrong

Correct linktest 02 link

""" """

""" """1""" """

  1. prvi footnote """ """""" """^
  2. """ """

""" """""" ) book.add_item(doc1) # create epub file f = io.BytesIO() epub.write_epub( f, book, { "plugins": [ booktype.BooktypeLinks(book), booktype.BooktypeFootnotes(book), ] }, ) f.seek(0) book2 = epub.read_epub(f) chapter1 = book2.get_item_with_id("chap_syntax") html_tree = parse_html_string(chapter1.get_content()) body = html_tree.find("body") link01 = body.xpath(".//a[@id='test01']")[0] link02 = body.xpath(".//a[@id='test02']")[0] assert link01.attrib.get("href") == "../test01.xhtml" assert link02.attrib.get("href") == "test02.xhtml#something" link_noteref = body.xpath(".//a[@href='#InsertNoteID_1']")[0] assert link_noteref.attrib.get("href") == "#InsertNoteID_1" assert link_noteref.text == "1" aside = body.xpath(".//aside[@id='InsertNoteID_1']")[0] assert aside.find("p").text == "prvi footnote " def test_sourcecode_plugin(self): book = self._create_basic_book() doc1 = epub.EpubHtml(uid="chap_syntax", file_name="syntax.xhtml") doc1.set_content( "

Title

Wrong

print('Hello, world!')
" ) book.add_item(doc1) # create epub file f = io.BytesIO() epub.write_epub(f, book, {"plugins": [sourcecode.SourceHighlighter()]}) f.seek(0) book2 = epub.read_epub(f) chapter1 = book2.get_item_with_id("chap_syntax") html_tree = parse_html_string(chapter1.get_content()) body = html_tree.find("body") highlight_div = body.xpath(".//div[@class='highlight']") assert len(highlight_div) == 1 def _test_metadata(self, book): assert book.get_metadata("DC", "subject") == [("Fiction", None)] assert book.get_metadata("DC", "contributor") == [("Editor", {"role": "edt"})] assert book.get_metadata(None, "meta") == [ ("", {"name": "cover", "content": "cover-img"}), ("", {"name": "key", "content": "value"}), ("", {"name": "key2", "content": "value2"}), ] def test_epubbook_add_metadata(self): book = self._create_basic_book() book.add_metadata("DC", "subject", "Fiction") book.add_metadata("DC", "contributor", "Editor", {"role": "edt"}) book.add_metadata(None, "meta", "", {"name": "key", "content": "value"}) book.add_metadata(None, "meta", "", {"name": "key2", "content": "value2"}) self._test_metadata(book) def _test_epubbook(self, name): # TODO: # - add other roles here besides creator book = epub.read_epub(name) assert book.get_metadata("DC", "identifier") == [("sample123456", {"id": "id"})] assert book.uid == "sample123456" assert book.get_metadata("DC", "title") == [("Sample book", {})] assert book.get_metadata("DC", "language") == [("en", {})] assert book.language == "en" assert book.get_metadata("DC", "creator") == [("Aleksandar Erkalovic", {"id": "creator"})] assert len(list(book.get_items())) == 6 def test_epubbook_read_epub_as_filepath(self): if six.PY2: self._test_epubbook(os.path.join(os.path.dirname(__file__), "resources", "test01.epub")) else: self._test_epubbook(Path(__file__).parent / "resources" / "test01.epub") def test_epubbook_read_epub_as_string(self): self._test_epubbook(os.path.join(os.path.dirname(__file__), "resources", "test01.epub")) def test_epubbook_read_epub_as_bytes(self): self._test_epubbook(io.BytesIO((Path(__file__).parent / "resources" / "test01.epub").open("rb").read())) ebooklib-0.20/tests/test_epub_cover.py000066400000000000000000000003501507750473400201370ustar00rootroot00000000000000import ebooklib from ebooklib import epub class TestEpubCover: def test_init_default(self): cover = epub.EpubCover() assert cover.get_id() == "cover-img" assert cover.get_type() == ebooklib.ITEM_COVER ebooklib-0.20/tests/test_epub_html.py000066400000000000000000000052621507750473400177740ustar00rootroot00000000000000import logging import ebooklib from ebooklib import epub from ebooklib.utils import parse_html_string logger = logging.getLogger(__name__) class TestEpubHtml: def test_init(self): doc = epub.EpubHtml() assert doc.is_chapter() is True assert doc.get_type() == ebooklib.ITEM_DOCUMENT def test_language(self): doc1 = epub.EpubHtml(lang="en") assert doc1.get_language() == "en" doc2 = epub.EpubHtml() doc2.set_language("es") assert doc2.get_language() == "es" def test_links(self): doc1 = epub.EpubHtml() doc1.add_link(href="Styles/styles1.css", rel="stylesheet", type="text/css") doc1.add_link(href="Styles/styles2.css", rel="stylesheet", type="text/css") assert len(list(doc1.get_links())) == 2 doc1.add_link( rel="alternate", type="application/rss+xml", title="Feed", href="https://www.github.com/feed/", ) assert len(list(doc1.get_links())) == 3 doc1.add_link(rel="icon", href="https://www.github.com/favicon.ico", sizes="32x32") assert len(list(doc1.get_links())) == 4 doc1.add_link(href="Scripts/script.js", rel="stylesheet", type="text/javascript") assert len(list(doc1.get_links())) == 5 assert "scripted" in doc1.properties def test_items(self): doc1 = epub.EpubHtml() default_css = epub.EpubItem( uid="style_default", file_name="style/default.css", media_type="text/css", content="", ) doc1.add_item(default_css) assert len(list(doc1.get_links())) == 1 def test_content_1(self): book = epub.EpubBook() doc1 = epub.EpubHtml(file_name="test.xhtml") doc1.set_content( """This is my title

Title

Paragraph with some

""" ) book.add_item(doc1) html_tree = parse_html_string(doc1.get_content()) assert len(html_tree.find("head").getchildren()) == 0 assert len(html_tree.find("body").getchildren()) == 2 assert len(html_tree.xpath("//h1[contains(text(), 'Title')]")) == 1 doc1.title = "This is my title" html_tree = parse_html_string(doc1.get_content()) assert len(html_tree.find("head").getchildren()) == 1 assert len(html_tree.xpath("//title[contains(text(), 'This is my title')]")) == 1 doc1.set_language("de") html_tree = parse_html_string(doc1.get_content()) assert html_tree.attrib["lang"] == "de" ebooklib-0.20/tests/test_epub_item.py000066400000000000000000000055551507750473400177730ustar00rootroot00000000000000import six import ebooklib from ebooklib import epub FILENAME_TYPES = [ ("images/image_my_123.jpg", ebooklib.ITEM_IMAGE, "image/jpeg"), ("images/image%20my%20123.jpeg", ebooklib.ITEM_IMAGE, "image/jpeg"), ("images/image%20my%20123.png", ebooklib.ITEM_IMAGE, "image/png"), ("style/image.css", ebooklib.ITEM_STYLE, "text/css"), ("image/image.svg", ebooklib.ITEM_VECTOR, "image/svg+xml"), ("scripts/image.js", ebooklib.ITEM_SCRIPT, "application/javascript"), ("fonts/font.otf", ebooklib.ITEM_FONT, "application/vnd.ms-opentype"), ("fonts/font.woff", ebooklib.ITEM_FONT, "application/font-woff"), ("video/video.mov", ebooklib.ITEM_VIDEO, "video/quicktime"), ("video/video.mp4", ebooklib.ITEM_VIDEO, "video/mp4"), ("audio/audio.mp3", ebooklib.ITEM_AUDIO, "audio/mpeg"), ("audio/audio.ogg", ebooklib.ITEM_AUDIO, "audio/ogg"), ("smil/document.smil", ebooklib.ITEM_SMIL, "application/smil+xml"), ] class TestEpubItemInitialization: def test_init_default(self): item = epub.EpubItem() assert item.id is None assert item.file_name == "" assert item.media_type == "" assert item.content == six.b("") assert item.is_linear is True assert item.manifest is True assert item.book is None def test_init_arguments(self): item = epub.EpubItem( uid="my_uid", file_name="Document/index.xhtml", content=six.b("THIS IS CONTENT"), ) assert item.get_id() == "my_uid" assert item.get_name() == "Document/index.xhtml" assert item.get_content() == six.b("THIS IS CONTENT") def test_content(self): item = epub.EpubItem( content=six.b("THIS IS CONTENT"), ) assert item.get_content() == six.b("THIS IS CONTENT") item.set_content(six.b("NEW CONTENT")) assert item.get_content() == six.b("NEW CONTENT") def test_get_type(self): """Test item.get_type() method.""" for file_name, file_type, _ in FILENAME_TYPES: item = epub.EpubItem(file_name=file_name) assert item.get_type() == file_type def test_get_items_created_book(self): book = epub.EpubBook() for file_name, _, media_type in FILENAME_TYPES: item = epub.EpubItem( uid="id-{file_name}".format(file_name=file_name), # noqa: UP032 file_name=file_name, media_type=media_type, ) book.add_item(item) assert book.get_item_with_id("id-{file_name}".format(file_name=file_name)) == item # noqa: UP032 assert book.get_item_with_href(file_name) == item assert len(list(book.get_items_of_type(ebooklib.ITEM_IMAGE))) == 3 assert len(list(book.get_items_of_media_type("image/jpeg"))) == 2 assert len(list(book.get_items())) == len(FILENAME_TYPES) ebooklib-0.20/tests/test_utils.py000066400000000000000000000016751507750473400171610ustar00rootroot00000000000000from ebooklib import epub, utils class TestEpubItemInitialization: def _create_basic_book(self): book = epub.EpubBook() book.set_identifier("test123456") book.set_title("Test book") book.set_language("en") book.add_author("Test Author") doc1 = epub.EpubHtml(uid="chap_1", file_name="test.xhtml") doc2 = epub.EpubHtml(uid="chap_2", file_name="test2.xhtml") book.add_item(doc1) book.add_item(doc2) book.toc = ( doc1, doc2, ) book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) book.spine = ["nav", doc1, doc2] return book def test_pagebreak(self): pb = utils.create_pagebreak("page1", label="label", html=True) assert pb == ( 'label' )

lorum ipsum.1