From 61da3234d69fb5640b74c44925842e4faa8e57d0 Mon Sep 17 00:00:00 2001
From: Thomas Kluyver <thomas.kluyver@xfel.eu>
Date: Wed, 31 Jul 2024 13:17:40 +0100
Subject: [PATCH] Sync DisplayTables implementation back to calcat_interface2

---
 src/cal_tools/calcat_interface2.py | 100 ++++++++++++++++++++---------
 1 file changed, 69 insertions(+), 31 deletions(-)

diff --git a/src/cal_tools/calcat_interface2.py b/src/cal_tools/calcat_interface2.py
index 10b05f23d..0a73958d7 100644
--- a/src/cal_tools/calcat_interface2.py
+++ b/src/cal_tools/calcat_interface2.py
@@ -756,7 +756,7 @@ class CalibrationData(Mapping):
         tables = []
         # Loop over groups of calibrations.
         for cal_group in cal_groups:
-            table = [[(t, None) for t in (["Modules"] + cal_group)]]
+            table = ["Modules"] + cal_group
 
             # Loop over calibrations and modules to form the next rows.
             for mod in modules:
@@ -767,7 +767,7 @@ class CalibrationData(Mapping):
                         singleconst = self[cname, mod]
                     except KeyError:
                         # Constant is not available for this module.
-                        mod_consts.append(("—", None))
+                        mod_consts.append("—")
                     else:
                         # Have the creation time a reference
                         # link to the CCV on CALCAT.
@@ -778,9 +778,9 @@ class CalibrationData(Mapping):
                             view_url = singleconst.metadata("view_url")
                             mod_consts.append((c_time, view_url))
                         except KeyError:
-                            mod_consts.append((f"{c_time} ({singleconst.ccv_id})", None))
+                            mod_consts.append(f"{c_time} ({singleconst.ccv_id})")
 
-                table.append([(mod, None)] + mod_consts)
+                table.append([mod] + mod_consts)
 
             tables.append(table)
 
@@ -998,43 +998,81 @@ class ShimadzuHPVX2Conditions(ConditionsBase):
     }
 
 
+def tex_escape(text):
+    """
+    Escape latex special characters found in the text
+
+    :param text: a plain text message
+    :return: the message escaped to appear correctly in LaTeX
+    """
+    conv = {
+        '&': r'\&',
+        '%': r'\%',
+        '$': r'\$',
+        '#': r'\#',
+        '_': r'\_',
+        '{': r'\{',
+        '}': r'\}',
+        '~': r'\textasciitilde{}',
+        '^': r'\^{}',
+        '\\': r'\textbackslash{}',
+        '<': r'\textless{}',
+        '>': r'\textgreater{}',
+        '—': r'\textemdash',
+    }
+
+    key_list = sorted(conv.keys(), key=lambda item: - len(item))
+    regex = re.compile('|'.join(re.escape(str(key)) for key in key_list))
+    return regex.sub(lambda match: conv[match.group()], text)
+
+
 class DisplayTables:
     def __init__(self, tables):
-        # list (tables) of lists (rows) of lists (cells) of (text, url) tuples
+        # list (tables) of lists (rows) of lists (cells). A cell may be str,
+        # or a (text, url) tuple to make a link.
         self.tables = tables
 
+    @staticmethod
+    def _build_table(table, fmt_link, escape=lambda s: s):
+        res = []
+        for row in table:
+            prepd_row = []
+            for cell in row:
+                if isinstance(cell, tuple):
+                    text, url = cell
+                else:
+                    text = cell
+                    url = None
+
+                if url is None:
+                    prepd_row.append(escape(text))
+                else:
+                    prepd_row.append(fmt_link(escape(text), url))
+            res.append(prepd_row)
+        return res
+
     def _repr_markdown_(self):
         from tabulate import tabulate
-        md_chunks = []
-        for table in self.tables:
-            prepd_table = []
-            for row in table:
-                prepd_row = []
-                for text, url in row:
-                    if url is None:
-                        prepd_row.append(text)
-                    else:
-                        prepd_row.append(f'[{text}]({url})')
-                prepd_table.append(prepd_row)
 
-            md_chunks.append(tabulate(prepd_table, tablefmt="pipe", headers="firstrow"))
+        def fmt_link(text, url):
+            return f"[{text}]({url})"
 
-        return '\n\n'.join(md_chunks)
+        prepd_tables = [self._build_table(table, fmt_link) for table in self.tables]
+
+        return '\n\n'.join(
+            tabulate(t, tablefmt="pipe", headers="firstrow")
+            for t in prepd_tables
+        )
 
     def _repr_latex_(self):
         from tabulate import tabulate
-        latex_chunks = []
-        for table in self.tables:
-            prepd_table = []
-            for row in table:
-                prepd_row = []
-                for text, url in row:
-                    if url is None:
-                        prepd_row.append(text)
-                    else:
-                        prepd_row.append('\href{%s}{%s}' % (url, text))
-                prepd_table.append(prepd_row)
 
-            latex_chunks.append(tabulate(prepd_table, tablefmt="latex_raw", headers="firstrow"))
+        def fmt_link(text, url):
+            return r'\href{%s}{%s}' % (url, text)
 
-        return '\n\n'.join(latex_chunks)
+        prepd_tables = [self._build_table(table, fmt_link, escape=tex_escape) for table in self.tables]
+
+        return '\n\n'.join(
+            tabulate(t, tablefmt="latex_raw", headers="firstrow")
+            for t in prepd_tables
+        )
-- 
GitLab