Source

rst2epub / rst2epub / package.py

Full commit
from xml.dom.minidom import Document, Element

class Package(Document):
    """Represents OPF Package Document see [OPF20]_.

       .. [OPF20] Open Packaging Format (OPF) 2.0 v0.9871.0
          http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html


        This file is required exactly once.
    """

    extension = ".opf"
    """File extension as specified in [OPF20]_."""

    media_type = "application/oebps-package+xml"
    """Media typ as specified in [OPF20]_."""

    path = "content" + extension    # conventions used in this implementation
    """Path of the OPF Package Document within the container."""

    namespace = "http://www.idpf.org/2007/opf"
    """Namespace for OPF Package Document."""

    version = "2.0"
    """OPF Package Document file version supported by this implemntation."""

    primary_identifier = "primaryID"
    """ID to identifier primaryID."""

    def __init__(self):
        Document.__init__(self)

        # see 2.1 in [OPF20]_
        self.package = self.createElement("package")
        self.appendChild(self.package)
        self.package.setAttribute("version", self.version)
        self.package.setAttribute("xmlns", self.namespace)
        self.package.setAttribute("unique-identifier", self.primary_identifier)

        self.metadata = self.create_metadata()
        self.manifest = self.create_manifest()
        self.spine = self.create_spine()


    def create_metadata(self):
        """Creata and append a new metadata element."""
        metadata = Metadata(self)
        self.package.appendChild(metadata)
        return metadata

    def create_manifest(self):
        """Creata and append a new manifest element."""
        manifest = Manifest(self)
        self.package.appendChild(manifest)
        return manifest

    def create_spine(self):
        """Creata and append a new spine element."""
        spine = Spine(self, self.manifest.get_ncx_id())
        self.package.appendChild(spine)
        return spine

    def create_guide(self):
        """Creata and append a new guide element."""
        self.guide = Guide(self)
        self.package.appendChild(self.guide)
        return self.guide

    def to_xml(self):
        return self.toprettyxml(indent="    ", encoding="utf-8")


class Metadata(Element):
    """Represents Metadata element see 2.2 in [OPF20]_.

       .. [OPF20] Open Packaging Format (OPF) 2.0 v0.9871.0
          http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html

        This element is required. Must have at least one of each title,
        identifier, language.
    """

    dc_namespace = "http://purl.org/dc/elements/1.1/"
    """Namespace for Dublin Core elemnts."""

    def __init__(self, package_document):
        """Should be called by Package.create_metadata."""

        Element.__init__(self, "metadata")
        self.ownerDocument = package_document
        self.setAttribute("xmlns:dc", self.dc_namespace)
        self.setAttribute("xmlns:opf", Package.namespace)

    def _create_dc_metadata_element(self, name, value):
        """Creates and append a Doublin Core metadataelement."""
        element = self.ownerDocument.createElement("dc:" + name)
        element.appendChild(self.ownerDocument.createTextNode(value))
        self.appendChild(element)
        return element

    def add_title(self, title):
        """See 2.2.1 in [OPF20]_."""
        self._create_dc_metadata_element("title", title)

    def add_creator(self, author):
        """See 2.2.2 in [OPF20]_."""
        self._create_dc_metadata_element("creator", author)

    def add_subject(self):
        """See 2.2.3 in [OPF20]_."""
        raise NotImplementedError

    def add_description(self):
        """See 2.2.4 in [OPF20]_."""
        raise NotImplementedError

    def add_publisher(self):
        """See 2.2.5 in [OPF20]_."""
        raise NotImplementedError

    def add_contributor(self):
        """See 2.2.6 in [OPF20]_."""
        raise NotImplementedError

    def add_date(self):
        """See 2.2.7 in [OPF20]_."""
        raise NotImplementedError

    def add_type(self):
        """See 2.2.8 in [OPF20]_."""
        raise NotImplementedError

    def add_format(self):
        """See 2.2.9 in [OPF20]_."""
        raise NotImplementedError

    def add_identifier(self, identifier, primary=False, scheme=None):
        """See 2.2.10 in [OPF20]_."""
        element = self._create_dc_metadata_element("identifier", "identifier")

        if primary:
            element.setAttribute("id", Package.primary_identifier)

        if scheme:
            element.setAttribute("opf:scheme", scheme)

    def add_source(self):
        """See 2.2.11 in [OPF20]_."""
        raise NotImplementedError

    def add_language(self, language):
        """See 2.2.12 in [OPF20]_."""
        self._create_dc_metadata_element("language", language)

    def add_relation(self):
        """See 2.2.13 in [OPF20]_."""
        raise NotImplementedError

    def add_coverage(self):
        """See 2.2.14 in [OPF20]_."""
        raise NotImplementedError

    def add_rights(self):
        """See 2.2.15 in [OPF20]_."""
        raise NotImplementedError

    def add_meta(self, name, content):
        element = self.ownerDocument.createElement("meta")
        element.setAttribute("name", name)
        element.setAttribute("content", content)
        self.appendChild(element)


class Manifest(Element):
    """Represents Manifest element see 2.3 in [OPF20]_.

       .. [OPF20] Open Packaging Format (OPF) 2.0 v0.9871.0
          http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html

        This element is required.
    """

    def __init__(self, package_document):
        Element.__init__(self, "manifest")
        self.ownerDocument = package_document

        # add item for required ncx file
        self.add_item("ncx", "toc.ncx", "application/x-dtbncx+xml")

    def add_item(self, id, href, media_type):
        item = self.ownerDocument.createElement("item")
        self.appendChild(item)
        item.setAttribute("id", id)
        item.setAttribute("href", href)
        item.setAttribute("media-type", media_type)
        # TODO support fallback mechanism

    def get_ncx_id(self):
        ncx_media_type = "application/x-dtbncx+xml"
        for item in self.getElementsByTagName("item"):
            if item.getAttribute("media-type") == ncx_media_type:
                return item.getAttribute("id")
        raise Exception("No item with media type %s found" % ncx_media_type)

class Spine(Element):
    """Represents Spine element see 2.4 in [OPF20]_.

       .. [OPF20] Open Packaging Format (OPF) 2.0 v0.9871.0
          http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html

        This element is required after manifest.
    """

    def __init__(self, package_document, ncx_id):
        Element.__init__(self, "spine")
        self.ownerDocument = package_document
        self.setAttribute("toc", ncx_id)

    def add_itemref(self, idref, linear=True):
        itemref = self.ownerDocument.createElement("itemref")
        self.appendChild(itemref)
        itemref.setAttribute("idref", idref)

        if not linear:
            itemref.setAttribute("linear", "no")
        # default if no linear attribute present
        # else:
            # itemref.setAttribute("linear", "yes")


class Guide(Element):
    """Represents Guide element see 2.6 in [OPF20]_.

       .. [OPF20] Open Packaging Format (OPF) 2.0 v0.9871.0
          http://www.idpf.org/2007/opf/OPF_2.0_final_spec.html

        This element is optional.
    """

    def __init__(self, package_document):
        Element.__init__(self, "guide")
        self.ownerDocument = package_document

    def add_reference(self, type, title, href):
        reference = self.ownerDocument.createElement("reference")
        self.appendChild(reference)
        reference.setAttribute("type", type)
        reference.setAttribute("title", title)
        reference.setAttribute("href", href)