Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion src/docx/opc/oxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from docx.opc.constants import NAMESPACE as NS
from docx.opc.constants import RELATIONSHIP_TARGET_MODE as RTM
from docx.opc.exceptions import OpcError

# configure XML parser
element_class_lookup = etree.ElementNamespaceClassLookup()
Expand All @@ -35,7 +36,12 @@

def parse_xml(text: str) -> etree._Element:
"""`etree.fromstring()` replacement that uses oxml parser."""
return etree.fromstring(text, oxml_parser)
try:
return etree.fromstring(text, oxml_parser)
except etree.XMLSyntaxError as exc:
# A malformed [Content_Types].xml or .rels part would otherwise raise a
# bare lxml XMLSyntaxError while opening the package.
raise OpcError("package contains invalid XML: %s" % exc) from exc


def qn(tag: str) -> str:
Expand Down
12 changes: 11 additions & 1 deletion src/docx/opc/part.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

from typing import TYPE_CHECKING, Callable, Type, cast

from lxml.etree import XMLSyntaxError

from docx.opc.exceptions import OpcError
from docx.opc.oxml import serialize_part_xml
from docx.opc.packuri import PackURI
from docx.opc.rel import Relationships
Expand Down Expand Up @@ -228,7 +231,14 @@ def element(self):

@classmethod
def load(cls, partname: PackURI, content_type: str, blob: bytes, package: Package):
element = parse_xml(blob)
try:
element = parse_xml(blob)
except XMLSyntaxError as exc:
# A part with malformed XML would otherwise raise a bare lxml
# XMLSyntaxError out of Document()/open; report it as a package error.
raise OpcError(
"part '%s' contains invalid XML: %s" % (partname, exc)
) from exc
return cls(partname, content_type, element, package)

@property
Expand Down
8 changes: 8 additions & 0 deletions tests/opc/test_part.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,14 @@ def it_can_be_constructed_by_PartFactory(
__init_.assert_called_once_with(ANY, partname_, content_type_, element_, package_)
assert isinstance(part, XmlPart)

def it_raises_OpcError_on_a_part_with_invalid_xml(
self, partname_, content_type_, package_
):
from docx.opc.exceptions import OpcError

with pytest.raises(OpcError):
XmlPart.load(partname_, content_type_, b"<not well formed", package_)

def it_can_serialize_to_xml(self, blob_fixture):
xml_part, element_, serialize_part_xml_ = blob_fixture
blob = xml_part.blob
Expand Down