From 1f8c4091c226ccb24f5a0e0995ffff41b30d3f7f Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 18:24:37 -0400 Subject: [PATCH 01/13] sync with opencage. update address formats --- .../load_address_formats_from_opencage.py | 429 ++++++++++++ pyproject.toml | 4 +- rigour/data/addresses/formats.yml | 654 ++++++++++++------ 3 files changed, 870 insertions(+), 217 deletions(-) create mode 100644 genscripts/load_address_formats_from_opencage.py diff --git a/genscripts/load_address_formats_from_opencage.py b/genscripts/load_address_formats_from_opencage.py new file mode 100644 index 00000000..6e0a6005 --- /dev/null +++ b/genscripts/load_address_formats_from_opencage.py @@ -0,0 +1,429 @@ +from enum import Enum +import requests +from mystace import create_mustache_tree +from mystace.mustache_tree import MustacheTreeNode +from ruamel.yaml import YAML +from ruamel.yaml.comments import CommentedMap +from ruamel.yaml.scalarstring import LiteralScalarString +from genscripts.util import CODE_PATH +import subprocess + + +# these don't exist in the rigour yaml -- by design? +DROP_VARS = {"hamlet", "place"} + +SPACE_LITERAL = " " +PRIMARY_OR_MUSTACHE_LITERAL = "||" +OR_MUSTACHE_LITERALS = {" || ", "|| ", "||", " ||"} +OR_JINJA_LITERAL = " or " +LITERAL_TOKEN = "literal" +OR_TOKEN = "or" +SECTION_TOKEN = "section" +END_TOKEN = "end" +NO_ESCAPE_VAR_TOKEN = "no escape" + + +class M2JType(Enum): + ROOT = -1 + LITERAL = 0 + SECTION = 2 + INVERTED_SECTION = 3 + PARTIAL = 5 + VARIABLE = 6 + VARIABLE_RAW = 7 + OR = 8 + CONCAT = 9 + + +class M2JNode: + tag_type: M2JType + data: str + children: list['M2JNode'] + + def __init__(self, tag_type: M2JType, data: str = "", children: list['M2JNode'] | None = None): + self.tag_type = tag_type + self.data = data + self.children = children if children is not None else [] + + def __repr__(self) -> str: + if self.data: + return f"<{self.__class__.__name__}: {self.tag_type}, {self.data!r}>" + return f"<{self.__class__.__name__}: {self.tag_type}>" + + +def _convert_tree(t: MustacheTreeNode) -> M2JNode: + j = M2JNode(tag_type=M2JType.ROOT, data=t.data) + + frontier = [(n, j) for n in t.children or []] + while frontier: + n, curr = frontier.pop(0) + curr.children.append(M2JNode(tag_type=M2JType(n.tag_type.value), data=n.data)) + if n.children: + for c in n.children: + frontier.append((c, curr.children[-1])) + + return j + + +def _split_space(t: M2JNode): + """ + Find any literal nodes that contain spaces. + Trim the spaces from the beginning and end of the literal when the literal is up against a boundary. + """ + + def _can_split(n: M2JNode): + return n.tag_type == M2JType.LITERAL and n.data != '\n' + + def _is_boundary(n: M2JNode): + return n.tag_type in {M2JType.OR} or n.tag_type == M2JType.LITERAL and n.data == '\n' + + if not t.children: + return + + i = 0 + while i < len(t.children): + n = t.children[i] + + if n.children: + _split_space(n) + + if not _can_split(n) or SPACE_LITERAL not in n.data: + i += 1 + continue + + curr = n.data + assert SPACE_LITERAL in curr, f"Expected {SPACE_LITERAL} in {curr}" + + if i == 0 or _is_boundary(t.children[i - 1]): + curr = curr.lstrip(SPACE_LITERAL) + + if i == len(t.children) - 1 or _is_boundary(t.children[i + 1]): + curr = curr.rstrip(SPACE_LITERAL) + + if curr: + n.data = curr + i += 1 + else: + t.children.pop(i) + + +def _split_or(t: M2JNode): + """ + Split any literal nodes that contain the OR mustache literal into separate nodes. + """ + + def _is_boundary(n: M2JNode): + """ + We stop backtracking or looking ahead when we hit a boundary. + """ + return n.tag_type != M2JType.LITERAL or n.data == '\n' + + if not t.children: + return + + i = 0 + while i < len(t.children): + n = t.children[i] + + if n.children: + _split_or(n) + + if _is_boundary(n) or PRIMARY_OR_MUSTACHE_LITERAL not in n.data: + i += 1 + continue + + assert n.data.count(PRIMARY_OR_MUSTACHE_LITERAL) == 1, f"Expected 1 {PRIMARY_OR_MUSTACHE_LITERAL} in {n.data}" + + t.children.pop(i) + + idx = n.data.index(PRIMARY_OR_MUSTACHE_LITERAL) + left = n.data[:idx].rstrip(SPACE_LITERAL) + right = n.data[idx + len(PRIMARY_OR_MUSTACHE_LITERAL) :].lstrip(SPACE_LITERAL) + + if left: + t.children.insert(i, M2JNode(tag_type=M2JType.LITERAL, data=left)) + i += 1 + + t.children.insert(i, M2JNode(tag_type=M2JType.OR, data=PRIMARY_OR_MUSTACHE_LITERAL)) + i += 1 + + if right: + t.children.insert(i, M2JNode(tag_type=M2JType.LITERAL, data=right)) + i += 1 + + +def _drop_vars_and_simplify(t: M2JNode): + def _is_boundary(n: M2JNode): + return n.tag_type in {M2JType.OR} or n.tag_type == M2JType.LITERAL and n.data == '\n' + + def _is_simple_section(n: M2JNode): + return n.tag_type in {M2JType.SECTION, M2JType.INVERTED_SECTION} and all( + c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW, M2JType.LITERAL} for c in n.children + ) + + if not t.children: + return + + i = 0 + while i < len(t.children): + c = t.children[i] + _drop_vars_and_simplify(c) + + is_against_boundary = ( + (i > 0 and _is_boundary(t.children[i - 1])) + or (i + 1 < len(t.children) and _is_boundary(t.children[i + 1])) + or i == 0 + or i == len(t.children) - 1 + ) + + # remove empty sections + if c.tag_type == M2JType.SECTION and not c.children: + t.children.pop(i) + continue + + is_whitespace = c.tag_type == M2JType.LITERAL and c.data.strip(SPACE_LITERAL) == "" + if is_whitespace and is_against_boundary: + # remove empty literals + t.children.pop(i) + continue + + if c.tag_type == M2JType.OR and is_against_boundary: + # remove dangling OR + t.children.pop(i) + continue + + if c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW} and c.data in DROP_VARS: + # remove dropped variables + t.children.pop(i) + if i > 0: + i -= 1 + continue + + # inline sections that contain only variables and literals + if _is_simple_section(c): + t.children.pop(i) + + j = i + for section_child in c.children: + t.children.insert(j, section_child) + j += 1 + + # merge literals at the beginning of the section + if ( + i > 0 + and t.children[i - 1].tag_type == M2JType.LITERAL + and t.children[i].tag_type == M2JType.LITERAL + and not _is_boundary(t.children[i - 1]) + ): + t.children[i - 1].data += t.children[i].data + t.children.pop(i) + i -= 1 + + # merge literals at the end of the section + if ( + j > 0 + and t.children[j - 1].tag_type == M2JType.LITERAL + and t.children[j].tag_type == M2JType.LITERAL + and not _is_boundary(t.children[j]) + ): + t.children[j - 1].data += t.children[j].data + t.children.pop(j) + + i += 1 + + +def _split_string_concat(t: M2JNode): + if not t.children: + return + + for c in t.children: + _split_string_concat(c) + + # outside of sections, we don't need to handle concats around ORs + if t.tag_type not in {M2JType.SECTION, M2JType.INVERTED_SECTION}: + return + + args: list[list[M2JNode]] = [[]] + for c in t.children: + if c.tag_type == M2JType.OR: + args.append([]) + else: + args[-1].append(c) + + # if we have only one argument, we can just return + if len(args) == 1: + return + + t.children = [] + for i, arg in enumerate(args): + if len(arg) == 1: + t.children.append(arg[0]) + else: + new_node = M2JNode(tag_type=M2JType.CONCAT, children=arg) + t.children.append(new_node) + if i < len(args) - 1: + t.children.append(M2JNode(tag_type=M2JType.OR)) + + return t + + +def jinja_tree_to_template(t: M2JNode, depth: int = 0) -> str: + if t.tag_type == M2JType.ROOT: + template = "" + for c in t.children: + template += jinja_tree_to_template(c, depth + 1) + return template + elif t.tag_type in {M2JType.SECTION, M2JType.INVERTED_SECTION}: + assert depth < 2, f'Too many nested sections: {depth}' + template = "{{" + for c in t.children: + template += jinja_tree_to_template(c, depth + 1) + template += "}}" + return template + elif t.tag_type == M2JType.CONCAT: + formatted_val = ' ~ '.join([jinja_tree_to_template(c, depth + 1) for c in t.children]) + vars = [c for c in t.children if c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW}] + return f'format_if({formatted_val}, {", ".join([c.data for c in vars])})' + elif t.tag_type == M2JType.LITERAL: + if depth < 2: + return t.data + else: + return f"'{t.data}'" + elif t.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW}: + if depth < 2: + return '{{' + t.data + '}}' + else: + return t.data + elif t.tag_type == M2JType.OR: + return OR_JINJA_LITERAL + else: + raise ValueError(f"Got unexpected tag type {t.tag_type}") + + +def mustache_to_jinja(template: str, dbg: bool = False) -> str: + jinja_template = "" + + tree = _convert_tree(create_mustache_tree(template)) + _split_or(tree) + _split_space(tree) + _drop_vars_and_simplify(tree) + _split_string_concat(tree) + + jinja_template = jinja_tree_to_template(tree) + return collapse_newlines(jinja_template) + + +def collapse_newlines(text: str) -> str: + prev = "" + while prev != text: + prev = text + text = text.replace("\n\n", "\n").replace("\n ", "\n").strip() + return text + + +def update_mustache_field(yaml_obj: dict, field: str, dbg: bool = False) -> LiteralScalarString: + current_anchor = yaml_obj[field].yaml_anchor() + txt = mustache_to_jinja(str(yaml_obj[field]), dbg=dbg) + if not txt.endswith("\n"): + txt += "\n" + yaml_obj[field] = LiteralScalarString(txt) + if current_anchor is not None: + yaml_obj[field].yaml_set_anchor(current_anchor.value, always_dump=current_anchor.always_dump) + return yaml_obj[field] + + +def normalize_add_component(country: CommentedMap) -> str | None: + trailing_comment: str | None = None + + # grab and detach the trailing comment (if any) + if len(country.ca.items) > 0: + last_key = list(country.keys())[-1] + last_comment = country.ca.items[last_key][2] + if last_comment is not None: + trailing_comment = country.ca.items[last_key][2].value + del country.ca.items[last_key] + + if trailing_comment: + if trailing_comment.startswith("\n\n"): + trailing_comment = f"\n{trailing_comment[2:]}" + trailing_comment = "\n".join([c.lstrip("# ") for c in trailing_comment.split("\n")]) + trailing_comment.rstrip("\n") + + # build the nested mapping + if "add_component" in country: + add_component_val = country["add_component"] + assert isinstance( + add_component_val, str + ), f"Invalid add_component type: {type(add_component_val)} for {country}" + fields = add_component_val.split("=") + + assert len(fields) == 2, f"Invalid add_component format: {add_component_val} for {country}" + k, v = fields + ac_map = CommentedMap({k: v}) + else: + ac_map = CommentedMap() + + if "change_country" in country: + ac_map["country"] = country.pop("change_country") + + country["add_component"] = ac_map + + return trailing_comment + + +OPENCAGE_FORMAT_YAML_TEMPLATE = 'https://raw.githubusercontent.com/OpenCageData/address-formatting/{hash}/conf/countries/worldwide.yaml' +OPENCAGE_REF = 'refs/heads/master' +FORMATS_DEST_PATH = CODE_PATH / "addresses" / "formats.yml" + + +def load_address_formats_from_opencage() -> None: + commit_hash = subprocess.check_output( + ["git", "ls-remote", "--heads", "https://github.com/OpenCageData/address-formatting.git", OPENCAGE_REF] + ).decode("utf-8").split()[0] + + github_raw_url = OPENCAGE_FORMAT_YAML_TEMPLATE.format(hash=commit_hash) + opencage_yaml_text = requests.get(github_raw_url).text + + yaml = YAML(typ="rt") + yaml.preserve_quotes = True + yaml.indent(mapping=2, sequence=4, offset=2) + + yaml_obj = yaml.load(opencage_yaml_text) + + orig_fields_by_value = {} + new_fields = {} + attach_trailing = None + + for k, v in yaml_obj.items(): + if attach_trailing: + yaml_obj.yaml_set_comment_before_after_key(k, before=attach_trailing, after=None) + attach_trailing = None + + if k.startswith("generic") or k.startswith("fallback"): + orig_fields_by_value[v] = k + # new_fields[k] = update_mustache_field(yaml_obj, k, dbg=k == 'generic2') + new_fields[k] = update_mustache_field(yaml_obj, k, dbg=False) + elif isinstance(v, dict): + for k2, v2 in v.items(): + # update references to the original mustache fields for countries that reference them. + if isinstance(v2, LiteralScalarString) and v2 in orig_fields_by_value: + v[k2] = new_fields[orig_fields_by_value[v2]] + + # one-off templates need to be converted to jinja. + elif k2 in {"address_template", "fallback_template"}: + update_mustache_field(v, k2, dbg=k == 'CA_en') + + if "change_country" in v or "add_component" in v: + attach_trailing = normalize_add_component(v) + + + with open(FORMATS_DEST_PATH, "w") as outfile: + outfile.write("# Generated from OpenCageData address-formatting repo\n") + outfile.write(f"# commit: {commit_hash}\n") + outfile.write(f"# {github_raw_url}\n") + yaml.dump(yaml_obj, outfile) + + +if __name__ == "__main__": + load_address_formats_from_opencage() + diff --git a/pyproject.toml b/pyproject.toml index 2f1683f6..b7f08c2a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ classifiers = [ ] requires-python = ">= 3.10" dependencies = [ - "pyicu >= 2.15.2, < 3.0.0", "babel >= 2.9.1, < 3.0.0", "pyyaml >= 5.0.0, < 7.0.0", "banal >= 1.0.6, < 1.1.0", @@ -53,6 +52,9 @@ dev = [ "types-setuptools", "types-PyYAML", "coverage>=4.1", + "ruamel.yaml==0.18.10", + "mystace==0.1.0", + "requests==2.32.3", ] docs = [ "pillow", diff --git a/rigour/data/addresses/formats.yml b/rigour/data/addresses/formats.yml index 0b18e90f..08b058d5 100644 --- a/rigour/data/addresses/formats.yml +++ b/rigour/data/addresses/formats.yml @@ -1,6 +1,8 @@ +# Generated from OpenCageData address-formatting repo +# commit: e8df874f42b18cbc1272b9cb491e460b72c29bc8 +# https://raw.githubusercontent.com/OpenCageData/address-formatting/e8df874f42b18cbc1272b9cb491e460b72c29bc8/conf/countries/worldwide.yaml # # generic mappings, specific territories get mapped to these -# derived from: https://github.com/OpenCageData/address-formatting # # postcode before city generic1: &generic1 | @@ -14,9 +16,9 @@ generic1: &generic1 | # postcode after city generic2: &generic2 | {{attention}} - {{house}}{%- if house and quarter -%}, {%- endif -%}{{quarter}} + {{house}}, {{quarter}} {{house_number}} {{road}} - {{village or city or town or municipality or county}} {{postcode}} + {{village or town or city or municipality or county}} {{postcode}} {{country or state}} # postcode before city @@ -24,7 +26,7 @@ generic3: &generic3 | {{attention}} {{house}} {{house_number}} {{road}} - {{postcode}} {{town or village or city or municipality or state}} + {{postcode}} {{town or village or city or municipality or state}} {{country}} # postcode after state @@ -32,7 +34,8 @@ generic4: &generic4 | {{attention}} {{house}} {{house_number}} {{road}} - {{city or town or state_district or village or suburb or municipality or county}}, {{state_code or state}} {{postcode}} + {{village}} + {{city or town or state_district or suburb or municipality or county}}, {{state_code or state}} {{postcode}} {{country}} # no postcode @@ -40,7 +43,7 @@ generic5: &generic5 | {{attention}} {{house}} {{house_number}} {{road}} - {{city or town or village}} + {{city or town or village}} {{state_district or state}} {{country}} @@ -49,23 +52,24 @@ generic6: &generic6 | {{attention}} {{house}} {{house_number}} {{road}} - {{city or town or village or municipality}} - {{county}} + {{city or town or village or municipality}} + {{county}} + {{state}} {{country}} # city, postcode generic7: &generic7 | {{attention}} {{house}} - {{road}} {{house_number}} - {{city or town or village}}, {{postcode}} + {{road}} {{house_number}} + {{city or town or village or state}}, {{postcode}} {{country}} # postcode and county generic8: &generic8 | {{attention}} {{house}} - {{road}}, {{house_number}} + {{road}} {{house_number}} {{postcode}} {{city or town or village or municipality}} {{county_code or county}} {{country}} @@ -80,7 +84,7 @@ generic9: &generic9 | generic10: &generic10 | {{attention}} {{house}} - {{road}} {{house_number}} + {{road}}, {{house_number}} {{suburb or city_district}} {{city or town or village}} {{state}} @@ -110,7 +114,7 @@ generic13: &generic13 | {{attention}} {{house}} {{house_number}} {{road}} - {{suburb or city or town or state_district or village or region}} {{state_code or state}} {{postcode}} + {{suburb or city_district or city or town or state_district or village or region}} {{state_code or state}} {{postcode}} {{country}} # postcode and state @@ -135,15 +139,15 @@ generic16: &generic16 | {{attention}} {{house}} {{house_number}} {{road}} - {{city or town or village or municipality or county or state_district or state}} + {{city or town or village or municipality or county or state_district or state}} {{country}} # no postcode, no state, just city generic17: &generic17 | {{attention}} {{house}} - {{road}} {{house_number}} - {{city or town or village or municipality or county or state_district or state}} + {{road}} {{house_number}} + {{city or town or village or municipality or county or state_district or state}} {{country}} # no postcode, just city comma after house number @@ -151,7 +155,7 @@ generic18: &generic18 | {{attention}} {{house}} {{house_number}}, {{road}} - {{city or town or village or suburb or city_district or neighbourhood or state}} + {{city or town or village or suburb or city_district or neighbourhood or state}} {{country}} # suburb and postcode after city @@ -160,25 +164,25 @@ generic19: &generic19 | {{house}} {{road}} {{house_number}} {{suburb or city_district or neighbourhood}} - {{city or town or village}} {{postcode}} + {{city or town or village}} {{postcode}} {{country}} # suburb and postcode after city generic20: &generic20 | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{suburb or city_district or neighbourhood}} - {{city or town or village}} {{postcode}} + {{city or town or village}} {{postcode}} {{country}} # suburb and city, no postcode generic21: &generic21 | {{attention}} {{house}} - {{road}} {{house_number}} + {{road}} {{house_number}} {{suburb or city_district or neighbourhood}} - {{city or town or village or state}} + {{city or town or village or state}} {{country}} # comma after housenumber, postcode before city @@ -189,13 +193,23 @@ generic22: &generic22 | {{postcode}} {{city or town or village or state}} {{country}} +# postcode on own line +generic23: &generic23 | + {{attention}} + {{house}} + {{house_number}} {{road}} + {{quarter}} + {{village or town or city or municipality or county}} + {{postcode}} + {{country or state}} + fallback1: &fallback1 | {{attention}} {{house}} {{road}} {{house_number}} {{suburb or city_district or neighbourhood or island}} {{city or town or village or municipality}} - {{county or state_district or state or region}} + {{county or state_district or state or region or format_if(island ~ ', ' ~ archipelago, island, archipelago)}} {{country}} fallback2: &fallback2 | @@ -211,7 +225,8 @@ fallback3: &fallback3 | {{house}} {{road}} {{house_number}} {{suburb or island}} - {{city or town or village or municipality}} + {{village or municipality}} + {{town or city}} {{county}} {{state or state_code}} {{country}} @@ -221,7 +236,7 @@ fallback4: &fallback4 | {{house}} {{road}} {{house_number}} {{suburb}} - {{city or town or village or municipality}} + {{city or town or village or municipality or county}} {{state or county}} {{country}} @@ -233,6 +248,7 @@ default: # please keep in alpha order by country code # + # Andorra AD: address_template: *generic3 @@ -244,7 +260,7 @@ AE: {{house}} {{house_number}} {{road}} {{suburb or city_district or neighbourhood}} - {{city or town or village}} + {{city or town or village}} {{state_district or state}} {{country}} @@ -261,19 +277,20 @@ AI: address_template: | {{attention}} {{house}} - {{road}} {{house_number}} + {{road}} {{house_number}} {{city or town or village}} {{postcode}} {{country}} + # Albania AL: address_template: | {{attention}} {{house}} - {{road}} {{house_number}} - {{postcode}} {{city or town or state_district or village}} + {{road}} {{house_number}} + {{postcode}} {{city or town or city_district or municipality or state_district or village}} {{country}} postformat_replace: - # fix the postcode to add - after numbers + # fix the postcode to add - after numbers - ["\n(\\d{4}) ([^,]*)\n", "\n$1-$2\n"] # Armenia @@ -281,11 +298,12 @@ AM: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} - {{postcode}} + {{house_number}} {{road}} + {{postcode}} {{city or town or village}} {{state_district or state}} {{country}} + # Angola AO: address_template: *generic7 @@ -293,17 +311,21 @@ AO: # Antarctica AQ: address_template: | - {{attention}} {{house}} {{city or town or village}} {{country or continent}} + fallback_template: | + {{house}} + {{city or town or village}} + {{country or continent}} + # Argentina AR: address_template: *generic9 replace: - ["^Autonomous City of ", ""] postformat_replace: - # fix the postcode to make it \w\d\d\d\d \w\w\w + # fix the postcode to make it \w\d\d\d\d \w\w\w - ["\n(\\w\\d{4})(\\w{3}) ", "\n$1 $2 "] # American Samoa @@ -352,17 +374,24 @@ BD: {{suburb or city_district or state_district}} {{city or town or village}} - {{postcode}} {{country}} + # Belgium BE: - address_template: *generic1 + address_template: | + {{attention}} + {{house}} + {{road}} {{house_number}} + {{postcode}} {{postal_city or town or city or village or municipality or county or state}} + {{archipelago}} + {{country}} # Burkina Faso BF: address_template: *generic6 -# Bulgaria +# Bulgaria - https://en.wikipedia.org/wiki/Address#Bulgaria BG: - address_template: *generic9 + address_template: *generic19 # Bahrain BH: @@ -391,10 +420,12 @@ BN: address_template: | {{attention}} {{house}} - {{house_number}}, {{road}} - {{city or town or village or municipality}} - {{county or state_district or state}} {{postcode}} + {{house_number}}, {{road}} + {{city or town or village or municipality}} + {{county or state_district or state}} {{postcode}} {{country}} + + # Bolivia BO: address_template: *generic17 @@ -412,9 +443,9 @@ BR: address_template: | {{attention}} {{house}} - {{road}} {{house_number}} - {{suburb or city_district}} - {{city or town or state_district or village}} - {{state_code or state}} + {{road}} {{house_number}}, {{quarter}} + {{suburb or city_district or village}} + {{city or town or state_district}} - {{state_code or state}} {{postcode}} {{country}} postformat_replace: @@ -425,10 +456,11 @@ BS: address_template: | {{attention}} {{house}} - {{road}} {{house_number}} - {{city or town or village or municipality}} - {{county}} + {{road}} {{house_number}} + {{city or town or village or municipality}} + {{county}} {{country}} + # Bhutan BT: address_template: | @@ -436,8 +468,9 @@ BT: {{house}} {{road}} {{house_number}}, {{house}} {{suburb or city_district or neighbourhood}} - {{city or town or village or state}} {{postcode}} + {{city or town or village or state}} {{postcode}} {{country}} + # Bouvet Island BV: use_country: "NO" @@ -449,10 +482,11 @@ BW: address_template: | {{attention}} {{house}} - {{road}} {{house_number}} + {{road}} {{house_number}} {{suburb or city_district or neighbourhood}} - {{city or town or village}} + {{city or town or village}} {{country}} + # Belarus BY: address_template: *generic11 @@ -461,29 +495,59 @@ BY: BZ: address_template: *generic16 -# Canada +# Canada - https://en.wikipedia.org/wiki/Address#Canada CA: address_template: | {{attention}} {{house}} - {{house_number}} {{road or suburb}} - {{city or town or state_district or village}}, {{state_code or state}} {{postcode}} + {{format_if(house_number ~ ' ' ~ road, house_number, road) or suburb}} + {{city or town or village or county or state_district}}, {{state_code or state}} {{postcode}} + {{country}} + fallback_template: | + {{attention}} + {{house}} + {{format_if(house_number ~ ' ' ~ road, house_number, road) or suburb}} + {{city or town or village or county or state_district or region}}, {{state or state_code}} {{postcode}} + {{country}} + postformat_replace: + # fix the postcode to make it \w\w\w \w\w\w + - [" ([A-Za-z]{2}) ([A-Za-z]\\d[A-Za-z])(\\d[A-Za-z]\\d)\n", " $1 $2 $3\n"] + +#Canada - English +CA_en: + address_template: | + {{attention}} + {{house}} + {{format_if(house_number ~ ' ' ~ road, house_number, road) or suburb}} + {{city or town or village or county or state_district}}, {{state_code or state}} {{postcode}} {{country}} fallback_template: | {{attention}} {{house}} - {{house_number}} {{road or suburb}} - {{city or town or state_district or village}}, {{state_code or state}} {{postcode}} + {{format_if(house_number ~ ' ' ~ road, house_number, road) or suburb}} + {{city or town or village or county or state_district}}, {{state_code or state}} {{postcode}} + {{country}} + postformat_replace: + # fix the postcode to make it \w\w\w \w\w\w + - [" ([A-Za-z]{2}) ([A-Za-z]\\d[A-Za-z])(\\d[A-Za-z]\\d)\n", " $1 $2 $3\n"] + +#Canada - French Quebec +CA_fr: + address_template: | + {{attention}} + {{house}} + {{format_if(house_number ~ ', ' ~ road, house_number, road) or suburb}} + {{city or town or village or county or state_district}} {{format_if('(' ~ state_code ~ ')', state_code) or state}} {{postcode}} {{country}} postformat_replace: - # fix the postcode to make it \w\w\w \w\w\w - - [" (\\w{2}) (\\w{3})(\\w{3})\n", " $1 $2 $3\n"] + # fix the postcode to make it \w\w\w \w\w\w + - [" ([A-Za-z]{2}) ([A-Za-z]\\d[A-Za-z])(\\d[A-Za-z]\\d)\n", " $1 $2 $3\n"] # Cocos (Keeling) Islands CC: use_country: AU add_component: - country: Cocos, Australia + country: Australia # Democratic Republic of the Congo CD: @@ -499,7 +563,17 @@ CG: # Switzerland CH: - address_template: *generic1 + address_template: | + {{attention}} + {{house}} + {{road}} {{house_number}} + {{postcode}} {{postal_city or town or city or municipality or village or county or state}} + {{country}} + replace: + - ["Verwaltungskreis", ""] + - ["Verwaltungsregion", ""] + - [" administrative district", ""] + - [" administrative region", ""] # Côte d'Ivoire CI: @@ -509,12 +583,15 @@ CI: CK: address_template: *generic16 -# Chile +# Chile - https://en.wikipedia.org/wiki/Address#Chile CL: - address_template: *generic1 - postformat_replace: - # fix the postcode to make it \d\d\d \d\d\d\d - - ["\n(\\d{3})(\\d{4}) ", "\n$1 $2 "] + address_template: | + {{attention}} + {{house}} + {{road}} {{house_number}} + {{postcode}} {{postal_city or town or city or village or municipality or county or state}} + {{region}} + {{country}} # Cameroon CM: @@ -523,36 +600,39 @@ CM: # China CN: address_template: | - {{attention}} - {{house}} - {{house_number}} {{road}} - {{suburb or city_district or neighbourhood}} + {{postcode}} {{country}} + {{state_code or state or state_district or region}} {{county}} - {{postcode}} {{state_code or state or city or town or municipality or state_district or region or village}} - {{country}} + {{city or town or municipality or village}} + {{suburb or city_district or neighbourhood}} + {{road}} {{house_number}} + {{house}} + {{attention}} + # China - English CN_en: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{suburb or city_district or neighbourhood}} {{county}} - {{postcode}} {{state_code or state or city or town or municipality or state_district or region or village}} - {{country}} -# China - Chinese + {{city or town or municipality or village}} + {{state_code or state or state_district or region}} + {{country}} {{postcode}} + +# China - Chinese Simplified CN_zh: address_template: | - {{country}} - {{postcode}} - {{state_code or state or region}} - {{state_district or county}} - {{city or town or village or municipality}} + {{postcode}} {{country}} + {{state_code or state or state_district or region}} + {{county}} + {{city or town or municipality or village}} {{suburb or city_district or neighbourhood}} - {{road}} - {{house_number}} + {{road}} {{house_number}} {{house}} {{attention}} + # Colombia CO: address_template: | @@ -563,7 +643,9 @@ CO: {{postcode}} {{city or town or state_district or village}}, {{state_code or state}} {{country}} postformat_replace: - - ["Bogota, Bogota", "Bogota"] + - ["Localidad ", " "] + - ["(Bogot[áa]),? (Distrito Capital|Capital District)", $1] + - ["(Bogot[áa]), Bogot[áa]", "$1"] # Costa Rica CR: @@ -573,6 +655,7 @@ CR: {{road}} {{house_number}} {{state}}, {{city or town or state_district or village}}, {{suburb or city_district or neighbourhood}} {{postcode}} {{country}} + # Cuba CU: address_template: *generic7 @@ -581,7 +664,7 @@ CU: CV: address_template: *generic1 postformat_replace: - # fix the postcode to add - after numbers + # fix the postcode to add - after numbers - ["\n(\\d{4}) ([^,]*)\n", "\n$1-$2\n"] # Curaçao @@ -602,19 +685,27 @@ CY: # Czech Republic CZ: address_template: *generic1 + replace: + - ["^Capital City of ", ""] postformat_replace: - # fix the postcode to make it \d\d\d \d\d + # fix the postcode to make it \d\d\d \d\d - ["\n(\\d{3})(\\d{2}) ", "\n$1 $2 "] # Germany DE: - address_template: *generic1 + address_template: | + {{attention}} + {{house}} + {{road}} {{house_number}} + {{postcode}} {{format_if(village ~ ' ' ~ postal_city, village, postal_city) or town or city or municipality or county or state}} + {{archipelago}} + {{country}} fallback_template: | {{attention}} {{house}} {{road}} {{house_number}} {{suburb or city_district or neighbourhood}} - {{town or city or village or municipality or county}} + {{village or town or city or municipality or county}} {{state or state_district}} {{country}} @@ -630,6 +721,7 @@ DE: - ["^Free State of ", ""] - ["^Freistaat ", ""] - ["^Regierungsbezirk ", ""] + - ["^Stadtgebiet ", ""] - ["^Gemeindefreies Gebiet ", ""] - ["city=Alt-Berlin", "Berlin"] postformat_replace: @@ -646,6 +738,9 @@ DJ: # Denmark DK: address_template: *generic1 + replace: + - ["state=Capital Region of Denmark", "Capital Region"] + - ["^Region of ", ""] # Dominica DM: @@ -656,10 +751,10 @@ DO: address_template: | {{attention}} {{house}} - {{road}} {{house_number}} + {{road}} {{house_number}} {{suburb or city_district or neighbourhood}} - {{city or town or village}}, {{state}} - {{postcode}} + {{city or town or village}}, {{state}} + {{postcode}} {{country}} postformat_replace: - [", Distrito Nacional", ", DN"] @@ -677,6 +772,7 @@ EC: {{postcode}} {{city or town or state_district or village}} {{country}} + # Egypt EG: address_template: | @@ -684,9 +780,10 @@ EG: {{house}} {{house_number}} {{road}} {{suburb or city_district or neighbourhood}} - {{city or town or village}} - {{postcode}} + {{city or town or village}} + {{postcode}} {{country}} + # Estonia EE: address_template: *generic1 @@ -704,6 +801,11 @@ ES: address_template: *generic15 fallback_template: *fallback4 + replace: + - ["Autonomous Community of the ", ""] + - ["Autonomous Community of ", ""] + - ["^Community of ", ""] + # Ethiopia ET: address_template: *generic1 @@ -739,35 +841,36 @@ FO: FR: address_template: *generic3 replace: - - [ - "Polynésie française, Îles du Vent \\(eaux territoriales\\)", - "Polynésie française", - ] + - ["Polynésie française, Îles du Vent \\(eaux territoriales\\)", "Polynésie française"] - ["France, Mayotte \\(eaux territoriales\\)", "Mayotte, France"] - ["France, La Réunion \\(eaux territoriales\\)", "La Réunion, France"] - ["Grande Terre et récifs d'Entrecasteaux", ""] - ["France, Nouvelle-Calédonie", "Nouvelle-Calédonie, France"] - ["\\(eaux territoriales\\)", ""] + - ["state= \\(France\\)$", ""] + - ["Paris (\\d+)(\\w+) Arrondissement$", "Paris"] + # Gabon GA: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{suburb or city_district or neighbourhood or village}} {{city or town or municipality or county or state_district or state}} {{country}} + GB: - address_template: *generic2 + address_template: *generic23 fallback_template: *fallback3 replace: + - ["village= CP$", ""] - ["^Borough of ", ""] - ["^County( of)? ", ""] - ["^Parish of ", ""] - - ["^Central ", ""] - ["^Greater London", "London"] - - ["^London Borough of .+", "London"] + - ["^London Borough of ", ""] - ["Royal Borough of ", ""] - ["County Borough of ", ""] postformat_replace: @@ -790,7 +893,7 @@ GE: GF: use_country: FR add_component: - country: French Guiana + country: France # Guernsey - same format as UK, but not part of UK GG: @@ -831,8 +934,11 @@ GQ: # Greece GR: address_template: *generic1 + replace: + - ["Municipal Unit of ", ""] + - ["Regional Unit of ", ""] postformat_replace: - # fix the postcode to make it \d\d\d \d\d + # fix the postcode to make it \d\d\d \d\d - ["\n(\\d{3})(\\d{2}) ", "\n$1 $2 "] # South Georgia and the South Sandwich Islands - same as UK @@ -874,18 +980,20 @@ HK: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{state_district}} {{state or country}} + # Hong Kong - English HK_en: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{state_district}} {{state}} {{country}} + # Hong Kong - Chinese HK_zh: address_template: | @@ -896,6 +1004,8 @@ HK_zh: {{house_number}} {{house}} {{attention}} + + # Heard Island and McDonald Islands - same as Australia HM: use_country: AU @@ -915,17 +1025,17 @@ HR: HT: address_template: *generic1 postformat_replace: - - [" Commune de", " "] + - [" Commune de ", " "] # Hungary +# https://e-nyelv.hu/2014-09-19/lakcim/ HU: address_template: | {{attention}} - {{house}} - {{city or town or village}} - {{road}} {{house_number}} - {{postcode}} + {{postcode}} {{city or town or village}} + {{road}} {{house_number}}. {{country}} + # Indonesia # https://en.wikipedia.org/wiki/Address_%28geography%29#Indonesia ID: @@ -937,6 +1047,7 @@ ID: {{city or town or village}} {{postcode}} {{state}} {{country}} + # Ireland # https://en.wikipedia.org/wiki/Postal_addresses_in_the_Republic_of_Ireland IE: @@ -947,17 +1058,25 @@ IE: {{suburb or city_district or neighbourhood}} {{city or town or village or municipality}} {{county}} + {{postcode}} {{country}} replace: - [" City$", ""] - ["The Municipal District of ", ""] - ["The Metropolitan District of ", ""] + - ["Municipal District", ""] + - ["Electoral Division", ""] postformat_replace: - ["Dublin\nCounty Dublin", "Dublin"] + - ["Dublin\nLeinster", "Dublin"] - ["Galway\nCounty Galway", "Galway"] - ["Kilkenny\nCounty Kilkenny", "Kilkenny"] - ["Limerick\nCounty Limerick", "Limerick"] - ["Tipperary\nCounty Tipperary", "Tipperary"] + # fix eircode formatting + #- ["\n(\\d{4})(\\w{2}) ","\n$1 $2 "] + - ["\n(([AC-FHKNPRTV-Y][0-9]{2}|D6W))[ -]?([0-9AC-FHKNPRTV-Y]{4})", "\n$1 $3"] + # Israel IL: @@ -965,12 +1084,15 @@ IL: # Isle of Man IM: - address_template: *generic2 + use_country: GB # India # http://en.wikipedia.org/wiki/Address_%28geography%29#India IN: address_template: *generic12 + postformat_replace: + # deal with - but no postcode + - [" -\n", "\n"] # British Indian Ocean Territory - same as UK IO: @@ -984,10 +1106,11 @@ IQ: {{attention}} {{house}} {{house_number}} {{city_district or neighbourhood or suburb}} - {{road}} + {{road}} {{city or town or state or village}} {{postcode}} {{country}} + # Iran IR: address_template: | @@ -997,9 +1120,10 @@ IR: {{suburb or city_district or neighbourhood}} {{road}} {{house_number}} - {{province or state}} + {{province or state or state_district}} {{postcode}} {{country}} + IR_en: address_template: | {{attention}} @@ -1008,12 +1132,15 @@ IR_en: {{suburb or city_district or neighbourhood}} {{road}} {{house_number}} - {{province or state}} + {{state or state_district}} {{postcode}} {{country}} + IR_fa: address_template: | {{country}} + {{state}} + {{state_district}} {{state or province}} {{city or town or village}} {{suburb or city_district or neighbourhood}} @@ -1022,6 +1149,7 @@ IR_fa: {{house}} {{attention}} {{postcode}} + # Iceland IS: address_template: *generic1 @@ -1032,6 +1160,7 @@ IT: replace: - ["Città metropolitana di ", ""] - ["Metropolitan City of ", ""] + - ["^Provincia di ", ""] postformat_replace: - ["Vatican City\nVatican City$", "\nVatican City"] - ["Città del Vaticano\nCittà del Vaticano$", "Città del Vaticano\n"] @@ -1060,9 +1189,11 @@ JP: {{city or town or village}}, {{state or state_district}} {{postcode}} {{country}} postformat_replace: - # fix the postcode to make it \d\d\d-\d\d\d\d + # fix the postcode to make it \d\d\d-\d\d\d\d - [" (\\d{3})(\\d{4})\n", " $1-$2\n"] + + # Japan - English JP_en: address_template: | @@ -1073,9 +1204,10 @@ JP_en: {{city or town or village}}, {{state or state_district}} {{postcode}} {{country}} postformat_replace: - # fix the postcode to make it \d\d\d-\d\d\d\d + # fix the postcode to make it \d\d\d-\d\d\d\d - [" (\\d{3})(\\d{4})\n", " $1-$2\n"] + # Japan - Japanese JP_ja: address_template: | @@ -1089,7 +1221,7 @@ JP_ja: {{house}} {{attention}} postformat_replace: - # fix the postcode to make it \d\d\d-\d\d\d\d + # fix the postcode to make it \d\d\d-\d\d\d\d - [" (\\d{3})(\\d{4})\n", " $1-$2\n"] # Kenya @@ -1101,20 +1233,14 @@ KE: {{city or town or state or village}} {{postcode}} {{country}} + # Kyrgyzstan KG: address_template: *generic11 # Cambodia KH: - address_template: | - {{attention}} - {{house}} - {{house_number}} {{road}} - {{suburb or city_district or neighbourhood}} - {{city or town or village}} {{postcode}} - {{country}} -# Kiribati + address_template: *generic20 KI: address_template: *generic17 @@ -1127,63 +1253,65 @@ KM: {{city or town or village}} {{suburb or city_district or neighbourhood}} {{country}} + # Saint Kitts and Nevis KN: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{city or town or village}}, {{state or island}} {{country}} + # Democratic People's Republic of Korea / North Korea KP: - address_template: | - {{attention}} - {{house}} - {{road}} {{house_number}} - {{suburb or city_district or neighbourhood}} - {{city or town or village}} - {{state or province}} - {{country}} + address_template: *generic21 -# Republic of Korea / South Korea +# Republic of Korea / South Korea -- https://en.wikipedia.org/wiki/Address#South_Korea KR: address_template: | + {{country}} + {{state}} {{city or town or village}} {{suburb or city_district or neighbourhood}} {{road}} {{house_number}} {{attention}} - {{house}} - {{house_number}} {{road}} - {{suburb or city_district or neighbourhood}}, {{city or town or village}}, {{state}} {{postcode}} + {{postcode}} + fallback_template: | {{country}} + {{state}} {{city or town or village}} {{suburb or city_district or neighbourhood}} + {{attention}} + # South Korea - English KR_en: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} - {{suburb or city_district or neighbourhood}}, {{city or town or village}}, {{state}} {{postcode}} + {{house_number}} {{road}} + {{suburb or city_district or neighbourhood}}, {{city or town or village}} {{postcode}} + {{state}} {{country}} + # South Korea - Korean KR_ko: address_template: | {{country}} - {{state}} - {{city or town or village}} - {{suburb or city_district or neighbourhood}} - {{road}} - {{house_number}} - {{house}} + {{state}} {{city or town or village}} {{suburb or city_district or neighbourhood}} {{road}} {{house_number}} {{attention}} {{postcode}} + fallback_template: | + {{country}} + {{state}} {{city or town or village}} {{suburb or city_district or neighbourhood}} + {{attention}} + # Kuwait KW: address_template: | {{attention}} {{house}} {{suburb or city_district or neighbourhood}} - {{road}} + {{road}} {{house_number}} {{house}} {{postcode}} {{city or town or village}} {{country}} + # Cayman Islands KY: address_template: *generic2 @@ -1200,9 +1328,8 @@ LA: LB: address_template: *generic2 postformat_replace: - # fix the postcode to make it nonbreaking space - - [" (\\d{4}) (\\d{4})\n", " $1 $2\n"] - # - ["\n(\\d{4}) (\\d{4}) ","\n$1 $2 "] + # fix the postcode to make it nonbreaking space + - [" (\\d{4}) (\\d{4})\n", " $1 $2\n"] # Saint Lucia LC: @@ -1256,6 +1383,7 @@ MD: {{road}}, {{house_number}} {{postcode}} {{city or town or village or state}} {{country}} + # Montenegro ME: address_template: *generic1 @@ -1277,10 +1405,11 @@ MG: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{suburb or city_district or neighbourhood}} - {{postcode}} {{city or town or village}} + {{postcode}} {{city or town or village}} {{country}} + # North Macedonia MK: address_template: *generic1 @@ -1294,21 +1423,24 @@ MM: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} - {{city or town or village or state}}, {{postcode}} + {{house_number}} {{road}} + {{city or town or village or state}}, {{postcode}} {{country}} + + # Mongolia MN: address_template: | {{attention}} {{house}} - {{city_district}} + {{city_district}} {{suburb or neighbourhood}} - {{road}} - {{house_number}} + {{road}} + {{house_number}} {{postcode}} - {{city or town or village}} + {{city or town or village}} {{country}} + # Macau MO: address_template: | @@ -1317,6 +1449,7 @@ MO: {{road}} {{house_number}} {{suburb or village or state_district}} {{country}} + # Macao - Portuguese MO_pt: address_template: | @@ -1325,15 +1458,17 @@ MO_pt: {{road}} {{house_number}} {{suburb or village or state_district}} {{country}} + # Macao - Chinese MO_zh: address_template: | {{country}} {{suburb or village or state_district}} {{road}} - {{house_number}} + {{house_number}} {{house}} {{attention}} + # Northern Mariana Islands MP: use_country: US @@ -1350,10 +1485,11 @@ MT: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} - {{city or town or suburb or village}} + {{house_number}} {{road}} + {{city or town or suburb or village}} {{postcode}} {{country}} + # Martinique - overseas territory of France (FR) MQ: use_country: FR @@ -1366,7 +1502,13 @@ MR: # Mauritius MU: - address_template: *generic18 + address_template: | + {{attention}} + {{house}} + {{house_number}}, {{road}} + {{suburb or city_district or neighbourhood}} + {{city or town or village or state}} {{postcode}} + {{country}} # Maldives MV: @@ -1385,6 +1527,7 @@ MX: {{suburb or city_district or neighbourhood}} {{postcode}} {{city or town or state_district or village}}, {{state_code or state}} {{country}} + # Malaysia MY: address_template: | @@ -1395,6 +1538,7 @@ MY: {{postcode}} {{city or town or village}} {{state}} {{country}} + # Mozambique MZ: address_template: *generic15 @@ -1415,7 +1559,7 @@ NE: address_template: | {{attention}} {{house}} - {{house_number}} + {{house_number}} {{road}} {{city or town or village}} {{country}} @@ -1436,6 +1580,7 @@ NG: {{city or town or village}} {{postcode}} {{state}} {{country}} + # Nicaragua NI: address_template: *generic21 @@ -1444,10 +1589,11 @@ NI: NL: address_template: *generic1 postformat_replace: - # fix the postcode to make it \d\d\d\d \w\w + # fix the postcode to make it \d\d\d\d \w\w - ["\n(\\d{4})(\\w{2}) ", "\n$1 $2 "] - ["\nKoninkrijk der Nederlanden$", "\nNederland"] + # Norway # quoted since python interprets it as a boolean. Silly python! "NO": @@ -1458,10 +1604,11 @@ NP: address_template: | {{attention}} {{house}} - {{road}} {{house_number}} + {{road}} {{house_number}} {{suburb or neighbourhood or city}} - {{municipality or county or state_district or state}} {{postcode}} + {{municipality or county or state_district or state}} {{postcode}} {{country}} + # Nauru NR: address_template: *generic16 @@ -1473,17 +1620,20 @@ NU: # New Zealand NZ: address_template: *generic20 + postformat_replace: + - ["Wellington\nWellington City", "Wellington"] # Oman OM: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{postcode}} {{city or town or state_district or village}} {{state}} {{country}} + # Panama PA: address_template: | @@ -1496,8 +1646,8 @@ PA: {{state}} {{country}} replace: - - ["city=Panama", "Panama City"] - - ["city=Panamá", "Ciudad de Panamá"] + - ["city=Panama$", "Panama City"] + - ["city=Panamá$", "Ciudad de Panamá"] # Peru PE: @@ -1506,31 +1656,30 @@ PE: # French Polynesia - same as FR PF: use_country: FR - add_component: - country: Polynésie française, France replace: - - [ - "Polynésie française, Îles du Vent \\(eaux territoriales\\)", - "Polynésie française", - ] + - ["Polynésie française, Îles du Vent \\(eaux territoriales\\)", "Polynésie française"] + # Papau New Guinea + add_component: + country: Polynésie française, France PG: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} - {{city or town or village}} {{postcode}} {{state}} + {{house_number}} {{road}} + {{city or town or village}} {{postcode}} {{state}} {{country}} -# Philippines + +# Philippines - https://en.wikipedia.org/wiki/Address#Philippines PH: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} - {{city or town or village or suburb or state_district}} - {{postcode}} {{state}} + {{house_number}} {{road}}, {{suburb or city_district or neighbourhood}}, {{city or town or village or suburb or state_district}} + {{postcode}} {{municipality or region or state}} {{country}} + # Pakistan PK: address_template: | @@ -1538,15 +1687,17 @@ PK: {{house}} {{house_number}} {{road}} {{suburb or city_district or neighbourhood}} - {{city or town or village or state}} {{postcode}} + {{city or town or village or state}} {{postcode}} {{country}} + # Poland PL: address_template: *generic1 postformat_replace: - # fix the postcode to make it \d\d-\d\d\d + # fix the postcode to make it \d\d-\d\d\d - ["\n(\\d{2})(\\w{3}) ", "\n$1-$2 "] + # Saint Pierre and Miquelon - same as FR PM: use_country: FR @@ -1558,8 +1709,9 @@ PN: address_template: | {{attention}} {{house}} - {{city or town or island}} + {{city or town or island}} {{country}} + # Puerto Rico, same as USA PR: use_country: US @@ -1574,6 +1726,10 @@ PS: # Portugal PT: address_template: *generic1 + postformat_replace: + # fix the postcode to add - after numbers + - ["\n(\\d{4})(\\d{3}) ", "\n$1-$2 "] + # Palau PW: @@ -1593,6 +1749,7 @@ RE: add_component: country: La Réunion, France + # Romania RO: address_template: *generic1 @@ -1607,11 +1764,12 @@ RU: fallback_template: | {{attention}} {{house}} - {{road}} {{house_number}} + {{road}}, {{house_number}} {{suburb or city_district or neighbourhood or island or village}} {{city or town or municipality}} {{county or state_district or state}} {{country}} + # Rwanda RW: address_template: *generic16 @@ -1624,6 +1782,7 @@ SA: {{house_number}} {{road}}, {{village or city_district or suburb or neighbourhood}} {{city or town or state_district}} {{postcode}} {{country}} + # Solomon Islands SB: address_template: *generic17 @@ -1633,10 +1792,11 @@ SC: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} - {{city or town or village or island}} + {{house_number}} {{road}} + {{city or town or village or island}} {{island}} {{country}} + # Sudan SD: address_template: *generic1 @@ -1645,18 +1805,23 @@ SD: SE: address_template: *generic1 postformat_replace: - # fix the postcode to make it \d\d\d \d\d + # fix the postcode to make it \d\d\d \d\d - ["\n(\\d{3})(\\d{2}) ", "\n$1 $2 "] # Singapore SG: - address_template: *generic2 + address_template: | + {{attention}} + {{house}}, {{quarter}} + {{house_number}} {{road}}, {{residential}} + {{country or town or city or municipality or village or county}} {{postcode}} + {{country}} # Saint Helena, Ascension and Tristan da Cunha - same as UK SH: use_country: GB add_component: - country: Saint Helena, United Kingdom + country: $state, United Kingdom # Slovenia SI: @@ -1670,9 +1835,18 @@ SJ: # Slovakia SK: - address_template: *generic1 + address_template: | + {{attention}} + {{house}} + {{road}} {{house_number}} + {{postcode}} {{postal_city or city or town or village or municipality or city_district or county or state}} + {{country}} replace: - ["^District of ", ""] + - ["^Region of ", ""] + postformat_replace: + # fix the postcode to make it \d\d\d \d\d + - ["\n(\\d{3})(\\d{2}) ", "\n$1 $2 "] # Sierra Leone SL: @@ -1685,6 +1859,10 @@ SM: # Senegal SN: address_template: *generic3 + replace: + - ["^Commune de ", ""] + - ["^Arrondissement de ", ""] + - ["^Département de ", ""] # Somalia SO: @@ -1707,9 +1885,9 @@ SV: address_template: | {{attention}} {{house}} - {{road}} {{house_number}} - {{postcode}} - {{city or town or village}} - {{state}} + {{road}} {{house_number}} + {{postcode}} - {{city or town or village}} + {{state}} {{country}} postformat_replace: - ["\n- ", "\n "] @@ -1726,8 +1904,9 @@ SY: {{road}}, {{house_number}} {{village or city_district or neighbourhood or suburb}} {{postcode}} {{city or town or state_district or state}} - {{country}} + + # Swaziland SZ: address_template: | @@ -1735,11 +1914,19 @@ SZ: {{house}} {{road}} {{house_number}} {{city or town or village or state}} - {{postcode}} + {{postcode}} {{country}} -# Turks and Caicos Islands - same as UK + +# Turks and Caicos Islands TC: - use_country: GB + address_template: *generic23 + fallback_template: | + {{attention}} + {{house_number}} {{road}} + {{quarter}} + {{village or town or city or municipality or county}} + {{island}} + {{country}} # Chad TD: @@ -1755,16 +1942,17 @@ TF: TG: address_template: *generic18 -# Thailand +# Thailand -- https://en.wikipedia.org/wiki/Thai_addressing_system TH: address_template: | {{attention}} {{house}} {{house_number}} {{village}} - {{road}} + {{road}} {{neighbourhood or city or town}}, {{suburb or city_district or state_district}} - {{state}} {{postcode}} + {{state}} {{postcode}} {{country}} + # Tajikistan TJ: address_template: *generic1 @@ -1804,6 +1992,7 @@ TT: {{suburb or city_district or state_district}} {{city or town or state_district or village}}, {{postcode}} {{country}} + # Tuvalu TV: address_template: | @@ -1813,24 +2002,32 @@ TV: {{city or town or village or municipality}} {{county or state_district or state or island}} {{country}} -# Taiwan + +# Taiwan -- https://en.wikipedia.org/wiki/Address#Taiwan TW: - address_template: *generic20 + address_template: | + {{country}} + {{postcode}} + {{city or town or village or municipality}} {{suburb or city_district or neighbourhood}} {{road}} {{house_number}} + {{house}} + {{attention}} TW_en: - address_template: *generic20 + address_template: | + {{attention}} + {{house}} + {{house_number}}, {{road}} + {{suburb or city_district or neighbourhood}}, {{city or town or village}} {{postcode}} + {{country}} TW_zh: address_template: | {{country}} {{postcode}} - {{city or town or village or municipality}} - {{city_district}} - {{suburb or city_district or neighbourhood}} - {{road}} - {{house_number}} + {{city or town or village or municipality}} {{suburb or city_district or neighbourhood}} {{road}} {{house_number}} {{house}} {{attention}} + # Tanzania TZ: address_template: *generic14 @@ -1838,16 +2035,18 @@ TZ: postformat_replace: - ["Dar es Salaam\nDar es Salaam", "Dar es Salaam"] -# Ukraine +# Ukraine -- https://en.wikipedia.org/wiki/Address#Ukraine UA: address_template: | {{attention}} {{house}} {{road}}, {{house_number}} {{suburb or city_district or state_district}} - {{city or town or village or municipality or state}} - {{postcode}} + {{city or town or village or municipality}} + {{region or state}} + {{postcode}} {{country}} + # Uganda UG: address_template: *generic16 @@ -1871,6 +2070,8 @@ US: - ["\nUS$", "\nUnited States of America"] - ["\nUSA$", "\nUnited States of America"] - ["\nUnited States$", "\nUnited States of America"] + - ["Town of ", ""] + - ["Township of ", ""] # Uzbekistan UZ: @@ -1878,10 +2079,11 @@ UZ: {{attention}} {{house}} {{road}} {{house_number}} - {{city or town or village}} - {{state or state_district}} + {{city or town or village}} + {{state or state_district}} {{country}} {{postcode}} + # Uruguay UY: address_template: *generic1 @@ -1902,14 +2104,16 @@ VE: {{road}} {{house_number}} {{city or town or state_district or village}} {{postcode}}, {{state_code or state}} {{country}} + # British Virgin Islands VG: address_template: | {{attention}} {{house}} - {{house_number}} {{road}} + {{house_number}} {{road}} {{city or town or village}}, {{island}} {{country}}, {{postcode}} + # US Virgin Islands, same as USA VI: use_country: US @@ -1917,15 +2121,17 @@ VI: state: US Virgin Islands country: United States of America -# Vietnam +# Vietnam -- https://en.wikipedia.org/wiki/Address#Vietnam VN: address_template: | {{attention}} {{house}} - {{house_number}}, {{road}} - {{suburb or city_district or neighbourhood}}, {{city or town or state_district or village}} + {{house_number}} {{road}} + {{suburb or city_district or neighbourhood}} + {{city or town or village or state_district}} {{state}} {{postcode}} {{country}} + # Vanuatu VU: address_template: *generic17 @@ -1940,6 +2146,21 @@ WF: WS: address_template: *generic17 +# Sovereign Base Areas of Akrotiri and Dhekelia +# not an official ISO code +XC: + address_template: *generic6 + +# Kosovo +# not an official ISO code +XK: + address_template: | + {{attention}} + {{house}} + {{house_number}}, {{road}} + {{city or town or village or state}} {{postcode}} + {{country}} + # Yemen YE: address_template: *generic18 @@ -1957,9 +2178,10 @@ ZA: {{house}} {{house_number}} {{road}} {{suburb or city_district or state_district}} - {{city or town or village or state}} + {{city or town or village or state}} {{postcode}} {{country}} + # Zambia ZM: address_template: *generic3 From 70ef21942dca9446d52ce060b14d3bebf775eb9e Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 18:29:24 -0400 Subject: [PATCH 02/13] documentation --- genscripts/load_address_formats_from_opencage.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/genscripts/load_address_formats_from_opencage.py b/genscripts/load_address_formats_from_opencage.py index 6e0a6005..7198b8aa 100644 --- a/genscripts/load_address_formats_from_opencage.py +++ b/genscripts/load_address_formats_from_opencage.py @@ -377,6 +377,15 @@ def normalize_add_component(country: CommentedMap) -> str | None: def load_address_formats_from_opencage() -> None: + """ + Load address formats from OpenCageData's address-formatting repo. + Converts the mustache templates to jinja2 format. + Uses the following conventions for mustache to jinja2 conversion: + - literals between variables in a `first` block explicitly concatenated with ~ + - relies on a macro "format_if" to handle concatenated strings in `first` blocks that contain only delimiters. + - e.g. if we have {{#first}} {{{house_number}}}, {{{road}}} || {{{suburb}}} {{/first}} + - we only want to use {{{house_number}}}, {{{road}}} if both `house_number` and `road` are present. + """ commit_hash = subprocess.check_output( ["git", "ls-remote", "--heads", "https://github.com/OpenCageData/address-formatting.git", OPENCAGE_REF] ).decode("utf-8").split()[0] From d103af9b70b3861a75f67a595ad706eec3ff7a79 Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 18:30:13 -0400 Subject: [PATCH 03/13] remove debug code --- genscripts/load_address_formats_from_opencage.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/genscripts/load_address_formats_from_opencage.py b/genscripts/load_address_formats_from_opencage.py index 7198b8aa..baa1064a 100644 --- a/genscripts/load_address_formats_from_opencage.py +++ b/genscripts/load_address_formats_from_opencage.py @@ -300,7 +300,7 @@ def jinja_tree_to_template(t: M2JNode, depth: int = 0) -> str: raise ValueError(f"Got unexpected tag type {t.tag_type}") -def mustache_to_jinja(template: str, dbg: bool = False) -> str: +def mustache_to_jinja(template: str) -> str: jinja_template = "" tree = _convert_tree(create_mustache_tree(template)) @@ -321,9 +321,9 @@ def collapse_newlines(text: str) -> str: return text -def update_mustache_field(yaml_obj: dict, field: str, dbg: bool = False) -> LiteralScalarString: +def update_mustache_field(yaml_obj: dict, field: str) -> LiteralScalarString: current_anchor = yaml_obj[field].yaml_anchor() - txt = mustache_to_jinja(str(yaml_obj[field]), dbg=dbg) + txt = mustache_to_jinja(str(yaml_obj[field])) if not txt.endswith("\n"): txt += "\n" yaml_obj[field] = LiteralScalarString(txt) @@ -410,8 +410,7 @@ def load_address_formats_from_opencage() -> None: if k.startswith("generic") or k.startswith("fallback"): orig_fields_by_value[v] = k - # new_fields[k] = update_mustache_field(yaml_obj, k, dbg=k == 'generic2') - new_fields[k] = update_mustache_field(yaml_obj, k, dbg=False) + new_fields[k] = update_mustache_field(yaml_obj, k) elif isinstance(v, dict): for k2, v2 in v.items(): # update references to the original mustache fields for countries that reference them. @@ -420,7 +419,7 @@ def load_address_formats_from_opencage() -> None: # one-off templates need to be converted to jinja. elif k2 in {"address_template", "fallback_template"}: - update_mustache_field(v, k2, dbg=k == 'CA_en') + update_mustache_field(v, k2) if "change_country" in v or "add_component" in v: attach_trailing = normalize_add_component(v) From f65bd5d4cf4520a77cc33f055cfa92a1d6f2875b Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 18:31:37 -0400 Subject: [PATCH 04/13] rename --- .../load_address_formats_from_opencage.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/genscripts/load_address_formats_from_opencage.py b/genscripts/load_address_formats_from_opencage.py index baa1064a..9257ad41 100644 --- a/genscripts/load_address_formats_from_opencage.py +++ b/genscripts/load_address_formats_from_opencage.py @@ -65,7 +65,7 @@ def _convert_tree(t: MustacheTreeNode) -> M2JNode: return j -def _split_space(t: M2JNode): +def split_space(t: M2JNode): """ Find any literal nodes that contain spaces. Trim the spaces from the beginning and end of the literal when the literal is up against a boundary. @@ -85,7 +85,7 @@ def _is_boundary(n: M2JNode): n = t.children[i] if n.children: - _split_space(n) + split_space(n) if not _can_split(n) or SPACE_LITERAL not in n.data: i += 1 @@ -107,7 +107,7 @@ def _is_boundary(n: M2JNode): t.children.pop(i) -def _split_or(t: M2JNode): +def split_or(t: M2JNode): """ Split any literal nodes that contain the OR mustache literal into separate nodes. """ @@ -126,7 +126,7 @@ def _is_boundary(n: M2JNode): n = t.children[i] if n.children: - _split_or(n) + split_or(n) if _is_boundary(n) or PRIMARY_OR_MUSTACHE_LITERAL not in n.data: i += 1 @@ -152,7 +152,7 @@ def _is_boundary(n: M2JNode): i += 1 -def _drop_vars_and_simplify(t: M2JNode): +def drop_vars_and_simplify(t: M2JNode): def _is_boundary(n: M2JNode): return n.tag_type in {M2JType.OR} or n.tag_type == M2JType.LITERAL and n.data == '\n' @@ -167,7 +167,7 @@ def _is_simple_section(n: M2JNode): i = 0 while i < len(t.children): c = t.children[i] - _drop_vars_and_simplify(c) + drop_vars_and_simplify(c) is_against_boundary = ( (i > 0 and _is_boundary(t.children[i - 1])) @@ -232,12 +232,12 @@ def _is_simple_section(n: M2JNode): i += 1 -def _split_string_concat(t: M2JNode): +def split_string_concat(t: M2JNode): if not t.children: return for c in t.children: - _split_string_concat(c) + split_string_concat(c) # outside of sections, we don't need to handle concats around ORs if t.tag_type not in {M2JType.SECTION, M2JType.INVERTED_SECTION}: @@ -304,10 +304,10 @@ def mustache_to_jinja(template: str) -> str: jinja_template = "" tree = _convert_tree(create_mustache_tree(template)) - _split_or(tree) - _split_space(tree) - _drop_vars_and_simplify(tree) - _split_string_concat(tree) + split_or(tree) + split_space(tree) + drop_vars_and_simplify(tree) + split_string_concat(tree) jinja_template = jinja_tree_to_template(tree) return collapse_newlines(jinja_template) From 0a4f09b97eb01cab156e96e45122e8c1bc907765 Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 18:39:49 -0400 Subject: [PATCH 05/13] update tests --- rigour/addresses/format.py | 4 ++++ tests/addresses/test_format.py | 22 ++++++++++++++++++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/rigour/addresses/format.py b/rigour/addresses/format.py index fb53b50a..54c2401a 100644 --- a/rigour/addresses/format.py +++ b/rigour/addresses/format.py @@ -7,7 +7,11 @@ from rigour.env import ENCODING from rigour.addresses.cleaning import clean_address +def format_if(value: str, *args: str) -> str: + return value if all(args) else "" + env = Environment() +env.globals.update(format_if=format_if) class Format(TypedDict): diff --git a/tests/addresses/test_format.py b/tests/addresses/test_format.py index 6442ff1f..15712ccb 100644 --- a/tests/addresses/test_format.py +++ b/tests/addresses/test_format.py @@ -19,7 +19,7 @@ def test_format_address(): "city": "Birmingham", "postcode": "B15 1DT", } - expect = "160 Broad St\nBirmingham B15 1DT" + expect = "160 Broad St\nBirmingham\nB15 1DT" assert format_address(data, country="GB") == expect data = { @@ -28,9 +28,27 @@ def test_format_address(): "city": "London", "postcode": "SW1Y 5HX", } - expect = "Marlborough House\nPall Mall\nLondon SW1Y 5HX" + expect = "Marlborough House\nPall Mall\nLondon\nSW1Y 5HX" assert format_address(data, country="GB") == expect + data = { + "suburb": "Beverley", + "road": "Beverley Rd", + "city": "Kingston", + "state": "Ontario", + } + expect = "Beverley\nKingston, Ontario" + assert format_address(data, country="CA") == expect + + data = { + "suburb": "Beverley", + "road": "Beverley Rd", + "house_number": "10", + "city": "Kingston", + "state": "Ontario", + } + expect = "10 Beverley Rd\nKingston, Ontario" + assert format_address(data, country="CA") == expect def test_format_address_line(): addr = { From 9bf2acd8b9aae7de17bcebebebf777e11bb24a60 Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 18:41:58 -0400 Subject: [PATCH 06/13] add back pyicu --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b7f08c2a..f787d9dd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ classifiers = [ ] requires-python = ">= 3.10" dependencies = [ + "pyicu >= 2.15.2, < 3.0.0", "babel >= 2.9.1, < 3.0.0", "pyyaml >= 5.0.0, < 7.0.0", "banal >= 1.0.6, < 1.1.0", From 5d2f6d998c46d99aed18d69dc54aba248b44323f Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 18:45:46 -0400 Subject: [PATCH 07/13] remove unused vars --- genscripts/load_address_formats_from_opencage.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/genscripts/load_address_formats_from_opencage.py b/genscripts/load_address_formats_from_opencage.py index 9257ad41..cb9d1f60 100644 --- a/genscripts/load_address_formats_from_opencage.py +++ b/genscripts/load_address_formats_from_opencage.py @@ -14,13 +14,7 @@ SPACE_LITERAL = " " PRIMARY_OR_MUSTACHE_LITERAL = "||" -OR_MUSTACHE_LITERALS = {" || ", "|| ", "||", " ||"} OR_JINJA_LITERAL = " or " -LITERAL_TOKEN = "literal" -OR_TOKEN = "or" -SECTION_TOKEN = "section" -END_TOKEN = "end" -NO_ESCAPE_VAR_TOKEN = "no escape" class M2JType(Enum): From d0208f69abadefacd442e2ca2d3511d90ef761ce Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 19:39:50 -0400 Subject: [PATCH 08/13] add test to confirm all formats are valid --- tests/addresses/test_format.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/tests/addresses/test_format.py b/tests/addresses/test_format.py index 15712ccb..7d83b76d 100644 --- a/tests/addresses/test_format.py +++ b/tests/addresses/test_format.py @@ -1,4 +1,4 @@ -from rigour.addresses.format import format_address, format_address_line +from rigour.addresses.format import format_address, format_address_line, _load_formats def test_format_address(): @@ -84,3 +84,28 @@ def test_format_address_line(): addr = {"road": "Main Street", "house_number": "16", "city": "Guerntown"} expect = "16 Main Street, Guerntown, Guernsey, Channel Islands" assert format_address_line(addr, country="GG") == expect + + +def test_all_formats_valid(): + fields = { + "building": "Embassy of the Federal Republic of Germany", + "city": "Luanda", + "country": "Angola", + "country_code": "ao", + "house_number": "120", + "neighbourhood": "Mutamba", + "road": "Avenida 4 de Fevereiro", + "state": "Luanda", + "postcode": "0000", + "archipelago": "São Tomé and Príncipe", + "suburb": "Mutamba", + } + + formats = _load_formats() + for country in formats.keys(): + try: + format_address(fields, country=country) + except Exception as e: + raise RuntimeError( + f"Failed to format address for country {country}: {e}" + ) from e \ No newline at end of file From 29efdb25bb05417348ca8bffd9790ce70fc71f21 Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 19:54:18 -0400 Subject: [PATCH 09/13] based on a re-read of OpenCage, format_if should pass through if any args is non-empty --- genscripts/load_address_formats_from_opencage.py | 2 +- rigour/addresses/format.py | 2 +- tests/addresses/test_format.py | 9 ++++++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/genscripts/load_address_formats_from_opencage.py b/genscripts/load_address_formats_from_opencage.py index cb9d1f60..12f0dc5f 100644 --- a/genscripts/load_address_formats_from_opencage.py +++ b/genscripts/load_address_formats_from_opencage.py @@ -378,7 +378,7 @@ def load_address_formats_from_opencage() -> None: - literals between variables in a `first` block explicitly concatenated with ~ - relies on a macro "format_if" to handle concatenated strings in `first` blocks that contain only delimiters. - e.g. if we have {{#first}} {{{house_number}}}, {{{road}}} || {{{suburb}}} {{/first}} - - we only want to use {{{house_number}}}, {{{road}}} if both `house_number` and `road` are present. + - we only want to use {{{house_number}}}, {{{road}}} if both `house_number` and `road` are non-empty. """ commit_hash = subprocess.check_output( ["git", "ls-remote", "--heads", "https://github.com/OpenCageData/address-formatting.git", OPENCAGE_REF] diff --git a/rigour/addresses/format.py b/rigour/addresses/format.py index 54c2401a..af135e42 100644 --- a/rigour/addresses/format.py +++ b/rigour/addresses/format.py @@ -8,7 +8,7 @@ from rigour.addresses.cleaning import clean_address def format_if(value: str, *args: str) -> str: - return value if all(args) else "" + return value if any(args) else "" env = Environment() env.globals.update(format_if=format_if) diff --git a/tests/addresses/test_format.py b/tests/addresses/test_format.py index 7d83b76d..077f941b 100644 --- a/tests/addresses/test_format.py +++ b/tests/addresses/test_format.py @@ -33,13 +33,20 @@ def test_format_address(): data = { "suburb": "Beverley", - "road": "Beverley Rd", "city": "Kingston", "state": "Ontario", } expect = "Beverley\nKingston, Ontario" assert format_address(data, country="CA") == expect + data = { + "road": "Beverley Rd", + "city": "Kingston", + "state": "Ontario", + } + expect = "Beverley Rd\nKingston, Ontario" + assert format_address(data, country="CA") == expect + data = { "suburb": "Beverley", "road": "Beverley Rd", From e0891de2e8126dad0fbdcd0767f89d056beb7707 Mon Sep 17 00:00:00 2001 From: b-gran Date: Fri, 2 May 2025 19:55:25 -0400 Subject: [PATCH 10/13] improve test --- tests/addresses/test_format.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/addresses/test_format.py b/tests/addresses/test_format.py index 077f941b..abaee0ea 100644 --- a/tests/addresses/test_format.py +++ b/tests/addresses/test_format.py @@ -111,7 +111,10 @@ def test_all_formats_valid(): formats = _load_formats() for country in formats.keys(): try: - format_address(fields, country=country) + result = format_address(fields, country=country) + assert result is not None and len(result) > 0, ( + f"Formatting failed for country {country} with fields: {fields}" + ) except Exception as e: raise RuntimeError( f"Failed to format address for country {country}: {e}" From cab41af1b7b5bace7db44b5b00edeb20850d7f95 Mon Sep 17 00:00:00 2001 From: b-gran Date: Sun, 4 May 2025 15:27:17 -0400 Subject: [PATCH 11/13] move fetching to Makefile --- Makefile | 10 +- .../load_address_formats_from_opencage.py | 431 ---- resources/addresses/opencage_worldwide.yaml | 2168 +++++++++++++++++ rigour/data/addresses/formats.yml | 4 +- 4 files changed, 2177 insertions(+), 436 deletions(-) delete mode 100644 genscripts/load_address_formats_from_opencage.py create mode 100644 resources/addresses/opencage_worldwide.yaml diff --git a/Makefile b/Makefile index 8afa3f28..9a386cad 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,10 @@ test: fetch-scripts: curl -o resources/text/scripts.txt https://www.unicode.org/Public/UCD/latest/ucd/Scripts.txt -fetch: fetch-scripts +fetch-opencage-addresses: + curl -o resources/addresses/opencage_worldwide.yaml https://raw.githubusercontent.com/OpenCageData/address-formatting/refs/heads/master/conf/countries/worldwide.yaml + +fetch: fetch-scripts fetch-opencage-addresses build-iso639: python genscripts/generate_langs.py @@ -25,7 +28,10 @@ build-addresses: build-names: python genscripts/generate_names.py -build: build-iso639 build-territories build-addresses build-names +build-address-formats: + python genscripts/generate_address_formats.py + +build: build-iso639 build-territories build-addresses build-names build-address-formats docs: mkdocs build -c -d site \ No newline at end of file diff --git a/genscripts/load_address_formats_from_opencage.py b/genscripts/load_address_formats_from_opencage.py deleted file mode 100644 index 12f0dc5f..00000000 --- a/genscripts/load_address_formats_from_opencage.py +++ /dev/null @@ -1,431 +0,0 @@ -from enum import Enum -import requests -from mystace import create_mustache_tree -from mystace.mustache_tree import MustacheTreeNode -from ruamel.yaml import YAML -from ruamel.yaml.comments import CommentedMap -from ruamel.yaml.scalarstring import LiteralScalarString -from genscripts.util import CODE_PATH -import subprocess - - -# these don't exist in the rigour yaml -- by design? -DROP_VARS = {"hamlet", "place"} - -SPACE_LITERAL = " " -PRIMARY_OR_MUSTACHE_LITERAL = "||" -OR_JINJA_LITERAL = " or " - - -class M2JType(Enum): - ROOT = -1 - LITERAL = 0 - SECTION = 2 - INVERTED_SECTION = 3 - PARTIAL = 5 - VARIABLE = 6 - VARIABLE_RAW = 7 - OR = 8 - CONCAT = 9 - - -class M2JNode: - tag_type: M2JType - data: str - children: list['M2JNode'] - - def __init__(self, tag_type: M2JType, data: str = "", children: list['M2JNode'] | None = None): - self.tag_type = tag_type - self.data = data - self.children = children if children is not None else [] - - def __repr__(self) -> str: - if self.data: - return f"<{self.__class__.__name__}: {self.tag_type}, {self.data!r}>" - return f"<{self.__class__.__name__}: {self.tag_type}>" - - -def _convert_tree(t: MustacheTreeNode) -> M2JNode: - j = M2JNode(tag_type=M2JType.ROOT, data=t.data) - - frontier = [(n, j) for n in t.children or []] - while frontier: - n, curr = frontier.pop(0) - curr.children.append(M2JNode(tag_type=M2JType(n.tag_type.value), data=n.data)) - if n.children: - for c in n.children: - frontier.append((c, curr.children[-1])) - - return j - - -def split_space(t: M2JNode): - """ - Find any literal nodes that contain spaces. - Trim the spaces from the beginning and end of the literal when the literal is up against a boundary. - """ - - def _can_split(n: M2JNode): - return n.tag_type == M2JType.LITERAL and n.data != '\n' - - def _is_boundary(n: M2JNode): - return n.tag_type in {M2JType.OR} or n.tag_type == M2JType.LITERAL and n.data == '\n' - - if not t.children: - return - - i = 0 - while i < len(t.children): - n = t.children[i] - - if n.children: - split_space(n) - - if not _can_split(n) or SPACE_LITERAL not in n.data: - i += 1 - continue - - curr = n.data - assert SPACE_LITERAL in curr, f"Expected {SPACE_LITERAL} in {curr}" - - if i == 0 or _is_boundary(t.children[i - 1]): - curr = curr.lstrip(SPACE_LITERAL) - - if i == len(t.children) - 1 or _is_boundary(t.children[i + 1]): - curr = curr.rstrip(SPACE_LITERAL) - - if curr: - n.data = curr - i += 1 - else: - t.children.pop(i) - - -def split_or(t: M2JNode): - """ - Split any literal nodes that contain the OR mustache literal into separate nodes. - """ - - def _is_boundary(n: M2JNode): - """ - We stop backtracking or looking ahead when we hit a boundary. - """ - return n.tag_type != M2JType.LITERAL or n.data == '\n' - - if not t.children: - return - - i = 0 - while i < len(t.children): - n = t.children[i] - - if n.children: - split_or(n) - - if _is_boundary(n) or PRIMARY_OR_MUSTACHE_LITERAL not in n.data: - i += 1 - continue - - assert n.data.count(PRIMARY_OR_MUSTACHE_LITERAL) == 1, f"Expected 1 {PRIMARY_OR_MUSTACHE_LITERAL} in {n.data}" - - t.children.pop(i) - - idx = n.data.index(PRIMARY_OR_MUSTACHE_LITERAL) - left = n.data[:idx].rstrip(SPACE_LITERAL) - right = n.data[idx + len(PRIMARY_OR_MUSTACHE_LITERAL) :].lstrip(SPACE_LITERAL) - - if left: - t.children.insert(i, M2JNode(tag_type=M2JType.LITERAL, data=left)) - i += 1 - - t.children.insert(i, M2JNode(tag_type=M2JType.OR, data=PRIMARY_OR_MUSTACHE_LITERAL)) - i += 1 - - if right: - t.children.insert(i, M2JNode(tag_type=M2JType.LITERAL, data=right)) - i += 1 - - -def drop_vars_and_simplify(t: M2JNode): - def _is_boundary(n: M2JNode): - return n.tag_type in {M2JType.OR} or n.tag_type == M2JType.LITERAL and n.data == '\n' - - def _is_simple_section(n: M2JNode): - return n.tag_type in {M2JType.SECTION, M2JType.INVERTED_SECTION} and all( - c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW, M2JType.LITERAL} for c in n.children - ) - - if not t.children: - return - - i = 0 - while i < len(t.children): - c = t.children[i] - drop_vars_and_simplify(c) - - is_against_boundary = ( - (i > 0 and _is_boundary(t.children[i - 1])) - or (i + 1 < len(t.children) and _is_boundary(t.children[i + 1])) - or i == 0 - or i == len(t.children) - 1 - ) - - # remove empty sections - if c.tag_type == M2JType.SECTION and not c.children: - t.children.pop(i) - continue - - is_whitespace = c.tag_type == M2JType.LITERAL and c.data.strip(SPACE_LITERAL) == "" - if is_whitespace and is_against_boundary: - # remove empty literals - t.children.pop(i) - continue - - if c.tag_type == M2JType.OR and is_against_boundary: - # remove dangling OR - t.children.pop(i) - continue - - if c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW} and c.data in DROP_VARS: - # remove dropped variables - t.children.pop(i) - if i > 0: - i -= 1 - continue - - # inline sections that contain only variables and literals - if _is_simple_section(c): - t.children.pop(i) - - j = i - for section_child in c.children: - t.children.insert(j, section_child) - j += 1 - - # merge literals at the beginning of the section - if ( - i > 0 - and t.children[i - 1].tag_type == M2JType.LITERAL - and t.children[i].tag_type == M2JType.LITERAL - and not _is_boundary(t.children[i - 1]) - ): - t.children[i - 1].data += t.children[i].data - t.children.pop(i) - i -= 1 - - # merge literals at the end of the section - if ( - j > 0 - and t.children[j - 1].tag_type == M2JType.LITERAL - and t.children[j].tag_type == M2JType.LITERAL - and not _is_boundary(t.children[j]) - ): - t.children[j - 1].data += t.children[j].data - t.children.pop(j) - - i += 1 - - -def split_string_concat(t: M2JNode): - if not t.children: - return - - for c in t.children: - split_string_concat(c) - - # outside of sections, we don't need to handle concats around ORs - if t.tag_type not in {M2JType.SECTION, M2JType.INVERTED_SECTION}: - return - - args: list[list[M2JNode]] = [[]] - for c in t.children: - if c.tag_type == M2JType.OR: - args.append([]) - else: - args[-1].append(c) - - # if we have only one argument, we can just return - if len(args) == 1: - return - - t.children = [] - for i, arg in enumerate(args): - if len(arg) == 1: - t.children.append(arg[0]) - else: - new_node = M2JNode(tag_type=M2JType.CONCAT, children=arg) - t.children.append(new_node) - if i < len(args) - 1: - t.children.append(M2JNode(tag_type=M2JType.OR)) - - return t - - -def jinja_tree_to_template(t: M2JNode, depth: int = 0) -> str: - if t.tag_type == M2JType.ROOT: - template = "" - for c in t.children: - template += jinja_tree_to_template(c, depth + 1) - return template - elif t.tag_type in {M2JType.SECTION, M2JType.INVERTED_SECTION}: - assert depth < 2, f'Too many nested sections: {depth}' - template = "{{" - for c in t.children: - template += jinja_tree_to_template(c, depth + 1) - template += "}}" - return template - elif t.tag_type == M2JType.CONCAT: - formatted_val = ' ~ '.join([jinja_tree_to_template(c, depth + 1) for c in t.children]) - vars = [c for c in t.children if c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW}] - return f'format_if({formatted_val}, {", ".join([c.data for c in vars])})' - elif t.tag_type == M2JType.LITERAL: - if depth < 2: - return t.data - else: - return f"'{t.data}'" - elif t.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW}: - if depth < 2: - return '{{' + t.data + '}}' - else: - return t.data - elif t.tag_type == M2JType.OR: - return OR_JINJA_LITERAL - else: - raise ValueError(f"Got unexpected tag type {t.tag_type}") - - -def mustache_to_jinja(template: str) -> str: - jinja_template = "" - - tree = _convert_tree(create_mustache_tree(template)) - split_or(tree) - split_space(tree) - drop_vars_and_simplify(tree) - split_string_concat(tree) - - jinja_template = jinja_tree_to_template(tree) - return collapse_newlines(jinja_template) - - -def collapse_newlines(text: str) -> str: - prev = "" - while prev != text: - prev = text - text = text.replace("\n\n", "\n").replace("\n ", "\n").strip() - return text - - -def update_mustache_field(yaml_obj: dict, field: str) -> LiteralScalarString: - current_anchor = yaml_obj[field].yaml_anchor() - txt = mustache_to_jinja(str(yaml_obj[field])) - if not txt.endswith("\n"): - txt += "\n" - yaml_obj[field] = LiteralScalarString(txt) - if current_anchor is not None: - yaml_obj[field].yaml_set_anchor(current_anchor.value, always_dump=current_anchor.always_dump) - return yaml_obj[field] - - -def normalize_add_component(country: CommentedMap) -> str | None: - trailing_comment: str | None = None - - # grab and detach the trailing comment (if any) - if len(country.ca.items) > 0: - last_key = list(country.keys())[-1] - last_comment = country.ca.items[last_key][2] - if last_comment is not None: - trailing_comment = country.ca.items[last_key][2].value - del country.ca.items[last_key] - - if trailing_comment: - if trailing_comment.startswith("\n\n"): - trailing_comment = f"\n{trailing_comment[2:]}" - trailing_comment = "\n".join([c.lstrip("# ") for c in trailing_comment.split("\n")]) - trailing_comment.rstrip("\n") - - # build the nested mapping - if "add_component" in country: - add_component_val = country["add_component"] - assert isinstance( - add_component_val, str - ), f"Invalid add_component type: {type(add_component_val)} for {country}" - fields = add_component_val.split("=") - - assert len(fields) == 2, f"Invalid add_component format: {add_component_val} for {country}" - k, v = fields - ac_map = CommentedMap({k: v}) - else: - ac_map = CommentedMap() - - if "change_country" in country: - ac_map["country"] = country.pop("change_country") - - country["add_component"] = ac_map - - return trailing_comment - - -OPENCAGE_FORMAT_YAML_TEMPLATE = 'https://raw.githubusercontent.com/OpenCageData/address-formatting/{hash}/conf/countries/worldwide.yaml' -OPENCAGE_REF = 'refs/heads/master' -FORMATS_DEST_PATH = CODE_PATH / "addresses" / "formats.yml" - - -def load_address_formats_from_opencage() -> None: - """ - Load address formats from OpenCageData's address-formatting repo. - Converts the mustache templates to jinja2 format. - Uses the following conventions for mustache to jinja2 conversion: - - literals between variables in a `first` block explicitly concatenated with ~ - - relies on a macro "format_if" to handle concatenated strings in `first` blocks that contain only delimiters. - - e.g. if we have {{#first}} {{{house_number}}}, {{{road}}} || {{{suburb}}} {{/first}} - - we only want to use {{{house_number}}}, {{{road}}} if both `house_number` and `road` are non-empty. - """ - commit_hash = subprocess.check_output( - ["git", "ls-remote", "--heads", "https://github.com/OpenCageData/address-formatting.git", OPENCAGE_REF] - ).decode("utf-8").split()[0] - - github_raw_url = OPENCAGE_FORMAT_YAML_TEMPLATE.format(hash=commit_hash) - opencage_yaml_text = requests.get(github_raw_url).text - - yaml = YAML(typ="rt") - yaml.preserve_quotes = True - yaml.indent(mapping=2, sequence=4, offset=2) - - yaml_obj = yaml.load(opencage_yaml_text) - - orig_fields_by_value = {} - new_fields = {} - attach_trailing = None - - for k, v in yaml_obj.items(): - if attach_trailing: - yaml_obj.yaml_set_comment_before_after_key(k, before=attach_trailing, after=None) - attach_trailing = None - - if k.startswith("generic") or k.startswith("fallback"): - orig_fields_by_value[v] = k - new_fields[k] = update_mustache_field(yaml_obj, k) - elif isinstance(v, dict): - for k2, v2 in v.items(): - # update references to the original mustache fields for countries that reference them. - if isinstance(v2, LiteralScalarString) and v2 in orig_fields_by_value: - v[k2] = new_fields[orig_fields_by_value[v2]] - - # one-off templates need to be converted to jinja. - elif k2 in {"address_template", "fallback_template"}: - update_mustache_field(v, k2) - - if "change_country" in v or "add_component" in v: - attach_trailing = normalize_add_component(v) - - - with open(FORMATS_DEST_PATH, "w") as outfile: - outfile.write("# Generated from OpenCageData address-formatting repo\n") - outfile.write(f"# commit: {commit_hash}\n") - outfile.write(f"# {github_raw_url}\n") - yaml.dump(yaml_obj, outfile) - - -if __name__ == "__main__": - load_address_formats_from_opencage() - diff --git a/resources/addresses/opencage_worldwide.yaml b/resources/addresses/opencage_worldwide.yaml new file mode 100644 index 00000000..58420f53 --- /dev/null +++ b/resources/addresses/opencage_worldwide.yaml @@ -0,0 +1,2168 @@ +# +# generic mappings, specific territories get mapped to these +# +# postcode before city +generic1: &generic1 | + {{{attention}}} + {{{house}}} + {{#first}} {{{road}}} || {{{place}}} || {{{hamlet}}} {{/first}} {{{house_number}}} + {{{postcode}}} {{#first}} {{{postal_city}}} || {{{town}}} || {{{city}}} || {{{village}}} || {{{municipality}}} || {{{hamlet}}} || {{{county}}} || {{{state}}} {{/first}} + {{{archipelago}}} + {{{country}}} + +# postcode after city +generic2: &generic2 | + {{{attention}}} + {{{house}}}, {{{quarter}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{village}}} || {{{town}}} || {{{city}}} || {{{municipality}}} || {{{hamlet}}} || {{{county}}} {{/first}} {{{postcode}}} + {{#first}} {{{country}}} || {{{state}}} {{/first}} + +# postcode before city +generic3: &generic3 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{{place}}} + {{{postcode}}} {{#first}} {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{city}}} || {{{municipality}}} || {{{state}}} {{/first}} + {{{country}}} + +# postcode after state +generic4: &generic4 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{suburb}}} || {{{municipality}}} || {{{county}}} {{/first}}, {{#first}} {{{state_code}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + +# no postcode +generic5: &generic5 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{state_district}}} || {{{state}}} {{/first}} + {{{country}}} + +# no postcode, county +generic6: &generic6 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} + {{{county}}} + {{{state}}} + {{{country}}} + +# city, postcode +generic7: &generic7 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}}{{/first}}, {{{postcode}}} + {{{country}}} + +# postcode and county +generic8: &generic8 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} {{#first}} {{{county_code}}} || {{{county}}} {{/first}} + {{{country}}} + +generic9: &generic9 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{state_district}}} {{/first}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} + {{{country}}} + +generic10: &generic10 | + {{{attention}}} + {{{house}}} + {{{road}}}, {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{state}}} + {{{country}}} + {{{postcode}}} + +generic11: &generic11 | + {{{country}}} + {{{state}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{suburb}}} + {{{road}}}, {{{house_number}}} + {{{house}}} + {{{attention}}} + +# city - postcode +generic12: &generic12 | + {{{attention}}} + {{{house}}} + {{{house_number}}}, {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{state_district}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} - {{{postcode}}} + {{{state}}} + {{{country}}} + +generic13: &generic13 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} || {{{region}}} {{/first}} {{#first}} {{{state_code}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + +# postcode and state +generic14: &generic14 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state_district}}} {{/first}} + {{{state}}} + {{{country}}} + +# postcode and comma before house number +generic15: &generic15 | + {{{attention}}} + {{{house}}} + {{{road}}}, {{{house_number}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} || {{{state}}} || {{{county}}} {{/first}} + {{{country}}} + +# no postcode, no state, just city +generic16: &generic16 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} || {{{county}}} || {{{state_district}}} || {{{state}}} {{/first}} + {{{country}}} + +# no postcode, no state, just city +generic17: &generic17 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} || {{{county}}} || {{{state_district}}} || {{{state}}} {{/first}} + {{{country}}} + +# no postcode, just city comma after house number +generic18: &generic18 | + {{{attention}}} + {{{house}}} + {{{house_number}}}, {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} || {{{state}}} {{/first}} + {{{country}}} + +# suburb and postcode after city +generic19: &generic19 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}} + {{{country}}} + +# suburb and postcode after city +generic20: &generic20 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}} + {{{country}}} + +# suburb and city, no postcode +generic21: &generic21 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} + {{{country}}} + +# comma after housenumber, postcode before city +generic22: &generic22 | + {{{attention}}} + {{{house}}} + {{{house_number}}}, {{{road}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} + {{{country}}} + +# postcode on own line +generic23: &generic23 | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{{quarter}}} + {{#first}} {{{village}}} || {{{town}}} || {{{city}}} || {{{municipality}}} || {{{hamlet}}} || {{{county}}} {{/first}} + {{{postcode}}} + {{#first}} {{{country}}} || {{{state}}} {{/first}} + +fallback1: &fallback1 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{place}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} || {{{island}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} + {{#first}} {{{county}}} || {{{state_district}}} || {{{state}}} || {{{region}}} || {{{island}}}, {{{archipelago}}} {{/first}} + {{{country}}} + +fallback2: &fallback2 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{place}}} + {{#first}} {{{suburb}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{municipality}}} || {{{county}}} || {{{island}}} || {{{state_district}}} {{/first}}, {{#first}} {{{state}}} || {{{state_code}}} {{/first}} + {{{country}}} + +fallback3: &fallback3 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{place}}} + {{#first}} {{{suburb}}} || {{{island}}} {{/first}} + {{#first}} {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} + {{#first}} {{{town}}} || {{{city}}}{{/first}} + {{{county}}} + {{#first}} {{{state}}} || {{{state_code}}} {{/first}} + {{{country}}} + +fallback4: &fallback4 | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{place}}} + {{{suburb}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} || {{{county}}} {{/first}} + {{#first}} {{{state}}} || {{{county}}} {{/first}} + {{{country}}} + +default: + address_template: *generic1 + fallback_template: *fallback1 + +# country / territory specific mappings +# please keep in alpha order by country code +# + + +# Andorra +AD: + address_template: *generic3 + +# United Arab Emirates +AE: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{state_district}}} || {{{state}}} {{/first}} + {{{country}}} + +# Afghanistan +AF: + address_template: *generic21 + +# Antigua and Barbuda +AG: + address_template: *generic16 + +# Anguilla +AI: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{postcode}}} {{{country}}} + +# Albania +AL: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{city_district}}} || {{{municipality}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{country}}} + postformat_replace: + # fix the postcode to add - after numbers + - ["\n(\\d{4}) ([^,]*)\n","\n$1-$2\n"] + +# Armenia +AM: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{{postcode}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{state_district}}} || {{{state}}} {{/first}} + {{{country}}} + +# Angola +AO: + address_template: *generic7 + +# Antarctica +AQ: + address_template: | + {{{house}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{country}}} || {{{continent}}} {{/first}} + fallback_template: | + {{{house}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{country}}} || {{{continent}}} {{/first}} + +# Argentina +AR: + address_template: *generic9 + replace: + - ["^Autonomous City of ",""] + postformat_replace: + # fix the postcode to make it \w\d\d\d\d \w\w\w + - ["\n(\\w\\d{4})(\\w{3}) ","\n$1 $2 "] + +# American Samoa +AS: + use_country: US + change_country: United States of America + add_component: state=American Samoa + +# Austria +AT: + address_template: *generic1 + +# Australia +AU: + address_template: *generic13 + +# Aruba +AW: + address_template: *generic17 + +# Åland Islands, part of Finnland +AX: + use_country: FI + change_country: Åland, Finland + +# Azerbaijan +AZ: + address_template: *generic3 + +# Bosnia +BA: + address_template: *generic1 + +# Barbados +BB: + address_template: *generic16 + +# Bangladesh +BD: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{state_district}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} - {{{postcode}}} + {{{country}}} + +# Belgium +BE: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}} {{#first}} {{{postal_city}}} || {{{town}}} || {{{city}}} || {{{village}}} || {{{municipality}}} || {{{hamlet}}} || {{{county}}} || {{{state}}} {{/first}} + {{{archipelago}}} + {{{country}}} + +# Burkina Faso +BF: + address_template: *generic6 + +# Bulgaria - https://en.wikipedia.org/wiki/Address#Bulgaria +BG: + address_template: *generic19 + +# Bahrain +BH: + address_template: *generic2 + +# Burundi +BI: + address_template: *generic17 + +# Benin +BJ: + address_template: *generic18 + +# Saint Barthélemy - same as FR +BL: + use_country: FR + change_country: Saint-Barthélemy, France + +# Bermuda +BM: + address_template: *generic2 + +# Brunei +BN: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}}, {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} + {{#first}} {{{county}}} || {{{state_district}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + + +# Bolivia +BO: + address_template: *generic17 + replace: + - ["^Municipio Nuestra Senora de ",""] + +# Dutch Caribbean / Bonaire +BQ: + use_country: NL + change_country: Caribbean Netherlands + +# Brazil +BR: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}}{{#first}}, {{{quarter}}}{{/first}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{village}}} || {{{hamlet}}}{{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} {{/first}} - {{#first}} {{{state_code}}} || {{{state}}} {{/first}} + {{{postcode}}} + {{{country}}} + postformat_replace: + - ["\\b(\\d{5})(\\d{3})\\b","$1-$2"] + +# Bahamas +BS: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} + {{{county}}} + {{{country}}} + +# Bhutan +BT: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}}, {{{house}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + +# Bouvet Island +BV: + use_country: "NO" + change_country: Bouvet Island, Norway + +# Botswana +BW: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{country}}} + +# Belarus +BY: + address_template: *generic11 + +# Belize +BZ: + address_template: *generic16 + +# Canada - https://en.wikipedia.org/wiki/Address#Canada +CA: + address_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{house_number}}} {{{road}}} || {{{suburb}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{county}}} || {{{state_district}}} {{/first}}, {{#first}} {{{state_code}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + fallback_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{house_number}}} {{{road}}} || {{{suburb}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{county}}} || {{{state_district}}} || {{{region}}}{{/first}}, {{#first}} {{{state}}} || {{{state_code}}} {{/first}} {{{postcode}}} + {{{country}}} + postformat_replace: + # fix the postcode to make it \w\w\w \w\w\w + - [" ([A-Za-z]{2}) ([A-Za-z]\\d[A-Za-z])(\\d[A-Za-z]\\d)\n"," $1 $2 $3\n"] + +#Canada - English +CA_en: + address_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{house_number}}} {{{road}}} || {{{suburb}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{county}}} || {{{state_district}}} {{/first}}, {{#first}} {{{state_code}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + fallback_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{house_number}}} {{{road}}} || {{{suburb}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{county}}} || {{{state_district}}} {{/first}}, {{#first}} {{{state_code}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + postformat_replace: + # fix the postcode to make it \w\w\w \w\w\w + - [" ([A-Za-z]{2}) ([A-Za-z]\\d[A-Za-z])(\\d[A-Za-z]\\d)\n"," $1 $2 $3\n"] + +#Canada - French Quebec +CA_fr: + address_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{house_number}}}, {{{road}}} || {{{suburb}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{county}}} || {{{state_district}}} {{/first}} {{#first}} ({{{state_code}}}) || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + postformat_replace: + # fix the postcode to make it \w\w\w \w\w\w + - [" ([A-Za-z]{2}) ([A-Za-z]\\d[A-Za-z])(\\d[A-Za-z]\\d)\n"," $1 $2 $3\n"] + +# Cocos (Keeling) Islands +CC: + use_country: AU + change_country: Australia + +# Democratic Republic of the Congo +CD: + address_template: *generic18 + +# Central African Republic +CF: + address_template: *generic17 + +# Republic of the Congo / Congo-Brazzaville +CG: + address_template: *generic18 + +# Switzerland +CH: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}} {{#first}} {{{postal_city}}} || {{{town}}} || {{{city}}} || {{{municipality}}} || {{{village}}} || {{{hamlet}}} || {{{county}}} || {{{state}}} {{/first}} + {{{country}}} + replace: + - ["Verwaltungskreis",""] + - ["Verwaltungsregion",""] + - [" administrative district",""] + - [" administrative region",""] + +# Côte d'Ivoire +CI: + address_template: *generic16 + +# Cook Islands +CK: + address_template: *generic16 + +# Chile - https://en.wikipedia.org/wiki/Address#Chile +CL: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}} {{#first}} {{{postal_city}}} || {{{town}}} || {{{city}}} || {{{village}}} || {{{municipality}}} || {{{hamlet}}} || {{{county}}} || {{{state}}} {{/first}} + {{{region}}} + {{{country}}} + +# Cameroon +CM: + address_template: *generic17 + +# China +CN: + address_template: | + {{{postcode}}} {{{country}}} + {{#first}} {{{state_code}}} || {{{state}}} || {{{state_district}}} || {{{region}}}{{/first}} + {{{county}}} + {{#first}}{{{city}}} || {{{town}}} || {{{municipality}}}|| {{{village}}}|| {{{hamlet}}}{{/first}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{road}}} {{{house_number}}} + {{{house}}} + {{{attention}}} + +# China - English +CN_en: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{county}}} + {{#first}}{{{city}}} || {{{town}}} || {{{municipality}}}|| {{{village}}}|| {{{hamlet}}}{{/first}} + {{#first}} {{{state_code}}} || {{{state}}} || {{{state_district}}} || {{{region}}}{{/first}} + {{{country}}} {{{postcode}}} + +# China - Chinese Simplified +CN_zh: + address_template: | + {{{postcode}}} {{{country}}} + {{#first}} {{{state_code}}} || {{{state}}} || {{{state_district}}} || {{{region}}}{{/first}} + {{{county}}} + {{#first}}{{{city}}} || {{{town}}} || {{{municipality}}}|| {{{village}}}|| {{{hamlet}}}{{/first}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{road}}} {{{house_number}}} + {{{house}}} + {{{attention}}} + +# Colombia +CO: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{#first}} {{{state_code}}} || {{{state}}} {{/first}} + {{{country}}} + postformat_replace: + - ["Localidad "," "] + - ["(Bogot[áa]),? (Distrito Capital|Capital District)",$1] + - ["(Bogot[áa]), Bogot[áa]","$1"] + +# Costa Rica +CR: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{state}}}, {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{postcode}}} {{{country}}} + +# Cuba +CU: + address_template: *generic7 + +# Cape Verde +CV: + address_template: *generic1 + postformat_replace: + # fix the postcode to add - after numbers + - ["\n(\\d{4}) ([^,]*)\n","\n$1-$2\n"] + +# Curaçao +CW: + address_template: *generic17 + +# Christmas Island - same as Australia +CX: + use_country: AU + add_component: state=Christmas Island + change_country: Australia + +# Cyprus +CY: + address_template: *generic1 + +# Czech Republic +CZ: + address_template: *generic1 + replace: + - ["^Capital City of ",""] + postformat_replace: + # fix the postcode to make it \d\d\d \d\d + - ["\n(\\d{3})(\\d{2}) ","\n$1 $2 "] + +# Germany +DE: + address_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{road}}} || {{{place}}} || {{{hamlet}}} {{/first}} {{{house_number}}} + {{{postcode}}} {{#first}} {{{village}}} {{{postal_city}}} || {{{town}}} || {{{city}}} || {{{municipality}}} || {{{hamlet}}} || {{{county}}} || {{{state}}} {{/first}} + {{{archipelago}}} + {{{country}}} + fallback_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{road}}} || {{{place}}} || {{{hamlet}}} {{/first}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{village}}} || {{{town}}} || {{{city}}} || {{{hamlet}}} || {{{municipality}}} || {{{county}}} {{/first}} + {{#first}} {{{state}}} || {{{state_district}}} {{/first}} + {{{country}}} + + replace: + - ["^Stadtteil ",""] + - ["^Stadtbezirk (\\d+)",""] + - ["^Ortsbeirat (\\d+) :",""] + - ["^Gemeinde ",""] + - ["^Gemeindeverwaltungsverband ",""] + - ["^Landkreis ",""] + - ["^Kreis ",""] + - ["^Grenze ",""] + - ["^Free State of ",""] + - ["^Freistaat ",""] + - ["^Regierungsbezirk ",""] + - ["^Stadtgebiet ",""] + - ["^Gemeindefreies Gebiet ",""] + - ["city=Alt-Berlin","Berlin"] + postformat_replace: + - ["Berlin\nBerlin","Berlin"] + - ["Bremen\nBremen","Bremen"] + - ["Hamburg\nHamburg","Hamburg"] + +# Djibouti +DJ: + address_template: *generic16 + replace: + - ["city=Djibouti","Djibouti-Ville"] + +# Denmark +DK: + address_template: *generic1 + replace: + - ["state=Capital Region of Denmark","Capital Region"] + - ["^Region of ",""] + +# Dominica +DM: + address_template: *generic16 + +# Dominican Republic +DO: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{{state}}} + {{{postcode}}} + {{{country}}} + postformat_replace: + - [", Distrito Nacional",", DN"] + +# Algeria +DZ: + address_template: *generic3 + +# Ecuador +EC: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}} + {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{country}}} + +# Egypt +EG: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{postcode}}} + {{{country}}} + +# Estonia +EE: + address_template: *generic1 + +# Western Sahara +EH: + address_template: *generic17 + +# Eritrea +ER: + address_template: *generic17 + +# Spain +ES: + address_template: *generic15 + fallback_template: *fallback4 + + replace: + - ["Autonomous Community of the ",""] + - ["Autonomous Community of ",""] + - ["^Community of ",""] + +# Ethiopia +ET: + address_template: *generic1 + +# Finnland +FI: + address_template: *generic1 + +# Fiji +FJ: + address_template: *generic16 + +# Falkland Islands +FK: + use_country: GB + change_country: Falkland Islands, United Kingdom + +# Federated States of Micronesia +FM: + use_country: US + change_country: United States of America + add_component: state=Micronesia + +# Faroe Islands +FO: + address_template: *generic1 + postformat_replace: + - ["Territorial waters of Faroe Islands","Faroe Islands"] + +# France +FR: + address_template: *generic3 + replace: + - ["Polynésie française, Îles du Vent \\(eaux territoriales\\)","Polynésie française"] + - ["France, Mayotte \\(eaux territoriales\\)","Mayotte, France"] + - ["France, La Réunion \\(eaux territoriales\\)","La Réunion, France"] + - ["Grande Terre et récifs d'Entrecasteaux",""] + - ["France, Nouvelle-Calédonie","Nouvelle-Calédonie, France"] + - ["\\(eaux territoriales\\)",""] + - ["state= \\(France\\)$",""] + - ["Paris (\\d+)(\\w+) Arrondissement$","Paris"] + + +# Gabon +GA: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{municipality}}} || {{{county}}} || {{{state_district}}} || {{{state}}} {{/first}} + {{{country}}} + +GB: + address_template: *generic23 + fallback_template: *fallback3 + replace: + - ["village= CP$",""] + - ["^Borough of ",""] + - ["^County( of)? ",""] + - ["^Parish of ",""] + - ["^Greater London","London"] + - ["^London Borough of ",""] + - ["Royal Borough of ",""] + - ["County Borough of ",""] + postformat_replace: + - ["London, London","London"] + - ["London, Greater London","London"] + - ["City of Westminster","London"] + - ["City of Nottingham","Nottingham"] + - [", United Kingdom$","\nUnited Kingdom"] + - ["London\nEngland\nUnited Kingdom","London\nUnited Kingdom"] + +# Grenada +GD: + address_template: *generic17 + +# Georgia +GE: + address_template: *generic1 + +# French Guiana - same as FR +GF: + use_country: FR + change_country: France + +# Guernsey - same format as UK, but not part of UK +GG: + use_country: GB + change_country: Guernsey, Channel Islands + +# Ghana +GH: + address_template: *generic16 + +# Gibraltar +GI: + address_template: *generic16 + +# Greenland +GL: + address_template: *generic1 + +# The Gambia +GM: + address_template: *generic16 + +# Guinea +GN: + address_template: *generic14 + +# Guadeloupe - same as FR +GP: + use_country: FR + change_country: Guadeloupe, France + +# Equatorial Guinea +GQ: + address_template: *generic17 + +# Greece +GR: + address_template: *generic1 + replace: + - ["Municipal Unit of ",""] + - ["Regional Unit of ",""] + postformat_replace: + # fix the postcode to make it \d\d\d \d\d + - ["\n(\\d{3})(\\d{2}) ","\n$1 $2 "] + +# South Georgia and the South Sandwich Islands - same as UK +GS: + use_country: GB + change_country: United Kingdom + add_component: county=South Georgia + +# Guatemala +GT: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}}-{{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} || {{{state}}} {{/first}} + {{{country}}} + postformat_replace: + - ["\n(\\d{5})- ","\n$1-"] + - ["\n -","\n"] + +# Guam +GU: + use_country: US + change_country: United States of America + add_component: state=Guam + +# Guinea-Bissau +GW: + address_template: *generic1 + +# Guyana +GY: + address_template: *generic16 + +# Hong Kong +HK: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{{state_district}}} + {{#first}} {{{state}}} || {{{country}}} {{/first}} + +# Hong Kong - English +HK_en: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{{state_district}}} + {{{state}}} + {{{country}}} + +# Hong Kong - Chinese +HK_zh: + address_template: | + {{{country}}} + {{{state}}} + {{{state_district}}} + {{{road}}} + {{{house_number}}} + {{{house}}} + {{{attention}}} + + +# Heard Island and McDonald Islands - same as Australia +HM: + use_country: AU + change_country: Australia + add_component: state=Heard Island and McDonald Islands + +# Honduras +HN: + address_template: *generic1 + +# Croatia +HR: + address_template: *generic1 + +# Haiti +HT: + address_template: *generic1 + postformat_replace: + - [" Commune de "," "] + +# Hungary +# https://e-nyelv.hu/2014-09-19/lakcim/ +HU: + address_template: | + {{{attention}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{road}}} {{{house_number}}}. + {{{country}}} + +# Indonesia +# https://en.wikipedia.org/wiki/Address_%28geography%29#Indonesia +ID: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}} + {{{state}}} + {{{country}}} + +# Ireland +# https://en.wikipedia.org/wiki/Postal_addresses_in_the_Republic_of_Ireland +IE: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} + {{{county}}} + {{{postcode}}} + {{{country}}} + replace: + - [" City$",""] + - ["The Municipal District of ",""] + - ["The Metropolitan District of ",""] + - ["Municipal District",""] + - ["Electoral Division",""] + postformat_replace: + - ["Dublin\nCounty Dublin","Dublin"] + - ["Dublin\nLeinster","Dublin"] + - ["Galway\nCounty Galway","Galway"] + - ["Kilkenny\nCounty Kilkenny","Kilkenny"] + - ["Limerick\nCounty Limerick","Limerick"] + - ["Tipperary\nCounty Tipperary","Tipperary"] + # fix eircode formatting + #- ["\n(\\d{4})(\\w{2}) ","\n$1 $2 "] + - ["\n(([AC-FHKNPRTV-Y][0-9]{2}|D6W))[ -]?([0-9AC-FHKNPRTV-Y]{4})", "\n$1 $3"] + + +# Israel +IL: + address_template: *generic1 + +# Isle of Man +IM: + use_country: GB + +# India +# http://en.wikipedia.org/wiki/Address_%28geography%29#India +IN: + address_template: *generic12 + postformat_replace: + # deal with - but no postcode + - [" -\n","\n"] + +# British Indian Ocean Territory - same as UK +IO: + use_country: GB + change_country: British Indian Ocean Territory, United Kingdom + +# Iraq +IQ: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{#first}} {{{city_district}}} || {{{neighbourhood}}} || {{{suburb}}} {{/first}} + {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{state}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{postcode}}} + {{{country}}} + +# Iran +IR: + address_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{road}}} + {{{house_number}}} + {{#first}}{{{province}}} || {{{state}}} || {{{state_district}}}{{/first}} + {{{postcode}}} + {{{country}}} + +IR_en: + address_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{road}}} + {{{house_number}}} + {{#first}}{{{state}}} || {{{state_district}}}{{/first}} + {{{postcode}}} + {{{country}}} + +IR_fa: + address_template: | + {{{country}}} + {{{state}}} + {{{state_district}}} + {{#first}} {{{state}}} || {{{province}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{road}}} + {{{house_number}}} + {{{house}}} + {{{attention}}} + {{{postcode}}} + +# Iceland +IS: + address_template: *generic1 + +# Italy +IT: + address_template: *generic8 + replace: + - ["Città metropolitana di ",""] + - ["Metropolitan City of ",""] + - ["^Provincia di ",""] + postformat_replace: + - ["Vatican City\nVatican City$","\nVatican City"] + - ["Città del Vaticano\nCittà del Vaticano$","Città del Vaticano\n"] + +# Jersey - same format as UK, but not part of UK +JE: + use_country: GB + change_country: Jersey, Channel Islands + +# Jamaica +JM: + address_template: *generic20 + +# Jordan +JO: + address_template: *generic1 + +# Japan +JP: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{#first}} {{{state}}} || {{{state_district}}} {{/first}} {{{postcode}}} + {{{country}}} + postformat_replace: + # fix the postcode to make it \d\d\d-\d\d\d\d + - [" (\\d{3})(\\d{4})\n"," $1-$2\n"] + + + +# Japan - English +JP_en: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{#first}} {{{state}}} || {{{state_district}}} {{/first}} {{{postcode}}} + {{{country}}} + postformat_replace: + # fix the postcode to make it \d\d\d-\d\d\d\d + - [" (\\d{3})(\\d{4})\n"," $1-$2\n"] + + +# Japan - Japanese +JP_ja: + address_template: | + {{{country}}} + {{{postcode}}} + {{#first}} {{{state}}} || {{{state_district}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{road}}} + {{{house_number}}} + {{{house}}} + {{{attention}}} + postformat_replace: + # fix the postcode to make it \d\d\d-\d\d\d\d + - [" (\\d{3})(\\d{4})\n"," $1-$2\n"] + +# Kenya +KE: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{state}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{postcode}}} + {{{country}}} + +# Kyrgyzstan +KG: + address_template: *generic11 + +# Cambodia +KH: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}} + {{{country}}} + +# Kiribati +KI: + address_template: *generic17 + +# Comoros +KM: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{country}}} + +# Saint Kitts and Nevis +KN: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{#first}} {{{state}}} || {{{island}}} {{/first}} + {{{country}}} + +# Democratic People's Republic of Korea / North Korea +KP: + address_template: *generic21 + +# Republic of Korea / South Korea -- https://en.wikipedia.org/wiki/Address#South_Korea +KR: + address_template: | + {{{country}}} + {{{state}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} {{{road}}} {{{house_number}}} + {{{attention}}} + {{{postcode}}} + fallback_template: | + {{{country}}} + {{{state}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{attention}}} + +# South Korea - English +KR_en: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}}, {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}} + {{{state}}} + {{{country}}} + +# South Korea - Korean +KR_ko: + address_template: | + {{{country}}} + {{{state}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} {{{road}}} {{{house_number}}} + {{{attention}}} + {{{postcode}}} + fallback_template: | + {{{country}}} + {{{state}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{attention}}} + +# Kuwait +KW: + address_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + + {{{road}}} + {{{house_number}}} {{{house}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{country}}} + +# Cayman Islands +KY: + address_template: *generic2 + +# Kazakhstan +KZ: + address_template: *generic11 + +# Laos +LA: + address_template: *generic22 + +# Lebanon +LB: + address_template: *generic2 + postformat_replace: + # fix the postcode to make it nonbreaking space + - [" (\\d{4}) (\\d{4})\n"," $1 $2\n"] + +# Saint Lucia +LC: + address_template: *generic17 + +# Liechtenstein, same as Switzerland +LI: + use_country: CH + +# Sri Lanka +LK: + address_template: *generic20 + +# Liberia +LR: + address_template: *generic1 + +# Lesotho +LS: + address_template: *generic2 + +# Lithuania +LT: + address_template: *generic1 + +# Luxemburg +LU: + address_template: *generic3 + +# Latvia +LV: + address_template: *generic7 + +# Libya +LY: + address_template: *generic17 + +# Morocco +MA: + address_template: *generic3 + +# Monaco +MC: + address_template: *generic3 + +# Moldova +MD: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}}, {{{house_number}}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} + {{{country}}} + +# Montenegro +ME: + address_template: *generic1 + +# Collectivité de Saint-Martin +MF: + use_country: FR + change_country: France + +# Marshall Islands +MH: + use_country: US + add_component: state=Marshall Islands + +# Madagascar +MG: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{country}}} + +# North Macedonia +MK: + address_template: *generic1 + +# Mali +ML: + address_template: *generic17 + +# Myanmar (Burma) +MM: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}}, {{{postcode}}} + {{{country}}} + + +# Mongolia +MN: + address_template: | + {{{attention}}} + {{{house}}} + {{{city_district}}} + {{#first}} {{{suburb}}} || {{{neighbourhood}}} {{/first}} + {{{road}}} + {{{house_number}}} + {{{postcode}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{country}}} + +# Macau +MO: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{village}}} || {{{hamlet}}} || {{{state_district}}} {{/first}} + {{{country}}} + +# Macao - Portuguese +MO_pt: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{village}}} || {{{hamlet}}} || {{{state_district}}} {{/first}} + {{{country}}} + +# Macao - Chinese +MO_zh: + address_template: | + {{{country}}} + {{#first}} {{{suburb}}} || {{{village}}} || {{{hamlet}}} || {{{state_district}}} {{/first}} + {{{road}}} + {{{house_number}}} + {{{house}}} + {{{attention}}} + +# Northern Mariana Islands +MP: + use_country: US + change_country: United States of America + add_component: state=Northern Mariana Islands + +# Montserrat +MS: + address_template: *generic16 + +# Malta +MT: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{suburb}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{postcode}}} + {{{country}}} + +# Martinique - overseas territory of France (FR) +MQ: + use_country: FR + change_country: Martinique, France + +# Mauritania +MR: + address_template: *generic18 + +# Mauritius +MU: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}}, {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + +# Maldives +MV: + address_template: *generic2 + +# Malawi +MW: + address_template: *generic16 + +# Mexico +MX: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{#first}} {{{state_code}}} || {{{state}}} {{/first}} + {{{country}}} + +# Malaysia +MY: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{state}}} + {{{country}}} + +# Mozambique +MZ: + address_template: *generic15 + fallback_template: *fallback4 + +# Namibia +NA: + address_template: *generic2 + +# New Caledonia, special collectivity of France +NC: + use_country: FR + change_country: Nouvelle-Calédonie, France + +# Niger +NE: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} + {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{country}}} + +# Norfolk Island - same as Australia +NF: + use_country: AU + add_component: state=Norfolk Island + change_country: Australia + +# Nigeria +NG: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}} + {{{state}}} + {{{country}}} + +# Nicaragua +NI: + address_template: *generic21 + +# Netherlands +NL: + address_template: *generic1 + postformat_replace: + # fix the postcode to make it \d\d\d\d \w\w + - ["\n(\\d{4})(\\w{2}) ","\n$1 $2 "] + - ["\nKoninkrijk der Nederlanden$","\nNederland"] + + +# Norway +# quoted since python interprets it as a boolean. Silly python! +"NO": + address_template: *generic1 + +# Nepal +NP: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{neighbourhood}}} || {{{city}}} {{/first}} + {{#first}} {{{municipality}}} || {{{county}}} || {{{state_district}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + +# Nauru +NR: + address_template: *generic16 + +# Niue +NU: + address_template: *generic16 + +# New Zealand +NZ: + address_template: *generic20 + postformat_replace: + - ["Wellington\nWellington City","Wellington"] + +# Oman +OM: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{{postcode}}} + {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{state}}} + {{{country}}} + +# Panama +PA: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{{postcode}}} + {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{state}}} + {{{country}}} + replace: + - ["city=Panama$","Panama City"] + - ["city=Panamá$","Ciudad de Panamá"] + +# Peru +PE: + address_template: *generic19 + +# French Polynesia - same as FR +PF: + use_country: FR + change_country: Polynésie française, France + replace: + - ["Polynésie française, Îles du Vent \\(eaux territoriales\\)","Polynésie française"] + + +# Papau New Guinea +PG: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}} {{{state}}} + {{{country}}} + +# Philippines - https://en.wikipedia.org/wiki/Address#Philippines +PH: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}}, {{#first}}{{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}}{{/first}}, {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{suburb}}} || {{{state_district}}} {{/first}} + {{{postcode}}} {{#first}} {{{municipality}}} || {{{region}}} || {{{state}}} || {{/first}} + {{{country}}} + +# Pakistan +PK: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + +# Poland +PL: + address_template: *generic1 + postformat_replace: + # fix the postcode to make it \d\d-\d\d\d + - ["\n(\\d{2})(\\w{3}) ","\n$1-$2 "] + + +# Saint Pierre and Miquelon - same as FR +PM: + use_country: FR + change_country: Saint-Pierre-et-Miquelon, France + +# Pitcairn Islands +PN: + address_template: | + {{{attention}}} + {{{house}}} + {{#first}} {{{city}}} || {{{town}}} || {{{island}}} {{/first}} + {{{country}}} + +# Puerto Rico, same as USA +PR: + use_country: US + change_country: United States of America + add_component: state=Puerto Rico + +# Palestine +PS: + use_country: IL + +# Portugal +PT: + address_template: *generic1 + postformat_replace: + # fix the postcode to add - after numbers + - ["\n(\\d{4})(\\d{3}) ","\n$1-$2 "] + + +# Palau +PW: + address_template: *generic1 + +# Parguay +PY: + address_template: *generic1 + +# Qatar +QA: + address_template: *generic17 + +# Réunion - same as FR +RE: + use_country: FR + change_country: La Réunion, France + + +# Romania +RO: + address_template: *generic1 + +# Serbia +RS: + address_template: *generic1 + +# Russia +RU: + address_template: *generic10 + fallback_template: | + {{{attention}}} + {{{house}}} + {{{road}}}, {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} || {{{island}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{municipality}}} {{/first}} + {{#first}} {{{county}}} || {{{state_district}}} || {{{state}}} {{/first}} + {{{country}}} + +# Rwanda +RW: + address_template: *generic16 + +# Saudi Arabia +SA: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}}, {{#first}} {{{village}}} || {{{hamlet}}} || {{{city_district}}} || {{{suburb}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} {{/first}} {{{postcode}}} + {{{country}}} + +# Solomon Islands +SB: + address_template: *generic17 + +# Seychelles +SC: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{island}}} {{/first}} + {{{island}}} + {{{country}}} + +# Sudan +SD: + address_template: *generic1 + +# Sweden +SE: + address_template: *generic1 + postformat_replace: + # fix the postcode to make it \d\d\d \d\d + - ["\n(\\d{3})(\\d{2}) ","\n$1 $2 "] + +# Singapore +SG: + address_template: | + {{{attention}}} + {{{house}}}, {{{quarter}}} + {{{house_number}}} {{{road}}}, {{{residential}}} + {{#first}} {{{country}}} || {{{town}}} || {{{city}}} || {{{municipality}}} || {{{hamlet}}} || {{{village}}} || {{{county}}} {{/first}} {{{postcode}}} + {{{country}}} + +# Saint Helena, Ascension and Tristan da Cunha - same as UK +SH: + use_country: GB + change_country: $state, United Kingdom + +# Slovenia +SI: + address_template: *generic1 + +# Svalbard and Jan Mayen - same as Norway +SJ: + use_country: "NO" + change_country: Norway + +# Slovakia +SK: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}} {{#first}} {{{postal_city}}} || {{{city}}} || {{{town}}} || {{{village}}} || {{{municipality}}} || {{{city_district}}} || {{{hamlet}}} || {{{county}}} || {{{state}}} {{/first}} + {{{country}}} + replace: + - ["^District of ",""] + - ["^Region of ",""] + postformat_replace: + # fix the postcode to make it \d\d\d \d\d + - ["\n(\\d{3})(\\d{2}) ","\n$1 $2 "] + +# Sierra Leone +SL: + address_template: *generic16 + +# San Marino - same as IT +SM: + use_country: IT + +# Senegal +SN: + address_template: *generic3 + replace: + - ["^Commune de ",""] + - ["^Arrondissement de ",""] + - ["^Département de ",""] + +# Somalia +SO: + address_template: *generic21 + +# Suriname +SR: + address_template: *generic21 + +# South Sudan +SS: + address_template: *generic17 + +# São Tomé and Príncipe +ST: + address_template: *generic17 + +# El Salvador +SV: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{{postcode}}} - {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{{state}}} + {{{country}}} + postformat_replace: + - ["\n- ","\n "] + +# Sint Maarten +SX: + address_template: *generic17 + +# Syria +SY: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}}, {{{house_number}}} + {{#first}} {{{village}}} || {{{hamlet}}} || {{{city_district}}} || {{{neighbourhood}}} || {{{suburb}}} {{/first}} + {{{postcode}}} {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{state}}} {{/first}} + + {{{country}}} + + +# Swaziland +SZ: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} + {{{postcode}}} + {{{country}}} + +# Turks and Caicos Islands +TC: + address_template: *generic23 + fallback_template: | + {{{attention}}} + {{{house_number}}} {{{road}}} + {{{quarter}}} + {{#first}} {{{village}}} || {{{town}}} || {{{city}}} || {{{municipality}}} || {{{hamlet}}} || {{{county}}} {{/first}} + {{{island}}} + {{{country}}} + +# Chad +TD: + address_template: *generic21 + +# French Southern and Antarctic Lands +TF: + use_country: FR + change_country: Terres australes et antarctiques françaises, France + +# Togo +TG: + address_template: *generic18 + +# Thailand -- https://en.wikipedia.org/wiki/Thai_addressing_system +TH: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{#first}} {{{village}}} || {{{hamlet}}} {{/first}} + {{{road}}} + {{#first}} {{{neighbourhood}}} || {{{city}}} || {{{town}}} {{/first}}, {{#first}} {{{suburb}}} || {{{city_district}}} || {{{state_district}}} {{/first}} + {{{state}}} {{{postcode}}} + {{{country}}} + +# Tajikistan +TJ: + address_template: *generic1 + +# Tokelau, territory of New Zealand +TK: + use_country: NZ + change_country: Tokelau, New Zealand + +# Timor-Leste/East Timor +TL: + address_template: *generic17 + +# Turkmenistan +TM: + address_template: *generic22 + +# Tunisia +TN: + address_template: *generic3 + +# Tonga +TO: + address_template: *generic16 + +# Turkey +TR: + address_template: *generic1 + +# Trinidad and Tobago +TT: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{state_district}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{{postcode}}} + {{{country}}} + +# Tuvalu +TV: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} + {{#first}} {{{county}}} || {{{state_district}}} || {{{state}}} || {{{island}}} {{/first}} + {{{country}}} + +# Taiwan -- https://en.wikipedia.org/wiki/Address#Taiwan +TW: + address_template: | + {{{country}}} + {{{postcode}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} {{{road}}} {{{house_number}}} + {{{house}}} + {{{attention}}} + +TW_en: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}}, {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}}, {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}} + {{{country}}} + +TW_zh: + address_template: | + {{{country}}} + {{{postcode}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} {{{road}}} {{{house_number}}} + {{{house}}} + {{{attention}}} + +# Tanzania +TZ: + address_template: *generic14 + fallback_template: *generic14 + postformat_replace: + - ["Dar es Salaam\nDar es Salaam","Dar es Salaam"] + +# Ukraine -- https://en.wikipedia.org/wiki/Address#Ukraine +UA: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}}, {{{house_number}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{state_district}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{municipality}}} {{/first}} + {{#first}} {{{region}}} || {{{state}}} {{/first}} + {{{postcode}}} + {{{country}}} + +# Uganda +UG: + address_template: *generic16 + +# US Minor Outlying Islands, same as USA +UM: + fallback_template: *fallback2 + use_country: US + change_country: United States of America + add_component: state=US Minor Outlying Islands + +# USA +US: + address_template: *generic4 + fallback_template: *fallback2 + replace: + - ["state=United States Virgin Islands","US Virgin Islands"] + - ["state=USVI","US Virgin Islands"] + postformat_replace: + - ["\nUS$","\nUnited States of America"] + - ["\nUSA$","\nUnited States of America"] + - ["\nUnited States$","\nUnited States of America"] + - ["Town of ",""] + - ["Township of ",""] + +# Uzbekistan +UZ: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}} + {{#first}} {{{state}}} || {{{state_district}}} {{/first}} + {{{country}}} + {{{postcode}}} + +# Uruguay +UY: + address_template: *generic1 + +# Vatican City - same as IT +VA: + use_country: IT + +# Saint Vincent and the Grenadines +VC: + address_template: *generic17 + +# Venezuela +VE: + address_template: | + {{{attention}}} + {{{house}}} + {{{road}}} {{{house_number}}} + {{#first}} {{{city}}} || {{{town}}} || {{{state_district}}} || {{{village}}} || {{{hamlet}}} {{/first}} {{{postcode}}}, {{#first}} {{{state_code}}} || {{{state}}} {{/first}} + {{{country}}} + +# British Virgin Islands +VG: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} {{/first}}, {{{island}}} + {{{country}}}, {{{postcode}}} + +# US Virgin Islands, same as USA +VI: + use_country: US + change_country: United States of America + add_component: state=US Virgin Islands + +# Vietnam -- https://en.wikipedia.org/wiki/Address#Vietnam +VN: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{neighbourhood}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state_district}}} {{/first}} + {{{state}}} {{{postcode}}} + {{{country}}} + +# Vanuatu +VU: + address_template: *generic17 + +# Wallis and Futuna, same as France +WF: + use_country: FR + change_country: Wallis-et-Futuna, France + +# Samoa +WS: + address_template: *generic17 + +# Sovereign Base Areas of Akrotiri and Dhekelia +# not an official ISO code +XC: + address_template: *generic6 + +# Kosovo +# not an official ISO code +XK: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}}, {{{road}}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} {{{postcode}}} + {{{country}}} + +# Yemen +YE: + address_template: *generic18 + +# Mayotte - same as FR +YT: + use_country: FR + change_country: Mayotte, France + +# South Africa +ZA: + address_template: | + {{{attention}}} + {{{house}}} + {{{house_number}}} {{{road}}} + {{#first}} {{{suburb}}} || {{{city_district}}} || {{{state_district}}} {{/first}} + {{#first}} {{{city}}} || {{{town}}} || {{{village}}} || {{{hamlet}}} || {{{state}}} {{/first}} + {{{postcode}}} + {{{country}}} + +# Zambia +ZM: + address_template: *generic3 + +# Zimbabwe +ZW: + address_template: *generic16 diff --git a/rigour/data/addresses/formats.yml b/rigour/data/addresses/formats.yml index 08b058d5..6da25dfe 100644 --- a/rigour/data/addresses/formats.yml +++ b/rigour/data/addresses/formats.yml @@ -1,6 +1,4 @@ -# Generated from OpenCageData address-formatting repo -# commit: e8df874f42b18cbc1272b9cb491e460b72c29bc8 -# https://raw.githubusercontent.com/OpenCageData/address-formatting/e8df874f42b18cbc1272b9cb491e460b72c29bc8/conf/countries/worldwide.yaml +# Derived from: https://github.com/OpenCageData/address-formatting # # generic mappings, specific territories get mapped to these # From d9232e82f7e5d711e232697ecf7645f37eaf0a2a Mon Sep 17 00:00:00 2001 From: b-gran Date: Sun, 4 May 2025 15:27:55 -0400 Subject: [PATCH 12/13] dependency no longer needed --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f787d9dd..2187a27f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,6 @@ dev = [ "coverage>=4.1", "ruamel.yaml==0.18.10", "mystace==0.1.0", - "requests==2.32.3", ] docs = [ "pillow", From adada845b2d174af473df8bb50c01f971bb68933 Mon Sep 17 00:00:00 2001 From: b-gran Date: Sun, 4 May 2025 15:30:18 -0400 Subject: [PATCH 13/13] missing file --- genscripts/generate_address_formats.py | 421 +++++++++++++++++++++++++ 1 file changed, 421 insertions(+) create mode 100644 genscripts/generate_address_formats.py diff --git a/genscripts/generate_address_formats.py b/genscripts/generate_address_formats.py new file mode 100644 index 00000000..9d16f448 --- /dev/null +++ b/genscripts/generate_address_formats.py @@ -0,0 +1,421 @@ +from enum import Enum +from mystace import create_mustache_tree +from mystace.mustache_tree import MustacheTreeNode +from ruamel.yaml import YAML +from ruamel.yaml.comments import CommentedMap +from ruamel.yaml.scalarstring import LiteralScalarString +from genscripts.util import CODE_PATH, RESOURCES_PATH + + +OPENCAGE_YAML_PATH = RESOURCES_PATH / "addresses" / "opencage_worldwide.yaml" +FORMATS_DEST_PATH = CODE_PATH / "addresses" / "formats.yml" + + +# these don't exist in the rigour yaml -- by design? +DROP_VARS = {"hamlet", "place"} + +SPACE_LITERAL = " " +PRIMARY_OR_MUSTACHE_LITERAL = "||" +OR_JINJA_LITERAL = " or " + +class M2JType(Enum): + ROOT = -1 + LITERAL = 0 + SECTION = 2 + INVERTED_SECTION = 3 + PARTIAL = 5 + VARIABLE = 6 + VARIABLE_RAW = 7 + OR = 8 + CONCAT = 9 + + +class M2JNode: + tag_type: M2JType + data: str + children: list['M2JNode'] + + def __init__(self, tag_type: M2JType, data: str = "", children: list['M2JNode'] | None = None): + self.tag_type = tag_type + self.data = data + self.children = children if children is not None else [] + + def __repr__(self) -> str: + if self.data: + return f"<{self.__class__.__name__}: {self.tag_type}, {self.data!r}>" + return f"<{self.__class__.__name__}: {self.tag_type}>" + + +def _convert_tree(t: MustacheTreeNode) -> M2JNode: + j = M2JNode(tag_type=M2JType.ROOT, data=t.data) + + frontier = [(n, j) for n in t.children or []] + while frontier: + n, curr = frontier.pop(0) + curr.children.append(M2JNode(tag_type=M2JType(n.tag_type.value), data=n.data)) + if n.children: + for c in n.children: + frontier.append((c, curr.children[-1])) + + return j + + +def split_space(t: M2JNode): + """ + Find any literal nodes that contain spaces. + Trim the spaces from the beginning and end of the literal when the literal is up against a boundary. + """ + + def _can_split(n: M2JNode): + return n.tag_type == M2JType.LITERAL and n.data != '\n' + + def _is_boundary(n: M2JNode): + return n.tag_type in {M2JType.OR} or n.tag_type == M2JType.LITERAL and n.data == '\n' + + if not t.children: + return + + i = 0 + while i < len(t.children): + n = t.children[i] + + if n.children: + split_space(n) + + if not _can_split(n) or SPACE_LITERAL not in n.data: + i += 1 + continue + + curr = n.data + assert SPACE_LITERAL in curr, f"Expected {SPACE_LITERAL} in {curr}" + + if i == 0 or _is_boundary(t.children[i - 1]): + curr = curr.lstrip(SPACE_LITERAL) + + if i == len(t.children) - 1 or _is_boundary(t.children[i + 1]): + curr = curr.rstrip(SPACE_LITERAL) + + if curr: + n.data = curr + i += 1 + else: + t.children.pop(i) + + +def split_or(t: M2JNode): + """ + Split any literal nodes that contain the OR mustache literal into separate nodes. + """ + + def _is_boundary(n: M2JNode): + """ + We stop backtracking or looking ahead when we hit a boundary. + """ + return n.tag_type != M2JType.LITERAL or n.data == '\n' + + if not t.children: + return + + i = 0 + while i < len(t.children): + n = t.children[i] + + if n.children: + split_or(n) + + if _is_boundary(n) or PRIMARY_OR_MUSTACHE_LITERAL not in n.data: + i += 1 + continue + + assert n.data.count(PRIMARY_OR_MUSTACHE_LITERAL) == 1, f"Expected 1 {PRIMARY_OR_MUSTACHE_LITERAL} in {n.data}" + + t.children.pop(i) + + idx = n.data.index(PRIMARY_OR_MUSTACHE_LITERAL) + left = n.data[:idx].rstrip(SPACE_LITERAL) + right = n.data[idx + len(PRIMARY_OR_MUSTACHE_LITERAL) :].lstrip(SPACE_LITERAL) + + if left: + t.children.insert(i, M2JNode(tag_type=M2JType.LITERAL, data=left)) + i += 1 + + t.children.insert(i, M2JNode(tag_type=M2JType.OR, data=PRIMARY_OR_MUSTACHE_LITERAL)) + i += 1 + + if right: + t.children.insert(i, M2JNode(tag_type=M2JType.LITERAL, data=right)) + i += 1 + + +def drop_vars_and_simplify(t: M2JNode): + def _is_boundary(n: M2JNode): + return n.tag_type in {M2JType.OR} or n.tag_type == M2JType.LITERAL and n.data == '\n' + + def _is_simple_section(n: M2JNode): + return n.tag_type in {M2JType.SECTION, M2JType.INVERTED_SECTION} and all( + c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW, M2JType.LITERAL} for c in n.children + ) + + if not t.children: + return + + i = 0 + while i < len(t.children): + c = t.children[i] + drop_vars_and_simplify(c) + + is_against_boundary = ( + (i > 0 and _is_boundary(t.children[i - 1])) + or (i + 1 < len(t.children) and _is_boundary(t.children[i + 1])) + or i == 0 + or i == len(t.children) - 1 + ) + + # remove empty sections + if c.tag_type == M2JType.SECTION and not c.children: + t.children.pop(i) + continue + + is_whitespace = c.tag_type == M2JType.LITERAL and c.data.strip(SPACE_LITERAL) == "" + if is_whitespace and is_against_boundary: + # remove empty literals + t.children.pop(i) + continue + + if c.tag_type == M2JType.OR and is_against_boundary: + # remove dangling OR + t.children.pop(i) + continue + + if c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW} and c.data in DROP_VARS: + # remove dropped variables + t.children.pop(i) + if i > 0: + i -= 1 + continue + + # inline sections that contain only variables and literals + if _is_simple_section(c): + t.children.pop(i) + + j = i + for section_child in c.children: + t.children.insert(j, section_child) + j += 1 + + # merge literals at the beginning of the section + if ( + i > 0 + and t.children[i - 1].tag_type == M2JType.LITERAL + and t.children[i].tag_type == M2JType.LITERAL + and not _is_boundary(t.children[i - 1]) + ): + t.children[i - 1].data += t.children[i].data + t.children.pop(i) + i -= 1 + + # merge literals at the end of the section + if ( + j > 0 + and t.children[j - 1].tag_type == M2JType.LITERAL + and t.children[j].tag_type == M2JType.LITERAL + and not _is_boundary(t.children[j]) + ): + t.children[j - 1].data += t.children[j].data + t.children.pop(j) + + i += 1 + + +def split_string_concat(t: M2JNode): + if not t.children: + return + + for c in t.children: + split_string_concat(c) + + # outside of sections, we don't need to handle concats around ORs + if t.tag_type not in {M2JType.SECTION, M2JType.INVERTED_SECTION}: + return + + args: list[list[M2JNode]] = [[]] + for c in t.children: + if c.tag_type == M2JType.OR: + args.append([]) + else: + args[-1].append(c) + + # if we have only one argument, we can just return + if len(args) == 1: + return + + t.children = [] + for i, arg in enumerate(args): + if len(arg) == 1: + t.children.append(arg[0]) + else: + new_node = M2JNode(tag_type=M2JType.CONCAT, children=arg) + t.children.append(new_node) + if i < len(args) - 1: + t.children.append(M2JNode(tag_type=M2JType.OR)) + + return t + + +def jinja_tree_to_template(t: M2JNode, depth: int = 0) -> str: + if t.tag_type == M2JType.ROOT: + template = "" + for c in t.children: + template += jinja_tree_to_template(c, depth + 1) + return template + elif t.tag_type in {M2JType.SECTION, M2JType.INVERTED_SECTION}: + assert depth < 2, f'Too many nested sections: {depth}' + template = "{{" + for c in t.children: + template += jinja_tree_to_template(c, depth + 1) + template += "}}" + return template + elif t.tag_type == M2JType.CONCAT: + formatted_val = ' ~ '.join([jinja_tree_to_template(c, depth + 1) for c in t.children]) + vars = [c for c in t.children if c.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW}] + return f'format_if({formatted_val}, {", ".join([c.data for c in vars])})' + elif t.tag_type == M2JType.LITERAL: + if depth < 2: + return t.data + else: + return f"'{t.data}'" + elif t.tag_type in {M2JType.VARIABLE, M2JType.VARIABLE_RAW}: + if depth < 2: + return '{{' + t.data + '}}' + else: + return t.data + elif t.tag_type == M2JType.OR: + return OR_JINJA_LITERAL + else: + raise ValueError(f"Got unexpected tag type {t.tag_type}") + + +def mustache_to_jinja(template: str) -> str: + jinja_template = "" + + tree = _convert_tree(create_mustache_tree(template)) + split_or(tree) + split_space(tree) + drop_vars_and_simplify(tree) + split_string_concat(tree) + + jinja_template = jinja_tree_to_template(tree) + return collapse_newlines(jinja_template) + + +def collapse_newlines(text: str) -> str: + prev = "" + while prev != text: + prev = text + text = text.replace("\n\n", "\n").replace("\n ", "\n").strip() + return text + + +def update_mustache_field(yaml_obj: dict, field: str) -> LiteralScalarString: + current_anchor = yaml_obj[field].yaml_anchor() + txt = mustache_to_jinja(str(yaml_obj[field])) + if not txt.endswith("\n"): + txt += "\n" + yaml_obj[field] = LiteralScalarString(txt) + if current_anchor is not None: + yaml_obj[field].yaml_set_anchor(current_anchor.value, always_dump=current_anchor.always_dump) + return yaml_obj[field] + + +def normalize_add_component(country: CommentedMap) -> str | None: + trailing_comment: str | None = None + + # grab and detach the trailing comment (if any) + if len(country.ca.items) > 0: + last_key = list(country.keys())[-1] + last_comment = country.ca.items[last_key][2] + if last_comment is not None: + trailing_comment = country.ca.items[last_key][2].value + del country.ca.items[last_key] + + if trailing_comment: + if trailing_comment.startswith("\n\n"): + trailing_comment = f"\n{trailing_comment[2:]}" + trailing_comment = "\n".join([c.lstrip("# ") for c in trailing_comment.split("\n")]) + trailing_comment.rstrip("\n") + + # build the nested mapping + if "add_component" in country: + add_component_val = country["add_component"] + assert isinstance( + add_component_val, str + ), f"Invalid add_component type: {type(add_component_val)} for {country}" + fields = add_component_val.split("=") + + assert len(fields) == 2, f"Invalid add_component format: {add_component_val} for {country}" + k, v = fields + ac_map = CommentedMap({k: v}) + else: + ac_map = CommentedMap() + + if "change_country" in country: + ac_map["country"] = country.pop("change_country") + + country["add_component"] = ac_map + + return trailing_comment + + +def load_address_formats_from_opencage() -> None: + """ + Load address formats from OpenCageData's address-formatting repo. + Converts the mustache templates to jinja2 format. + Uses the following conventions for mustache to jinja2 conversion: + - literals between variables in a `first` block explicitly concatenated with ~ + - relies on a macro "format_if" to handle concatenated strings in `first` blocks that contain only delimiters. + - e.g. if we have {{#first}} {{{house_number}}}, {{{road}}} || {{{suburb}}} {{/first}} + - we only want to use {{{house_number}}}, {{{road}}} if both `house_number` and `road` are non-empty. + """ + if not OPENCAGE_YAML_PATH.exists(): + raise FileNotFoundError(f"OpenCage YAML file not found ({OPENCAGE_YAML_PATH}). Please run make fetch-opencage-addresses to download.") + + yaml = YAML(typ="rt") + yaml.preserve_quotes = True + yaml.indent(mapping=2, sequence=4, offset=2) + + yaml_obj = yaml.load(OPENCAGE_YAML_PATH.read_text()) + + orig_fields_by_value = {} + new_fields = {} + attach_trailing = None + + for k, v in yaml_obj.items(): + if attach_trailing: + yaml_obj.yaml_set_comment_before_after_key(k, before=attach_trailing, after=None) + attach_trailing = None + + if k.startswith("generic") or k.startswith("fallback"): + orig_fields_by_value[v] = k + new_fields[k] = update_mustache_field(yaml_obj, k) + elif isinstance(v, dict): + for k2, v2 in v.items(): + # update references to the original mustache fields for countries that reference them. + if isinstance(v2, LiteralScalarString) and v2 in orig_fields_by_value: + v[k2] = new_fields[orig_fields_by_value[v2]] + + # one-off templates need to be converted to jinja. + elif k2 in {"address_template", "fallback_template"}: + update_mustache_field(v, k2) + + if "change_country" in v or "add_component" in v: + attach_trailing = normalize_add_component(v) + + + with open(FORMATS_DEST_PATH, "w") as outfile: + outfile.write("# Derived from: https://github.com/OpenCageData/address-formatting\n") + yaml.dump(yaml_obj, outfile) + + +if __name__ == "__main__": + load_address_formats_from_opencage() +