prompt: You will be provided with a partial code base and an issue statement explaining a problem to resolve.
  <issue>
  boots.py spider doesn't correctly pick up all opticians
  The current test in boots.py to switch the brand tags for opticians is `properties["name"].startswith("Opticians - ")`:
  https://github.com/alltheplaces/alltheplaces/blob/master/locations/spiders/boots.py#L73
  
  But this is not general enough to catch all of them. The displayed name of some opticians branches only start with "Opticians " or "Opticians-". For example https://www.boots.com/stores/3730-tewkesbury-high-street-opticians-gl20-5jz and https://www.boots.com/stores/3947-camden-high-street-opticians-nw1-0lu
  
  I think you could safely change the test to `properties["name"].startswith("Opticians")` but the code a few lines below to strip out the "Opticians" prefix would need to be more complicated.
  
  </issue>
  <code>
  [start of locations/spiders/boots.py]
  1 import scrapy
  2 
  3 from locations.items import GeojsonPointItem
  4 
  5 
  6 class BootsSpider(scrapy.Spider):
  7     name = "boots"
  8     item_attributes = {"brand": "Boots", "brand_wikidata": "Q6123139"}
  9     allowed_domains = ["www.boots.com", "www.boots.ie"]
  10     download_delay = 0.5
  11     start_urls = ["http://www.boots.com/store-a-z", "http://www.boots.ie/store-a-z"]
  12 
  13     def parse_hours(self, lis):
  14         hours = []
  15         for li in lis:
  16             day = li.xpath(
  17                 'normalize-space(./td[@class="store_hours_day"]/text())'
  18             ).extract_first()
  19             times = (
  20                 li.xpath('normalize-space(./td[@class="store_hours_time"]/text())')
  21                 .extract_first()
  22                 .replace(" ", "")
  23                 .replace("Closed-Closed", "off")
  24             )
  25             if times and day:
  26                 hours.append(day[:2] + " " + times)
  27 
  28         return "; ".join(hours)
  29 
  30     def parse_stores(self, response):
  31         addr_full = response.xpath(
  32             '//section[@class="store_details_content rowContainer"]/dl[@class="store_info_list"][1]/dd[@class="store_info_list_item"]/text()'
  33         ).extract()
  34         address = ", ".join(map(str.strip, addr_full))
  35         # Handle blank store pages e.g. https://www.boots.com/stores/2250-alnwick-paikes-street-ne66-1hx
  36         if len(address) == 0:
  37             return
  38 
  39         properties = {
  40             "ref": response.xpath(
  41                 'normalize-space(//input[@id="bootsStoreId"]/@value)'
  42             ).extract_first(),
  43             "name": response.xpath(
  44                 'normalize-space(//input[@id="inputLocation"][@name="inputLocation"]/@value)'
  45             ).extract_first(),
  46             "postcode": response.xpath(
  47                 'normalize-space(//input[@id="storePostcode"]/@value)'
  48             ).extract_first(),
  49             "addr_full": address,
  50             "phone": response.xpath(
  51                 '//section[@class="store_details_content rowContainer"]/dl[@class="store_info_list"][3]/dd[@class="store_info_list_item"]/a/text()'
  52             ).extract_first(),
  53             "country": response.xpath(
  54                 'normalize-space(//input[@id="countryCode"][@name="countryCode"]/@value)'
  55             ).extract_first(),
  56             "website": response.url,
  57             "lat": response.xpath(
  58                 'normalize-space(//input[@id="lat"]/@value)'
  59             ).extract_first(),
  60             "lon": response.xpath(
  61                 'normalize-space(//input[@id="lon"]/@value)'
  62             ).extract_first(),
  63         }
  64 
  65         hours = self.parse_hours(
  66             response.xpath(
  67                 '//div[@class="row store_all_opening_hours"]/div[1]/table[@class="store_opening_hours "]/tbody/tr'
  68             )
  69         )
  70         if hours:
  71             properties["opening_hours"] = hours
  72 
  73         if properties["name"].startswith("Opticians - "):
  74             properties["brand"] = "Boots Opticians"
  75             properties["brand_wikidata"] = "Q4944037"
  76             properties["name"] = properties["name"][12:]
  77 
  78         yield GeojsonPointItem(**properties)
  79 
  80     def parse(self, response):
  81         urls = response.xpath(
  82             '//div[@class="brand_list_viewer"]/div[@class="column"]/ul/li/a/@href'
  83         ).extract()
  84         for path in urls:
  85             yield scrapy.Request(response.urljoin(path), callback=self.parse_stores)
  86
  [end of locations/spiders/boots.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/boots.py b/locations/spiders/boots.py
  --- a/locations/spiders/boots.py
  +++ b/locations/spiders/boots.py
  @@ -70,10 +70,10 @@
           if hours:
               properties["opening_hours"] = hours
   
  -        if properties["name"].startswith("Opticians - "):
  +        if properties["name"].startswith("Opticians"):
               properties["brand"] = "Boots Opticians"
               properties["brand_wikidata"] = "Q4944037"
  -            properties["name"] = properties["name"][12:]
  +            properties["name"] = properties["name"].replace("Opticians", "").strip("- ")
   
           yield GeojsonPointItem(**properties)
  
verification_info: {"golden_diff": "diff --git a/locations/spiders/boots.py b/locations/spiders/boots.py\n--- a/locations/spiders/boots.py\n+++ b/locations/spiders/boots.py\n@@ -70,10 +70,10 @@\n         if hours:\n             properties[\"opening_hours\"] = hours\n \n-        if properties[\"name\"].startswith(\"Opticians - \"):\n+        if properties[\"name\"].startswith(\"Opticians\"):\n             properties[\"brand\"] = \"Boots Opticians\"\n             properties[\"brand_wikidata\"] = \"Q4944037\"\n-            properties[\"name\"] = properties[\"name\"][12:]\n+            properties[\"name\"] = properties[\"name\"].replace(\"Opticians\", \"\").strip(\"- \")\n \n         yield GeojsonPointItem(**properties)\n", "issue": "boots.py spider doesn't correctly pick up all opticians\nThe current test in boots.py to switch the brand tags for opticians is `properties[\"name\"].startswith(\"Opticians - \")`:\r\nhttps://github.com/alltheplaces/alltheplaces/blob/master/locations/spiders/boots.py#L73\r\n\r\nBut this is not general enough to catch all of them. The displayed name of some opticians branches only start with \"Opticians \" or \"Opticians-\". For example https://www.boots.com/stores/3730-tewkesbury-high-street-opticians-gl20-5jz and https://www.boots.com/stores/3947-camden-high-street-opticians-nw1-0lu\r\n\r\nI think you could safely change the test to `properties[\"name\"].startswith(\"Opticians\")` but the code a few lines below to strip out the \"Opticians\" prefix would need to be more complicated.\n", "before_files": [{"content": "import scrapy\n\nfrom locations.items import GeojsonPointItem\n\n\nclass BootsSpider(scrapy.Spider):\n    name = \"boots\"\n    item_attributes = {\"brand\": \"Boots\", \"brand_wikidata\": \"Q6123139\"}\n    allowed_domains = [\"www.boots.com\", \"www.boots.ie\"]\n    download_delay = 0.5\n    start_urls = [\"http://www.boots.com/store-a-z\", \"http://www.boots.ie/store-a-z\"]\n\n    def parse_hours(self, lis):\n        hours = []\n        for li in lis:\n            day = li.xpath(\n                'normalize-space(./td[@class=\"store_hours_day\"]/text())'\n            ).extract_first()\n            times = (\n                li.xpath('normalize-space(./td[@class=\"store_hours_time\"]/text())')\n                .extract_first()\n                .replace(\" \", \"\")\n                .replace(\"Closed-Closed\", \"off\")\n            )\n            if times and day:\n                hours.append(day[:2] + \" \" + times)\n\n        return \"; \".join(hours)\n\n    def parse_stores(self, response):\n        addr_full = response.xpath(\n            '//section[@class=\"store_details_content rowContainer\"]/dl[@class=\"store_info_list\"][1]/dd[@class=\"store_info_list_item\"]/text()'\n        ).extract()\n        address = \", \".join(map(str.strip, addr_full))\n        # Handle blank store pages e.g. https://www.boots.com/stores/2250-alnwick-paikes-street-ne66-1hx\n        if len(address) == 0:\n            return\n\n        properties = {\n            \"ref\": response.xpath(\n                'normalize-space(//input[@id=\"bootsStoreId\"]/@value)'\n            ).extract_first(),\n            \"name\": response.xpath(\n                'normalize-space(//input[@id=\"inputLocation\"][@name=\"inputLocation\"]/@value)'\n            ).extract_first(),\n            \"postcode\": response.xpath(\n                'normalize-space(//input[@id=\"storePostcode\"]/@value)'\n            ).extract_first(),\n            \"addr_full\": address,\n            \"phone\": response.xpath(\n                '//section[@class=\"store_details_content rowContainer\"]/dl[@class=\"store_info_list\"][3]/dd[@class=\"store_info_list_item\"]/a/text()'\n            ).extract_first(),\n            \"country\": response.xpath(\n                'normalize-space(//input[@id=\"countryCode\"][@name=\"countryCode\"]/@value)'\n            ).extract_first(),\n            \"website\": response.url,\n            \"lat\": response.xpath(\n                'normalize-space(//input[@id=\"lat\"]/@value)'\n            ).extract_first(),\n            \"lon\": response.xpath(\n                'normalize-space(//input[@id=\"lon\"]/@value)'\n            ).extract_first(),\n        }\n\n        hours = self.parse_hours(\n            response.xpath(\n                '//div[@class=\"row store_all_opening_hours\"]/div[1]/table[@class=\"store_opening_hours \"]/tbody/tr'\n            )\n        )\n        if hours:\n            properties[\"opening_hours\"] = hours\n\n        if properties[\"name\"].startswith(\"Opticians - \"):\n            properties[\"brand\"] = \"Boots Opticians\"\n            properties[\"brand_wikidata\"] = \"Q4944037\"\n            properties[\"name\"] = properties[\"name\"][12:]\n\n        yield GeojsonPointItem(**properties)\n\n    def parse(self, response):\n        urls = response.xpath(\n            '//div[@class=\"brand_list_viewer\"]/div[@class=\"column\"]/ul/li/a/@href'\n        ).extract()\n        for path in urls:\n            yield scrapy.Request(response.urljoin(path), callback=self.parse_stores)\n", "path": "locations/spiders/boots.py"}]}