1 # SPDX-License-Identifier: GPL-3.0-or-later
3 # This file is part of Nominatim. (https://nominatim.org)
5 # Copyright (C) 2024 by the Nominatim developer community.
6 # For a full list of authors see the git log.
8 Helper functions for output of results in XML format.
10 from typing import Mapping, Any, Optional, Union
12 import xml.etree.ElementTree as ET
14 from ..results import AddressLines, ReverseResult, ReverseResults, \
15 SearchResult, SearchResults
16 from . import classtypes as cl
18 #pylint: disable=too-many-branches
20 def _write_xml_address(root: ET.Element, address: AddressLines,
21 country_code: Optional[str]) -> None:
26 label = cl.get_label_tag(line.category, line.extratags,
27 line.rank_address, country_code)
28 if label not in parts:
29 parts[label] = line.local_name
30 if line.names and 'ISO3166-2' in line.names and line.admin_level:
31 parts[f"ISO3166-2-lvl{line.admin_level}"] = line.names['ISO3166-2']
33 for k,v in parts.items():
34 ET.SubElement(root, k).text = v
37 ET.SubElement(root, 'country_code').text = country_code
40 def _create_base_entry(result: Union[ReverseResult, SearchResult],
41 root: ET.Element, simple: bool) -> ET.Element:
42 place = ET.SubElement(root, 'result' if simple else 'place')
43 if result.place_id is not None:
44 place.set('place_id', str(result.place_id))
46 osm_type = cl.OSM_TYPE_NAME.get(result.osm_object[0], None)
47 if osm_type is not None:
48 place.set('osm_type', osm_type)
49 place.set('osm_id', str(result.osm_object[1]))
50 if result.names and 'ref' in result.names:
51 place.set('ref', result.names['ref'])
52 elif result.locale_name:
53 # bug reproduced from PHP
54 place.set('ref', result.locale_name)
55 place.set('lat', f"{result.centroid.lat:.7f}")
56 place.set('lon', f"{result.centroid.lon:.7f}")
58 bbox = cl.bbox_from_result(result)
59 place.set('boundingbox',
60 f"{bbox.minlat:.7f},{bbox.maxlat:.7f},{bbox.minlon:.7f},{bbox.maxlon:.7f}")
62 place.set('place_rank', str(result.rank_search))
63 place.set('address_rank', str(result.rank_address))
66 for key in ('text', 'svg'):
67 if key in result.geometry:
68 place.set('geo' + key, result.geometry[key])
69 if 'kml' in result.geometry:
70 ET.SubElement(root if simple else place, 'geokml')\
71 .append(ET.fromstring(result.geometry['kml']))
72 if 'geojson' in result.geometry:
73 place.set('geojson', result.geometry['geojson'])
76 place.text = result.display_name or ''
78 place.set('display_name', result.display_name or '')
79 place.set('class', result.category[0])
80 place.set('type', result.category[1])
81 place.set('importance', str(result.calculated_importance()))
86 def format_base_xml(results: Union[ReverseResults, SearchResults],
87 options: Mapping[str, Any],
88 simple: bool, xml_root_tag: str,
89 xml_extra_info: Mapping[str, str]) -> str:
90 """ Format the result into an XML response. With 'simple' exactly one
91 result will be output, otherwise a list.
93 root = ET.Element(xml_root_tag)
94 root.set('timestamp', dt.datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S +00:00'))
95 root.set('attribution', cl.OSM_ATTRIBUTION)
96 for k, v in xml_extra_info.items():
99 if simple and not results:
100 ET.SubElement(root, 'error').text = 'Unable to geocode'
102 for result in results:
103 place = _create_base_entry(result, root, simple)
105 if not simple and options.get('icon_base_url', None):
106 icon = cl.ICONS.get(result.category)
108 place.set('icon', icon)
110 if options.get('addressdetails', False) and result.address_rows:
111 _write_xml_address(ET.SubElement(root, 'addressparts') if simple else place,
112 result.address_rows, result.country_code)
114 if options.get('extratags', False):
115 eroot = ET.SubElement(root if simple else place, 'extratags')
117 for k, v in result.extratags.items():
118 ET.SubElement(eroot, 'tag', attrib={'key': k, 'value': v})
120 if options.get('namedetails', False):
121 eroot = ET.SubElement(root if simple else place, 'namedetails')
123 for k,v in result.names.items():
124 ET.SubElement(eroot, 'name', attrib={'desc': k}).text = v
126 return '<?xml version="1.0" encoding="UTF-8" ?>\n' + ET.tostring(root, encoding='unicode')