aboutsummaryrefslogtreecommitdiff
path: root/mammoth
diff options
context:
space:
mode:
authorMichael Williamson <mike@zwobble.org>2015-07-20 18:57:46 +0100
committerMichael Williamson <mike@zwobble.org>2015-07-20 18:58:15 +0100
commit61cf58387f239633bf1b81f4e8252459152d2598 (patch)
tree99aaf0469c35a330a973618dc5f7cb176cb14e18 /mammoth
parent3d20b52a4105d48e319493adf2481cdb5985f72d (diff)
Support linked images
Diffstat (limited to 'mammoth')
-rw-r--r--mammoth/docx/__init__.py7
-rw-r--r--mammoth/docx/body_xml.py45
-rw-r--r--mammoth/docx/content_types_xml.py43
-rw-r--r--mammoth/docx/files.py25
4 files changed, 104 insertions, 16 deletions
diff --git a/mammoth/docx/__init__.py b/mammoth/docx/__init__.py
index ec8d363..1ca8cb0 100644
--- a/mammoth/docx/__init__.py
+++ b/mammoth/docx/__init__.py
@@ -1,5 +1,6 @@
import zipfile
import contextlib
+import os
from .. import results, lists
from .xmlparser import parse_xml
@@ -9,6 +10,7 @@ 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 .notes_xml import create_footnotes_reader, create_endnotes_reader
+from .files import Files
from . import body_xml
@@ -26,7 +28,7 @@ _namespaces = [
def read(fileobj):
zip_file = zipfile.ZipFile(fileobj)
- body_readers = _body_readers(zip_file)
+ body_readers = _body_readers(getattr(fileobj, "name"), zip_file)
return _read_notes(zip_file, body_readers).bind(lambda notes:
_read_document(zip_file, body_readers, notes))
@@ -55,7 +57,7 @@ def _read_document(zip_file, body_readers, notes):
)
-def _body_readers(zip_file):
+def _body_readers(document_path, zip_file):
with _open_entry(zip_file, "[Content_Types].xml") as content_types_fileobj:
content_types = read_content_types_xml_element(_parse_docx_xml(content_types_fileobj))
@@ -77,6 +79,7 @@ def _body_readers(zip_file):
relationships=relationships,
styles=styles,
docx_file=zip_file,
+ files=Files(os.path.dirname(document_path)),
)
return for_name
diff --git a/mammoth/docx/body_xml.py b/mammoth/docx/body_xml.py
index cb42f05..54f9dd3 100644
--- a/mammoth/docx/body_xml.py
+++ b/mammoth/docx/body_xml.py
@@ -10,7 +10,8 @@ def reader(numbering=None,
content_types=None,
relationships=None,
styles=None,
- docx_file=None):
+ docx_file=None,
+ files=None):
read, read_all = _create_reader(
numbering=numbering,
@@ -18,6 +19,7 @@ def reader(numbering=None,
relationships=relationships,
styles=styles,
docx_file=docx_file,
+ files=files,
)
return _BodyReader(read, read_all)
@@ -37,7 +39,7 @@ class _BodyReader(object):
return results.Result(result.value, result.messages)
-def _create_reader(numbering, content_types, relationships, styles, docx_file):
+def _create_reader(numbering, content_types, relationships, styles, docx_file, files):
_handlers = {}
_ignored_elements = set([
"w:bookmarkStart",
@@ -221,17 +223,9 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file):
return _combine_results(map(lambda blip: _read_blip(blip, alt_text), blips))
def _read_blip(element, alt_text):
- relationship_id = element.attributes["r:embed"]
- image_path = "word/" + relationships[relationship_id].target.lstrip("/")
+ image_path, open_image = _read_blip_image(element)
content_type = content_types.find_content_type(image_path)
- def open_image():
- image_file = docx_file.open(image_path)
- if hasattr(image_file, "__exit__"):
- return image_file
- else:
- return contextlib.closing(image_file)
-
image = documents.image(alt_text=alt_text, content_type=content_type, open=open_image)
if content_type in ["image/png", "image/gif", "image/jpeg", "image/svg+xml", "image/tiff"]:
@@ -241,6 +235,35 @@ def _create_reader(numbering, content_types, relationships, styles, docx_file):
return _element_result_with_messages(image, messages)
+ def _read_blip_image(element):
+ embed_relationship_id = element.attributes.get("r:embed")
+ link_relationship_id = element.attributes.get("r:link")
+ if embed_relationship_id is not None:
+ return _read_embedded_blip_image(embed_relationship_id)
+ elif link_relationship_id is not None:
+ return _read_linked_blip_image(link_relationship_id)
+
+ def _read_embedded_blip_image(relationship_id):
+ image_path = "word/" + relationships[relationship_id].target.lstrip("/")
+
+ def open_image():
+ image_file = docx_file.open(image_path)
+ if hasattr(image_file, "__exit__"):
+ return image_file
+ else:
+ return contextlib.closing(image_file)
+
+ return image_path, open_image
+
+
+ def _read_linked_blip_image(relationship_id):
+ image_path = relationships[relationship_id].target
+
+ def open_image():
+ return files.open(image_path)
+
+ return image_path, open_image
+
def note_reference_reader(note_type):
@handler("w:{0}Reference".format(note_type))
def note_reference(element):
diff --git a/mammoth/docx/content_types_xml.py b/mammoth/docx/content_types_xml.py
index a2eb14c..f0406b7 100644
--- a/mammoth/docx/content_types_xml.py
+++ b/mammoth/docx/content_types_xml.py
@@ -7,7 +7,10 @@ def read_content_types_xml_element(element):
_read_override,
element.find_children("content-types:Override")
))
- return ContentTypes(extension_defaults, overrides)
+ return _ContentTypes([
+ _XmlContentTypes(extension_defaults, overrides),
+ _FallbackContentTypes(),
+ ])
def _read_default(element):
@@ -22,7 +25,37 @@ def _read_override(element):
return part_name.lstrip("/"), content_type
-class ContentTypes(object):
+class _ContentTypes(object):
+ def __init__(self, finders):
+ self._finders = finders
+
+ def find_content_type(self, path):
+ for finder in self._finders:
+ content_type = finder.find_content_type(path)
+ if content_type is not None:
+ return content_type
+
+
+class _FallbackContentTypes(object):
+ _image_content_types = {
+ "png": "png",
+ "gif": "gif",
+ "jpeg": "jpeg",
+ "jpg": "jpeg",
+ "tif": "tiff",
+ "tiff": "tiff",
+ "bmp": "bmp",
+ }
+
+ def find_content_type(self, path):
+ extension = _get_extension(path).lower()
+ if extension in self._image_content_types:
+ return "image/" + self._image_content_types[extension]
+ else:
+ return None
+
+
+class _XmlContentTypes(object):
def __init__(self, extension_defaults, overrides):
self._extension_defaults = extension_defaults
self._overrides = overrides
@@ -31,5 +64,9 @@ class ContentTypes(object):
if path in self._overrides:
return self._overrides[path]
else:
- extension = path.rpartition(".")[2]
+ extension = _get_extension(path)
return self._extension_defaults.get(extension)
+
+
+def _get_extension(path):
+ return path.rpartition(".")[2]
diff --git a/mammoth/docx/files.py b/mammoth/docx/files.py
new file mode 100644
index 0000000..d919de9
--- /dev/null
+++ b/mammoth/docx/files.py
@@ -0,0 +1,25 @@
+import os
+import contextlib
+try:
+ from urllib2 import urlopen
+except ImportError:
+ from urllib.request import urlopen
+try:
+ from urllib.parse import urlparse
+except ImportError:
+ from urlparse import urlparse
+
+
+class Files(object):
+ def __init__(self, base):
+ self._base = base
+
+ def open(self, uri):
+ if _is_absolute(uri):
+ return contextlib.closing(urlopen(uri))
+ else:
+ return open(os.path.join(self._base, uri), "rb")
+
+
+def _is_absolute(url):
+ return urlparse(url).scheme != ""