From df5644cde6ba9b70870ddb01caaee423e00e49ad Mon Sep 17 00:00:00 2001 From: Michael Williamson Date: Fri, 19 Jan 2018 20:12:18 +0000 Subject: Read document XML path from _rels/.rels --- mammoth/docx/__init__.py | 45 +++++++++++++++++++++++++++------ mammoth/docx/content_types_xml.py | 1 + mammoth/docx/relationships_xml.py | 3 +++ tests/docx/docx_tests.py | 53 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/mammoth/docx/__init__.py b/mammoth/docx/__init__.py index 8fb150a..c4c6877 100644 --- a/mammoth/docx/__init__.py +++ b/mammoth/docx/__init__.py @@ -2,10 +2,10 @@ import os from .. import results, lists from .document_xml import read_document_xml_element -from .content_types_xml import read_content_types_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 +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 @@ -50,7 +50,16 @@ def _read_comments(zip_file, body_readers): def _read_document(zip_file, body_readers, notes, comments): - with zip_file.open("word/document.xml") as document_fileobj: + file_relationships = _try_read_entry_or_default( + zip_file, + "_rels/.rels", + read_relationships_xml_element, + default=Relationships.EMPTY, + ) + + document_filename = _find_document_filename(zip_file, file_relationships) + + with zip_file.open(document_filename) as document_fileobj: document_xml = office_xml.read(document_fileobj) return read_document_xml_element( document_xml, @@ -60,21 +69,41 @@ def _read_document(zip_file, body_readers, notes, comments): ) +def _find_document_filename(zip_file, relationships): + targets = ["word/document.xml"] + [ + target.lstrip("/") + for target in relationships.find_targets_by_type("http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument") + ] + 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): - with zip_file.open("[Content_Types].xml") as content_types_fileobj: - content_types = read_content_types_xml_element(office_xml.read(content_types_fileobj)) + 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({})) - with zip_file.open("word/styles.xml") as styles_fileobj: - styles = read_styles_xml_element(office_xml.read(styles_fileobj)) + 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({})) + default=Relationships.EMPTY) return body_xml.reader( numbering=numbering, diff --git a/mammoth/docx/content_types_xml.py b/mammoth/docx/content_types_xml.py index cdb8f6f..1180458 100644 --- a/mammoth/docx/content_types_xml.py +++ b/mammoth/docx/content_types_xml.py @@ -52,6 +52,7 @@ class _ContentTypes(object): return None +empty_content_types = _ContentTypes({}, {}) def _get_extension(path): return path.rpartition(".")[2] diff --git a/mammoth/docx/relationships_xml.py b/mammoth/docx/relationships_xml.py index c30c428..555ba2d 100644 --- a/mammoth/docx/relationships_xml.py +++ b/mammoth/docx/relationships_xml.py @@ -18,6 +18,9 @@ class Relationships(object): return self._targets_by_type[relationship_type] +Relationships.EMPTY = Relationships([]) + + Relationship = collections.namedtuple("Relationship", ["relationship_id", "target", "type"]) diff --git a/tests/docx/docx_tests.py b/tests/docx/docx_tests.py index ea69dc2..fd53d4f 100644 --- a/tests/docx/docx_tests.py +++ b/tests/docx/docx_tests.py @@ -1,3 +1,7 @@ +import io +import textwrap +import zipfile + from nose.tools import istest, assert_equal from mammoth import docx, documents @@ -18,3 +22,52 @@ class ReadTests(object): ]) ]) assert_equal(expected_document, result.value) + + +_relationship_namespaces = { + "r": "http://schemas.openxmlformats.org/package/2006/relationships", +} + + +@istest +def when_document_xml_is_not_present_then_document_is_found_using_file_relationships(): + fileobj = _create_zip({ + "word/document2.xml": textwrap.dedent("""\ + + + + + + Hello. + + + + + """), + "_rels/.rels": textwrap.dedent("""\ + + + + + """), + }) + result = docx.read(fileobj=fileobj) + expected_document = documents.document([ + documents.paragraph([ + documents.run([ + documents.text("Hello.") + ]) + ]) + ]) + assert_equal(expected_document, result.value) + + +def _create_zip(files): + fileobj = io.BytesIO() + + with zipfile.ZipFile(fileobj, "w") as zip_file: + for name, contents in files.items(): + zip_file.writestr(name, contents) + + fileobj.seek(0) + return fileobj -- cgit v1.2.3