diff --git a/deploy_scattering_table.sh b/deploy_scattering_table.sh
new file mode 100644
index 0000000..9b76ecc
--- /dev/null
+++ b/deploy_scattering_table.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+TARGET_DIR=${TARGET_DIR:-/var/www/html/resources/n-lengths}
+
+python -m util.scattering_table_html $TARGET_DIR
diff --git a/util/scattering_table_html.py b/util/scattering_table_html.py
new file mode 100644
index 0000000..18dd268
--- /dev/null
+++ b/util/scattering_table_html.py
@@ -0,0 +1,179 @@
+from pathlib import Path
+
+import numpy as np
+from periodictable.core import default_table, PeriodicTable
+from periodictable.nsf import Neutron
+
+# TODO: add a citation column to the html scattering table
+def scattering_table_html(path: Path|str|None=None, table: PeriodicTable|None=None) -> str:
+ """
+ Generate an html table, returning it as a string. If path is given, write the
+ html to that path.
+
+ Note: requires the uncertainties package, which is not otherwise required by periodictable.
+ """
+ from uncertainties import ufloat as U # type: ignore[import-untyped]
+
+ head = """\
+
+
+ Neutron Cross Sections
+
+"""
+ table = default_table(table)
+
+ def format_num(re, re_unc, im=None, im_unc=None):
+ # Note: using NaN for uncertainty for derived values that should be
+ # left blank in the table (currently 191,193Ir cross sections)
+ re_str = "" if re is None or np.isnan(re_unc) else f"{U(re, re_unc):fS}" if re_unc else str(re) if re else "0"
+ if im is not None:
+ im_value = f"{U(abs(im), im_unc):fS}" if im_unc else str(abs(im))
+ im_str = f"
{'+' if im >= 0 else '–'} {im_value}j"
+ else:
+ im_str = ""
+ return f"{re_str}{im_str}"
+
+ rows = []
+ rows.append(f"""
+
+ |
+ Z |
+ A |
+ I(π) |
+ abundance % |
+ bc {Neutron.b_c_units} |
+ b+ {Neutron.bp_units} |
+ b– {Neutron.bm_units} |
+ σc {Neutron.coherent_units} |
+ σi {Neutron.incoherent_units} |
+ σs {Neutron.total_units} |
+ σa {Neutron.absorption_units} |
+
""")
+
+ # Generate table rows
+ for el in [table.n, *table]:
+ element_number = el.number
+ isotopes = [iso for iso in el if iso.neutron.absorption is not None or iso.abundance]
+ singleton = len(isotopes) == 1
+ symbol = el.symbol
+ row_id = symbol if symbol != "n" else "" # Special case for bare neutron
+ # print(f"{el=} {A=} {singleton=} {el.neutron.has_sld()=} {[*el]}")
+ if element_number <= 96 and not singleton:
+ # Multiple isotopes: put element summary above
+ n = el.neutron
+ rows.append(f"""
+
+ | {symbol} |
+ {element_number} |
+ |
+ |
+ |
+ {format_num(n.b_c, n.b_c_unc, n.b_c_i, n.b_c_i_unc)} |
+ {format_num(n.bp, n.bp_unc, n.bp_i, n.bp_i_unc)} |
+ {format_num(n.bm, n.bm_unc, n.bm_i, n.bm_i_unc)} |
+ {format_num(n.coherent, n.coherent_unc)} |
+ {format_num(n.incoherent, n.incoherent_unc)} |
+ {format_num(n.total, n.total_unc)} |
+ {format_num(n.absorption, n.absorption_unc)} |
+
""")
+
+ for iso in isotopes:
+ isotope_number = iso.isotope
+ spin = getattr(iso, "nuclear_spin", "")
+ n = iso.neutron
+ abundance = (
+ f"{U(iso.abundance, iso._abundance_unc):fS}" if iso._abundance_unc
+ else "100" if iso.abundance == 100.0
+ else "" if iso.abundance == 0.0
+ else f"{iso.abundance}"
+ )
+ rows.append(f"""
+
+ | {symbol if singleton else ''} |
+ {element_number if singleton else ''} |
+ {isotope_number} |
+ {spin} |
+ {abundance} |
+ {format_num(n.b_c, n.b_c_unc, n.b_c_i, n.b_c_i_unc)} |
+ {format_num(n.bp, n.bp_unc, n.bp_i, n.bp_i_unc)} |
+ {format_num(n.bm, n.bm_unc, n.bm_i, n.bm_i_unc)} |
+ {format_num(n.coherent, n.coherent_unc)} |
+ {format_num(n.incoherent, n.incoherent_unc)} |
+ {format_num(n.total, n.total_unc)} |
+ {format_num(n.absorption, n.absorption_unc)} |
+
""")
+
+ # Note: don't need \n between rows since we add it to each.
+ formatted_table = ''.join(rows)
+ html = f"""
+
+
+{head}
+
+Scattering lengths and cross sections for various isotopes evaluated at 2200 m s–1
+
+
+This table has been compiled from various sources for the user's convenience and does not represent a critical evaluation by the NIST Center for Neutron Research.
+See python-periodictable on github for a list of citations.
+Natural abundance is from IUPAC Commission on Isotopic Abundances and Atomic Weights (CIAAW)
+
+
+"""
+
+ if path:
+ # Save the HTML to a file
+ with open(path, 'w', encoding='utf-8') as f:
+ f.write(html)
+
+ print(f"HTML table saved to {str(path)}")
+
+ return html
+
+if __name__ == "__main__":
+ import sys
+ # Example usage: generate the HTML table and save it to a file
+ output_dir = sys.argv[1] if len(sys.argv) > 1 else "."
+ output_filename = sys.argv[2] if len(sys.argv) > 2 else "scattering_table.html"
+ output_path = Path(output_dir) / output_filename
+ scattering_table_html(path=output_path)
\ No newline at end of file