146 lines
4.7 KiB
Python
146 lines
4.7 KiB
Python
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
|
# Copyright (C) 2014 Jesús Espino <jespinog@gmail.com>
|
|
# Copyright (C) 2014 David Barragán <bameda@dbarragan.com>
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program 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 Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import hashlib
|
|
import functools
|
|
import bleach
|
|
|
|
# BEGIN PATCH
|
|
import html5lib
|
|
from html5lib.serializer.htmlserializer import HTMLSerializer
|
|
|
|
|
|
def _serialize(domtree):
|
|
walker = html5lib.treewalkers.getTreeWalker('etree')
|
|
stream = walker(domtree)
|
|
serializer = HTMLSerializer(quote_attr_values=True,
|
|
omit_optional_tags=False,
|
|
alphabetical_attributes=True)
|
|
|
|
return serializer.render(stream)
|
|
|
|
bleach._serialize = _serialize
|
|
# END PATCH
|
|
|
|
from django.core.cache import cache
|
|
from django.utils.encoding import force_bytes
|
|
|
|
from markdown import Markdown
|
|
|
|
from .extensions.autolink import AutolinkExtension
|
|
from .extensions.automail import AutomailExtension
|
|
from .extensions.semi_sane_lists import SemiSaneListExtension
|
|
from .extensions.spaced_link import SpacedLinkExtension
|
|
from .extensions.strikethrough import StrikethroughExtension
|
|
from .extensions.wikilinks import WikiLinkExtension
|
|
from .extensions.emojify import EmojifyExtension
|
|
from .extensions.mentions import MentionsExtension
|
|
from .extensions.references import TaigaReferencesExtension
|
|
|
|
|
|
# Bleach configuration
|
|
bleach.ALLOWED_TAGS += ["p", "table", "thead", "tbody", "th", "tr", "td", "h1",
|
|
"h2", "h3", "h4", "h5", "h6", "div", "pre", "span",
|
|
"hr", "dl", "dt", "dd", "sup", "img", "del", "br",
|
|
"ins"]
|
|
|
|
bleach.ALLOWED_STYLES.append("background")
|
|
|
|
bleach.ALLOWED_ATTRIBUTES["a"] = ["href", "title", "alt"]
|
|
bleach.ALLOWED_ATTRIBUTES["img"] = ["alt", "src"]
|
|
bleach.ALLOWED_ATTRIBUTES["*"] = ["class", "style"]
|
|
|
|
|
|
def _make_extensions_list(project=None):
|
|
return [AutolinkExtension(),
|
|
AutomailExtension(),
|
|
SemiSaneListExtension(),
|
|
SpacedLinkExtension(),
|
|
StrikethroughExtension(),
|
|
WikiLinkExtension(project),
|
|
EmojifyExtension(),
|
|
MentionsExtension(),
|
|
TaigaReferencesExtension(project),
|
|
"extra",
|
|
"codehilite",
|
|
"sane_lists",
|
|
"toc",
|
|
"nl2br"]
|
|
|
|
|
|
import diff_match_patch
|
|
|
|
|
|
def cache_by_sha(func):
|
|
@functools.wraps(func)
|
|
def _decorator(project, text):
|
|
sha1_hash = hashlib.sha1(force_bytes(text)).hexdigest()
|
|
key = "{}-{}".format(sha1_hash, project.id)
|
|
|
|
# Try to get it from the cache
|
|
cached = cache.get(key)
|
|
if cached is not None:
|
|
return cached
|
|
|
|
returned_value = func(project, text)
|
|
cache.set(key, returned_value, timeout=None)
|
|
return returned_value
|
|
|
|
return _decorator
|
|
|
|
|
|
def _get_markdown(project):
|
|
extensions = _make_extensions_list(project=project)
|
|
md = Markdown(extensions=extensions)
|
|
md.extracted_data = {"mentions": [], "references": []}
|
|
return md
|
|
|
|
|
|
@cache_by_sha
|
|
def render(project, text):
|
|
md = _get_markdown(project)
|
|
return bleach.clean(md.convert(text))
|
|
|
|
|
|
def render_and_extract(project, text):
|
|
md = _get_markdown(project)
|
|
result = bleach.clean(md.convert(text))
|
|
return (result, md.extracted_data)
|
|
|
|
|
|
class DiffMatchPatch(diff_match_patch.diff_match_patch):
|
|
def diff_pretty_html(self, diffs):
|
|
html = []
|
|
for (op, data) in diffs:
|
|
text = (data.replace("&", "&").replace("<", "<")
|
|
.replace(">", ">").replace("\n", "<br />"))
|
|
if op == self.DIFF_INSERT:
|
|
html.append("<ins style=\"background:#e6ffe6;\">%s</ins>" % text)
|
|
elif op == self.DIFF_DELETE:
|
|
html.append("<del style=\"background:#ffe6e6;\">%s</del>" % text)
|
|
elif op == self.DIFF_EQUAL:
|
|
html.append("<span>%s</span>" % text)
|
|
return "".join(html)
|
|
|
|
|
|
def get_diff_of_htmls(html1, html2):
|
|
diffutil = DiffMatchPatch()
|
|
diffs = diffutil.diff_main(html1, html2)
|
|
diffutil.diff_cleanupSemantic(diffs)
|
|
return diffutil.diff_pretty_html(diffs)
|
|
|
|
__all__ = ["render", "get_diff_of_htmls", "render_and_extract"]
|