We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[html saver] Saving typed columns as html (int/vlen/bool) causes exception
I tried to copy (yank) a couple of rows from the frequency sheet and it provided me the following error. I believe this is due to the html parser expecting strings? A similar error also occurs in other sheets when using unexpected py types (e.g. bool).

FrequencySheet error
```
Traceback (most recent call last):
  File "/Documents/pyv/py3/lib/python3.7/site-packages/visidata/threads.py", line 201, in _toplevelTryFunc
    t.status = func(*args, **kwargs)
  File "/Documents/pyv/py3/lib/python3.7/site-packages/visidata/loaders/html.py", line 124, in save_html
    fp.write(html.escape(val))
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/html/__init__.py", line 19, in escape
    s = s.replace("&", "&amp;") # Must be done first!
AttributeError: 'vlen' object has no attribute 'replace'
```

Sheet with a bool column error:
```
Traceback (most recent call last):
  File "/Documents/pyv/py3/lib/python3.7/site-packages/visidata/threads.py", line 201, in _toplevelTryFunc
    t.status = func(*args, **kwargs)
  File "/Documents/pyv/py3/lib/python3.7/site-packages/visidata/loaders/html.py", line 124, in save_html
    fp.write(html.escape(val))
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/html/__init__.py", line 19, in escape
    s = s.replace("&", "&amp;") # Must be done first!
AttributeError: 'bool' object has no attribute 'replace'
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `visidata/loaders/html.py`
Content:
```
1 import html
2 from visidata import *
3 
4 
5 class HtmlTablesSheet(IndexSheet):
6     rowtype = 'sheets'  # rowdef: HtmlTableSheet (sheet.html = lxml.html.HtmlElement)
7     columns = IndexSheet.columns + [
8         Column('tag', width=0, getter=lambda col,row: row.html.tag),
9         Column('id', getter=lambda col,row: row.html.attrib.get('id')),
10         Column('classes', getter=lambda col,row: row.html.attrib.get('class')),
11     ]
12     def iterload(self):
13         import lxml.html
14         from lxml import etree
15         utf8_parser = etree.HTMLParser(encoding='utf-8')
16         with self.source.open_text() as fp:
17             html = lxml.html.etree.parse(fp, parser=utf8_parser)
18         self.setKeys([self.column('name')])
19         self.column('keys').hide()
20         self.column('source').hide()
21 
22         for i, e in enumerate(html.iter('table')):
23             if e.tag == 'table':
24                 vs = HtmlTableSheet(e.attrib.get("id", "table_" + str(i)), source=e)
25                 vs.reload()
26                 vs.html = e
27                 yield vs
28 
29 
30 def is_header(elem):
31     scope = elem.attrib.get('scope', '')
32 
33     if elem.tag == 'th':
34         if not scope or scope == 'col':
35             return True
36 
37     return False
38 
39 class HtmlTableSheet(Sheet):
40     rowtype = 'rows'  #  list of strings
41     columns = []
42 
43     def iterload(self):
44         headers = []
45 
46         maxlinks = {}  # [colnum] -> nlinks:int
47 
48         for rownum, r in enumerate(self.source.iter('tr')):
49             row = []
50 
51             colnum = 0
52             # get starting column, which might be different if there were rowspan>1 already
53             if rownum < len(headers):
54                 while colnum < len(headers[rownum]):
55                     if headers[rownum][colnum] is None:
56                         break
57                     colnum += 1
58 
59             for cell in r.getchildren():
60                 colspan = int(cell.attrib.get('colspan', 1))
61                 rowspan = int(cell.attrib.get('rowspan', 1))
62                 cellval = ' '.join(x.strip() for x in cell.itertext())  # text only without markup
63                 links = [x.get('href') for x in cell.iter('a')]
64                 maxlinks[colnum] = max(maxlinks.get(colnum, 0), len(links))
65 
66                 if is_header(cell):
67                     for k in range(rownum, rownum+rowspan):
68                         while k >= len(headers):  # extend headers list with lists for all header rows
69                             headers.append([])
70 
71                         for j in range(colnum, colnum+colspan):
72                             while j >= len(headers[k]):
73                                 headers[k].append(None)
74                             headers[k][j] = cellval
75                         cellval = ''   # use empty non-None value for subsequent rows in the rowspan
76                 else:
77                     while colnum >= len(row):
78                         row.append(None)
79                     row[colnum] = (cellval, links)
80 
81                 colnum += colspan
82 
83             if any(row):
84                 yield row
85 
86         self.columns = []
87         if headers:
88             it = itertools.zip_longest(*headers, fillvalue='')
89         else:
90             it = [list(x) for x in self.rows[0]]
91             self.rows = self.rows[1:]
92 
93         for colnum, names in enumerate(it):
94             name = '_'.join(str(x) for x in names if x)
95             self.addColumn(Column(name, getter=lambda c,r,i=colnum: r[i][0]))
96             for linknum in range(maxlinks.get(colnum, 0)):
97                 self.addColumn(Column(name+'_link'+str(linknum), width=20, getter=lambda c,r,i=colnum,j=linknum: r[i][1][j]))
98 
99 
100 @VisiData.api
101 def save_html(vd, p, *vsheets):
102     'Save vsheets as HTML tables in a single file'
103 
104     with open(p, 'w', encoding='ascii', errors='xmlcharrefreplace') as fp:
105         for sheet in vsheets:
106 
107             fp.write('<h2 class="sheetname">%s</h2>\n'.format(sheetname=html.escape(sheet.name)))
108 
109             fp.write('<table id="{sheetname}">\n'.format(sheetname=html.escape(sheet.name)))
110 
111             # headers
112             fp.write('<tr>')
113             for col in sheet.visibleCols:
114                 contents = html.escape(col.name)
115                 fp.write('<th>{colname}</th>'.format(colname=contents))
116             fp.write('</tr>\n')
117 
118             # rows
119             with Progress(gerund='saving'):
120                 for typedvals in sheet.iterdispvals(format=False):
121                     fp.write('<tr>')
122                     for col, val in typedvals.items():
123                         fp.write('<td>')
124                         fp.write(html.escape(val))
125                         fp.write('</td>')
126                     fp.write('</tr>\n')
127 
128             fp.write('</table>')
129             vd.status('%s save finished' % p)
130 
131 
132 VisiData.save_htm = VisiData.save_html
133 
134 
135 vd.filetype('html', HtmlTablesSheet)
136 vd.filetype('htm', HtmlTablesSheet)
137
```
--- 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/visidata/loaders/html.py b/visidata/loaders/html.py
--- a/visidata/loaders/html.py
+++ b/visidata/loaders/html.py
@@ -121,7 +121,7 @@
                     fp.write('<tr>')
                     for col, val in typedvals.items():
                         fp.write('<td>')
-                        fp.write(html.escape(val))
+                        fp.write(html.escape(str(val)))
                         fp.write('</td>')
                     fp.write('</tr>\n')

{"golden_diff": "diff --git a/visidata/loaders/html.py b/visidata/loaders/html.py\n--- a/visidata/loaders/html.py\n+++ b/visidata/loaders/html.py\n@@ -121,7 +121,7 @@\n                     fp.write('<tr>')\n                     for col, val in typedvals.items():\n                         fp.write('<td>')\n-                        fp.write(html.escape(val))\n+                        fp.write(html.escape(str(val)))\n                         fp.write('</td>')\n                     fp.write('</tr>\\n')\n", "issue": "[html saver] Saving typed columns as html (int/vlen/bool) causes exception\nI tried to copy (yank) a couple of rows from the frequency sheet and it provided me the following error. I believe this is due to the html parser expecting strings? A similar error also occurs in other sheets when using unexpected py types (e.g. bool).\r\n\r\nFrequencySheet error\r\n```\r\nTraceback (most recent call last):\r\n  File \"/Documents/pyv/py3/lib/python3.7/site-packages/visidata/threads.py\", line 201, in _toplevelTryFunc\r\n    t.status = func(*args, **kwargs)\r\n  File \"/Documents/pyv/py3/lib/python3.7/site-packages/visidata/loaders/html.py\", line 124, in save_html\r\n    fp.write(html.escape(val))\r\n  File \"/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/html/__init__.py\", line 19, in escape\r\n    s = s.replace(\"&\", \"&amp;\") # Must be done first!\r\nAttributeError: 'vlen' object has no attribute 'replace'\r\n```\r\n\r\nSheet with a bool column error:\r\n```\r\nTraceback (most recent call last):\r\n  File \"/Documents/pyv/py3/lib/python3.7/site-packages/visidata/threads.py\", line 201, in _toplevelTryFunc\r\n    t.status = func(*args, **kwargs)\r\n  File \"/Documents/pyv/py3/lib/python3.7/site-packages/visidata/loaders/html.py\", line 124, in save_html\r\n    fp.write(html.escape(val))\r\n  File \"/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/html/__init__.py\", line 19, in escape\r\n    s = s.replace(\"&\", \"&amp;\") # Must be done first!\r\nAttributeError: 'bool' object has no attribute 'replace'\r\n```\n", "before_files": [{"content": "import html\nfrom visidata import *\n\n\nclass HtmlTablesSheet(IndexSheet):\n    rowtype = 'sheets'  # rowdef: HtmlTableSheet (sheet.html = lxml.html.HtmlElement)\n    columns = IndexSheet.columns + [\n        Column('tag', width=0, getter=lambda col,row: row.html.tag),\n        Column('id', getter=lambda col,row: row.html.attrib.get('id')),\n        Column('classes', getter=lambda col,row: row.html.attrib.get('class')),\n    ]\n    def iterload(self):\n        import lxml.html\n        from lxml import etree\n        utf8_parser = etree.HTMLParser(encoding='utf-8')\n        with self.source.open_text() as fp:\n            html = lxml.html.etree.parse(fp, parser=utf8_parser)\n        self.setKeys([self.column('name')])\n        self.column('keys').hide()\n        self.column('source').hide()\n\n        for i, e in enumerate(html.iter('table')):\n            if e.tag == 'table':\n                vs = HtmlTableSheet(e.attrib.get(\"id\", \"table_\" + str(i)), source=e)\n                vs.reload()\n                vs.html = e\n                yield vs\n\n\ndef is_header(elem):\n    scope = elem.attrib.get('scope', '')\n\n    if elem.tag == 'th':\n        if not scope or scope == 'col':\n            return True\n\n    return False\n\nclass HtmlTableSheet(Sheet):\n    rowtype = 'rows'  #  list of strings\n    columns = []\n\n    def iterload(self):\n        headers = []\n\n        maxlinks = {}  # [colnum] -> nlinks:int\n\n        for rownum, r in enumerate(self.source.iter('tr')):\n            row = []\n\n            colnum = 0\n            # get starting column, which might be different if there were rowspan>1 already\n            if rownum < len(headers):\n                while colnum < len(headers[rownum]):\n                    if headers[rownum][colnum] is None:\n                        break\n                    colnum += 1\n\n            for cell in r.getchildren():\n                colspan = int(cell.attrib.get('colspan', 1))\n                rowspan = int(cell.attrib.get('rowspan', 1))\n                cellval = ' '.join(x.strip() for x in cell.itertext())  # text only without markup\n                links = [x.get('href') for x in cell.iter('a')]\n                maxlinks[colnum] = max(maxlinks.get(colnum, 0), len(links))\n\n                if is_header(cell):\n                    for k in range(rownum, rownum+rowspan):\n                        while k >= len(headers):  # extend headers list with lists for all header rows\n                            headers.append([])\n\n                        for j in range(colnum, colnum+colspan):\n                            while j >= len(headers[k]):\n                                headers[k].append(None)\n                            headers[k][j] = cellval\n                        cellval = ''   # use empty non-None value for subsequent rows in the rowspan\n                else:\n                    while colnum >= len(row):\n                        row.append(None)\n                    row[colnum] = (cellval, links)\n\n                colnum += colspan\n\n            if any(row):\n                yield row\n\n        self.columns = []\n        if headers:\n            it = itertools.zip_longest(*headers, fillvalue='')\n        else:\n            it = [list(x) for x in self.rows[0]]\n            self.rows = self.rows[1:]\n\n        for colnum, names in enumerate(it):\n            name = '_'.join(str(x) for x in names if x)\n            self.addColumn(Column(name, getter=lambda c,r,i=colnum: r[i][0]))\n            for linknum in range(maxlinks.get(colnum, 0)):\n                self.addColumn(Column(name+'_link'+str(linknum), width=20, getter=lambda c,r,i=colnum,j=linknum: r[i][1][j]))\n\n\n@VisiData.api\ndef save_html(vd, p, *vsheets):\n    'Save vsheets as HTML tables in a single file'\n\n    with open(p, 'w', encoding='ascii', errors='xmlcharrefreplace') as fp:\n        for sheet in vsheets:\n\n            fp.write('<h2 class=\"sheetname\">%s</h2>\\n'.format(sheetname=html.escape(sheet.name)))\n\n            fp.write('<table id=\"{sheetname}\">\\n'.format(sheetname=html.escape(sheet.name)))\n\n            # headers\n            fp.write('<tr>')\n            for col in sheet.visibleCols:\n                contents = html.escape(col.name)\n                fp.write('<th>{colname}</th>'.format(colname=contents))\n            fp.write('</tr>\\n')\n\n            # rows\n            with Progress(gerund='saving'):\n                for typedvals in sheet.iterdispvals(format=False):\n                    fp.write('<tr>')\n                    for col, val in typedvals.items():\n                        fp.write('<td>')\n                        fp.write(html.escape(val))\n                        fp.write('</td>')\n                    fp.write('</tr>\\n')\n\n            fp.write('</table>')\n            vd.status('%s save finished' % p)\n\n\nVisiData.save_htm = VisiData.save_html\n\n\nvd.filetype('html', HtmlTablesSheet)\nvd.filetype('htm', HtmlTablesSheet)\n", "path": "visidata/loaders/html.py"}], "after_files": [{"content": "import html\nfrom visidata import *\n\n\nclass HtmlTablesSheet(IndexSheet):\n    rowtype = 'sheets'  # rowdef: HtmlTableSheet (sheet.html = lxml.html.HtmlElement)\n    columns = IndexSheet.columns + [\n        Column('tag', width=0, getter=lambda col,row: row.html.tag),\n        Column('id', getter=lambda col,row: row.html.attrib.get('id')),\n        Column('classes', getter=lambda col,row: row.html.attrib.get('class')),\n    ]\n    def iterload(self):\n        import lxml.html\n        from lxml import etree\n        utf8_parser = etree.HTMLParser(encoding='utf-8')\n        with self.source.open_text() as fp:\n            html = lxml.html.etree.parse(fp, parser=utf8_parser)\n        self.setKeys([self.column('name')])\n        self.column('keys').hide()\n        self.column('source').hide()\n\n        for i, e in enumerate(html.iter('table')):\n            if e.tag == 'table':\n                vs = HtmlTableSheet(e.attrib.get(\"id\", \"table_\" + str(i)), source=e)\n                vs.reload()\n                vs.html = e\n                yield vs\n\n\ndef is_header(elem):\n    scope = elem.attrib.get('scope', '')\n\n    if elem.tag == 'th':\n        if not scope or scope == 'col':\n            return True\n\n    return False\n\nclass HtmlTableSheet(Sheet):\n    rowtype = 'rows'  #  list of strings\n    columns = []\n\n    def iterload(self):\n        headers = []\n\n        maxlinks = {}  # [colnum] -> nlinks:int\n\n        for rownum, r in enumerate(self.source.iter('tr')):\n            row = []\n\n            colnum = 0\n            # get starting column, which might be different if there were rowspan>1 already\n            if rownum < len(headers):\n                while colnum < len(headers[rownum]):\n                    if headers[rownum][colnum] is None:\n                        break\n                    colnum += 1\n\n            for cell in r.getchildren():\n                colspan = int(cell.attrib.get('colspan', 1))\n                rowspan = int(cell.attrib.get('rowspan', 1))\n                cellval = ' '.join(x.strip() for x in cell.itertext())  # text only without markup\n                links = [x.get('href') for x in cell.iter('a')]\n                maxlinks[colnum] = max(maxlinks.get(colnum, 0), len(links))\n\n                if is_header(cell):\n                    for k in range(rownum, rownum+rowspan):\n                        while k >= len(headers):  # extend headers list with lists for all header rows\n                            headers.append([])\n\n                        for j in range(colnum, colnum+colspan):\n                            while j >= len(headers[k]):\n                                headers[k].append(None)\n                            headers[k][j] = cellval\n                        cellval = ''   # use empty non-None value for subsequent rows in the rowspan\n                else:\n                    while colnum >= len(row):\n                        row.append(None)\n                    row[colnum] = (cellval, links)\n\n                colnum += colspan\n\n            if any(row):\n                yield row\n\n        self.columns = []\n        if headers:\n            it = itertools.zip_longest(*headers, fillvalue='')\n        else:\n            it = [list(x) for x in self.rows[0]]\n            self.rows = self.rows[1:]\n\n        for colnum, names in enumerate(it):\n            name = '_'.join(str(x) for x in names if x)\n            self.addColumn(Column(name, getter=lambda c,r,i=colnum: r[i][0]))\n            for linknum in range(maxlinks.get(colnum, 0)):\n                self.addColumn(Column(name+'_link'+str(linknum), width=20, getter=lambda c,r,i=colnum,j=linknum: r[i][1][j]))\n\n\n@VisiData.api\ndef save_html(vd, p, *vsheets):\n    'Save vsheets as HTML tables in a single file'\n\n    with open(p, 'w', encoding='ascii', errors='xmlcharrefreplace') as fp:\n        for sheet in vsheets:\n\n            fp.write('<h2 class=\"sheetname\">%s</h2>\\n'.format(sheetname=html.escape(sheet.name)))\n\n            fp.write('<table id=\"{sheetname}\">\\n'.format(sheetname=html.escape(sheet.name)))\n\n            # headers\n            fp.write('<tr>')\n            for col in sheet.visibleCols:\n                contents = html.escape(col.name)\n                fp.write('<th>{colname}</th>'.format(colname=contents))\n            fp.write('</tr>\\n')\n\n            # rows\n            with Progress(gerund='saving'):\n                for typedvals in sheet.iterdispvals(format=False):\n                    fp.write('<tr>')\n                    for col, val in typedvals.items():\n                        fp.write('<td>')\n                        fp.write(html.escape(str(val)))\n                        fp.write('</td>')\n                    fp.write('</tr>\\n')\n\n            fp.write('</table>')\n            vd.status('%s save finished' % p)\n\n\nVisiData.save_htm = VisiData.save_html\n\n\nvd.filetype('html', HtmlTablesSheet)\nvd.filetype('htm', HtmlTablesSheet)\n", "path": "visidata/loaders/html.py"}]}