diff --git a/.coveragerc b/.coveragerc index bb84fc73823..34e8d96aa17 100644 --- a/.coveragerc +++ b/.coveragerc @@ -973,6 +973,7 @@ omit = homeassistant/components/squeezebox/__init__.py homeassistant/components/squeezebox/browse_media.py homeassistant/components/squeezebox/media_player.py + homeassistant/components/ssdp/util.py homeassistant/components/starline/* homeassistant/components/starlingbank/sensor.py homeassistant/components/steam_online/sensor.py diff --git a/homeassistant/components/ssdp/descriptions.py b/homeassistant/components/ssdp/descriptions.py index def302641ed..e754b10669a 100644 --- a/homeassistant/components/ssdp/descriptions.py +++ b/homeassistant/components/ssdp/descriptions.py @@ -6,10 +6,11 @@ import logging import aiohttp from defusedxml import ElementTree -from netdisco import util from homeassistant.core import HomeAssistant, callback +from .util import etree_to_dict + _LOGGER = logging.getLogger(__name__) @@ -64,7 +65,5 @@ class DescriptionManager: _LOGGER.debug("Error parsing %s: %s", xml_location, err) return None - parsed: dict[str, str] = ( - util.etree_to_dict(tree).get("root", {}).get("device", {}) - ) - return parsed + root = etree_to_dict(tree).get("root") or {} + return root.get("device") or {} diff --git a/homeassistant/components/ssdp/util.py b/homeassistant/components/ssdp/util.py new file mode 100644 index 00000000000..c28f8ce088d --- /dev/null +++ b/homeassistant/components/ssdp/util.py @@ -0,0 +1,42 @@ +"""Util functions used by SSDP.""" +from __future__ import annotations + +from collections import defaultdict +from typing import Any + +from defusedxml import ElementTree + + +# Adapted from http://stackoverflow.com/a/10077069 +# to follow the XML to JSON spec +# https://www.xml.com/pub/a/2006/05/31/converting-between-xml-and-json.html +def etree_to_dict(tree: ElementTree) -> dict[str, dict[str, Any] | None]: + """Convert an ETree object to a dict.""" + # strip namespace + tag_name = tree.tag[tree.tag.find("}") + 1 :] + + tree_dict: dict[str, dict[str, Any] | None] = { + tag_name: {} if tree.attrib else None + } + children = list(tree) + if children: + child_dict: dict[str, list] = defaultdict(list) + for child in map(etree_to_dict, children): + for k, val in child.items(): + child_dict[k].append(val) + tree_dict = { + tag_name: {k: v[0] if len(v) == 1 else v for k, v in child_dict.items()} + } + dict_meta = tree_dict[tag_name] + if tree.attrib: + assert dict_meta is not None + dict_meta.update(("@" + k, v) for k, v in tree.attrib.items()) + if tree.text: + text = tree.text.strip() + if children or tree.attrib: + if text: + assert dict_meta is not None + dict_meta["#text"] = text + else: + tree_dict[tag_name] = text + return tree_dict