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_data
、handle_entityref
、handle_charref
是覆寫了HTMLParser的方法,get_data
函數是Django自己定義的,用於組合數據。
有關HTMLParser的使用方式這裏不在進行擴展,後期再專門寫一篇文章詳細講解吧。好了,這篇文章到此結束,最後說一下看源碼的感受吧,要想看懂源碼的話一定要懂面向對象的相關知識,因爲框架中的很多函數都是調用來調用去,很多類都是繼承來繼承去,如果不熟悉面向對象的內容,很容易套着套着就暈了。
另外一點就是,框架所考慮的東西很全面,很系統,而目前我自己寫代碼的時候就很難考慮的這麼全,所以還是要多看官方源碼,多受規範的、好代碼的薰陶纔行!