Source code for camcops_server.cc_modules.cc_html

#!/usr/bin/env python
# camcops_server/cc_modules/cc_html.py

"""
===============================================================================

    Copyright (C) 2012-2018 Rudolf Cardinal (rudolf@pobox.com).

    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 <http://www.gnu.org/licenses/>.

===============================================================================
"""

import base64
from typing import Any, Callable, List, Optional, TYPE_CHECKING, Union

import cardinal_pythonlib.rnc_web as ws

if TYPE_CHECKING:
    from .cc_request import CamcopsRequest


# =============================================================================
# HTML elements
# =============================================================================

[docs]def table_row(columns: List[str], classes: List[str] = None, colspans: List[str] = None, colwidths: List[str] = None, default: str = "", heading: bool = False) -> str: """Make HTML table row.""" n = len(columns) if not classes or len(classes) != n: # blank, or duff (in which case ignore) classes = [""] * n else: classes = [(' class="{}"'.format(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 = [(' colspan="{}"'.format(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 = [ (' width="{}"'.format(x) if x else '') for x in colwidths ] return ( "<tr>" + "".join([ "<{cellspec}{classdetail}{colspan}{colwidth}>" "{contents}</{cellspec}>".format( cellspec="th" if heading else "td", contents=default if columns[i] is None else columns[i], classdetail=classes[i], colspan=colspans[i], colwidth=colwidths[i], ) for i in range(n) ]) + "</tr>\n" )
[docs]def div(content: str, div_class: str = "") -> str: """Make simple HTML div.""" return """ <div{div_class}> {content} </div> """.format( content=content, div_class=' class="{}"'.format(div_class) if div_class else '', )
[docs]def table(content: str, table_class: str = "") -> str: """Make simple HTML table.""" return """ <table{table_class}> {content} </table> """.format( content=content, table_class=' class="{}"'.format(table_class) if table_class else '', )
[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 encapsulted. tr_class: table row class """ if literal: elements = args else: elements = [td(x) for x in args] return "<tr{tr_class}>{contents}</tr>\n".format( tr_class=' class="{}"'.format(tr_class) if tr_class else '', contents="".join(elements), )
[docs]def td(contents: Any, td_class: str = "", td_width: str = "") -> str: """Make simple HTML table data cell.""" return "<td{td_class}{td_width}>{contents}</td>\n".format( td_class=' class="{}"'.format(td_class) if td_class else '', td_width=' width="{}"'.format(td_width) if td_width else '', contents=contents, )
[docs]def th(contents: Any, th_class: str = "", th_width: str = "") -> str: """Make simple HTML table header cell.""" return "<th{th_class}{th_width}>{contents}</th>\n".format( th_class=' class="{}"'.format(th_class) if th_class else '', th_width=' width="{}"'.format(th_width) if th_width else '', contents=contents, )
[docs]def tr_qa(q: str, a: Any, default: str = "?", default_for_blank_strings: bool = False) -> str: """Make HTML two-column data row, with 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 spanning 2 columns.""" return tr_span_col(s, cols=2, tr_class="heading")
[docs]def subheading_spanning_two_columns(s: str, th_not_td: bool = False) -> str: """HTML table subheading spanning 2 columns.""" return tr_span_col(s, cols=2, tr_class="subheading", th_not_td=th_not_td)
[docs]def subheading_spanning_three_columns(s: str, th_not_td: bool = False) -> str: """HTML table subheading spanning 3 columns.""" return tr_span_col(s, cols=3, tr_class="subheading", th_not_td=th_not_td)
[docs]def subheading_spanning_four_columns(s: str, th_not_td: bool = False) -> str: """HTML table subheading spanning 4 columns.""" return tr_span_col(s, cols=4, tr_class="subheading", th_not_td=th_not_td)
[docs]def bold(x: str) -> str: """Applies HTML bold.""" return "<b>{}</b>".format(x)
[docs]def italic(x: str) -> str: """Applies HTML italic.""" return "<i>{}</i>".format(x)
[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 "<sub>{}</sub>".format(x)
[docs]def sup(x: str) -> str: """Applies HTML superscript.""" return "<sup>{}</sup>".format(x)
[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" return '<tr{tr_cl}><{c} colspan="{cols}"{td_cl}>{x}</{c}></tr>'.format( cols=cols, x=x, tr_cl=' class="{}"'.format(tr_class) if tr_class else "", td_cl=' class="{}"'.format(td_class) if td_class else "", c=cell, )
[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: data:MIMETYPE;base64,B64_ENCODED_DATA """ return "data:{mimetype};base64,{b64encdata}".format( mimetype=mimetype, b64encdata=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: <img src="DATA_URL"> """ return '<img src={}>'.format(get_data_url(mimetype, data))
# ============================================================================= # Field formatting # =============================================================================
[docs]def get_yes_no(req: "CamcopsRequest", x: Any) -> str: """'Yes' if x else 'No'""" return req.wappstring("yes") if x else req.wappstring("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.wappstring("true") if x else req.wappstring("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.wappstring("present") if x else req.wappstring("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)
def get_ternary(x: Any, value_true: Any = True, value_false: Any = False, value_none: Any = None) -> Any: if x is None: return value_none if x: return value_true return value_false def get_correct_incorrect_none(x: Any) -> Optional[str]: # noinspection PyTypeChecker return get_ternary(x, "Correct", "Incorrect", None)