展开

文章目录

修改历史

使用Flask搭建一个校园论坛9-支持Markdown语法

2021-10-13 17:58:35 系列教程 247

简介

在上一节中,我们实现了论坛的评论功能,细心的小伙伴会发现上一节中的评论功能只支持简单的文字,本节中就介绍如何让评论支持Markdown语法。PS:隔了这么久没有更新了,主要是因为拖延症晚期~~~

1. python-markdown

python的之所以变得非常的流行,其最强大的地方就是拥有很多第三库,通过第三方库可以快速的实现很多功能,论坛评论markdown语法的支持就是通过python-markdown库实现的,本节展示一下python-markdown库的基本用法。

首先通过pip命令进行安装

pip install markdown

最简单的markdown转换为html代码

from markdown import markdown

md = "## 标题2"
html = markdown(md)
print(html)

我们只需要将要转换的markdown语法的内容传递给markdown.markdown()方法,就会转换成相应的html代码,上述代码的输出结果是<h2>标题2</h2>

markdown这个库的生态十分完整,很多常用的功能它都自己内部解决了,如果不能自行解决,在markdown()方法中还可以通过extensions位置参数提供不同的扩展,下面的代码就是通过TOC扩展实现自动添加目录的功能

from markdown import markdown

md = """[TOC]
## 1. 标题1
我的祖国

### 1. 湖南
我的家乡在湖南
"""
html = markdown(md, extensions=['markdown.extensions.toc'])
print(html)

上面的代码输出如下图所示,通过TOC扩展,就可以实现自动生成目录的功能

上面的代码中的TOC拓展渲染出来的ul没有样式,如果我们想要自定义对应元素的样式该怎么办呢?我们可以自定义我们的扩展,如下面的代码片段所示

from markdown import extensions
from markdown.treeprocessors import Treeprocessor


class MyMDStyleTreeProcessor(Treeprocessor):
    def run(self, root):
        for child in root.iter():
            if child.tag == 'ul':
                child.set('class', 'ul-content')
            elif child.tag == 'li':
                child.set('class', 'li-content-text')
        return root


class MyMDStyleExtension(extensions.Extension):
    def extendMarkdown(self, md):
        md.registerExtension(self)
        self.processor = MyMDStyleTreeProcessor()
        self.processor.md = md
        self.processor.config = self.getConfigs()
        md.treeprocessors.add('mystyle', self.processor, '_end')


md = """[TOC]
## 1. 标题1
我的祖国

### 1. 湖南
我的家乡在湖南
"""
html = markdown(md, extensions=['markdown.extensions.toc', MyMDStyleExtension()])
print(html)

在上面的代码中,首先通过继承Treeprocessor类,通过判断节点的属性是否为ul或者li来给节点添加上对应的class属性,之后通过继承Extension类,将我们自定义的extension注册到markdown文本上去,上述代码输出如下图所示,可以看到在ul和li标签分别添加上了对应的class属性,在html页面中我们就可以通过设置class属性的css来调整目录的样式了。

2. 预览评论

有了上面一节的基础后就可以开始实现评论markdown语法支持了,在评论区域使用的是两个TAB,第一个TAB是输入评论内容的,第二TAB是预览所输入的评论被渲染后的样式(参照github设计),原始markdown内容如下图所示

点击预览渲染后的样式如下图所示

评论内容是支持代码内容渲染的,在extensions位置参数中添加extensions=['markdown.extensions.codehilite']即可,但是该扩展依赖于Pygments,所以我们使用之前应该先安装Pygments

pip install pygments

安装完成之后我们可以使用pygmentize -S default -f html -a .codehilite > styles.css 命令导出代码高亮样式,Pygments支持变成语言以及代码样式非常多,可以访问https://pygments.org/demo/#进行选择,然后通过前面的命令在命令行中导出相应的样式css文件。

完成上述步骤之后,我们就可以自定义我们的markdown扩展了,自定义代码片段如下所示

class MyMDStyleTreeProcessor(Treeprocessor):
    def run(self, root):
        for child in root.getiterator():
            if child.tag == 'table':
                child.set("class", "table table-bordered table-hover")
            elif child.tag == 'img':
                child.set("class", "img-fluid d-block img-pd10")
            elif child.tag == 'blockquote':
                child.set('class', 'blockquote-comment')
            elif child.tag == 'p':
                child.set('class', 'mt-0 mb-0 p-break')
            elif child.tag == 'pre':
                child.set('class', 'mb-0')
            elif child.tag == 'h1':
                child.set('class', 'comment-h1')
            elif child.tag == 'h2':
                child.set('class', 'comment-h2')
            elif child.tag == 'h3':
                child.set('class', 'comment-h3')
            elif child.tag in ['h4', 'h5', 'h6']:
                child.set('class', 'comment-h4')
        return root


# noinspection PyAttributeOutsideInit
class MyMDStyleExtension(extensions.Extension):
    def extendMarkdown(self, md):
        md.registerExtension(self)
        self.processor = MyMDStyleTreeProcessor()
        self.processor.md = md
        self.processor.config = self.getConfigs()
        md.treeprocessors.add('mystyle', self.processor, '_end')

与第一节给ul以及li标签添加class属性一致,通过上面的代码可以自定义一些标签的相关样式。有些小伙伴就比较好奇,为什么不用定义代码高亮的样式呢?因为codehilite扩展会自动将我们的代码转换成对应的html,不同的代码内容自动添加不同class,然后通过前面导出的样式文件实现代码高亮,所以这里不需要我们做任何处理。完成了自定义扩展之后,就是后端视图函数的编写了,代码片段如下。

@normal_bp.route('/comment/render-md/', methods=['POST'])
@login_required
def render_md():
    md = request.form.get('md')
    html = to_html(md)
    return jsonify({'html': html})

上面代码片段逻辑应该比较清楚了,就是获取前端传过来的原始md数据,然后通过to_html方式转换成html,然后返回给前端进行渲染,其中to_html()方法代码如下

def to_html(raw):
    allowed_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'abbr', 'b', 'br', 'blockquote', 'code', 'del', 'div',
                    'em', 'img', 'p', 'pre', 'strong', 'span', 'ul', 'li', 'ol']
    allowed_attributes = ['src', 'title', 'alt', 'href', 'class']
    html = markdown(raw, output_format='html',
                    extensions=['markdown.extensions.fenced_code',
                                'markdown.extensions.codehilite',
                                'markdown.extensions.tables', MyMDStyleExtension()])
    clean_html = clean(html, tags=allowed_tags, attributes=allowed_attributes)
    img_url = '<img class="img-emoji" src="/static/emojis/{}" title="{}" alt="{}">'
    for i in EMOJI_INFOS:
        for ii in i:
            emoji_url = img_url.format(ii[0], ii[1], ii[1])
            clean_html = re.sub(':{}:'.format(ii[1]), emoji_url, clean_html)
    return linkify(clean_html)

to_html()方法中又出现了一下新的内容clean()以及linkify()两个方法,这两个方法都来字bleach库中,关于bleach库这里不做过多的介绍,感兴趣的童鞋可以自己搜索相关文档了解,这里只介绍使用clean()以及linkify()的目的。

在使用clean之前我们定义了allowed_tags以及allowed_attributes两个列表,懂html的童鞋应该都知道这些事HTML的一些标签以及相应的标签属性,然后通过markdown方法将原始内容转换为HTML,然后使用clean方法,clean方法在这里的作用就是将不再allowed_tags以及allowed_attributes中的属性过滤掉。因为在提交评论的时候,一些不安好心的'良民'可能会在评论中添加一些恶意的JavaScript代码,就是我们所说的XSS攻击,通过clean方法就可以有效的防止这种攻击。linkify()方法的作用是将一些url转换为对应的HTML元素,例如下面的代码片段

from bleach import linkify

url = "链接: https://2dogz.cn"
print(linkify(url))
mail = "邮箱: 804022023@qq.com"
print(linkify(mail, parse_email=True))

输出如下图

到此为止就完成了评论预览的功能了。

3.渲染评论并保存

用户在提交评论后,我们就可以通过上面的方式将评论渲染成HTML内容,然后保存到数据库中去,代码片段如下

@post_bp.route('/post-comment/', methods=['POST'])
@login_required
@statistic_traffic(db, CommentStatistic)
def post_comment():
    ...
    comment_content = to_html(comment_content)
    com = Comments(body=comment_content, post_id=post_id, author_id=current_user.id)
    ...
    return jsonify({'tag': 1})


@post_bp.route('/reply-comment/', methods=['POST'])
@login_required
@statistic_traffic(db, CommentStatistic)
def reply_comment():
    ...
    comment = to_html(comment)
    post_id = request.form.get('post_id')
    reply = Comments(body=comment, replied_id=comment_id, author_id=current_user.id, post_id=post_id)
    ...
    return jsonify({'tag': 1})

内容基本与上一节中的添加评论代码一致,只是在其中添加了to_html()方法,将原始的评论内容转换为HTML内容然后保存到数据库中去。

博客文章中的代码只截取了部分关键代码,很多细节代码没有贴出来,如果想要在本地机器上运行调试可以访问我的github仓库或者gitee仓库仓库,如果想要看到实际效果请访问二狗学院

当前共有1条评论


lushishuai 用户

我就试着评论一下test

Macv 博主 回复:lushishuai

0.0