We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Unopened HTML tag causes exception in budoux 0.6
With budoux 0.6.0

```
budoux --html "foo</p>"

Traceback (most recent call last):
  File "/home/johnc/.local/bin/budoux", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/main.py", line 187, in main
    print(_main(test))
          ^^^^^^^^^^^
  File "/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/main.py", line 171, in _main
    res = parser.translate_html_string(inputs_html)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/parser.py", line 102, in translate_html_string
    return resolve(chunks, html)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/html_processor.py", line 124, in resolve
    resolver.feed(html)
  File "/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/html/parser.py", line 110, in feed
    self.goahead(0)
  File "/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/html/parser.py", line 172, in goahead
    k = self.parse_endtag(i)
        ^^^^^^^^^^^^^^^^^^^^
  File "/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/html/parser.py", line 413, in parse_endtag
    self.handle_endtag(elem)
  File "/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/html_processor.py", line 84, in handle_endtag
    self.to_skip = self.element_stack.get_nowait()
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/queue.py", line 199, in get_nowait
    return self.get(block=False)
           ^^^^^^^^^^^^^^^^^^^^^
  File "/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/queue.py", line 168, in get
    raise Empty
_queue.Empty
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `budoux/html_processor.py`
Content:
```
1 # Copyright 2023 Google LLC
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     https://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """HTML processor."""
15 
16 import json
17 import os
18 import queue
19 import typing
20 from html.parser import HTMLParser
21 
22 from .utils import SEP
23 
24 HTMLAttr = typing.List[typing.Tuple[str, typing.Union[str, None]]]
25 PARENT_CSS_STYLE = 'word-break: keep-all; overflow-wrap: anywhere;'
26 with open(
27     os.path.join(os.path.dirname(__file__), 'skip_nodes.json'),
28     encoding='utf-8') as f:
29   SKIP_NODES: typing.Set[str] = set(json.load(f))
30 
31 
32 class TextContentExtractor(HTMLParser):
33   """An HTML parser to extract text content.
34 
35   Attributes:
36     output (str): Accumulated text content.
37   """
38   output = ''
39 
40   def handle_data(self, data: str) -> None:
41     self.output += data
42 
43 
44 class HTMLChunkResolver(HTMLParser):
45   """An HTML parser to resolve the given HTML string and semantic chunks.
46 
47   Attributes:
48     output (str): The HTML string to output.
49   """
50   output = ''
51 
52   def __init__(self, chunks: typing.List[str], separator: str):
53     """Initializes the parser.
54 
55     Args:
56       chunks (List[str]): The chunks to resolve.
57       separator (str): The separator string.
58     """
59     HTMLParser.__init__(self)
60     self.chunks_joined = SEP.join(chunks)
61     self.separator = separator
62     self.to_skip = False
63     self.scan_index = 0
64     self.element_stack: queue.LifoQueue[bool] = queue.LifoQueue()
65 
66   def handle_starttag(self, tag: str, attrs: HTMLAttr) -> None:
67     attr_pairs = []
68     for attr in attrs:
69       if attr[1] is None:
70         attr_pairs.append(' ' + attr[0])
71       else:
72         attr_pairs.append(' %s="%s"' % (attr[0], attr[1]))
73     encoded_attrs = ''.join(attr_pairs)
74     self.element_stack.put(self.to_skip)
75     if tag.upper() in SKIP_NODES:
76       if not self.to_skip and self.chunks_joined[self.scan_index] == SEP:
77         self.scan_index += 1
78         self.output += self.separator
79       self.to_skip = True
80     self.output += '<%s%s>' % (tag, encoded_attrs)
81 
82   def handle_endtag(self, tag: str) -> None:
83     self.output += '</%s>' % (tag)
84     self.to_skip = self.element_stack.get_nowait()
85 
86   def handle_data(self, data: str) -> None:
87     for char in data:
88       if not char == self.chunks_joined[self.scan_index]:
89         if not self.to_skip:
90           self.output += self.separator
91         self.scan_index += 1
92       self.output += char
93       self.scan_index += 1
94 
95 
96 def get_text(html: str) -> str:
97   """Gets the text content from the input HTML string.
98 
99   Args:
100     html (str): Input HTML string.
101 
102   Returns:
103     The text content.
104   """
105   text_content_extractor = TextContentExtractor()
106   text_content_extractor.feed(html)
107   return text_content_extractor.output
108 
109 
110 def resolve(phrases: typing.List[str],
111             html: str,
112             separator: str = '\u200b') -> str:
113   """Wraps phrases in the HTML string with non-breaking markup.
114 
115   Args:
116     phrases (List[str]): The phrases included in the HTML string.
117     html (str): The HTML string to resolve.
118     separator (str, optional): The separator string.
119 
120   Returns:
121     The HTML string with phrases wrapped in non-breaking markup.
122   """
123   resolver = HTMLChunkResolver(phrases, separator)
124   resolver.feed(html)
125   result = '<span style="%s">%s</span>' % (PARENT_CSS_STYLE, resolver.output)
126   return result
127
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/budoux/html_processor.py b/budoux/html_processor.py
--- a/budoux/html_processor.py
+++ b/budoux/html_processor.py
@@ -29,6 +29,19 @@
   SKIP_NODES: typing.Set[str] = set(json.load(f))
 
 
+class ElementState(object):
+  """Represents the state for an element.
+
+  Attributes:
+    tag (str): The tag name.
+    to_skip (bool): Whether the content should be skipped or not.
+  """
+
+  def __init__(self, tag: str, to_skip: bool) -> None:
+    self.tag = tag
+    self.to_skip = to_skip
+
+
 class TextContentExtractor(HTMLParser):
   """An HTML parser to extract text content.
 
@@ -61,7 +74,7 @@
     self.separator = separator
     self.to_skip = False
     self.scan_index = 0
-    self.element_stack: queue.LifoQueue[bool] = queue.LifoQueue()
+    self.element_stack: queue.LifoQueue[ElementState] = queue.LifoQueue()
 
   def handle_starttag(self, tag: str, attrs: HTMLAttr) -> None:
     attr_pairs = []
@@ -71,7 +84,7 @@
       else:
         attr_pairs.append(' %s="%s"' % (attr[0], attr[1]))
     encoded_attrs = ''.join(attr_pairs)
-    self.element_stack.put(self.to_skip)
+    self.element_stack.put(ElementState(tag, self.to_skip))
     if tag.upper() in SKIP_NODES:
       if not self.to_skip and self.chunks_joined[self.scan_index] == SEP:
         self.scan_index += 1
@@ -81,7 +94,17 @@
 
   def handle_endtag(self, tag: str) -> None:
     self.output += '</%s>' % (tag)
-    self.to_skip = self.element_stack.get_nowait()
+    while not self.element_stack.empty():
+      state = self.element_stack.get_nowait()
+      if state.tag == tag:
+        self.to_skip = state.to_skip
+        break
+      # If the close tag doesn't match the open tag, remove it and keep looking.
+      # This means that close tags close their corresponding open tags.
+      # e.g., `<span>abc<img>def</span>` or `<p>abc<span>def</p>` are both valid
+      # HTML as per the HTML spec.
+      # Note the HTML "adoption agency algorithm" isn't fully supported.
+      # See https://html.spec.whatwg.org/multipage/parsing.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser
 
   def handle_data(self, data: str) -> None:
     for char in data:

{"golden_diff": "diff --git a/budoux/html_processor.py b/budoux/html_processor.py\n--- a/budoux/html_processor.py\n+++ b/budoux/html_processor.py\n@@ -29,6 +29,19 @@\n   SKIP_NODES: typing.Set[str] = set(json.load(f))\n \n \n+class ElementState(object):\n+  \"\"\"Represents the state for an element.\n+\n+  Attributes:\n+    tag (str): The tag name.\n+    to_skip (bool): Whether the content should be skipped or not.\n+  \"\"\"\n+\n+  def __init__(self, tag: str, to_skip: bool) -> None:\n+    self.tag = tag\n+    self.to_skip = to_skip\n+\n+\n class TextContentExtractor(HTMLParser):\n   \"\"\"An HTML parser to extract text content.\n \n@@ -61,7 +74,7 @@\n     self.separator = separator\n     self.to_skip = False\n     self.scan_index = 0\n-    self.element_stack: queue.LifoQueue[bool] = queue.LifoQueue()\n+    self.element_stack: queue.LifoQueue[ElementState] = queue.LifoQueue()\n \n   def handle_starttag(self, tag: str, attrs: HTMLAttr) -> None:\n     attr_pairs = []\n@@ -71,7 +84,7 @@\n       else:\n         attr_pairs.append(' %s=\"%s\"' % (attr[0], attr[1]))\n     encoded_attrs = ''.join(attr_pairs)\n-    self.element_stack.put(self.to_skip)\n+    self.element_stack.put(ElementState(tag, self.to_skip))\n     if tag.upper() in SKIP_NODES:\n       if not self.to_skip and self.chunks_joined[self.scan_index] == SEP:\n         self.scan_index += 1\n@@ -81,7 +94,17 @@\n \n   def handle_endtag(self, tag: str) -> None:\n     self.output += '</%s>' % (tag)\n-    self.to_skip = self.element_stack.get_nowait()\n+    while not self.element_stack.empty():\n+      state = self.element_stack.get_nowait()\n+      if state.tag == tag:\n+        self.to_skip = state.to_skip\n+        break\n+      # If the close tag doesn't match the open tag, remove it and keep looking.\n+      # This means that close tags close their corresponding open tags.\n+      # e.g., `<span>abc<img>def</span>` or `<p>abc<span>def</p>` are both valid\n+      # HTML as per the HTML spec.\n+      # Note the HTML \"adoption agency algorithm\" isn't fully supported.\n+      # See https://html.spec.whatwg.org/multipage/parsing.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser\n \n   def handle_data(self, data: str) -> None:\n     for char in data:\n", "issue": "Unopened HTML tag causes exception in budoux 0.6\nWith budoux 0.6.0\r\n\r\n```\r\nbudoux --html \"foo</p>\"\r\n\r\nTraceback (most recent call last):\r\n  File \"/home/johnc/.local/bin/budoux\", line 8, in <module>\r\n    sys.exit(main())\r\n             ^^^^^^\r\n  File \"/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/main.py\", line 187, in main\r\n    print(_main(test))\r\n          ^^^^^^^^^^^\r\n  File \"/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/main.py\", line 171, in _main\r\n    res = parser.translate_html_string(inputs_html)\r\n          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/parser.py\", line 102, in translate_html_string\r\n    return resolve(chunks, html)\r\n           ^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/html_processor.py\", line 124, in resolve\r\n    resolver.feed(html)\r\n  File \"/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/html/parser.py\", line 110, in feed\r\n    self.goahead(0)\r\n  File \"/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/html/parser.py\", line 172, in goahead\r\n    k = self.parse_endtag(i)\r\n        ^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/html/parser.py\", line 413, in parse_endtag\r\n    self.handle_endtag(elem)\r\n  File \"/home/johnc/.local/pipx/venvs/budoux/lib/python3.11/site-packages/budoux/html_processor.py\", line 84, in handle_endtag\r\n    self.to_skip = self.element_stack.get_nowait()\r\n                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/queue.py\", line 199, in get_nowait\r\n    return self.get(block=False)\r\n           ^^^^^^^^^^^^^^^^^^^^^\r\n  File \"/home/johnc/.pyenv/versions/3.11.3/lib/python3.11/queue.py\", line 168, in get\r\n    raise Empty\r\n_queue.Empty\r\n```\r\n\n", "before_files": [{"content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"HTML processor.\"\"\"\n\nimport json\nimport os\nimport queue\nimport typing\nfrom html.parser import HTMLParser\n\nfrom .utils import SEP\n\nHTMLAttr = typing.List[typing.Tuple[str, typing.Union[str, None]]]\nPARENT_CSS_STYLE = 'word-break: keep-all; overflow-wrap: anywhere;'\nwith open(\n    os.path.join(os.path.dirname(__file__), 'skip_nodes.json'),\n    encoding='utf-8') as f:\n  SKIP_NODES: typing.Set[str] = set(json.load(f))\n\n\nclass TextContentExtractor(HTMLParser):\n  \"\"\"An HTML parser to extract text content.\n\n  Attributes:\n    output (str): Accumulated text content.\n  \"\"\"\n  output = ''\n\n  def handle_data(self, data: str) -> None:\n    self.output += data\n\n\nclass HTMLChunkResolver(HTMLParser):\n  \"\"\"An HTML parser to resolve the given HTML string and semantic chunks.\n\n  Attributes:\n    output (str): The HTML string to output.\n  \"\"\"\n  output = ''\n\n  def __init__(self, chunks: typing.List[str], separator: str):\n    \"\"\"Initializes the parser.\n\n    Args:\n      chunks (List[str]): The chunks to resolve.\n      separator (str): The separator string.\n    \"\"\"\n    HTMLParser.__init__(self)\n    self.chunks_joined = SEP.join(chunks)\n    self.separator = separator\n    self.to_skip = False\n    self.scan_index = 0\n    self.element_stack: queue.LifoQueue[bool] = queue.LifoQueue()\n\n  def handle_starttag(self, tag: str, attrs: HTMLAttr) -> None:\n    attr_pairs = []\n    for attr in attrs:\n      if attr[1] is None:\n        attr_pairs.append(' ' + attr[0])\n      else:\n        attr_pairs.append(' %s=\"%s\"' % (attr[0], attr[1]))\n    encoded_attrs = ''.join(attr_pairs)\n    self.element_stack.put(self.to_skip)\n    if tag.upper() in SKIP_NODES:\n      if not self.to_skip and self.chunks_joined[self.scan_index] == SEP:\n        self.scan_index += 1\n        self.output += self.separator\n      self.to_skip = True\n    self.output += '<%s%s>' % (tag, encoded_attrs)\n\n  def handle_endtag(self, tag: str) -> None:\n    self.output += '</%s>' % (tag)\n    self.to_skip = self.element_stack.get_nowait()\n\n  def handle_data(self, data: str) -> None:\n    for char in data:\n      if not char == self.chunks_joined[self.scan_index]:\n        if not self.to_skip:\n          self.output += self.separator\n        self.scan_index += 1\n      self.output += char\n      self.scan_index += 1\n\n\ndef get_text(html: str) -> str:\n  \"\"\"Gets the text content from the input HTML string.\n\n  Args:\n    html (str): Input HTML string.\n\n  Returns:\n    The text content.\n  \"\"\"\n  text_content_extractor = TextContentExtractor()\n  text_content_extractor.feed(html)\n  return text_content_extractor.output\n\n\ndef resolve(phrases: typing.List[str],\n            html: str,\n            separator: str = '\\u200b') -> str:\n  \"\"\"Wraps phrases in the HTML string with non-breaking markup.\n\n  Args:\n    phrases (List[str]): The phrases included in the HTML string.\n    html (str): The HTML string to resolve.\n    separator (str, optional): The separator string.\n\n  Returns:\n    The HTML string with phrases wrapped in non-breaking markup.\n  \"\"\"\n  resolver = HTMLChunkResolver(phrases, separator)\n  resolver.feed(html)\n  result = '<span style=\"%s\">%s</span>' % (PARENT_CSS_STYLE, resolver.output)\n  return result\n", "path": "budoux/html_processor.py"}], "after_files": [{"content": "# Copyright 2023 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"HTML processor.\"\"\"\n\nimport json\nimport os\nimport queue\nimport typing\nfrom html.parser import HTMLParser\n\nfrom .utils import SEP\n\nHTMLAttr = typing.List[typing.Tuple[str, typing.Union[str, None]]]\nPARENT_CSS_STYLE = 'word-break: keep-all; overflow-wrap: anywhere;'\nwith open(\n    os.path.join(os.path.dirname(__file__), 'skip_nodes.json'),\n    encoding='utf-8') as f:\n  SKIP_NODES: typing.Set[str] = set(json.load(f))\n\n\nclass ElementState(object):\n  \"\"\"Represents the state for an element.\n\n  Attributes:\n    tag (str): The tag name.\n    to_skip (bool): Whether the content should be skipped or not.\n  \"\"\"\n\n  def __init__(self, tag: str, to_skip: bool) -> None:\n    self.tag = tag\n    self.to_skip = to_skip\n\n\nclass TextContentExtractor(HTMLParser):\n  \"\"\"An HTML parser to extract text content.\n\n  Attributes:\n    output (str): Accumulated text content.\n  \"\"\"\n  output = ''\n\n  def handle_data(self, data: str) -> None:\n    self.output += data\n\n\nclass HTMLChunkResolver(HTMLParser):\n  \"\"\"An HTML parser to resolve the given HTML string and semantic chunks.\n\n  Attributes:\n    output (str): The HTML string to output.\n  \"\"\"\n  output = ''\n\n  def __init__(self, chunks: typing.List[str], separator: str):\n    \"\"\"Initializes the parser.\n\n    Args:\n      chunks (List[str]): The chunks to resolve.\n      separator (str): The separator string.\n    \"\"\"\n    HTMLParser.__init__(self)\n    self.chunks_joined = SEP.join(chunks)\n    self.separator = separator\n    self.to_skip = False\n    self.scan_index = 0\n    self.element_stack: queue.LifoQueue[ElementState] = queue.LifoQueue()\n\n  def handle_starttag(self, tag: str, attrs: HTMLAttr) -> None:\n    attr_pairs = []\n    for attr in attrs:\n      if attr[1] is None:\n        attr_pairs.append(' ' + attr[0])\n      else:\n        attr_pairs.append(' %s=\"%s\"' % (attr[0], attr[1]))\n    encoded_attrs = ''.join(attr_pairs)\n    self.element_stack.put(ElementState(tag, self.to_skip))\n    if tag.upper() in SKIP_NODES:\n      if not self.to_skip and self.chunks_joined[self.scan_index] == SEP:\n        self.scan_index += 1\n        self.output += self.separator\n      self.to_skip = True\n    self.output += '<%s%s>' % (tag, encoded_attrs)\n\n  def handle_endtag(self, tag: str) -> None:\n    self.output += '</%s>' % (tag)\n    while not self.element_stack.empty():\n      state = self.element_stack.get_nowait()\n      if state.tag == tag:\n        self.to_skip = state.to_skip\n        break\n      # If the close tag doesn't match the open tag, remove it and keep looking.\n      # This means that close tags close their corresponding open tags.\n      # e.g., `<span>abc<img>def</span>` or `<p>abc<span>def</p>` are both valid\n      # HTML as per the HTML spec.\n      # Note the HTML \"adoption agency algorithm\" isn't fully supported.\n      # See https://html.spec.whatwg.org/multipage/parsing.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser\n\n  def handle_data(self, data: str) -> None:\n    for char in data:\n      if not char == self.chunks_joined[self.scan_index]:\n        if not self.to_skip:\n          self.output += self.separator\n        self.scan_index += 1\n      self.output += char\n      self.scan_index += 1\n\n\ndef get_text(html: str) -> str:\n  \"\"\"Gets the text content from the input HTML string.\n\n  Args:\n    html (str): Input HTML string.\n\n  Returns:\n    The text content.\n  \"\"\"\n  text_content_extractor = TextContentExtractor()\n  text_content_extractor.feed(html)\n  return text_content_extractor.output\n\n\ndef resolve(phrases: typing.List[str],\n            html: str,\n            separator: str = '\\u200b') -> str:\n  \"\"\"Wraps phrases in the HTML string with non-breaking markup.\n\n  Args:\n    phrases (List[str]): The phrases included in the HTML string.\n    html (str): The HTML string to resolve.\n    separator (str, optional): The separator string.\n\n  Returns:\n    The HTML string with phrases wrapped in non-breaking markup.\n  \"\"\"\n  resolver = HTMLChunkResolver(phrases, separator)\n  resolver.feed(html)\n  result = '<span style=\"%s\">%s</span>' % (PARENT_CSS_STYLE, resolver.output)\n  return result\n", "path": "budoux/html_processor.py"}]}