osmapi.dom

DOM parsing for the OpenStreetMap API.

  1"""
  2DOM parsing for the OpenStreetMap API.
  3"""
  4
  5from datetime import datetime
  6import xml.dom.minidom
  7import xml.parsers.expat
  8import logging
  9from typing import Any, Union, Optional
 10from xml.dom.minidom import Element
 11
 12from . import errors
 13from . import xmlbuilder
 14
 15logger = logging.getLogger(__name__)
 16
 17
 18def OsmResponseToDom(
 19    response: bytes, tag: str, single: bool = False, allow_empty: bool = False
 20) -> Union[Element, list[Element]]:
 21    """
 22    Returns the (sub-) DOM parsed from an OSM response
 23    """
 24    try:
 25        dom = xml.dom.minidom.parseString(response)
 26        osm_dom = dom.getElementsByTagName("osm")[0]
 27        all_data = osm_dom.getElementsByTagName(tag)
 28        first_element = all_data[0]
 29    except IndexError as e:
 30        if allow_empty:
 31            return []
 32        raise errors.XmlResponseInvalidError(
 33            f"The XML response from the OSM API is invalid: {e!r}"
 34        )
 35    except xml.parsers.expat.ExpatError as e:
 36        raise errors.XmlResponseInvalidError(
 37            f"The XML response from the OSM API is invalid: {e!r}"
 38        )
 39
 40    if single:
 41        return first_element
 42    return list(all_data)
 43
 44
 45def dom_parse_node(dom_element: Element) -> dict[str, Any]:
 46    """
 47    Returns NodeData for the node.
 48    """
 49    result = _dom_get_attributes(dom_element)
 50    result["tag"] = _dom_get_tag(dom_element)
 51    return result
 52
 53
 54def dom_parse_way(dom_element: Element) -> dict[str, Any]:
 55    """
 56    Returns WayData for the way.
 57    """
 58    result = _dom_get_attributes(dom_element)
 59    result["tag"] = _dom_get_tag(dom_element)
 60    result["nd"] = _dom_get_nd(dom_element)
 61    return result
 62
 63
 64def dom_parse_relation(dom_element: Element) -> dict[str, Any]:
 65    """
 66    Returns RelationData for the relation.
 67    """
 68    result = _dom_get_attributes(dom_element)
 69    result["tag"] = _dom_get_tag(dom_element)
 70    result["member"] = _dom_get_member(dom_element)
 71    return result
 72
 73
 74def dom_parse_changeset(
 75    dom_element: Element, include_discussion: bool = False
 76) -> dict[str, Any]:
 77    """
 78    Returns ChangesetData for the changeset.
 79    """
 80    result = _dom_get_attributes(dom_element)
 81    result["tag"] = _dom_get_tag(dom_element)
 82    if include_discussion:
 83        result["discussion"] = _dom_get_discussion(dom_element)
 84
 85    return result
 86
 87
 88def dom_parse_note(dom_element: Element) -> dict[str, Any]:
 89    """
 90    Returns NoteData for the note.
 91    """
 92    result = _dom_get_attributes(dom_element)
 93    result["id"] = xmlbuilder._get_xml_value(dom_element, "id")
 94    result["status"] = xmlbuilder._get_xml_value(dom_element, "status")
 95
 96    result["date_created"] = _parse_date(
 97        xmlbuilder._get_xml_value(dom_element, "date_created")
 98    )
 99    result["date_closed"] = _parse_date(
100        xmlbuilder._get_xml_value(dom_element, "date_closed")
101    )
102    result["comments"] = _dom_get_comments(dom_element)
103
104    return result
105
106
107def _dom_get_attributes(dom_element: Element) -> dict[str, Any]:
108    """
109    Returns a formated dictionnary of attributes of a dom_element.
110    """
111
112    def is_true(v: str) -> bool:
113        return v == "true"
114
115    attribute_mapping: dict[str, Any] = {
116        "uid": int,
117        "changeset": int,
118        "version": int,
119        "id": int,
120        "lat": float,
121        "lon": float,
122        "open": is_true,
123        "visible": is_true,
124        "ref": int,
125        "comments_count": int,
126        "timestamp": _parse_date,
127        "created_at": _parse_date,
128        "closed_at": _parse_date,
129        "date": _parse_date,
130    }
131    result: dict[str, Any] = {}
132    for k, v in dom_element.attributes.items():
133        try:
134            result[k] = attribute_mapping[k](v)
135        except KeyError:
136            result[k] = v
137    return result
138
139
140def _dom_get_tag(dom_element: Element) -> dict[str, str]:
141    """
142    Returns the dictionnary of tags of a dom_element.
143    """
144    result: dict[str, str] = {}
145    for t in dom_element.getElementsByTagName("tag"):
146        k = t.attributes["k"].value
147        v = t.attributes["v"].value
148        result[k] = v
149    return result
150
151
152def _dom_get_nd(dom_element: Element) -> list[int]:
153    """
154    Returns the list of nodes of a dom_element.
155    """
156    result: list[int] = []
157    for t in dom_element.getElementsByTagName("nd"):
158        result.append(int(int(t.attributes["ref"].value)))
159    return result
160
161
162def _dom_get_discussion(dom_element: Element) -> list[dict[str, Any]]:
163    """
164    Returns the dictionnary of comments of a dom_element.
165    """
166    result: list[dict[str, Any]] = []
167    try:
168        discussion = dom_element.getElementsByTagName("discussion")[0]
169        for t in discussion.getElementsByTagName("comment"):
170            comment = _dom_get_attributes(t)
171            comment["text"] = xmlbuilder._get_xml_value(t, "text")
172            result.append(comment)
173    except IndexError:
174        pass
175    return result
176
177
178def _dom_get_comments(dom_element: Element) -> list[dict[str, Any]]:
179    """
180    Returns the list of comments of a dom_element.
181    """
182    result: list[dict[str, Any]] = []
183    for t in dom_element.getElementsByTagName("comment"):
184        comment: dict[str, Any] = {}
185        comment["date"] = _parse_date(xmlbuilder._get_xml_value(t, "date"))
186        comment["action"] = xmlbuilder._get_xml_value(t, "action")
187        comment["text"] = xmlbuilder._get_xml_value(t, "text")
188        comment["html"] = xmlbuilder._get_xml_value(t, "html")
189        comment["uid"] = xmlbuilder._get_xml_value(t, "uid")
190        comment["user"] = xmlbuilder._get_xml_value(t, "user")
191        result.append(comment)
192    return result
193
194
195def _dom_get_member(dom_element: Element) -> list[dict[str, Any]]:
196    """
197    Returns a list of relation members.
198    """
199    result: list[dict[str, Any]] = []
200    for m in dom_element.getElementsByTagName("member"):
201        result.append(_dom_get_attributes(m))
202    return result
203
204
205def _parse_date(date_string: Optional[str]) -> Union[datetime, str, None]:
206    date_formats = ["%Y-%m-%d %H:%M:%S UTC", "%Y-%m-%dT%H:%M:%SZ"]
207    for date_format in date_formats:
208        try:
209            result = datetime.strptime(date_string, date_format)  # type: ignore[arg-type]  # noqa: E501
210            return result
211        except (ValueError, TypeError):
212            logger.debug(f"{date_string} does not match {date_format}")
213
214    return date_string
logger = <Logger osmapi.dom (WARNING)>
def OsmResponseToDom( response: bytes, tag: str, single: bool = False, allow_empty: bool = False) -> Union[xml.dom.minidom.Element, list[xml.dom.minidom.Element]]:
19def OsmResponseToDom(
20    response: bytes, tag: str, single: bool = False, allow_empty: bool = False
21) -> Union[Element, list[Element]]:
22    """
23    Returns the (sub-) DOM parsed from an OSM response
24    """
25    try:
26        dom = xml.dom.minidom.parseString(response)
27        osm_dom = dom.getElementsByTagName("osm")[0]
28        all_data = osm_dom.getElementsByTagName(tag)
29        first_element = all_data[0]
30    except IndexError as e:
31        if allow_empty:
32            return []
33        raise errors.XmlResponseInvalidError(
34            f"The XML response from the OSM API is invalid: {e!r}"
35        )
36    except xml.parsers.expat.ExpatError as e:
37        raise errors.XmlResponseInvalidError(
38            f"The XML response from the OSM API is invalid: {e!r}"
39        )
40
41    if single:
42        return first_element
43    return list(all_data)

Returns the (sub-) DOM parsed from an OSM response

def dom_parse_node(dom_element: xml.dom.minidom.Element) -> dict[str, typing.Any]:
46def dom_parse_node(dom_element: Element) -> dict[str, Any]:
47    """
48    Returns NodeData for the node.
49    """
50    result = _dom_get_attributes(dom_element)
51    result["tag"] = _dom_get_tag(dom_element)
52    return result

Returns NodeData for the node.

def dom_parse_way(dom_element: xml.dom.minidom.Element) -> dict[str, typing.Any]:
55def dom_parse_way(dom_element: Element) -> dict[str, Any]:
56    """
57    Returns WayData for the way.
58    """
59    result = _dom_get_attributes(dom_element)
60    result["tag"] = _dom_get_tag(dom_element)
61    result["nd"] = _dom_get_nd(dom_element)
62    return result

Returns WayData for the way.

def dom_parse_relation(dom_element: xml.dom.minidom.Element) -> dict[str, typing.Any]:
65def dom_parse_relation(dom_element: Element) -> dict[str, Any]:
66    """
67    Returns RelationData for the relation.
68    """
69    result = _dom_get_attributes(dom_element)
70    result["tag"] = _dom_get_tag(dom_element)
71    result["member"] = _dom_get_member(dom_element)
72    return result

Returns RelationData for the relation.

def dom_parse_changeset( dom_element: xml.dom.minidom.Element, include_discussion: bool = False) -> dict[str, typing.Any]:
75def dom_parse_changeset(
76    dom_element: Element, include_discussion: bool = False
77) -> dict[str, Any]:
78    """
79    Returns ChangesetData for the changeset.
80    """
81    result = _dom_get_attributes(dom_element)
82    result["tag"] = _dom_get_tag(dom_element)
83    if include_discussion:
84        result["discussion"] = _dom_get_discussion(dom_element)
85
86    return result

Returns ChangesetData for the changeset.

def dom_parse_note(dom_element: xml.dom.minidom.Element) -> dict[str, typing.Any]:
 89def dom_parse_note(dom_element: Element) -> dict[str, Any]:
 90    """
 91    Returns NoteData for the note.
 92    """
 93    result = _dom_get_attributes(dom_element)
 94    result["id"] = xmlbuilder._get_xml_value(dom_element, "id")
 95    result["status"] = xmlbuilder._get_xml_value(dom_element, "status")
 96
 97    result["date_created"] = _parse_date(
 98        xmlbuilder._get_xml_value(dom_element, "date_created")
 99    )
100    result["date_closed"] = _parse_date(
101        xmlbuilder._get_xml_value(dom_element, "date_closed")
102    )
103    result["comments"] = _dom_get_comments(dom_element)
104
105    return result

Returns NoteData for the note.