prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  Use clean_address function to join multiple free text lines together
  The `clean_address` method added in #7568 allows a standardised approach to taking messy ordered multiple line address strings (of any type of composition) and joining them together into a single string.
  
  We can now use `clean_address` to replace the many variants throughout spiders of attempting to join these multi-line address strings. An added benefit is being able to quickly find where multi-line address strings are parsed (via searching for `clean_address` instances), making it easier to change address handling in the future.
  
  Related to #5598
  
  </issue>
  <code>
  [start of locations/spiders/zizzi_gb.py]
  1 import scrapy
  2 
  3 from locations.dict_parser import DictParser
  4 
  5 
  6 class ZizziGBSpider(scrapy.Spider):
  7     name = "zizzi_gb"
  8     item_attributes = {"brand": "Zizzi", "brand_wikidata": "Q8072944"}
  9     start_urls = ["https://www.zizzi.co.uk/wp-json/locations/get_venues"]
  10 
  11     def parse(self, response):
  12         for store in response.json()["data"]:
  13             item = DictParser.parse(store)
  14             item["addr_full"] = ", ".join(store["address"].split("\r\n"))
  15             item["image"] = store["featured_image"]
  16             item["website"] = store["link"]
  17 
  18             if store["region"] == "Ireland":
  19                 item.pop("state")
  20                 item["country"] = "IE"
  21             else:
  22                 item["country"] = "GB"
  23 
  24             yield item
  25
  [end of locations/spiders/zizzi_gb.py]
  [start of locations/spiders/zambrero_au.py]
  1 import re
  2 
  3 from scrapy import Spider
  4 from scrapy.http import Request
  5 
  6 from locations.categories import Categories
  7 from locations.hours import OpeningHours
  8 from locations.items import Feature
  9 
  10 
  11 class ZambreroAUSpider(Spider):
  12     name = "zambrero_au"
  13     item_attributes = {"brand": "Zambrero", "brand_wikidata": "Q18636431", "extras": Categories.FAST_FOOD.value}
  14     allowed_domains = ["www.zambrero.com.au"]
  15 
  16     def start_requests(self):
  17         yield Request(url=f"https://{self.allowed_domains[0]}/locations", callback=self.parse_location_list)
  18 
  19     def parse_location_list(self, response):
  20         location_urls = response.xpath('//div[@data-location-id]//a[@title="Order & Store Info"]/@href').getall()
  21         for location_url in location_urls:
  22             yield Request(url=location_url, callback=self.parse_location)
  23 
  24     def parse_location(self, response):
  25         properties = {
  26             "ref": response.xpath("//@data-location-id").get(),
  27             "name": re.sub(r"\s+", " ", response.xpath("//div[@data-location-id]/h4/text()").get()).strip(),
  28             "lat": response.xpath("//@data-lat").get(),
  29             "lon": response.xpath("///@data-lng").get(),
  30             "addr_full": re.sub(
  31                 r"\s+",
  32                 " ",
  33                 " ".join(response.xpath('//div[@data-location-id]//span[contains(@class, "address")]/text()').getall()),
  34             ).strip(),
  35             "phone": response.xpath('//a[contains(@class, "phone")]/@href').get().replace("tel:", ""),
  36             "email": response.xpath('//a[contains(@href, "mailto:")]/@href').get().replace("mailto:", ""),
  37             "website": response.url,
  38             "opening_hours": OpeningHours(),
  39         }
  40         if "Temporarily Closed" in properties["name"]:
  41             return
  42         if properties["phone"] == "0":
  43             properties.pop("phone")
  44 
  45         hours_text = re.sub(
  46             r"\s+", " ", " ".join(response.xpath('//div[contains(@class, "hours-item")]/span/text()').getall())
  47         )
  48         properties["opening_hours"].add_ranges_from_string(hours_text)
  49 
  50         # Some store names and URLs contain "Opening Soon" but numerous of
  51         # these are already open and the URL hasn't been changed. A more
  52         # reliable way of knowing a store is not yet open is that it has
  53         # no opening hours specified.
  54         if not properties["opening_hours"].as_opening_hours():
  55             return
  56 
  57         yield Feature(**properties)
  58
  [end of locations/spiders/zambrero_au.py]
  [start of locations/spiders/woolworths_au.py]
  1 import scrapy
  2 
  3 from locations.dict_parser import DictParser
  4 
  5 
  6 class WoolworthsAUSpider(scrapy.Spider):
  7     name = "woolworths_au"
  8     item_attributes = {"brand": "Woolworths", "brand_wikidata": "Q3249145"}
  9     allowed_domains = ["woolworths.com.au"]
  10     start_urls = [
  11         "https://www.woolworths.com.au/apis/ui/StoreLocator/Stores?Max=10000&Division=SUPERMARKETS,PETROL,CALTEXWOW,AMPOLMETRO,AMPOL&Facility=&postcode=*"
  12     ]
  13     custom_settings = {"ROBOTSTXT_OBEY": False}
  14     requires_proxy = "AU"
  15 
  16     def parse(self, response):
  17         data = response.json()
  18 
  19         for i in data["Stores"]:
  20             if not i["IsOpen"]:
  21                 continue
  22 
  23             i["street_address"] = ", ".join(filter(None, [i["AddressLine1"], i["AddressLine2"]]))
  24             i["ref"] = i.pop("StoreNo")
  25             i["city"] = i.pop("Suburb")
  26 
  27             item = DictParser.parse(i)
  28 
  29             item["website"] = (
  30                 "https://www.woolworths.com.au/shop/storelocator/"
  31                 + "-".join([item["state"], item["city"], item["ref"], i["Division"]]).lower()
  32             )
  33 
  34             # TODO: types needs some work, NSI seems out of date too
  35             item["extras"] = {"type": i["Division"]}
  36 
  37             yield item
  38
  [end of locations/spiders/woolworths_au.py]
  </code>
  
  I need you to solve this issue by generating a single patch file that I can apply directly to this repository using git apply. Please respond with a single patch file in the following format.
  <patch>
  diff --git a/file.py b/file.py
  --- a/file.py
  +++ b/file.py
  @@ -1,27 +1,35 @@
   def euclidean(a, b):
  -    while b:
  -        a, b = b, a % b
  -    return a
  +    if b == 0:
  +        return a
  +    return euclidean(b, a % b)
   
   
   def bresenham(x0, y0, x1, y1):
       points = []
       dx = abs(x1 - x0)
       dy = abs(y1 - y0)
  -    sx = 1 if x0 < x1 else -1
  -    sy = 1 if y0 < y1 else -1
  -    err = dx - dy
  +    x, y = x0, y0
  +    sx = -1 if x0 > x1 else 1
  +    sy = -1 if y0 > y1 else 1
   
  -    while True:
  -        points.append((x0, y0))
  -        if x0 == x1 and y0 == y1:
  -            break
  -        e2 = 2 * err
  -        if e2 > -dy:
  -            err -= dy
  -            x0 += sx
  -        if e2 < dx:
  -            err += dx
  -            y0 += sy
  +    if dx > dy:
  +        err = dx / 2.0
  +        while x != x1:
  +            points.append((x, y))
  +            err -= dy
  +            if err < 0:
  +                y += sy
  +                err += dx
  +            x += sx
  +    else:
  +        err = dy / 2.0
  +        while y != y1:
  +            points.append((x, y))
  +            err -= dx
  +            if err < 0:
  +                x += sx
  +                err += dy
  +            y += sy
  +
  +    points.append((x, y))
       return points
  
  </patch>
golden_diff: diff --git a/locations/spiders/woolworths_au.py b/locations/spiders/woolworths_au.py
  --- a/locations/spiders/woolworths_au.py
  +++ b/locations/spiders/woolworths_au.py
  @@ -1,6 +1,7 @@
   import scrapy
   
   from locations.dict_parser import DictParser
  +from locations.pipelines.address_clean_up import clean_address
   
   
   class WoolworthsAUSpider(scrapy.Spider):
  @@ -20,7 +21,7 @@
               if not i["IsOpen"]:
                   continue
   
  -            i["street_address"] = ", ".join(filter(None, [i["AddressLine1"], i["AddressLine2"]]))
  +            i["street_address"] = clean_address([i["AddressLine1"], i["AddressLine2"]])
               i["ref"] = i.pop("StoreNo")
               i["city"] = i.pop("Suburb")
   
  diff --git a/locations/spiders/zambrero_au.py b/locations/spiders/zambrero_au.py
  --- a/locations/spiders/zambrero_au.py
  +++ b/locations/spiders/zambrero_au.py
  @@ -6,6 +6,7 @@
   from locations.categories import Categories
   from locations.hours import OpeningHours
   from locations.items import Feature
  +from locations.pipelines.address_clean_up import clean_address
   
   
   class ZambreroAUSpider(Spider):
  @@ -27,11 +28,9 @@
               "name": re.sub(r"\s+", " ", response.xpath("//div[@data-location-id]/h4/text()").get()).strip(),
               "lat": response.xpath("//@data-lat").get(),
               "lon": response.xpath("///@data-lng").get(),
  -            "addr_full": re.sub(
  -                r"\s+",
  -                " ",
  -                " ".join(response.xpath('//div[@data-location-id]//span[contains(@class, "address")]/text()').getall()),
  -            ).strip(),
  +            "addr_full": clean_address(
  +                " ".join(response.xpath('//div[@data-location-id]//span[contains(@class, "address")]/text()').getall())
  +            ),
               "phone": response.xpath('//a[contains(@class, "phone")]/@href').get().replace("tel:", ""),
               "email": response.xpath('//a[contains(@href, "mailto:")]/@href').get().replace("mailto:", ""),
               "website": response.url,
  diff --git a/locations/spiders/zizzi_gb.py b/locations/spiders/zizzi_gb.py
  --- a/locations/spiders/zizzi_gb.py
  +++ b/locations/spiders/zizzi_gb.py
  @@ -1,6 +1,7 @@
   import scrapy
   
   from locations.dict_parser import DictParser
  +from locations.pipelines.address_clean_up import clean_address
   
   
   class ZizziGBSpider(scrapy.Spider):
  @@ -11,7 +12,7 @@
       def parse(self, response):
           for store in response.json()["data"]:
               item = DictParser.parse(store)
  -            item["addr_full"] = ", ".join(store["address"].split("\r\n"))
  +            item["addr_full"] = clean_address(store["address"].split("\r\n"))
               item["image"] = store["featured_image"]
               item["website"] = store["link"]
  
verification_info: {"golden_diff": "diff --git a/locations/spiders/woolworths_au.py b/locations/spiders/woolworths_au.py\n--- a/locations/spiders/woolworths_au.py\n+++ b/locations/spiders/woolworths_au.py\n@@ -1,6 +1,7 @@\n import scrapy\n \n from locations.dict_parser import DictParser\n+from locations.pipelines.address_clean_up import clean_address\n \n \n class WoolworthsAUSpider(scrapy.Spider):\n@@ -20,7 +21,7 @@\n             if not i[\"IsOpen\"]:\n                 continue\n \n-            i[\"street_address\"] = \", \".join(filter(None, [i[\"AddressLine1\"], i[\"AddressLine2\"]]))\n+            i[\"street_address\"] = clean_address([i[\"AddressLine1\"], i[\"AddressLine2\"]])\n             i[\"ref\"] = i.pop(\"StoreNo\")\n             i[\"city\"] = i.pop(\"Suburb\")\n \ndiff --git a/locations/spiders/zambrero_au.py b/locations/spiders/zambrero_au.py\n--- a/locations/spiders/zambrero_au.py\n+++ b/locations/spiders/zambrero_au.py\n@@ -6,6 +6,7 @@\n from locations.categories import Categories\n from locations.hours import OpeningHours\n from locations.items import Feature\n+from locations.pipelines.address_clean_up import clean_address\n \n \n class ZambreroAUSpider(Spider):\n@@ -27,11 +28,9 @@\n             \"name\": re.sub(r\"\\s+\", \" \", response.xpath(\"//div[@data-location-id]/h4/text()\").get()).strip(),\n             \"lat\": response.xpath(\"//@data-lat\").get(),\n             \"lon\": response.xpath(\"///@data-lng\").get(),\n-            \"addr_full\": re.sub(\n-                r\"\\s+\",\n-                \" \",\n-                \" \".join(response.xpath('//div[@data-location-id]//span[contains(@class, \"address\")]/text()').getall()),\n-            ).strip(),\n+            \"addr_full\": clean_address(\n+                \" \".join(response.xpath('//div[@data-location-id]//span[contains(@class, \"address\")]/text()').getall())\n+            ),\n             \"phone\": response.xpath('//a[contains(@class, \"phone\")]/@href').get().replace(\"tel:\", \"\"),\n             \"email\": response.xpath('//a[contains(@href, \"mailto:\")]/@href').get().replace(\"mailto:\", \"\"),\n             \"website\": response.url,\ndiff --git a/locations/spiders/zizzi_gb.py b/locations/spiders/zizzi_gb.py\n--- a/locations/spiders/zizzi_gb.py\n+++ b/locations/spiders/zizzi_gb.py\n@@ -1,6 +1,7 @@\n import scrapy\n \n from locations.dict_parser import DictParser\n+from locations.pipelines.address_clean_up import clean_address\n \n \n class ZizziGBSpider(scrapy.Spider):\n@@ -11,7 +12,7 @@\n     def parse(self, response):\n         for store in response.json()[\"data\"]:\n             item = DictParser.parse(store)\n-            item[\"addr_full\"] = \", \".join(store[\"address\"].split(\"\\r\\n\"))\n+            item[\"addr_full\"] = clean_address(store[\"address\"].split(\"\\r\\n\"))\n             item[\"image\"] = store[\"featured_image\"]\n             item[\"website\"] = store[\"link\"]\n", "issue": "Use clean_address function to join multiple free text lines together\nThe `clean_address` method added in #7568 allows a standardised approach to taking messy ordered multiple line address strings (of any type of composition) and joining them together into a single string.\r\n\r\nWe can now use `clean_address` to replace the many variants throughout spiders of attempting to join these multi-line address strings. An added benefit is being able to quickly find where multi-line address strings are parsed (via searching for `clean_address` instances), making it easier to change address handling in the future.\r\n\r\nRelated to #5598\n", "before_files": [{"content": "import scrapy\n\nfrom locations.dict_parser import DictParser\n\n\nclass ZizziGBSpider(scrapy.Spider):\n    name = \"zizzi_gb\"\n    item_attributes = {\"brand\": \"Zizzi\", \"brand_wikidata\": \"Q8072944\"}\n    start_urls = [\"https://www.zizzi.co.uk/wp-json/locations/get_venues\"]\n\n    def parse(self, response):\n        for store in response.json()[\"data\"]:\n            item = DictParser.parse(store)\n            item[\"addr_full\"] = \", \".join(store[\"address\"].split(\"\\r\\n\"))\n            item[\"image\"] = store[\"featured_image\"]\n            item[\"website\"] = store[\"link\"]\n\n            if store[\"region\"] == \"Ireland\":\n                item.pop(\"state\")\n                item[\"country\"] = \"IE\"\n            else:\n                item[\"country\"] = \"GB\"\n\n            yield item\n", "path": "locations/spiders/zizzi_gb.py"}, {"content": "import re\n\nfrom scrapy import Spider\nfrom scrapy.http import Request\n\nfrom locations.categories import Categories\nfrom locations.hours import OpeningHours\nfrom locations.items import Feature\n\n\nclass ZambreroAUSpider(Spider):\n    name = \"zambrero_au\"\n    item_attributes = {\"brand\": \"Zambrero\", \"brand_wikidata\": \"Q18636431\", \"extras\": Categories.FAST_FOOD.value}\n    allowed_domains = [\"www.zambrero.com.au\"]\n\n    def start_requests(self):\n        yield Request(url=f\"https://{self.allowed_domains[0]}/locations\", callback=self.parse_location_list)\n\n    def parse_location_list(self, response):\n        location_urls = response.xpath('//div[@data-location-id]//a[@title=\"Order & Store Info\"]/@href').getall()\n        for location_url in location_urls:\n            yield Request(url=location_url, callback=self.parse_location)\n\n    def parse_location(self, response):\n        properties = {\n            \"ref\": response.xpath(\"//@data-location-id\").get(),\n            \"name\": re.sub(r\"\\s+\", \" \", response.xpath(\"//div[@data-location-id]/h4/text()\").get()).strip(),\n            \"lat\": response.xpath(\"//@data-lat\").get(),\n            \"lon\": response.xpath(\"///@data-lng\").get(),\n            \"addr_full\": re.sub(\n                r\"\\s+\",\n                \" \",\n                \" \".join(response.xpath('//div[@data-location-id]//span[contains(@class, \"address\")]/text()').getall()),\n            ).strip(),\n            \"phone\": response.xpath('//a[contains(@class, \"phone\")]/@href').get().replace(\"tel:\", \"\"),\n            \"email\": response.xpath('//a[contains(@href, \"mailto:\")]/@href').get().replace(\"mailto:\", \"\"),\n            \"website\": response.url,\n            \"opening_hours\": OpeningHours(),\n        }\n        if \"Temporarily Closed\" in properties[\"name\"]:\n            return\n        if properties[\"phone\"] == \"0\":\n            properties.pop(\"phone\")\n\n        hours_text = re.sub(\n            r\"\\s+\", \" \", \" \".join(response.xpath('//div[contains(@class, \"hours-item\")]/span/text()').getall())\n        )\n        properties[\"opening_hours\"].add_ranges_from_string(hours_text)\n\n        # Some store names and URLs contain \"Opening Soon\" but numerous of\n        # these are already open and the URL hasn't been changed. A more\n        # reliable way of knowing a store is not yet open is that it has\n        # no opening hours specified.\n        if not properties[\"opening_hours\"].as_opening_hours():\n            return\n\n        yield Feature(**properties)\n", "path": "locations/spiders/zambrero_au.py"}, {"content": "import scrapy\n\nfrom locations.dict_parser import DictParser\n\n\nclass WoolworthsAUSpider(scrapy.Spider):\n    name = \"woolworths_au\"\n    item_attributes = {\"brand\": \"Woolworths\", \"brand_wikidata\": \"Q3249145\"}\n    allowed_domains = [\"woolworths.com.au\"]\n    start_urls = [\n        \"https://www.woolworths.com.au/apis/ui/StoreLocator/Stores?Max=10000&Division=SUPERMARKETS,PETROL,CALTEXWOW,AMPOLMETRO,AMPOL&Facility=&postcode=*\"\n    ]\n    custom_settings = {\"ROBOTSTXT_OBEY\": False}\n    requires_proxy = \"AU\"\n\n    def parse(self, response):\n        data = response.json()\n\n        for i in data[\"Stores\"]:\n            if not i[\"IsOpen\"]:\n                continue\n\n            i[\"street_address\"] = \", \".join(filter(None, [i[\"AddressLine1\"], i[\"AddressLine2\"]]))\n            i[\"ref\"] = i.pop(\"StoreNo\")\n            i[\"city\"] = i.pop(\"Suburb\")\n\n            item = DictParser.parse(i)\n\n            item[\"website\"] = (\n                \"https://www.woolworths.com.au/shop/storelocator/\"\n                + \"-\".join([item[\"state\"], item[\"city\"], item[\"ref\"], i[\"Division\"]]).lower()\n            )\n\n            # TODO: types needs some work, NSI seems out of date too\n            item[\"extras\"] = {\"type\": i[\"Division\"]}\n\n            yield item\n", "path": "locations/spiders/woolworths_au.py"}]}