将markdown渲染成html

Markdown是一种非常简洁的文本语言,很多程序员和技术工作者都用它来写博文和说明文档,想要将markdown渲染成html可以使用Pythonmarkdown包。

  1. pip install markdown

在渲染页面的视图函数中,将文章主题利用markdown渲染

  1. def detail(request, id):
  2. post = Post.objects.get(id=id)
  3. post.body = markdown.markdown(post.body,
  4. extensions=[
  5. 'markdown.extensions.extra',
  6. 'markdown.extensions.codehilite',
  7. 'markdown.extensions.toc',
  8. ])
  9. return render(request, 'blog/detail.html', {'post': post})

在前端页面上通过safe过滤器就能将其渲染成html代码,{{post.body | safe}}

markdown 目录

在页面的任何地方插入目录

上述方式的一个局限局限性就是只能通过 [TOC] 标记在文章内容中插入目录。如果我想在页面的其它地方,比如侧边栏插入一个目录该怎么做呢?方法其实也很简单,只需要稍微改动一下解析 Markdown 文本内容的方式即可,具体代码就像这样:

  1. def detail(request, pk):
  2. post = get_object_or_404(Post, pk=pk)
  3. md = markdown.Markdown(extensions=[
  4. 'markdown.extensions.extra',
  5. 'markdown.extensions.codehilite',
  6. 'markdown.extensions.toc',
  7. ])
  8. post.body = md.convert(post.body)
  9. post.toc = md.toc
  10. return render(request, 'blog/detail.html', context={'post': post})

markdown.markdown()方法来渲染 post.body 中的内容,而是先实例化了一个 markdown.Markdown对象 md,和 markdown.markdown() 方法一样,也传入了extensions 参数。接着我们便使用该实例的 convert 方法将 post.body 中的 Markdown 文本解析成 HTML 文本。而一旦调用该方法后,实例 md就会多出一个 toc 属性,这个属性的值就是内容的目录,我们把 md.toc的值赋给 post.toc 属性(要注意这个 post 实例本身是没有 toc 属性的,我们给它动态添加了 toc属性,这就是 Python 动态语言的好处)。

接下来就在博客文章详情页的文章目录侧边栏渲染文章的目录吧!删掉占位用的目录内容,替换成如下代码:

  1. {% block toc %}
  2. <div class="widget widget-content">
  3. <h3 class="widget-title">文章目录</h3>
  4. {{ post.toc|safe }}
  5. </div>
  6. {% endblock toc %}

即使用模板变量标签 {{ post.toc }}显示模板变量的值,注意 post.toc 实际是一段 HTML 代码,我们知道 django会对模板中的 HTML 代码进行转义,所以要使用 safe 标签防止 django 对其转义。其最终渲染后的效果就是:

美化标题

文章内容的标题被设置了锚点,点击目录中的某个标题,页面就会跳到该文章内容中标题所在的位置,这时候浏览器的 URL 显示的值可能不太美观,比如像下面的样子:

  1. http://127.0.0.1:8000/posts/8/#_1
  2. http://127.0.0.1:8000/posts/8/#_3

_1 就是锚点,Markdown 在设置锚点时利用的是标题的值,由于通常我们的标题都是中文,Markdown 没法处理,所以它就忽略的标题的值,而是简单地在后面加了个 _1 这样的锚点值。为了解决这一个问题,需要修改一下传给 extentions 的参数,其具体做法如下:

  1. from django.utils.text import slugify
  2. from markdown.extensions.toc import TocExtension
  3. def detail(request, pk):
  4. post = get_object_or_404(Post, pk=pk)
  5. md = markdown.Markdown(extensions=[
  6. 'markdown.extensions.extra',
  7. 'markdown.extensions.codehilite',
  8. # 记得在顶部引入 TocExtension 和 slugify
  9. TocExtension(slugify=slugify),
  10. ])
  11. post.body = md.convert(post.body)
  12. m = re.search(r'<div class="toc">\s*<ul>(.*)</ul>\s*</div>', md.toc, re.S)
  13. post.toc = m.group(1) if m is not None else ''
  14. return render(request, 'blog/detail.html', context={'post': post})

和之前不同的是,extensions 中的 toc 拓展不再是字符串 markdown.extensions.toc ,而是 TocExtension 的实例。TocExtension 在实例化时其 slugify 参数可以接受一个函数,这个函数将被用于处理标题的锚点值。Markdown 内置的处理方法不能处理中文标题,所以我们使用了 django.utils.text 中的 slugify 方法,该方法可以很好地处理中文。

这时候标题的锚点 URL 变得好看多了。

  1. http://127.0.0.1:8000/posts/8/#我是标题一
  2. http://127.0.0.1:8000/posts/8/#我是标题二下的子标题