csv_to_html: Add column sort

This commit is contained in:
2024-12-25 23:01:34 +03:00
parent 2b3521544b
commit 6df8a33db7
+105 -25
View File
@@ -2,9 +2,19 @@ import csv
import html import html
import sys import sys
# language=css
page_css = """ page_css = """
body { body {
font-family: consolas, monospace; margin: 0;
padding: 0.5rem;
font-family: menlo, consolas, monospace;
font-size: 14px;
}
input {
font-size: inherit;
font-family: inherit;
background-color: transparent;
padding: 0.25em 0.5em;
} }
table { table {
border-collapse: collapse; border-collapse: collapse;
@@ -16,34 +26,106 @@ th, td {
text-align: left; text-align: left;
} }
th { th {
background-color: #f2f2f2; background-color: #f2f2f2;
} }
tr:nth-child(even) { tr:nth-child(even) {
background-color: #fdfdfd; background-color: #fdfdfd;
} }
""" """
# language=js
page_script = """
function sortableTable(table) {
const sortStates = new Map();
table.querySelectorAll('th').forEach((header, colIndex) => {
header.style.cursor = 'pointer';
header.onclick = () => {
const isAsc = !sortStates.get(header);
const tbody = table.querySelector('tbody');
const rows = [...tbody.rows];
rows.sort((a, b) => {
const [valA, valB] = [a, b].map(row =>
row.cells[colIndex].textContent.trim());
return /^\d+$/.test(valA)
? (valA - valB) * (isAsc ? 1 : -1)
: valA.localeCompare(valB) * (isAsc ? 1 : -1);
});
sortStates.set(header, isAsc);
tbody.replaceChildren(...rows); // Better than innerHTML = ''
};
});
}
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
function addFilterInput(tableElement) {
const filterInput = document.createElement('input');
filterInput.type = 'text';
filterInput.placeholder = "Search...";
tableElement.parentNode.insertBefore(filterInput, tableElement);
const debouncedFilter = debounce(filter, 100);
filterInput.addEventListener('input', () => {
debouncedFilter();
});
function filter() {
const filterValue = filterInput.value.trim();
const rows = tableElement.querySelectorAll('tbody tr');
rows.forEach(row => {
const rowData = row.textContent.toLowerCase();
if (filterValue.startsWith('/') && filterValue.endsWith('/')) {
const regex = new RegExp(filterValue.slice(1, -1), 'ig');
row.style.display = regex.test(rowData) ? '' : 'none';
} else {
row.style.display = rowData.includes(filterValue.toLowerCase()) ? '' : 'none';
}
});
}
}
sortableTable(document.querySelector('table'));
addFilterInput(document.querySelector('table'));
"""
def csv_to_html(csv_input: str) -> str: def csv_to_html(csv_input: str) -> str:
"""
Converts TSV input from stdin to an HTML table.
Args:
csv_input: The TSV input as a string.
Returns:
A string containing the HTML table, or None if an error occurs.
"""
try: try:
delimiter = "\t" if "\t" in csv_input else "," lines = csv_input.splitlines()
reader = csv.reader(csv_input.splitlines(), delimiter=delimiter) delimiter = "\t" if "\t" in lines[0] else ","
reader = csv.reader(lines, delimiter=delimiter)
header = next(reader) # Get the header row header = next(reader) # Get the header row
header_html = f'{"".join(f"<th>{html.escape(col)}</th>" for col in header)}' header_html = f'{"".join(f"<th>{html.escape(col)}</th>" for col in header)}'
rows = [f"<tr>{''.join(f'<td>{html.escape(cell)}</td>' for cell in row)}</tr>" for row in reader] rows = [
f"<tr>{''.join(f'<td>{html.escape(cell)}</td>' for cell in row)}</tr>"
for row in reader
]
rows_html = "\n".join(rows) rows_html = "\n".join(rows)
except csv.Error as e:
raise ValueError(f"Error parsing CSV: {e}")
except StopIteration:
return "<p>No data found in CSV input.</p>"
rendered = f""" rendered = f"""
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@@ -59,26 +141,24 @@ def csv_to_html(csv_input: str) -> str:
{rows_html} {rows_html}
</tbody> </tbody>
</table> </table>
<script>{page_script}</script>
</body> </body>
</html> </html>
""" """
return rendered return rendered
except csv.Error as e:
raise ValueError(f"Error parsing TSV: {e}")
except StopIteration:
return "<p>No data found in TSV input.</p>"
def main(): def main():
try: try:
tsv_data = sys.stdin.read() csv_data = sys.stdin.read()
html_output = csv_to_html(tsv_data) html_output = csv_to_html(csv_data)
if html_output: if html_output:
print(html_output) print(html_output)
except UnicodeDecodeError as e: except UnicodeDecodeError as e:
raise ValueError(f"Error decoding input: {e}. Ensure your TSV data is encoded correctly (e.g., UTF-8).") raise ValueError(
f"Error decoding input: {e}. Ensure your CSV data is encoded correctly (e.g., UTF-8)."
)
except Exception as e: except Exception as e:
raise Exception(f"An unexpected error occurred: {e}") raise Exception(f"An unexpected error occurred: {e}")