"""
camcops_server/cc_modules/cc_html.py
===============================================================================
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CamCOPS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
===============================================================================
**Basic HTML creation functions.**
"""
import base64
from typing import Any, Callable, List, Optional, TYPE_CHECKING, Union
import cardinal_pythonlib.rnc_web as ws
from camcops_server.cc_modules.cc_constants import CssClass
from camcops_server.cc_modules.cc_text import SS
if TYPE_CHECKING:
from camcops_server.cc_modules.cc_request import CamcopsRequest
# =============================================================================
# HTML elements
# =============================================================================
[docs]def table_row(
columns: List[str],
classes: List[str] = None,
colspans: List[Union[str, int]] = None,
colwidths: List[str] = None,
default: str = "",
heading: bool = False,
) -> str:
"""
Make HTML table row.
Args:
columns: contents of HTML table columns
classes: optional CSS classes, one for each column
colspans: ``colspan`` values for each column
colwidths: ``width`` values for each column
default: content to use if a ``column`` value is None
heading: use ``<th>`` rather than ``<td>`` for contents?
Returns:
the ``<tr>...</tr>`` string
"""
n = len(columns)
if not classes or len(classes) != n:
# blank, or duff (in which case ignore)
classes = [""] * n
else:
classes = [(f' class="{x}"' if x else "") for x in classes]
if not colspans or len(colspans) != n:
# blank, or duff (in which case ignore)
colspans = [""] * n
else:
colspans = [(f' colspan="{x}"' if x else "") for x in colspans]
if not colwidths or len(colwidths) != n:
# blank, or duff (in which case ignore)
colwidths = [""] * n
else:
colwidths = [(f' width="{x}"' if x else "") for x in colwidths]
celltype = "th" if heading else "td"
rows = "".join(
[
(
f"<{celltype}{classes[i]}{colspans[i]}{colwidths[i]}>"
f"{default if columns[i] is None else columns[i]}"
f"</{celltype}>"
)
for i in range(n)
]
)
return f"<tr>{rows}</tr>\n"
[docs]def div(content: str, div_class: str = "") -> str:
"""
Make simple HTML div.
"""
class_str = f' class="{div_class}"' if div_class else ""
return f"""
<div{class_str}>
{content}
</div>
"""
[docs]def table(content: str, table_class: str = "") -> str:
"""
Make simple HTML table.
"""
class_str = f' class="{table_class}"' if table_class else ""
return f"""
<table{class_str}>
{content}
</table>
"""
[docs]def tr(*args, tr_class: str = "", literal: bool = False) -> str:
"""
Make simple HTML table data row.
Args:
*args: Set of columns data.
literal: Treat elements as literals with their own ``<td> ... </td>``,
rather than things to be encapsulated.
tr_class: table row class
"""
if literal:
elements = args
else:
elements = [td(x) for x in args]
tr_class = f' class="{tr_class}"' if tr_class else ""
contents = "".join(elements)
return f"<tr{tr_class}>{contents}</tr>\n"
[docs]def td(contents: Any, td_class: str = "", td_width: str = "") -> str:
"""
Make simple HTML table data ``<td>...</td>`` cell.
"""
td_class = f' class="{td_class}"' if td_class else ""
td_width = f' width="{td_width}"' if td_width else ""
return f"<td{td_class}{td_width}>{contents}</td>\n"
[docs]def th(contents: Any, th_class: str = "", th_width: str = "") -> str:
"""
Make simple HTML table header ``<th>...</th>`` cell.
"""
th_class = f' class="{th_class}"' if th_class else ""
th_width = f' width="{th_width}"' if th_width else ""
return f"<th{th_class}{th_width}>{contents}</th>\n"
[docs]def tr_qa(
q: str, a: Any, default: str = "?", default_for_blank_strings: bool = False
) -> str:
"""
Make HTML two-column data row (``<tr>...</tr>``), with the right-hand
column formatted as an answer.
"""
return tr(
q,
answer(
a,
default=default,
default_for_blank_strings=default_for_blank_strings,
),
)
[docs]def heading_spanning_two_columns(s: str) -> str:
"""
HTML table heading row spanning 2 columns.
"""
return tr_span_col(s, cols=2, tr_class=CssClass.HEADING)
[docs]def subheading_spanning_two_columns(s: str, th_not_td: bool = False) -> str:
"""
HTML table subheading row spanning 2 columns.
"""
return tr_span_col(
s, cols=2, tr_class=CssClass.SUBHEADING, th_not_td=th_not_td
)
[docs]def subheading_spanning_three_columns(s: str, th_not_td: bool = False) -> str:
"""
HTML table subheading row spanning 3 columns.
"""
return tr_span_col(
s, cols=3, tr_class=CssClass.SUBHEADING, th_not_td=th_not_td
)
[docs]def subheading_spanning_four_columns(s: str, th_not_td: bool = False) -> str:
"""
HTML table subheading row spanning 4 columns.
"""
return tr_span_col(
s, cols=4, tr_class=CssClass.SUBHEADING, th_not_td=th_not_td
)
[docs]def bold(x: str) -> str:
"""
Applies HTML bold.
"""
return f"<b>{x}</b>"
[docs]def italic(x: str) -> str:
"""
Applies HTML italic.
"""
return f"<i>{x}</i>"
[docs]def identity(x: Any) -> Any:
"""
Returns argument unchanged.
"""
return x
[docs]def bold_webify(x: str) -> str:
"""
Webifies the string, then makes it bold.
"""
return bold(ws.webify(x))
[docs]def sub(x: str) -> str:
"""
Applies HTML subscript.
"""
return f"<sub>{x}</sub>"
[docs]def sup(x: str) -> str:
"""
Applies HTML superscript.
"""
return f"<sup>{x}</sup>"
[docs]def answer(
x: Any,
default: str = "?",
default_for_blank_strings: bool = False,
formatter_answer: Callable[[str], str] = bold_webify,
formatter_blank: Callable[[str], str] = italic,
) -> str:
"""
Formats answer in bold, or the default value if None.
Avoid the word "None" for the default, e.g.
"Score indicating likelihood of abuse: None"... may be misleading!
Prefer "?" instead.
"""
if x is None:
return formatter_blank(default)
if default_for_blank_strings and not x and isinstance(x, str):
return formatter_blank(default)
return formatter_answer(x)
[docs]def tr_span_col(
x: str,
cols: int = 2,
tr_class: str = "",
td_class: str = "",
th_not_td: bool = False,
) -> str:
"""
HTML table data row spanning several columns.
Args:
x: Data.
cols: Number of columns to span.
tr_class: CSS class to apply to tr.
td_class: CSS class to apply to td.
th_not_td: make it a th, not a td.
"""
cell = "th" if th_not_td else "td"
tr_cl = f' class="{tr_class}"' if tr_class else ""
td_cl = f' class="{td_class}"' if td_class else ""
return f'<tr{tr_cl}><{cell} colspan="{cols}"{td_cl}>{x}</{cell}></tr>'
[docs]def get_data_url(mimetype: str, data: Union[bytes, memoryview]) -> str:
"""
Takes data (in binary format) and returns a data URL as per RFC 2397
(https://tools.ietf.org/html/rfc2397), such as:
.. code-block:: none
data:MIMETYPE;base64,B64_ENCODED_DATA
"""
return f"data:{mimetype};base64,{base64.b64encode(data).decode('ascii')}"
[docs]def get_embedded_img_tag(mimetype: str, data: Union[bytes, memoryview]) -> str:
"""
Takes a binary image and its MIME type, and produces an HTML tag of the
form:
.. code-block:: none
<img src="DATA_URL">
"""
return f"<img src={get_data_url(mimetype, data)}>"
# =============================================================================
# Field formatting
# =============================================================================
[docs]def get_yes_no(req: "CamcopsRequest", x: Any) -> str:
"""
'Yes' if x else 'No'
"""
return req.sstring(SS.YES) if x else req.sstring(SS.NO)
[docs]def get_yes_no_none(req: "CamcopsRequest", x: Any) -> Optional[str]:
"""
Returns 'Yes' for True, 'No' for False, or None for None.
"""
if x is None:
return None
return get_yes_no(req, x)
[docs]def get_yes_no_unknown(req: "CamcopsRequest", x: Any) -> str:
"""
Returns 'Yes' for True, 'No' for False, or '?' for None.
"""
if x is None:
return "?"
return get_yes_no(req, x)
[docs]def get_true_false(req: "CamcopsRequest", x: Any) -> str:
"""
'True' if x else 'False'
"""
return req.sstring(SS.TRUE) if x else req.sstring(SS.FALSE)
[docs]def get_true_false_none(req: "CamcopsRequest", x: Any) -> Optional[str]:
"""
Returns 'True' for True, 'False' for False, or None for None.
"""
if x is None:
return None
return get_true_false(req, x)
[docs]def get_true_false_unknown(req: "CamcopsRequest", x: Any) -> str:
"""
Returns 'True' for True, 'False' for False, or '?' for None.
"""
if x is None:
return "?"
return get_true_false(req, x)
[docs]def get_present_absent(req: "CamcopsRequest", x: Any) -> str:
"""
'Present' if x else 'Absent'
"""
return req.sstring(SS.PRESENT) if x else req.sstring(SS.ABSENT)
[docs]def get_present_absent_none(req: "CamcopsRequest", x: Any) -> Optional[str]:
"""
Returns 'Present' for True, 'Absent' for False, or None for None.
"""
if x is None:
return None
return get_present_absent(req, x)
[docs]def get_present_absent_unknown(req: "CamcopsRequest", x: str) -> str:
"""
Returns 'Present' for True, 'Absent' for False, or '?' for None.
"""
if x is None:
return "?"
return get_present_absent(req, x)
[docs]def get_ternary(
x: Any,
value_true: Any = True,
value_false: Any = False,
value_none: Any = None,
) -> Any:
"""
Returns ``value_none`` if ``x`` is ``None``, ``value_true`` if it's truthy,
or ``value_false`` if it's falsy.
"""
if x is None:
return value_none
if x:
return value_true
return value_false
[docs]def get_correct_incorrect_none(x: Any) -> Optional[str]:
"""
Returns None if ``x`` is None, "Correct" if it's truthy, or "Incorrect" if
it's falsy.
"""
return get_ternary(x, "Correct", "Incorrect", None)
[docs]def pmid(x: int) -> str:
"""
Returns hyperlinked text to a PubMed ID (PMID).
Args:
x: The integer PMID.
Returns:
Hyperlinked text, as raw HTML.
"""
return f'<a href="https://pubmed.ncbi.nlm.nih.gov/{x}/">PMID {x}</a>'
[docs]def doi(x: str) -> str:
"""
Returns hyperlinked text to a digital object identifier (DOI).
Args:
x: The DOI.
Returns:
Hyperlinked text, as raw HTML.
"""
return f'<a href="https://doi.org/{x}">doi:{x}</a>'
[docs]def a_href(url: str, text: str = None) -> str:
"""
Returns text hyperlinked to a URL; by default, the text is the URL itself.
Args:
url: the raw URL
text: text to be shown
"""
text = text or url
return f'<a href="{url}">{text}</a>'