# sage_setup: distribution = sagemath-categories
"""
HTML Fragments

This module defines a HTML fragment class, which holds a piece of
HTML. This is primarily used in browser-based notebooks, though it
might be useful for creating static pages as well.

This module defines :class:`MathJax`, an object of which performs the task of
producing an HTML representation of any object. The produced HTML is
renderable in a browser-based notebook with the help of MathJax.
"""

# *****************************************************************************
#       Copyright (C) 2008 William Stein <wstein@gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#  as published by the Free Software Foundation; either version 2 of
#  the License, or (at your option) any later version.
#                  http://www.gnu.org/licenses/
# *****************************************************************************

import re

from sage.misc.latex import latex
from sage.structure.sage_object import SageObject

macro_regex = re.compile(r'\\newcommand{(?P<name>\\[a-zA-Z]+)}(\[.+\])?{(?P<definition>.+)}')


class HtmlFragment(str, SageObject):
    r"""
    A HTML fragment.

    This is a piece of HTML, usually not a complete document.  For
    example, just a ``<div>...</div>`` piece and not the entire
    ``<html>...</html>``.

    EXAMPLES::

        sage: from sage.misc.html import HtmlFragment
        sage: HtmlFragment('<b>test</b>')
        <b>test</b>

    .. automethod:: _rich_repr_
    """

    def _rich_repr_(self, display_manager, **kwds):
        """
        Rich Output Magic Method.

        See :mod:`sage.repl.rich_output` for details.

        EXAMPLES::

            sage: from sage.repl.rich_output import get_display_manager
            sage: dm = get_display_manager()
            sage: h = sage.misc.html.HtmlFragment('<b>old</b>')
            sage: h._rich_repr_(dm)  # the doctest backend does not support html
            OutputPlainText container
        """
        OutputHtml = display_manager.types.OutputHtml
        if OutputHtml in display_manager.supported_output():
            return OutputHtml(self)
        else:
            return display_manager.types.OutputPlainText(self)


def math_parse(s):
    r"""
    Transform the string ``s`` with TeX maths to an HTML string renderable by
    MathJax.

    INPUT:

    - ``s`` -- string

    OUTPUT: :class:`HtmlFragment`

    Specifically this method does the following:

    * Replace all ``\$text\$``\'s by ``\(text\)``
    * Replace all ``\$\$text\$\$``\'s by ``\[text\]``
    * Replace all ``\\\$``\'s by ``\$``\'s. Note that this has precedence over
      the above two cases.

    EXAMPLES::

        sage: print(sage.misc.html.math_parse('This is $2+2$.'))
        This is \(2+2\).
        sage: print(sage.misc.html.math_parse('This is $$2+2$$.'))
        This is \[2+2\].
        sage: print(sage.misc.html.math_parse('This is \\[2+2\\].'))
        This is \[2+2\].
        sage: print(sage.misc.html.math_parse(r'\$2+2\$ is rendered to $2+2$.'))
        <span>$</span>2+2<span>$</span> is rendered to \(2+2\).
    """
    # Below t always has the "parsed so far" version of s, and s is
    # just the part of the original input s that hasn't been parsed.
    t = ''
    while True:
        i = s.find('$')
        if i == -1:
            # No dollar signs -- definitely done.
            return HtmlFragment(t + s)
        elif i > 0 and s[i-1] == '\\':
            # A dollar sign with a backslash right before it, so this is a
            # normal dollar sign. If processEscapes is enabled in MathJax, "\$"
            # will do the job. But as we do not assume that, we use the span
            # tag safely.
            t += s[:i-1] + '<span>$</span>'
            s = s[i+1:]
            continue
        elif i+1 < len(s) and s[i+1] == '$':
            # Found a math environment. Double dollar sign so display mode.
            disp = True
        else:
            # Found math environment. Single dollar sign so default mode.
            disp = False

        # Now find the matching $ sign and form the html string.

        if disp:
            j = s[i+2:].find('$$')
            if j == -1:
                j = len(s)
                s += '$$'
            else:
                j += i + 2
            txt = s[i+2:j]
        else:
            j = s[i+2:].find('$')
            if j == -1:
                j = len(s)
                s += '$'
            else:
                j += i + 2
            txt = s[i+1:j]

        if disp:
            t += s[:i] + r'\[{0}\]'.format(' '.join(txt.splitlines()))
        else:
            t += s[:i] + r'\({0}\)'.format(' '.join(txt.splitlines()))
        s = s[j+1:]
        if disp:
            s = s[1:]
    return HtmlFragment(t)


class MathJaxExpr:
    """
    An arbitrary MathJax expression that can be nicely concatenated.

    EXAMPLES::

        sage: from sage.misc.html import MathJaxExpr
        sage: MathJaxExpr("a^{2}") + MathJaxExpr("x^{-1}")
        a^{2}x^{-1}
    """
    def __init__(self, y):
        """
        Initialize a MathJax expression.

        INPUT:

        - ``y`` -- string

        Note that no error checking is done on the type of ``y``.

        EXAMPLES::

            sage: from sage.misc.html import MathJaxExpr
            sage: jax = MathJaxExpr(3); jax  # indirect doctest
            3
            sage: TestSuite(jax).run(skip ='_test_pickling')
        """
        self.__y = y

    def __repr__(self):
        """
        Print representation.

        EXAMPLES::

            sage: from sage.misc.html import MathJaxExpr
            sage: jax = MathJaxExpr('3')
            sage: jax.__repr__()
            '3'
        """
        return str(self.__y)

    def __add__(self, y):
        """
        'Add' MathJaxExpr ``self`` to ``y``.  This concatenates them
        (assuming that they're strings).

        EXAMPLES::

            sage: from sage.misc.html import MathJaxExpr
            sage: j3 = MathJaxExpr('3')
            sage: jx = MathJaxExpr('x')
            sage: j3 + jx
            3x
        """
        return MathJaxExpr(self.__y + y)

    def __radd__(self, y):
        """
        'Add' MathJaxExpr ``y`` to ``self``.  This concatenates them
        (assuming that they're strings).

        EXAMPLES::

            sage: from sage.misc.html import MathJaxExpr
            sage: j3 = MathJaxExpr('3')
            sage: jx = MathJaxExpr('x')
            sage: j3.__radd__(jx)
            x3
        """
        return MathJaxExpr(y + self.__y)


class MathJax:
    r"""
    Render LaTeX input using MathJax.  This returns a :class:`MathJaxExpr`.

    EXAMPLES::

        sage: from sage.misc.html import MathJax
        sage: MathJax()(3)
        <html>\[3\]</html>
        sage: MathJax()(ZZ)
        <html>\[\newcommand{\Bold}[1]{\mathbf{#1}}\Bold{Z}\]</html>
    """

    def __call__(self, x, combine_all=False):
        r"""
        Render LaTeX input using MathJax.  This returns a :class:`MathJaxExpr`.

        INPUT:

        - ``x`` -- a Sage object

        - ``combine_all`` -- boolean (default: ``False``); if ``combine_all``
          is ``True`` and the input is a tuple, then it does not return a tuple
          and instead returns a string with all the elements separated by
          a single space

        OUTPUT: :class:`MathJaxExpr`

        EXAMPLES::

            sage: from sage.misc.html import MathJax
            sage: MathJax()(3)
            <html>\[3\]</html>
            sage: str(MathJax().eval(ZZ['x'], mode='display')) == str(MathJax()(ZZ['x']))
            True
        """
        return self.eval(x, combine_all=combine_all)

    def eval(self, x, globals=None, locals=None, mode='display', combine_all=False):
        r"""
        Render LaTeX input using MathJax.  This returns a :class:`MathJaxExpr`.

        INPUT:

        - ``x`` -- a Sage object

        - ``globals`` -- a globals dictionary

        - ``locals`` -- extra local variables used when
          evaluating Sage code in ``x``

        - ``mode`` -- string (default: ``'display'``);
          ``'display'`` for displaymath, ``'inline'`` for inline
          math, or ``'plain'`` for just the LaTeX code without the
          surrounding html and script tags

        - ``combine_all`` -- boolean (default: ``False``); if ``combine_all``
          is ``True`` and the input is a tuple, then it does not return a tuple
          and instead returns a string with all the elements separated by
          a single space

        OUTPUT: :class:`MathJaxExpr`

        EXAMPLES::

            sage: from sage.misc.html import MathJax
            sage: MathJax().eval(3, mode='display')
            <html>\[3\]</html>
            sage: MathJax().eval(3, mode='inline')
            <html>\(3\)</html>
            sage: MathJax().eval(type(3), mode='inline')
            <html>\(\verb|&lt;class|\verb| |\verb|'sage.rings.integer.Integer'>|\)</html>

        TESTS:

            sage: from sage.misc.html import MathJax
            sage: MathJax().eval(IntegerModRing(6))
            <html>\[\newcommand{\ZZ}{\Bold{Z}}\newcommand{\Bold}[1]{\mathbf{#1}}\ZZ/6\ZZ\]</html>
        """
        # Get a regular LaTeX representation of x
        x = latex(x, combine_all=combine_all)

        # The "\text{\texttt{...}}" blocks are reformed to be renderable by MathJax.
        # These blocks are produced by str_function() defined in sage.misc.latex.
        prefix = r"\text{\texttt{"
        parts = x.split(prefix)
        for i, part in enumerate(parts):
            if i == 0:
                continue    # Nothing to do with the head part
            n = 1
            for closing, c in enumerate(part):
                if c == "{" and part[closing - 1] != "\\":
                    n += 1
                if c == "}" and part[closing - 1] != "\\":
                    n -= 1
                if n == -1:
                    break
            # part should end in "}}", so omit the last two characters
            # from y
            y = part[:closing-1]
            for delimiter in r"""|"'`#%&,.:;?!@_~^+-/\=<>()[]{}0123456789E""":
                if delimiter not in y:
                    break
            if delimiter == "E":
                # y is too complicated
                delimiter = "|"
                y = "(complicated string)"
            wrapper = r"\verb" + delimiter + "%s" + delimiter
            y = y.replace("{ }", " ").replace("{-}", "-")
            for c in r"#$%&\^_{}~":
                char_wrapper = r"{\char`\%s}" % c
                y = y.replace(char_wrapper, c)
            subparts = []
            nspaces = 0
            for subpart in y.split(" "):
                if subpart == "":
                    nspaces += 1
                    continue
                if nspaces > 0:
                    subparts.append(wrapper % (" " * nspaces))
                nspaces = 1
                subparts.append(wrapper % subpart)
            subparts.append(part[closing + 1:])
            parts[i] = "".join(subparts)

        from sage.misc.latex_macros import sage_latex_macros
        from sage.misc.latex import _Latex_prefs

        latex_string = ''.join([_Latex_prefs._option['macros']] + parts)

        # add a macro definition only if it appears in the latex string
        macros_string = ''
        latex_macros = sage_latex_macros()
        test_string = latex_string
        while test_string:
            new_test_string = ''
            for line in latex_macros:
                m = macro_regex.match(line)
                if m['name'] in test_string:
                    macros_string += line
                    new_test_string += '{' + m['definition'] + '}'
            test_string = new_test_string

        latex_string = macros_string + latex_string

        mathjax_string = latex_string.replace('<', '&lt;')
        if mode == 'display':
            html = r'<html>\[{0}\]</html>'
        elif mode == 'inline':
            html = r'<html>\({0}\)</html>'
        elif mode == 'display_left':
            html = r'<html>\(\displaystyle {0}\)</html>'
        elif mode == 'plain':
            return mathjax_string
        else:
            raise ValueError("mode must be either 'display', 'inline', 'display_left' or 'plain'")
        return MathJaxExpr(html.format(mathjax_string))


class HTMLFragmentFactory(SageObject):

    def _repr_(self):
        """
        Return string representation.

        OUTPUT: string

        EXAMPLES::

            sage: html
            Create HTML output (see html? for details)
        """
        return 'Create HTML output (see html? for details)'

    def __call__(self, obj, concatenate=True, strict=False):
        r"""
        Construct a HTML fragment.

        INPUT:

        - ``obj`` -- anything. An object for which you want an HTML
          representation

        - ``concatenate`` -- if ``True``, combine HTML representations of
          elements of the container ``obj``

        - ``strict`` -- if ``True``, construct an HTML representation of
          ``obj`` even if ``obj`` is a string

        OUTPUT: :class:`HtmlFragment`

        EXAMPLES::

            sage: h = html('<hr>');  pretty_print(h)
            <hr>
            sage: type(h)
            <class 'sage.misc.html.HtmlFragment'>

            sage: html(1/2)
            <html>\(\displaystyle \frac{1}{2}\)</html>

            sage: html('<a href="http://sagemath.org">sagemath</a>')
            <a href="http://sagemath.org">sagemath</a>

            sage: html('<a href="http://sagemath.org">sagemath</a>', strict=True)
            <html>\(\displaystyle \verb|&lt;a|\verb| |\verb|href="http://sagemath.org">sagemath&lt;/a>|\)</html>

        Display preference ``align_latex`` affects rendering of LaTeX expressions::

            sage: from sage.repl.rich_output.display_manager import get_display_manager
            sage: dm = get_display_manager()
            sage: dm.preferences.align_latex = 'left'
            sage: html(1/2)
            <html>\(\displaystyle \frac{1}{2}\)</html>
            sage: dm.preferences.align_latex = 'center'
            sage: html(1/2)
            <html>\[\frac{1}{2}\]</html>
            sage: dm.preferences.align_latex = None  # same with left
            sage: html(1/2)
            <html>\(\displaystyle \frac{1}{2}\)</html>
        """
        # string obj is interpreted as an HTML in not strict mode
        if isinstance(obj, str) and not strict:
            return HtmlFragment(math_parse(obj))

        # prefer dedicated _html_() method
        try:
            result = obj._html_()
            return HtmlFragment(result)
        except AttributeError:
            pass

        from sage.repl.rich_output.display_manager import get_display_manager
        dm = get_display_manager()
        if dm.preferences.align_latex == 'center':
            mode = 'display'
        elif dm.preferences.align_latex == 'left':
            mode = 'display_left'
        else:
            mode = 'display_left'

        # otherwise convert latex to html
        if concatenate:
            if isinstance(obj, (tuple, list)):
                obj = tuple(obj)
            result = MathJax().eval(obj, mode=mode, combine_all=True)
        else:
            result = MathJax().eval(obj, mode=mode, combine_all=False)
        return HtmlFragment(result)

    def eval(self, s, locals=None):
        r"""
        Evaluate embedded <sage> tags.

        INPUT:

        - ``s`` -- string

        - ``globals`` -- dictionary; the global variables when
          evaluating ``s``. Default: the current global variables.

        OUTPUT: :class:`HtmlFragment`

        EXAMPLES::

            sage: a = 123
            sage: html.eval('<sage>a</sage>')
            \(123\)
            sage: html.eval('<sage>a</sage>', locals={'a': 456})
            \(456\)
        """
        from sage.misc.sage_eval import sage_eval
        if locals is None:
            from sage.repl.user_globals import get_globals
            locals = get_globals()
        s = str(s)
        s = math_parse(s)
        t = ''
        while s:
            i = s.find('<sage>')
            if i == -1:
                t += s
                break
            j = s.find('</sage>')
            if j == -1:
                t += s
                break
            t += s[:i] + r'\({}\)'.format(latex(sage_eval(s[6+i:j], locals=locals)))
            s = s[j+7:]
        return HtmlFragment(t)

    def iframe(self, url, height=400, width=800):
        r"""
        Generate an iframe HTML fragment.

        INPUT:

        - ``url`` -- string; a url, either with or without URI scheme
          (defaults to "http"), or an absolute file path

        - ``height`` -- the number of pixels for the page height
          Defaults to 400

        - ``width`` -- the number of pixels for the page width
          Defaults to 800

        OUTPUT: :class:`HtmlFragment`

        EXAMPLES::

            sage: pretty_print(html.iframe("sagemath.org"))
            <iframe height="400" width="800"
            src="http://sagemath.org"></iframe>
            sage: pretty_print(html.iframe("http://sagemath.org",30,40))
            <iframe height="30" width="40"
            src="http://sagemath.org"></iframe>
            sage: pretty_print(html.iframe("https://sagemath.org",30))
            <iframe height="30" width="800"
            src="https://sagemath.org"></iframe>
            sage: pretty_print(html.iframe("/home/admin/0/data/filename"))
            <iframe height="400" width="800"
            src="file:///home/admin/0/data/filename"></iframe>
            sage: pretty_print(html.iframe('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA'
            ....: 'AUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBA'
            ....: 'AO9TXL0Y4OHwAAAABJRU5ErkJggg=="'))
            <iframe height="400" width="800"
            src="http://data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==""></iframe>
        """
        if url.startswith('/'):
            url = 'file://{0}'.format(url)
        elif '://' not in url:
            url = 'http://{0}'.format(url)
        return HtmlFragment('<iframe height="{0}" width="{1}" src="{2}"></iframe>'
                            .format(height, width, url))


html = HTMLFragmentFactory()
html.__doc__ = HTMLFragmentFactory.__call__.__doc__


def pretty_print_default(enable=True):
    r"""
    Enable or disable default pretty printing.

    Pretty printing means rendering things in HTML and by MathJax so that a
    browser-based frontend can render real math.

    This function is pretty useless without the notebook, it should not
    be in the global namespace.

    INPUT:

    - ``enable`` -- boolean (default: ``True``);  if ``True``, turn on
      pretty printing. If ``False``, turn it off.

    EXAMPLES::

        sage: pretty_print_default(True)
        sage: 'foo'  # the doctest backend does not support html
        'foo'
        sage: pretty_print_default(False)
        sage: 'foo'
        'foo'
    """
    from sage.repl.rich_output import get_display_manager
    dm = get_display_manager()
    dm.preferences.text = 'latex' if enable else None
