source: sasmodels/sasmodels/rst2html.py @ 2d81cfe

core_shell_microgelsmagnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since 2d81cfe was 2d81cfe, checked in by Paul Kienzle <pkienzle@…>, 23 months ago

lint

  • Property mode set to 100644
File size: 7.8 KB
Line 
1r"""
2Convert a restructured text document to html.
3
4Inline math markup can uses the *math* directive, or it can use latex
5style *\$expression\$*.  Math can be rendered using simple html and
6unicode, or with mathjax.
7"""
8
9import re
10from contextlib import contextmanager
11
12# CRUFT: locale.getlocale() fails on some versions of OS X
13# See https://bugs.python.org/issue18378
14import locale
15if hasattr(locale, '_parse_localename'):
16    try:
17        locale._parse_localename('UTF-8')
18    except ValueError:
19        _old_parse_localename = locale._parse_localename
20        def _parse_localename(localename):
21            code = locale.normalize(localename)
22            if code == 'UTF-8':
23                return None, code
24            else:
25                return _old_parse_localename(localename)
26        locale._parse_localename = _parse_localename
27
28from docutils.core import publish_parts
29from docutils.writers.html4css1 import HTMLTranslator
30from docutils.nodes import SkipNode
31
32def rst2html(rst, part="whole", math_output="mathjax"):
33    r"""
34    Convert restructured text into simple html.
35
36    Valid *math_output* formats for formulas include:
37    - html
38    - mathml
39    - mathjax
40    See `<http://docutils.sourceforge.net/docs/user/config.html#math-output>`_
41    for details.
42
43    The following *part* choices are available:
44    - whole: the entire html document
45    - html_body: document division with title and contents and footer
46    - body: contents only
47
48    There are other parts, but they don't make sense alone:
49
50        subtitle, version, encoding, html_prolog, header, meta,
51        html_title, title, stylesheet, html_subtitle, html_body,
52        body, head, body_suffix, fragment, docinfo, html_head,
53        head_prefix, body_prefix, footer, body_pre_docinfo, whole
54    """
55    # Ick! mathjax doesn't work properly with math-output, and the
56    # others don't work properly with math_output!
57    if math_output == "mathjax":
58        settings = {"math_output": math_output}
59    else:
60        settings = {"math-output": math_output}
61
62    # TODO: support stylesheets
63    #html_root = "/full/path/to/_static/"
64    #sheets = [html_root+s for s in ["basic.css","classic.css"]]
65    #settings["embed_styesheet"] = True
66    #settings["stylesheet_path"] = sheets
67
68    # math2html and mathml do not support \frac12
69    rst = replace_compact_fraction(rst)
70
71    # mathml, html do not support \tfrac
72    if math_output in ("mathml", "html"):
73        rst = rst.replace(r'\tfrac', r'\frac')
74
75    rst = replace_dollar(rst)
76    with suppress_html_errors():
77        parts = publish_parts(source=rst, writer_name='html',
78                              settings_overrides=settings)
79    return parts[part]
80
81@contextmanager
82def suppress_html_errors():
83    r"""
84    Context manager for keeping error reports out of the generated HTML.
85
86    Within the context, system message nodes in the docutils parse tree
87    will be ignored.  After the context, the usual behaviour will be restored.
88    """
89    visit_system_message = HTMLTranslator.visit_system_message
90    HTMLTranslator.visit_system_message = _skip_node
91    yield None
92    HTMLTranslator.visit_system_message = visit_system_message
93
94def _skip_node(self, node):
95    raise SkipNode
96
97
98_compact_fraction = re.compile(r"(\\[cdt]?frac)([0-9])([0-9])")
99def replace_compact_fraction(content):
100    r"""
101    Convert \frac12 to \frac{1}{2} for broken latex parsers
102    """
103    return _compact_fraction.sub(r"\1{\2}{\3}", content)
104
105
106_dollar = re.compile(r"(?:^|(?<=\s|[-(]))[$]([^\n]*?)(?<![\\])[$](?:$|(?=\s|[-.,;:?\\)]))")
107_notdollar = re.compile(r"\\[$]")
108def replace_dollar(content):
109    r"""
110    Convert dollar signs to inline math markup in rst.
111    """
112    content = _dollar.sub(r":math:`\1`", content)
113    content = _notdollar.sub("$", content)
114    return content
115
116
117def test_dollar():
118    """
119    Test substitution of dollar signs with equivalent RST math markup
120    """
121    assert replace_dollar(u"no dollar") == u"no dollar"
122    assert replace_dollar(u"$only$") == u":math:`only`"
123    assert replace_dollar(u"$first$ is good") == u":math:`first` is good"
124    assert replace_dollar(u"so is $last$") == u"so is :math:`last`"
125    assert replace_dollar(u"and $mid$ too") == u"and :math:`mid` too"
126    assert replace_dollar(u"$first$, $mid$, $last$") == u":math:`first`, :math:`mid`, :math:`last`"
127    assert replace_dollar(u"dollar\\$ escape") == u"dollar$ escape"
128    assert replace_dollar(u"dollar \\$escape\\$ too") == u"dollar $escape$ too"
129    assert replace_dollar(u"spaces $in the$ math") == u"spaces :math:`in the` math"
130    assert replace_dollar(u"emb\\ $ed$\\ ed") == u"emb\\ :math:`ed`\\ ed"
131    assert replace_dollar(u"$first$a") == u"$first$a"
132    assert replace_dollar(u"a$last$") == u"a$last$"
133    assert replace_dollar(u"$37") == u"$37"
134    assert replace_dollar(u"($37)") == u"($37)"
135    assert replace_dollar(u"$37 - $43") == u"$37 - $43"
136    assert replace_dollar(u"($37, $38)") == u"($37, $38)"
137    assert replace_dollar(u"a $mid$dle a") == u"a $mid$dle a"
138    assert replace_dollar(u"a ($in parens$) a") == u"a (:math:`in parens`) a"
139    assert replace_dollar(u"a (again $in parens$) a") == u"a (again :math:`in parens`) a"
140
141def load_rst_as_html(filename):
142    from os.path import expanduser
143    with open(expanduser(filename)) as fid:
144        rst = fid.read()
145    html = rst2html(rst)
146    return html
147
148def wxview(html, url="", size=(850, 540)):
149    import wx
150    from wx.html2 import WebView
151    frame = wx.Frame(None, -1, size=size)
152    view = WebView.New(frame)
153    view.SetPage(html, url)
154    frame.Show()
155    return frame
156
157def view_html_wxapp(html, url=""):
158    import wx  # type: ignore
159    app = wx.App()
160    frame = wxview(html, url)
161    app.MainLoop()
162
163def view_url_wxapp(url):
164    import wx  # type: ignore
165    from wx.html2 import WebView
166    app = wx.App()
167    frame = wx.Frame(None, -1, size=(850, 540))
168    view = WebView.New(frame)
169    view.LoadURL(url)
170    frame.Show()
171    app.MainLoop()
172
173def qtview(html, url=""):
174    try:
175        from PyQt5.QtWebKitWidgets import QWebView
176        from PyQt5.QtCore import QUrl
177    except ImportError:
178        from PyQt4.QtWebKit import QWebView
179        from PyQt4.QtCore import QUrl
180    helpView = QWebView()
181    helpView.setHtml(html, QUrl(url))
182    helpView.show()
183    return helpView
184
185def view_html_qtapp(html, url=""):
186    import sys
187    try:
188        from PyQt5.QtWidgets import QApplication
189    except ImportError:
190        from PyQt4.QtGui import QApplication
191    app = QApplication([])
192    frame = qtview(html, url)
193    sys.exit(app.exec_())
194
195def view_url_qtapp(url):
196    import sys
197    try:
198        from PyQt5.QtWidgets import QApplication
199    except ImportError:
200        from PyQt4.QtGui import QApplication
201    app = QApplication([])
202    try:
203        from PyQt5.QtWebKitWidgets import QWebView
204        from PyQt5.QtCore import QUrl
205    except ImportError:
206        from PyQt4.QtWebKit import QWebView
207        from PyQt4.QtCore import QUrl
208    frame = QWebView()
209    frame.load(QUrl(url))
210    frame.show()
211    sys.exit(app.exec_())
212
213# Set default html viewer
214view_html = view_html_qtapp
215
216def can_use_qt():
217    """
218    Return True if QWebView exists.
219
220    Checks first in PyQt5 then in PyQt4
221    """
222    try:
223        from PyQt5.QtWebKitWidgets import QWebView
224        return True
225    except ImportError:
226        try:
227            from PyQt4.QtWebKit import QWebView
228            return True
229        except ImportError:
230            return False
231
232def view_help(filename, qt=False):
233    import os
234
235    if qt:
236        qt = can_use_qt()
237
238    url = "file:///"+os.path.abspath(filename).replace("\\", "/")
239    if filename.endswith('.rst'):
240        html = load_rst_as_html(filename)
241        if qt:
242            view_html_qtapp(html, url)
243        else:
244            view_html_wxapp(html, url)
245    else:
246        if qt:
247            view_url_qtapp(url)
248        else:
249            view_url_wxapp(url)
250
251def main():
252    import sys
253    view_help(sys.argv[1], qt=False)
254
255if __name__ == "__main__":
256    main()
Note: See TracBrowser for help on using the repository browser.