Django源碼01:過濾HTML標籤的strip_tags函數是如何實現的?

Django中有一個可以過濾HTML標籤的函數,名爲strip_tags,它位於的django.utils.html中,使用它可以完成一些特殊的字符串處理任務。好奇的我想知道它是如何實現的,於是打開了對應的Django源碼文件,打算一探究竟。使用Pycharm直接定位到函數位置,我們可以看到strip_tags這個函數是這樣定義的:

@keep_lazy_text
def strip_tags(value):
    """Return the given HTML with all tags stripped."""
    # Note: in typical case this loop executes _strip_once once. Loop condition
    # is redundant, but helps to reduce number of executions of _strip_once.
    value = force_text(value)
    while '<' in value and '>' in value:
        new_value = _strip_once(value)
        if len(new_value) >= len(value):
            # _strip_once was not able to detect more tags
            break
        value = new_value
    return value

先不管頂部的裝飾器,首先看一下函數的參數和返回值。該函數的參數是value,在實際使用中value是指一堆HTML代碼。返回值同樣是value,只不過是經過閹割版的value,是去除了所有HTML標籤的內容。

傳入的內容首先要經過一個force_text函數,大概能猜出來這個函數的功能是對value做一些預處理,具體是什麼樣的預處理呢?我們繼續定位到force_text定義的地方,查看源碼,該函數位於同目錄下的encoding.py文件中,從該文件的名稱我們可以看出,它的主要功能是進行各種形式的編碼:

def force_text(s, encoding='utf-8', strings_only=False, errors='strict'):
    """
    Similar to smart_text, except that lazy instances are resolved to
    strings, rather than kept as lazy objects.

    If strings_only is True, don't convert (some) non-string-like objects.
    """
    # Handle the common case first for performance reasons.
    
    # 首先判斷value是否屬於字符串類型,如果是直接原樣返回
    if issubclass(type(s), str):
        return s
    
    # 這裏不會執行,因爲Strings_only默認爲false
    if strings_only and is_protected_type(s):
        return s
    
    # 判斷value是否屬於二進制byte數據,甭管是不是都將其轉爲字符串類型
    try:
        if isinstance(s, bytes):
            s = str(s, encoding, errors)
        else:
            s = str(s)
    except UnicodeDecodeError as e:
        raise DjangoUnicodeDecodeError(s, *e.args)
    return s

相關注釋我已經在上面給出,可以看出force_text函數的作用就是保證value是字符串,以便後續處理。但是這個函數裏面有幾個函數,我這裏稍加說明。issubclass(parm1, parm2)函數接收兩個參數,主要用於用於判斷參數 parm1 是否是類型參數 parm2 的子類,返回值爲布爾類型。

type()isinstance()都可以判斷參數的數據類型,type()一般接收一個參數,返回值是此參數的數據類型,比如:

>>> type('django')
<type 'str'>

isinstance(parm1,parm2)接收兩個參數,返回值是布爾類型,用於判斷parm1是否屬於parm2的類型。

>>> isinstance('django',str)
True

但是type()isinstance()是有區別的:

  • type() 不會認爲子類是一種父類類型,不考慮繼承關係。
  • isinstance() 會認爲子類是一種父類類型,考慮繼承關係。

如果要判斷兩個類型是否相同推薦使用 isinstance()。

繼續讀strip_tags的源碼,接下來是一個while循環,此循環的作用是去除HTML代碼,循環條件是判斷value中是否還含有<或者>,如果有繼續去除。

其實在這個地方,源碼裏也做了註釋說明,其實這個循環通常情況下只會執行一次,也就是說一次性就能將HTML標籤去除。循環條件的給出也是多餘的,但有助於減少_strip_once函數的執行次數。

def strip_tags(value):
    # 對輸入做預處理,保證value爲字符串類型
    value = force_text(value)
    while '<' in value and '>' in value:
        new_value = _strip_once(value)
        if len(new_value) >= len(value):
            # _strip_once was not able to detect more tags
            break
        value = new_value
    return value

我們看到循環中有一個_strip_once函數,這個函數纔是核心,因爲它完成了標籤的去除工作,我們再來定位一下它:

def _strip_once(value):
    """
    Internal tag stripping utility used by strip_tags.
    """
    s = MLStripper()
    try:
        s.feed(value)
    except HTMLParseError:
        return value
    try:
        s.close()
    except HTMLParseError:
        return s.get_data() + s.rawdata
    else:
        return s.get_data()

它就在strip_tags函數的上面,該函數首先實例化了一個MLStripper類的對象,然後調用了該對象中的幾個方法,最終返回了去掉HTML標籤的數據。我們來看看這個類:

class MLStripper(HTMLParser):
    def __init__(self):
        HTMLParser.__init__(self)
        self.reset()
        self.fed = []

    def handle_data(self, d):
        self.fed.append(d)

    def handle_entityref(self, name):
        self.fed.append('&%s;' % name)

    def handle_charref(self, name):
        self.fed.append('&#%s;' % name)

    def get_data(self):
        return ''.join(self.fed)

看到這裏我明白了,原來這裏是Django使用了Python自帶的HTML解析工具:HTMLParser。而且僅僅是用了其中的一個功能,那就是解析HTML標籤之間內容的功能。其中handle_datahandle_entityrefhandle_charref是覆寫了HTMLParser的方法,get_data函數是Django自己定義的,用於組合數據。

有關HTMLParser的使用方式這裏不在進行擴展,後期再專門寫一篇文章詳細講解吧。好了,這篇文章到此結束,最後說一下看源碼的感受吧,要想看懂源碼的話一定要懂面向對象的相關知識,因爲框架中的很多函數都是調用來調用去,很多類都是繼承來繼承去,如果不熟悉面向對象的內容,很容易套着套着就暈了。

另外一點就是,框架所考慮的東西很全面,很系統,而目前我自己寫代碼的時候就很難考慮的這麼全,所以還是要多看官方源碼,多受規範的、好代碼的薰陶纔行!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章