diff --git a/changes/52.bugfix.rst b/changes/52.bugfix.rst new file mode 100644 index 0000000..8164c67 --- /dev/null +++ b/changes/52.bugfix.rst @@ -0,0 +1 @@ +Fix unbound prefix recovery when ``xmlns:dlna`` is absent. Devices that emit unbound prefixes (e.g. ````) without declaring ``xmlns:dlna`` are now handled correctly when ``strict=False``. diff --git a/didl_lite/didl_lite.py b/didl_lite/didl_lite.py index 116938b..f48c6b8 100644 --- a/didl_lite/didl_lite.py +++ b/didl_lite/didl_lite.py @@ -1063,12 +1063,22 @@ def from_xml_string(xml_string: str, strict: bool = True) -> List[Union[DidlObje # Identify prefixes used but not defined. missing_prefixes = used_prefixes - defined_prefixes - {"DIDL-Lite", "dc", "upnp", "dlna"} - # Remove the "if missing_prefixes:" line and just keep the for loop - for prefix in missing_prefixes: - dlna_ns = 'xmlns:dlna="urn:schemas-dlna-org:metadata-1-0/"' - if dlna_ns in xml_string: - replacement = f'{dlna_ns} xmlns:{prefix}="http://tempuri.org/{prefix}/"' - xml_string = xml_string.replace(dlna_ns, replacement) + if missing_prefixes: + # Inject temporary namespace declarations into the DIDL-Lite root + # opening tag. Anchoring on `` without `xmlns:dlna`). + injections = " ".join( + f'xmlns:{prefix}="http://tempuri.org/{prefix}/"' for prefix in sorted(missing_prefixes) + ) + xml_string = re.sub( + r" None: assert objs[0].sub_title == "Test Subtitle" assert isinstance(objs[0], didl_lite.MusicTrack) + def test_from_xml_string_unbound_prefix_without_dlna_namespace(self) -> None: + """Test unbound prefix recovery when xmlns:dlna is absent. + + Regression: the previous implementation anchored the namespace + injection on an existing `xmlns:dlna` declaration. Devices such as + JBL Authentics and WiiM/LinkPlay players emit `` tags + without declaring `xmlns:dlna`, leaving the unbound prefix in place + and breaking parsing even with strict=False. + """ + broken_xml = ( + '' + '' + "Test Title" + "Test Subtitle" + "object.item.audioItem.musicTrack" + "" + "" + ) + + objs = didl_lite.from_xml_string(broken_xml, strict=False) + + assert len(objs) == 1 + assert objs[0].title == "Test Title" + assert "sub_title" in objs[0].__dict__ + assert objs[0].sub_title == "Test Subtitle" + assert isinstance(objs[0], didl_lite.MusicTrack) + def test_music_track_artist_and_genre(self) -> None: """Test MusicTrack artist and genre properties.""" track = didl_lite.MusicTrack(