aboutsummaryrefslogtreecommitdiff
path: root/mammoth/docx/__init__.py
blob: 8ecf8c9ad8ff5c91b759a3a0032b329f8660a774 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import os

from .. import results, lists
from .document_xml import read_document_xml_element
from .content_types_xml import empty_content_types, read_content_types_xml_element
from .relationships_xml import read_relationships_xml_element, Relationships
from .numbering_xml import read_numbering_xml_element, Numbering
from .styles_xml import read_styles_xml_element, Styles
from .notes_xml import create_footnotes_reader, create_endnotes_reader
from .comments_xml import create_comments_reader
from .files import Files
from . import body_xml, office_xml
from ..zips import open_zip


_empty_result = results.success([])


def read(fileobj):
    zip_file = open_zip(fileobj, "r")
    body_readers = _body_readers(getattr(fileobj, "name", None), zip_file)
    
    return results.combine([
        _read_notes(zip_file, body_readers),
        _read_comments(zip_file, body_readers),
    ]).bind(lambda referents:
        _read_document(zip_file, body_readers, notes=referents[0], comments=referents[1])
    )


def _read_notes(zip_file, body_readers):
    read_footnotes_xml = create_footnotes_reader(body_readers("footnotes"))
    footnotes = _try_read_entry_or_default(
        zip_file, "word/footnotes.xml", read_footnotes_xml, default=_empty_result)
    
    read_endnotes_xml = create_endnotes_reader(body_readers("endnotes"))
    endnotes = _try_read_entry_or_default(
        zip_file, "word/endnotes.xml", read_endnotes_xml, default=_empty_result)
    
    return results.combine([footnotes, endnotes]).map(lists.flatten)


def _read_comments(zip_file, body_readers):
    return _try_read_entry_or_default(
        zip_file,
        "word/comments.xml",
        create_comments_reader(body_readers("comments")),
        default=_empty_result,
    )

    
def _read_document(zip_file, body_readers, notes, comments):
    package_relationships = _try_read_entry_or_default(
        zip_file,
        "_rels/.rels",
        read_relationships_xml_element,
        default=Relationships.EMPTY,
    )
    
    document_filename = _find_document_filename(zip_file, package_relationships)
    
    with zip_file.open(document_filename) as document_fileobj:
        document_xml = office_xml.read(document_fileobj)
        return read_document_xml_element(
            document_xml,
            body_reader=body_readers("document"),
            notes=notes,
            comments=comments,
        )


def _find_document_filename(zip_file, relationships):
    office_document_type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
    targets = [
        target.lstrip("/")
        for target in relationships.find_targets_by_type(office_document_type)
    ] + ["word/document.xml"]
    valid_targets = list(filter(lambda target: zip_file.exists(target), targets))
    if len(valid_targets) == 0:
        return None
    else:
        return valid_targets[0]


def _body_readers(document_path, zip_file):
    content_types = _try_read_entry_or_default(
        zip_file,
        "[Content_Types].xml",
        read_content_types_xml_element,
        empty_content_types,
    )

    numbering = _try_read_entry_or_default(
        zip_file, "word/numbering.xml", read_numbering_xml_element, default=Numbering({}))
    
    styles = _try_read_entry_or_default(
        zip_file,
        "word/styles.xml",
        read_styles_xml_element,
        Styles.EMPTY,
    )
    
    def for_name(name):
        relationships_path = "word/_rels/{0}.xml.rels".format(name)
        relationships = _try_read_entry_or_default(
            zip_file, relationships_path, read_relationships_xml_element,
            default=Relationships.EMPTY)
            
        return body_xml.reader(
            numbering=numbering,
            content_types=content_types,
            relationships=relationships,
            styles=styles,
            docx_file=zip_file,
            files=Files(None if document_path is None else os.path.dirname(document_path)),
        )
    
    return for_name


def _try_read_entry_or_default(zip_file, name, reader, default):
    if zip_file.exists(name):
        with zip_file.open(name) as fileobj:
            return reader(office_xml.read(fileobj))
    else:
        return default