Skip to content

Commit 03274af

Browse files
committed
fix: preserve legacy EPUB namespace fallback
1 parent 0ec60ff commit 03274af

2 files changed

Lines changed: 59 additions & 12 deletions

File tree

‎packages/markitdown/src/markitdown/converters/_epub_converter.py‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,18 +161,19 @@ def _resolve_manifest_href(
161161
return candidates[0]
162162

163163
def _get_elements(
164-
self, dom: Document, namespace: str, local_name: str
164+
self,
165+
dom: Document,
166+
namespace: str,
167+
local_name: str,
168+
legacy_name: str | None = None,
165169
) -> List[Element]:
166170
# Match namespace URIs, not the arbitrary prefix chosen by the author.
167171
nodes = list(dom.getElementsByTagNameNS(namespace, local_name))
168172
if nodes:
169173
return nodes
170-
# Retain support for older files that omit the container/OPF namespace.
171-
return [
172-
node
173-
for node in dom.getElementsByTagName(local_name)
174-
if node.namespaceURI is None
175-
]
174+
# Preserve the original tag-name lookup for legacy/malformed namespace
175+
# URIs, but only when there are no elements in the standard namespace.
176+
return list(dom.getElementsByTagName(legacy_name or local_name))
176177

177178
def _get_text_from_node(self, dom: Document, tag_name: str) -> str | None:
178179
"""Convenience function to extract a single occurrence of a tag (e.g., title)."""
@@ -185,7 +186,9 @@ def _get_text_from_node(self, dom: Document, tag_name: str) -> str | None:
185186
def _get_all_texts_from_nodes(self, dom: Document, tag_name: str) -> List[str]:
186187
"""Helper function to extract all occurrences of a tag (e.g., multiple authors)."""
187188
texts: List[str] = []
188-
for node in self._get_elements(dom, DC_NAMESPACE, tag_name.removeprefix("dc:")):
189+
for node in self._get_elements(
190+
dom, DC_NAMESPACE, tag_name.removeprefix("dc:"), legacy_name=tag_name
191+
):
189192
text_parts: List[str] = []
190193
self._collect_node_text(node, text_parts)
191194
text_val = "".join(text_parts).strip()

‎packages/markitdown/tests/test_epub.py‎

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,10 +219,6 @@ def test_epub_metadata_and_text_are_unchanged() -> None:
219219
assert "Chapter text." in result.markdown
220220

221221

222-
if __name__ == "__main__":
223-
raise SystemExit(pytest.main([__file__]))
224-
225-
226222
@pytest.mark.parametrize("prefixed_part", ["container", "package", "metadata"])
227223
def test_namespace_prefixes_do_not_change_epub_conversion(prefixed_part):
228224
original = _build_epub(
@@ -276,3 +272,51 @@ def test_namespace_prefixes_do_not_change_epub_conversion(prefixed_part):
276272
text = result.markdown.replace("\\", "")
277273
assert "SECOND_BODY" in text and "FIRST_BODY" in text
278274
assert text.index("SECOND_BODY") < text.index("FIRST_BODY")
275+
276+
277+
@pytest.mark.parametrize(
278+
"original_namespace,replacement_namespace",
279+
[
280+
(
281+
"http://www.idpf.org/2007/opf",
282+
"http://openebook.org/namespaces/oeb-package/1.0/",
283+
),
284+
("http://purl.org/dc/elements/1.1/", "http://purl.org/dc/elements/1.0/"),
285+
("http://purl.org/dc/elements/1.1/", "http://purl.org/dc/elements/1.1"),
286+
("urn:oasis:names:tc:opendocument:xmlns:container", "urn:legacy:container"),
287+
("urn:oasis:names:tc:opendocument:xmlns:container", ""),
288+
("http://www.idpf.org/2007/opf", ""),
289+
],
290+
)
291+
def test_legacy_namespace_uris_keep_metadata_and_chapters(
292+
original_namespace, replacement_namespace
293+
):
294+
original = _build_epub(
295+
[("c1", "one.xhtml")],
296+
["c1"],
297+
{"OEBPS/one.xhtml": ("First", "CHAPTER_BODY")},
298+
)
299+
archive = io.BytesIO()
300+
with zipfile.ZipFile(original) as source, zipfile.ZipFile(archive, "w") as target:
301+
for entry in source.infolist():
302+
content = source.read(entry)
303+
if entry.filename in ("META-INF/container.xml", "OEBPS/content.opf"):
304+
text = content.decode().replace(
305+
original_namespace, replacement_namespace
306+
)
307+
if entry.filename == "OEBPS/content.opf":
308+
text = text.replace(
309+
"</metadata>",
310+
"<dc:creator>Example Author</dc:creator></metadata>",
311+
)
312+
content = text.encode()
313+
target.writestr(entry, content)
314+
archive.seek(0)
315+
result = EpubConverter().convert(archive, StreamInfo(extension=".epub"))
316+
assert result.title == "Encoded Hrefs"
317+
assert "**Authors:** Example Author" in result.markdown
318+
assert "CHAPTER_BODY" in result.markdown.replace("\\", "")
319+
320+
321+
if __name__ == "__main__":
322+
raise SystemExit(pytest.main([__file__]))

0 commit comments

Comments
 (0)