We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
BUG: Graphviz repr should escape HTML
The current notebook graphviz repr breaks when there are unintentional HTML characters in column names or types. An example of this is array types, which includes angle brackets, so a type like `array<string>` fails to render because it produces invalid HTML.

The fix is fairly straightforward: names and columns should be escaped. I should be able to submit a PR.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `ibis/expr/visualize.py`
Content:
```
1 import tempfile
2 
3 import graphviz as gv
4 
5 import ibis
6 import ibis.common.exceptions as com
7 import ibis.expr.operations as ops
8 import ibis.expr.types as ir
9 
10 
11 def get_type(expr):
12     try:
13         return str(expr.type())
14     except (AttributeError, NotImplementedError):
15         pass
16 
17     try:
18         schema = expr.schema()
19     except (AttributeError, NotImplementedError):
20         try:
21             # As a last resort try get the name of the output_type class
22             return expr.op().output_type().__name__
23         except (AttributeError, NotImplementedError):
24             return '\u2205'  # empty set character
25     except com.IbisError:
26         op = expr.op()
27         assert isinstance(op, ops.Join)
28         left_table_name = getattr(op.left.op(), 'name', None) or ops.genname()
29         left_schema = op.left.schema()
30         right_table_name = (
31             getattr(op.right.op(), 'name', None) or ops.genname()
32         )
33         right_schema = op.right.schema()
34         pairs = [
35             ('{}.{}'.format(left_table_name, left_column), type)
36             for left_column, type in left_schema.items()
37         ] + [
38             ('{}.{}'.format(right_table_name, right_column), type)
39             for right_column, type in right_schema.items()
40         ]
41         schema = ibis.schema(pairs)
42 
43     return (
44         ''.join(
45             '<BR ALIGN="LEFT" />  <I>{}</I>: {}'.format(name, type)
46             for name, type in zip(schema.names, schema.types)
47         )
48         + '<BR ALIGN="LEFT" />'
49     )
50 
51 
52 def get_label(expr, argname=None):
53     import ibis.expr.operations as ops
54 
55     node = expr.op()
56     typename = get_type(expr)
57     name = type(node).__name__
58     nodename = getattr(node, 'name', argname)
59     if nodename is not None:
60         if isinstance(node, ops.TableNode):
61             label_fmt = '<<I>{}</I>: <B>{}</B>{}>'
62         else:
63             label_fmt = '<<I>{}</I>: <B>{}</B> \u27f6 {}>'
64         label = label_fmt.format(nodename, name, typename)
65     else:
66         if isinstance(node, ops.TableNode):
67             label_fmt = '<<B>{}</B>{}>'
68         else:
69             label_fmt = '<<B>{}</B> \u27f6 {}>'
70         label = label_fmt.format(name, typename)
71     return label
72 
73 
74 DEFAULT_NODE_ATTRS = {'shape': 'box', 'fontname': 'Deja Vu Sans Mono'}
75 
76 
77 def to_graph(expr, node_attr=None, edge_attr=None):
78     stack = [(expr, expr._safe_name)]
79     seen = set()
80     g = gv.Digraph(
81         node_attr=node_attr or DEFAULT_NODE_ATTRS, edge_attr=edge_attr or {}
82     )
83 
84     g.attr(rankdir='BT')
85 
86     while stack:
87         e, ename = stack.pop()
88         vkey = e._key, ename
89 
90         if vkey not in seen:
91             seen.add(vkey)
92 
93             vlabel = get_label(e, argname=ename)
94             vhash = str(hash(vkey))
95             g.node(vhash, label=vlabel)
96 
97             node = e.op()
98             args = node.args
99             for arg, name in zip(args, node.signature.names()):
100                 if isinstance(arg, ir.Expr):
101                     u = arg, name
102                     ukey = arg._key, name
103                     uhash = str(hash(ukey))
104                     ulabel = get_label(arg, argname=name)
105                     g.node(uhash, label=ulabel)
106                     g.edge(uhash, vhash)
107                     stack.append(u)
108     return g
109 
110 
111 def draw(graph, path=None, format='png'):
112     piped_source = graph.pipe(format=format)
113 
114     if path is None:
115         with tempfile.NamedTemporaryFile(
116             delete=False, suffix='.{}'.format(format), mode='wb'
117         ) as f:
118             f.write(piped_source)
119         return f.name
120     else:
121         with open(path, mode='wb') as f:
122             f.write(piped_source)
123         return path
124 
125 
126 if __name__ == '__main__':
127     t = ibis.table(
128         [('a', 'int64'), ('b', 'double'), ('c', 'string')], name='t'
129     )
130     left = ibis.table([('a', 'int64'), ('b', 'string')])
131     right = ibis.table([('b', 'string'), ('c', 'int64'), ('d', 'string')])
132     joined = left.inner_join(right, left.b == right.b)
133     df = joined[left.a, right.c.name('b'), right.d.name('c')]
134     a = df.a
135     b = df.b
136     filt = df[(a + b * 2 * b / b ** 3 > 4) & (b > 5)]
137     expr = filt.groupby(filt.c).aggregate(
138         amean=filt.a.mean(), bsum=filt.b.sum()
139     )
140     expr.visualize()
141
```
--- 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/ibis/expr/visualize.py b/ibis/expr/visualize.py
--- a/ibis/expr/visualize.py
+++ b/ibis/expr/visualize.py
@@ -1,4 +1,5 @@
 import tempfile
+from html import escape
 
 import graphviz as gv
 
@@ -42,7 +43,9 @@
 
     return (
         ''.join(
-            '<BR ALIGN="LEFT" />  <I>{}</I>: {}'.format(name, type)
+            '<BR ALIGN="LEFT" />  <I>{}</I>: {}'.format(
+                escape(name), escape(str(type))
+            )
             for name, type in zip(schema.names, schema.types)
         )
         + '<BR ALIGN="LEFT" />'
@@ -53,7 +56,7 @@
     import ibis.expr.operations as ops
 
     node = expr.op()
-    typename = get_type(expr)
+    typename = get_type(expr)  # Already an escaped string
     name = type(node).__name__
     nodename = getattr(node, 'name', argname)
     if nodename is not None:
@@ -61,13 +64,13 @@
             label_fmt = '<<I>{}</I>: <B>{}</B>{}>'
         else:
             label_fmt = '<<I>{}</I>: <B>{}</B> \u27f6 {}>'
-        label = label_fmt.format(nodename, name, typename)
+        label = label_fmt.format(escape(nodename), escape(name), typename)
     else:
         if isinstance(node, ops.TableNode):
             label_fmt = '<<B>{}</B>{}>'
         else:
             label_fmt = '<<B>{}</B> \u27f6 {}>'
-        label = label_fmt.format(name, typename)
+        label = label_fmt.format(escape(name), typename)
     return label

{"golden_diff": "diff --git a/ibis/expr/visualize.py b/ibis/expr/visualize.py\n--- a/ibis/expr/visualize.py\n+++ b/ibis/expr/visualize.py\n@@ -1,4 +1,5 @@\n import tempfile\n+from html import escape\n \n import graphviz as gv\n \n@@ -42,7 +43,9 @@\n \n     return (\n         ''.join(\n-            '<BR ALIGN=\"LEFT\" />  <I>{}</I>: {}'.format(name, type)\n+            '<BR ALIGN=\"LEFT\" />  <I>{}</I>: {}'.format(\n+                escape(name), escape(str(type))\n+            )\n             for name, type in zip(schema.names, schema.types)\n         )\n         + '<BR ALIGN=\"LEFT\" />'\n@@ -53,7 +56,7 @@\n     import ibis.expr.operations as ops\n \n     node = expr.op()\n-    typename = get_type(expr)\n+    typename = get_type(expr)  # Already an escaped string\n     name = type(node).__name__\n     nodename = getattr(node, 'name', argname)\n     if nodename is not None:\n@@ -61,13 +64,13 @@\n             label_fmt = '<<I>{}</I>: <B>{}</B>{}>'\n         else:\n             label_fmt = '<<I>{}</I>: <B>{}</B> \\u27f6 {}>'\n-        label = label_fmt.format(nodename, name, typename)\n+        label = label_fmt.format(escape(nodename), escape(name), typename)\n     else:\n         if isinstance(node, ops.TableNode):\n             label_fmt = '<<B>{}</B>{}>'\n         else:\n             label_fmt = '<<B>{}</B> \\u27f6 {}>'\n-        label = label_fmt.format(name, typename)\n+        label = label_fmt.format(escape(name), typename)\n     return label\n", "issue": "BUG: Graphviz repr should escape HTML\nThe current notebook graphviz repr breaks when there are unintentional HTML characters in column names or types. An example of this is array types, which includes angle brackets, so a type like `array<string>` fails to render because it produces invalid HTML.\r\n\r\nThe fix is fairly straightforward: names and columns should be escaped. I should be able to submit a PR.\n", "before_files": [{"content": "import tempfile\n\nimport graphviz as gv\n\nimport ibis\nimport ibis.common.exceptions as com\nimport ibis.expr.operations as ops\nimport ibis.expr.types as ir\n\n\ndef get_type(expr):\n    try:\n        return str(expr.type())\n    except (AttributeError, NotImplementedError):\n        pass\n\n    try:\n        schema = expr.schema()\n    except (AttributeError, NotImplementedError):\n        try:\n            # As a last resort try get the name of the output_type class\n            return expr.op().output_type().__name__\n        except (AttributeError, NotImplementedError):\n            return '\\u2205'  # empty set character\n    except com.IbisError:\n        op = expr.op()\n        assert isinstance(op, ops.Join)\n        left_table_name = getattr(op.left.op(), 'name', None) or ops.genname()\n        left_schema = op.left.schema()\n        right_table_name = (\n            getattr(op.right.op(), 'name', None) or ops.genname()\n        )\n        right_schema = op.right.schema()\n        pairs = [\n            ('{}.{}'.format(left_table_name, left_column), type)\n            for left_column, type in left_schema.items()\n        ] + [\n            ('{}.{}'.format(right_table_name, right_column), type)\n            for right_column, type in right_schema.items()\n        ]\n        schema = ibis.schema(pairs)\n\n    return (\n        ''.join(\n            '<BR ALIGN=\"LEFT\" />  <I>{}</I>: {}'.format(name, type)\n            for name, type in zip(schema.names, schema.types)\n        )\n        + '<BR ALIGN=\"LEFT\" />'\n    )\n\n\ndef get_label(expr, argname=None):\n    import ibis.expr.operations as ops\n\n    node = expr.op()\n    typename = get_type(expr)\n    name = type(node).__name__\n    nodename = getattr(node, 'name', argname)\n    if nodename is not None:\n        if isinstance(node, ops.TableNode):\n            label_fmt = '<<I>{}</I>: <B>{}</B>{}>'\n        else:\n            label_fmt = '<<I>{}</I>: <B>{}</B> \\u27f6 {}>'\n        label = label_fmt.format(nodename, name, typename)\n    else:\n        if isinstance(node, ops.TableNode):\n            label_fmt = '<<B>{}</B>{}>'\n        else:\n            label_fmt = '<<B>{}</B> \\u27f6 {}>'\n        label = label_fmt.format(name, typename)\n    return label\n\n\nDEFAULT_NODE_ATTRS = {'shape': 'box', 'fontname': 'Deja Vu Sans Mono'}\n\n\ndef to_graph(expr, node_attr=None, edge_attr=None):\n    stack = [(expr, expr._safe_name)]\n    seen = set()\n    g = gv.Digraph(\n        node_attr=node_attr or DEFAULT_NODE_ATTRS, edge_attr=edge_attr or {}\n    )\n\n    g.attr(rankdir='BT')\n\n    while stack:\n        e, ename = stack.pop()\n        vkey = e._key, ename\n\n        if vkey not in seen:\n            seen.add(vkey)\n\n            vlabel = get_label(e, argname=ename)\n            vhash = str(hash(vkey))\n            g.node(vhash, label=vlabel)\n\n            node = e.op()\n            args = node.args\n            for arg, name in zip(args, node.signature.names()):\n                if isinstance(arg, ir.Expr):\n                    u = arg, name\n                    ukey = arg._key, name\n                    uhash = str(hash(ukey))\n                    ulabel = get_label(arg, argname=name)\n                    g.node(uhash, label=ulabel)\n                    g.edge(uhash, vhash)\n                    stack.append(u)\n    return g\n\n\ndef draw(graph, path=None, format='png'):\n    piped_source = graph.pipe(format=format)\n\n    if path is None:\n        with tempfile.NamedTemporaryFile(\n            delete=False, suffix='.{}'.format(format), mode='wb'\n        ) as f:\n            f.write(piped_source)\n        return f.name\n    else:\n        with open(path, mode='wb') as f:\n            f.write(piped_source)\n        return path\n\n\nif __name__ == '__main__':\n    t = ibis.table(\n        [('a', 'int64'), ('b', 'double'), ('c', 'string')], name='t'\n    )\n    left = ibis.table([('a', 'int64'), ('b', 'string')])\n    right = ibis.table([('b', 'string'), ('c', 'int64'), ('d', 'string')])\n    joined = left.inner_join(right, left.b == right.b)\n    df = joined[left.a, right.c.name('b'), right.d.name('c')]\n    a = df.a\n    b = df.b\n    filt = df[(a + b * 2 * b / b ** 3 > 4) & (b > 5)]\n    expr = filt.groupby(filt.c).aggregate(\n        amean=filt.a.mean(), bsum=filt.b.sum()\n    )\n    expr.visualize()\n", "path": "ibis/expr/visualize.py"}], "after_files": [{"content": "import tempfile\nfrom html import escape\n\nimport graphviz as gv\n\nimport ibis\nimport ibis.common.exceptions as com\nimport ibis.expr.operations as ops\nimport ibis.expr.types as ir\n\n\ndef get_type(expr):\n    try:\n        return str(expr.type())\n    except (AttributeError, NotImplementedError):\n        pass\n\n    try:\n        schema = expr.schema()\n    except (AttributeError, NotImplementedError):\n        try:\n            # As a last resort try get the name of the output_type class\n            return expr.op().output_type().__name__\n        except (AttributeError, NotImplementedError):\n            return '\\u2205'  # empty set character\n    except com.IbisError:\n        op = expr.op()\n        assert isinstance(op, ops.Join)\n        left_table_name = getattr(op.left.op(), 'name', None) or ops.genname()\n        left_schema = op.left.schema()\n        right_table_name = (\n            getattr(op.right.op(), 'name', None) or ops.genname()\n        )\n        right_schema = op.right.schema()\n        pairs = [\n            ('{}.{}'.format(left_table_name, left_column), type)\n            for left_column, type in left_schema.items()\n        ] + [\n            ('{}.{}'.format(right_table_name, right_column), type)\n            for right_column, type in right_schema.items()\n        ]\n        schema = ibis.schema(pairs)\n\n    return (\n        ''.join(\n            '<BR ALIGN=\"LEFT\" />  <I>{}</I>: {}'.format(\n                escape(name), escape(str(type))\n            )\n            for name, type in zip(schema.names, schema.types)\n        )\n        + '<BR ALIGN=\"LEFT\" />'\n    )\n\n\ndef get_label(expr, argname=None):\n    import ibis.expr.operations as ops\n\n    node = expr.op()\n    typename = get_type(expr)  # Already an escaped string\n    name = type(node).__name__\n    nodename = getattr(node, 'name', argname)\n    if nodename is not None:\n        if isinstance(node, ops.TableNode):\n            label_fmt = '<<I>{}</I>: <B>{}</B>{}>'\n        else:\n            label_fmt = '<<I>{}</I>: <B>{}</B> \\u27f6 {}>'\n        label = label_fmt.format(escape(nodename), escape(name), typename)\n    else:\n        if isinstance(node, ops.TableNode):\n            label_fmt = '<<B>{}</B>{}>'\n        else:\n            label_fmt = '<<B>{}</B> \\u27f6 {}>'\n        label = label_fmt.format(escape(name), typename)\n    return label\n\n\nDEFAULT_NODE_ATTRS = {'shape': 'box', 'fontname': 'Deja Vu Sans Mono'}\n\n\ndef to_graph(expr, node_attr=None, edge_attr=None):\n    stack = [(expr, expr._safe_name)]\n    seen = set()\n    g = gv.Digraph(\n        node_attr=node_attr or DEFAULT_NODE_ATTRS, edge_attr=edge_attr or {}\n    )\n\n    g.attr(rankdir='BT')\n\n    while stack:\n        e, ename = stack.pop()\n        vkey = e._key, ename\n\n        if vkey not in seen:\n            seen.add(vkey)\n\n            vlabel = get_label(e, argname=ename)\n            vhash = str(hash(vkey))\n            g.node(vhash, label=vlabel)\n\n            node = e.op()\n            args = node.args\n            for arg, name in zip(args, node.signature.names()):\n                if isinstance(arg, ir.Expr):\n                    u = arg, name\n                    ukey = arg._key, name\n                    uhash = str(hash(ukey))\n                    ulabel = get_label(arg, argname=name)\n                    g.node(uhash, label=ulabel)\n                    g.edge(uhash, vhash)\n                    stack.append(u)\n    return g\n\n\ndef draw(graph, path=None, format='png'):\n    piped_source = graph.pipe(format=format)\n\n    if path is None:\n        with tempfile.NamedTemporaryFile(\n            delete=False, suffix='.{}'.format(format), mode='wb'\n        ) as f:\n            f.write(piped_source)\n        return f.name\n    else:\n        with open(path, mode='wb') as f:\n            f.write(piped_source)\n        return path\n\n\nif __name__ == '__main__':\n    t = ibis.table(\n        [('a', 'int64'), ('b', 'double'), ('c', 'string')], name='t'\n    )\n    left = ibis.table([('a', 'int64'), ('b', 'string')])\n    right = ibis.table([('b', 'string'), ('c', 'int64'), ('d', 'string')])\n    joined = left.inner_join(right, left.b == right.b)\n    df = joined[left.a, right.c.name('b'), right.d.name('c')]\n    a = df.a\n    b = df.b\n    filt = df[(a + b * 2 * b / b ** 3 > 4) & (b > 5)]\n    expr = filt.groupby(filt.c).aggregate(\n        amean=filt.a.mean(), bsum=filt.b.sum()\n    )\n    expr.visualize()\n", "path": "ibis/expr/visualize.py"}]}