Skip to content

Commit 0b3b407

Browse files
fix(parser): handle angle-bracket image paths in Markdown (#4533)
1 parent 30ef75c commit 0b3b407

2 files changed

Lines changed: 32 additions & 3 deletions

File tree

openviking/parse/parsers/markdown.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -949,7 +949,7 @@ def _resolve_image_path(
949949
allowed root, otherwise None
950950
"""
951951
try:
952-
path = Path(path_str)
952+
path = Path(self._unwrap_link_destination(path_str))
953953

954954
# Reject absolute paths: they can point anywhere on the host
955955
if path.is_absolute():
@@ -1024,7 +1024,15 @@ def _is_valid_image(self, image_bytes: bytes, source_path: Path) -> bool:
10241024
return is_valid_image(image_bytes, source_path)
10251025

10261026
@staticmethod
1027-
def _is_remote_uri(path: str) -> bool:
1027+
def _unwrap_link_destination(path: str) -> str:
1028+
"""Return the path represented by a Markdown ``<destination>``."""
1029+
path = path.strip()
1030+
if len(path) >= 2 and path.startswith("<") and path.endswith(">"):
1031+
return path[1:-1]
1032+
return path
1033+
1034+
@classmethod
1035+
def _is_remote_uri(cls, path: str) -> bool:
10281036
"""
10291037
Check if a path is a remote URI.
10301038
@@ -1035,7 +1043,7 @@ def _is_remote_uri(path: str) -> bool:
10351043
True if path starts with http://, https://, viking://, data:, or ftp://
10361044
"""
10371045
remote_prefixes = ("http://", "https://", "viking://", "data:", "ftp://")
1038-
return path.startswith(remote_prefixes)
1046+
return cls._unwrap_link_destination(path).startswith(remote_prefixes)
10391047

10401048
@staticmethod
10411049
def _deduplicate_filename(filename: str, used_names: set[str]) -> str:

tests/parse/test_markdown_link_rewrite.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,27 @@ async def test_ingestable_image_left_untouched_and_copied(self, tmp_path: Path):
553553
assert any(u.endswith("/photo.png") for u in fake.files), fake.files
554554
assert any(u.endswith(".image_mappings.json") for u in fake.files), fake.files
555555

556+
async def test_angle_bracket_image_destination_with_spaces_is_ingested(self, tmp_path: Path):
557+
kb = tmp_path / "kb"
558+
image_dir = kb / "resource name" / "images"
559+
image_dir.mkdir(parents=True)
560+
_write_valid_png(image_dir / "photo.png")
561+
src = kb / "page.md"
562+
image_ref = "<resource name/images/photo.png>"
563+
src.write_text(f"![p]({image_ref})", encoding="utf-8")
564+
565+
fake = FakeVikingFS()
566+
with patch.object(BaseParser, "_get_viking_fs", return_value=fake):
567+
await MarkdownParser().parse(
568+
str(src), enable_link_rewrite=True, link_rewrite_root=str(kb)
569+
)
570+
571+
md = [_decode(c) for u, c in fake.files.items() if u.endswith(".md")]
572+
assert md and f"![p]({image_ref})" in md[0], fake.files
573+
assert any(u.endswith("/photo.png") for u in fake.files), fake.files
574+
mapping = [_decode(c) for u, c in fake.files.items() if u.endswith(".image_mappings.json")]
575+
assert mapping and image_ref in mapping[0], fake.files
576+
556577
async def test_image_outside_base_dir_depth_adjusted(self, tmp_path: Path):
557578
# The md lives in kb/sub; the image lives in kb/img — outside base_dir
558579
# (= the md's own directory) but inside the import root. #2429 cannot

0 commit comments

Comments
 (0)