使用Python從Markdown文檔中自動生成標題導航


概述###

Markdown 很適合於技術寫作,因為技術寫作並不需要花哨的排版和內容, 只要內容生動而嚴謹,文筆朴實而優美。

為了編寫對讀者更友好的文章,有必要生成文章的標題導航,讓讀者有個預期的閱讀概覽。當文章標題比較多時,手工去編寫導航錨點比較費時,因此決定使用Python解析Markdown文檔自動生成標題導航。

知識與思路###

寫過Markdown的人知道,Markdown的標題是使用一到六個# 左右包圍住標題文字,而錨點是 [標題](#標題)。 比如 ## 知識與思路 ## ,錨點連接是 [知識與思路](#知識與思路)。標題文字允許包含空格,不過最好不要用空格或特殊不可打印字符,因為在網頁上點擊帶錨點的鏈接時可能失效,並且網站對Markdown錨點的支持可能有特殊規則,比如博客園會將標題中的大寫字母自動轉成小寫字母,並且對於有空格的標題可能無法鏈接到錨點。

這樣,就需要解析標題行,從中提取出標題並生成錨點鏈接。通常使用正則表達式來匹配和提取文本內容。此外,為了排版,也要根據標題的級別生成相應的縮進。考慮到段落中很可能含有#字符,因此標題最好從第二級開始,不超過四級。
標題行的正則表達式是:

\s*(#{2,6})\s*(.*?)\s*(?:\1)\s+

知識點:

  1. 2-6 個#號使用 #{2,6} 匹配;
  2. 由於行起始和標題前后都可能含有空格,因此需要使用\s*來兼容;行末尾至少有個換行符,因此要使用 \s+;
  3. 使用(subregex)捕獲 subregex 匹配的文本, 使用 \1 表示后面與前面對應的被匹配的分組文本, 使用 (?:subregex) 表示匹配 subregex 的文本,但在捕獲分組時忽略,不作為提取內容;
  4. 使用 (.*?) 匹配標題內容, ? 表示非貪婪模式,避免將 # 也包含到標題中;
  5. 標題的縮進比較簡單,第二級的不縮進,第N級縮進 (N-2)個指定字符,這里字符選定為中文空格  
  6. 程序會接受一系列 Markdown 文檔的文件路徑參數,並輸出對應於每個文件的導航文本;由於參數比較簡單,就不使用 argparse 這樣的參數解析模塊了;
  7. 使用: python mdnav.py filename1 filename2 ... filenameN

代碼實現###

#!/usr/bin/python                                                           
#_*_encoding:utf-8_*_

##########################################################################
# mdnav.py: Generate nav anchors for given markdown files
# usage:  python mdnav.py md_filename1 md_filename2 ... md_filenameN
##########################################################################

import re
import sys

mdTitleRegex = r'\s*(#{2,6})\s*(.*?)\s*(?:\1)\s+'
mdTitlePatt = re.compile(mdTitleRegex)

def parseLineByRegex(line, regex_patt):
    m = regex_patt.match(line)
    return m.groups() if m else ()

def outputAnchor(titleTuple):
    if len(titleTuple) == 2:
        intents = ' ' * (len(titleTuple[0])-2)
        title = titleTuple[1]
        anchor = '[%s](#%s)' % (title, title)
        print intents,anchor

def procLine(line):
    outputAnchor(parseLineByRegex(line, mdTitlePatt))

def help():
    print '%s usage: need at least one param as markdown filename' % sys.argv[0]
    print 'python %s filename1 filename2 ... filenameN' % sys.argv[0]

if __name__ == '__main__':
    if len(sys.argv) < 2:
        help()
        exit(1)
    for ind in range(1, len(sys.argv)):
        filename = sys.argv[ind]
        print 'file: ', filename
        with open(filename) as mdtext:
            map(procLine, mdtext.readlines())


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM