在學習和工作中,我們經常需要使用日志來記錄程序的運行狀態和調試信息。而為了更好地區分不同的日志等級,我們可以使用不同的顏色來呈現,使其更加醒目和易于閱讀。
在下圖運行結果中,我們使用了 colorlog 庫來實現彩色日志輸出。通過定義不同日志等級對應的顏色,我們可以在控制臺中以彩色的方式顯示日志信息。例如,DEBUG 級別的日志使用白色,INFO 級別的日志使用綠色,WARNING 級別的日志使用黃色,ERROR 級別的日志使用紅色,CRITICAL 級別的日志使用藍色。
但是在查看日志文件時,我們會發現日志信息是系統默認的字體顏色,并且前后多了一些特殊符號,例如 [32m 等。這是因為在控制臺中使用的是 ANSI 轉義序列來實現彩色文本效果,而這些特殊符號是 ANSI 轉義序列的一部分。如下圖所示:
現在有一個需求,在前端頁面直接查看日志內容并還原彩色文本效果,因此,我們將進行以下內容講解:
本文代碼點擊此處跳轉,往期系列文章請訪問博主的 項目實戰專欄,博文中的所有代碼全部收集在博主的 GitHub 倉庫中;
ANSI 轉義序列是美國國家標準化組織(American National Standards Institute,ANSI)制定的標準,是一種用于控制文本終端顯示的特殊字符序列。它們以 3[ 開頭,以字母和數字組合的形式表示不同的控制功能。
ANSI 轉義序列可以用于控制文本的顏色、背景色、文本樣式(如粗體、斜體等)、光標位置、清屏等操作。通過在輸出文本中插入適當的 ANSI 轉義序列,可以實現豐富的終端顯示效果。
以下是一些常用的 ANSI 轉義序列示例:
需要注意的是,ANSI 轉義序列在不同的終端和操作系統上的支持程度可能會有所不同。在某些終端中,可能無法正確解釋和顯示 ANSI 轉義序列。
我們以 3[31m 和 3[42m 為例,輸出一個綠底紅字的句子 Hello World! --sidiot.,代碼如下所示:
log.debug("3[42m3[31mHello World! --sidiot.3[0m3[0m")
運行結果:
這里的話,我們使用 Python 的 http.server 模塊來啟動一個簡單的 HTTP 服務器。
比較快捷的方式就是在日志文件夾中打開終端,輸入 python -m http.server 8888 即可,運行結果如下所示:
不過這種方式相對來說還是不太安全的,因此我們可以通過設置白名單的方式,來規避一些潛在的安全隱患,代碼如下所示:
import http.server
import socketserver
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
def check_client_address(self):
# 設置白名單,只允許特定的IP地址或主機訪問
whitelist = ['127.0.0.1', 'localhost']
client_address = self.client_address[0]
if client_address not in whitelist:
self.send_response(403)
self.end_headers()
self.wfile.write(b'Forbidden. Please contact sidiot.')
return False
return True
def do_GET(self):
if not self.check_client_address():
return
super().do_GET()
with socketserver.TCPServer(('0.0.0.0', 8888), HTTPRequestHandler) as httpd:
httpd.serve_forever()
目前本機的 IP 為 192.168.124.23,當我們以 127.0.0.1 來訪問 8888 服務端口時,訪問是成功的,但是當我們用 192.168.124.23 來訪問服務端口時,訪問是失敗的。
運行結果:
現在我們點擊文件,它會直接通過瀏覽器直接下載,但是我們需要的是在網頁上能夠直接閱覽文件中的內容,因此我們可以從 do_GET() 下手。
我們可以設計一個根據傳入的文件名參數,讀取本地文件并作為響應結果進行返回的方法,然后根據一定的規則進行觸發,代碼如下所示:
def read_file(self):
try:
self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(open(self.path[6:], 'rb').read())
except FileNotFoundError:
self.send_response(404)
self.end_headers()
self.wfile.write(b'File not found!')
def do_GET(self):
if self.check_client_address():
if self.path.startswith("/?log="):
self.read_file()
else:
super().do_GET()
上述代碼通過檢查請求的資源路徑來處理 GET 請求。如果請求的資源路徑前綴是 /?log=,且是當前目錄下存在的日志文件,它會讀取文件并將其內容作為響應發送。否則,它會使用基類的默認行為處理普通的 GET 請求。
運行結果:
至此,我們已經實現了前端頁面直接查看日志內容的功能。
原理分析
當我們想要在前端頁面展示 ANSI 字體的彩色效果時,我們只需要簡單地將 ANSI 轉義序列轉換成相應的 HTML 代碼就可以實現了。這個轉換過程實際上可以通過編寫一個 Python 函數來實現,該函數可以接受包含 ANSI 控制碼的字符串作為輸入,并將其轉換為帶有相應樣式的 HTML 代碼輸出,代碼如下所示:
def convert_ansi_to_html(ansi_text):
ansi_to_html = {
'\x1b[31m': '<span style="color: red;">',
'\x1b[42m': '<span style="background-color: green;">',
...,
}
html_text = re.sub(r'\x1b[[0-9;]*m', lambda match: ansi_to_html.get(match.group(0), ''), ansi_text)
return html_text
if __name__ == '__main__':
ansi = "\033[42m\033[31mHello World! --sidiot.\033[0m\033[0m"
print(ansi)
html = convert_ansi_to_html(ansi)
print(f"convert content: {html}")
需要注意的是,在 ANSI 轉義序列中,\x1b 和 3 都代 表ASCII 碼中的 Escape 字符,用于開始一個轉義序列。
運行結果:
使用 ansiconv 轉換
接下來,我們借助已有的庫函數 ansiconv 進行 ANSI 的轉換。
通過 pip 進行安裝:
pip install ansiconv
根據 ansiconv 的官方文檔使用其中的三個方法 to_plain(),to_html() 和 base_css() 來實現在前端頁面展示 ANSI 字體的彩色效果,代碼如下所示:
import ansiconv
ansi = "\033[42m\033[31mHello World! --sidiot.\033[0m\033[0m"
print(f"Ansi: {ansi}")
plain = ansiconv.to_plain(ansi)
html = ansiconv.to_html(ansi)
print(f"Convert Plain: {plain}")
print(f"Convert HTML: {html}")
在 base_css() 中會有相關的 CSS 映射表,如下所示:
css_rule('.ansi31', color="#FF0000"),
css_rule('.ansi42', background_color="#00FF00"),
運行結果:
研究 ansiconv 源碼
我們將通過研究 ansiconv 的源碼,以便深入了解它是如何將 ANSI 轉換成純文本或 HTML 代碼的工作原理。
to_plain() 的源碼如下所示:
上述代碼使用正則表達式匹配字符串中的 ANSI 轉義序列,并將其替換為空字符串,從而得到不包含轉義序列的純文本。
正則表達式的含義如下:
我們通過 re.findall() 方法來獲取所有匹配的結果,這樣夠清晰地捕獲所有符合條件的匹配項,從而更好地理解 ansiconv 是如何進行 ANSI 到純文本的轉換,代碼如下所示:
ansi = "\033[42m\033[31mHello World! --sidiot.\033[0m\033[0m"
print(re.findall(r'\x1B[[0-9;]*[ABCDEFGHJKSTfmnsulh]', ansi))
運行結果:
to_html() 的源碼如下所示:
上述代碼將 ANSI 字符串分割成塊,并對每個塊調用 _block_to_html() 函數進行解析和轉換,同時還處理了 ANSI 命令 "A",模擬向上移動光標的行為。如果 replace_newline 為 True,則 HTML 字符串中的換行符 \n 將替換為 <br />\n 以保留 HTML 輸出中的換行符。
其中 _block_to_html() 的源碼如下所示:
上述代碼使用正則表達式匹配 ANSI 代碼,并根據匹配結果生成對應的 HTML 代碼。
正則表達式的含義:
我們可以通過運行源碼里的部分代碼來幫助理解,代碼如下所示:
text = ("\x1B[0;32;45msidiot\n"
"\033[42m\033[31mHello World! --sidiot.\033[0m\033[0m")
print(text)
blocks = text.split('\x1B')
print(blocks)
for block in blocks:
match = re.match(r'^[(?P<code>\d+(?:;\d+)*)?(?P<command>[Am])', block)
if match is not None:
print("\nmatch:", match, ", code:", match.group('code'), ", command:", match.group('command'))
運行結果:
實際應用
通過深入理解 ANSI 轉換思路和 ansiconv 源碼,我們可以為之前的 http.server 服務帶來全新的優化。
首先,將原先的 read_file() 方法進行優化,代碼如下所示:
def read_file(self, content_type, file_io):
try:
self.send_response(200)
self.send_header("Content-Type", f"{content_type}; charset=utf-8")
self.end_headers()
self.wfile.write(file_io)
except FileNotFoundError:
self.send_response(404)
self.send_header("Content-Type", "text/plain; charset=utf-8")
self.end_headers()
self.wfile.write(b'File not found!')
上述代碼通過接收 content_type 和 file_io 兩個參數,實現將自定義內容作為響應返回給客戶端。
然后修改請求路徑,使其能夠返回純文本和 HTML 兩種不同類型的內容,代碼如下所示:
def do_GET(self):
if self.check_client_address():
if self.path.startswith("/?plain="):
file = open(self.path[8:], 'rb').read()
plain = ansiconv.to_plain(file.decode('UTF-8'))
self.read_file("text/plain", plain.encode())
elif self.path.startswith("/?html="):
file = open(self.path[7:], 'rb').read()
conv = ansiconv.to_html(file.decode('UTF-8'))
css = ansiconv.base_css()
html = """
<html>
<head><style>{0}</style></head>
<body>
<pre class="ansi_fore ansi_back">{1}</pre>
</body>
</html>
""".format(css, conv)
print(html)
self.read_file("text/html", html.encode())
else:
super().do_GET()
這里要注意的是,需要設置 CSS 樣式,不然 class 類是無法進行渲染的。
純文本運行結果:
HTML 運行結果:
在本文中,我們探討了如何實現將 ANSI 字體在前端頁面進行彩色展示的方法。在前端頁面中直接顯示 ANSI 轉義序列是不起作用的,因為瀏覽器不會解析和處理這些轉義序列。
為了在前端頁面實現彩色展示,我們介紹了一種方法,即將 ANSI 轉義序列轉換為對應的 HTML 代碼。通過解析 ANSI 轉義序列并將其轉換為適當的 HTML 標簽和樣式,我們可以在前端頁面上還原彩色文本的效果。
在本文中,我們使用了 Python 中的 ansiconv 庫來實現 ANSI 轉換。該庫提供了 to_plain 和 to_html 兩個方法,分別用于將 ANSI 轉義序列轉換為純文本和 HTML 代碼。我們還展示了如何使用這些方法來轉換 ANSI 字符串,并在前端頁面上顯示轉換后的結果。
通過本文的介紹,讀者可以了解到如何在前端頁面實現彩色文本的展示,從而提升用戶體驗和可讀性。無論是在日志查看器、終端模擬器還是其他需要展示彩色文本的應用中,這種技術都能發揮重要作用。
以上就是 從終端到瀏覽器:實現 ANSI 字體在前端頁面的彩色展示 的所有內容了,希望本篇博文對大家有所幫助!歡迎大家持續關注我的博客,一起分享學習和成長的樂趣!?
作者:sidiot
鏈接:https://juejin.cn/post/7381820436274184202
于class和style我們并不陌生,這個在學習css的時候就是家常便飯了,操作元素的 class 列表和內聯樣式是數據綁定的一個常見需求。因為它們都是屬性,所以我們可以用 v-bind 處理它們,只需要通過表達式計算出字符串結果即可。不過,字符串拼接麻煩且易錯。因此,在將 v-bind 用于 class 和 style 時,Vue.js 做了專門的增強。表達式結果的類型除了字符串之外,還可以是對象或數組,所以本章將帶你了解vue中如何綁定class和style。
在Vue中,我們可以將DOM元素通過Vue的綁定機制,實現我們想要的樣式。接下來看style如何實現綁定。
我們先來看個簡單基礎的動畫案例,大家看案例代碼:
例7-1 Demo0701.html
<body>
<div id="app">
<h1 style="color:red">原生態的style樣式</h1>
</div>
</body>
<script>
let vm = new Vue({
el: '#app',
});
</script>
</html>
程序的運行結果如下:
圖 7- 1 直接在元素中加入style原生態樣式
通過例7-1中,我們可以像往常一樣直接在元素上添加行內樣式。
在Vue中有屬性綁定,我們也可以通過屬性綁定直接加行內樣式,如下代碼所示:
<div id="app">
<!-- 直接添加原生態的樣式 -->
<h1 style="color:red">原生態的style樣式</h1>
<!-- 通過vue屬性綁定直接添加 -->
<h1 :style="'color:red;font-size:60px;'">屬性綁定添加的style樣式</h1>
</div>
程序的運行結果如下:
圖 7- 2 通過屬性綁定添加樣式
通過以上示例代碼,我們發現通過屬性綁定和直接添加非常像,但是大家認真觀察,屬性綁定必須加雙重引號(雙中加單,單中加雙),這是因為,如果不對樣式規則加引號,則默認會去Vue實例中尋找對應的數據,但是這些并不是Vue實例中的數據而是我們自己頂的規則,所以必須加引號。
剛才使用屬性綁定的是一個普通的文本,一般在Vue中,屬性綁定的是data中的數據,我們來看如下代碼:
<body>
<div id="app">
<!-- 直接添加原生態的樣式 -->
<h1 style="color:red">原生態的style樣式</h1>
<!-- 通過vue屬性綁定直接添加 -->
<h1 :style="'color:red;font-size:60px;background-color:pink;'">屬性綁定添加的style樣式</h1>
<!-- 通過vue的數據對象添加樣式 -->
<h1 :style="styleObj"> 通過vue的數據對象添加樣式</h1>
</div>
</body>
<script>
let vm = new Vue({
el: '#app',
data: {
styleObj: "color:green;font-size:80px;background-color:red;",
}
});
</script>
屬性綁定的是data中一個數據,該數據寫的是樣式規則。
也可以綁定一個對象,在該對象中使用js語法控制樣式規則:
示例如下:
<body>
<div id="app">
<!-- 直接添加原生態的樣式 -->
<h1 style="color:red">原生態的style樣式</h1>
<!-- 通過vue屬性綁定直接添加 -->
<h1 :style="'color:red;font-size:60px;background-color:pink;'">屬性綁定添加的style樣式</h1>
<!-- 通過vue的數據對象添加樣式 -->
<h1 :style="styleObj"> 通過vue的數據對象添加樣式</h1>
<!-- 通過vue的對象添加樣式 -->
<h1 :style="testObj"> 通過vue的數據對象添加樣式</h1>
</div>
</body>
<script>
let vm = new Vue({
el: '#app',
data: {
styleObj: "color:green;font-size:80px;background-color:red;",
testObj: {
color: 'blue',//注意是對象,對象的各個屬性之間使用逗號隔開,而不是分號
fontSize: '90px',
backgroundColor: 'pink'
}
}
});
</script>
注意:在該示例代碼中,我們使用的是一個object類型的數據來定義樣式,在這必須使用JavaScript操控style的語法規則來定義對象的各個屬性,而且屬性值必須是字符串,使用引號括起來。
在正常的樣式中,一個DOM元素可以同時應用多個樣式規則,在Vue中,也可以綁定到一個數組對像,同時應用多個樣式規則,示例代碼如下:
<body>
<div id="app">
<!-- 直接添加原生態的樣式 -->
<h1 style="color:red">原生態的style樣式</h1>
<!-- 通過vue屬性綁定直接添加 -->
<h1 :style="'color:red;font-size:60px;background-color:pink;'">屬性綁定添加的style樣式</h1>
<!-- 通過vue的數據對象添加樣式 -->
<h1 :style="styleObj"> 通過vue的數據對象添加樣式</h1>
<!-- 通過vue的對象添加樣式 -->
<h1 :style="testObj"> 通過vue的數據對象添加樣式</h1>
<!-- 綁定數組 -->
<h1 :style="[styleObj,testObj]"> 綁定數組</h1>
</div>
</body>
<script>
let vm = new Vue({
el: '#app',
data: {
styleObj: "color:green;font-size:80px;background-color:red;",
testObj: {
color: 'blue',//注意是對象,對象的各個屬性之間使用逗號隔開,而不是分號
fontSize: '90px',
backgroundColor: 'pink'
}
}
});
</script>
在綁定數組的時候,數組中的元素來源于data中的數據,所以不需要加引號。
除了進行style行內樣式的綁定,也可以進行class樣式的綁定。
可以直接在DOM元素上加入class屬性,進行定義樣式,示例如下:
<style>
.red {
color: red;
}
.bk {
background-color: pink;
}
</style>
</head>
<body>
<div id="app">
<!-- 直接添加原生態的樣式 -->
<h1 class="red bk">直接添加class樣式</h1>
</div>
</body>
<script>
let vm = new Vue({
el: '#app',
data: {
}
});
</script>
直接在DOM元素上通過class屬性添加多個class樣式規則,注意此時class沒有加冒號。
通過Vue的屬性綁定,示例代碼如下:
<style>
.red {
color: red;
}
.bk {
background-color: pink;
}
</style>
</head>
<body>
<div id="app">
<!-- 直接添加原生態的樣式 -->
<h1 class="red bk">直接添加class樣式</h1>
<!-- 屬性綁定添加 -->
<h1 :class="'red bk'">屬性綁定添加</h1>
</div>
</body>
<script>
let vm = new Vue({
el: '#app',
data: {
}
});
</script>
程序運行結果如下:
圖 7- 3 原生態和屬性綁定添加
通過屬性綁定添加,后面的class類名要加引號,否則會去data中尋找red和bk這樣的變量,如果加了,則是直接引用這兩個class樣式。
現在我們讓屬性綁定一個data中的數據對象,示例代碼如下:
<style>
.red {
color: red;
}
.bk {
background-color: pink;
}
.fs {
font-size: larger;
font-style: italic;
}
</style>
</head>
<body>
<div id="app">
<!-- 直接添加原生態的樣式 -->
<h1 class="red bk">直接添加class樣式</h1>
<!-- 屬性綁定添加 -->
<h1 :class="'red bk'">屬性綁定添加</h1>
<!-- 對象綁定 -->
<h1 :class="clsobj">對象綁定</h1>
</div>
</body>
<script>
let vm = new Vue({
el: '#app',
data: {
flag: true,
clsobj: {
'red': true,
'bk': true,
'fs': true
}
}
});
</script>
程序運行結果如下:
圖 7- 4 使用Vue對象綁定
使用對象的時候,對象的屬性就是class的類名,值是Boolean類型,如果是true則是啟用這個類樣式,否則則是不使用。
style樣式代碼:
<style>
.red {
color: red;
}
.thin {
font-weight: 200;
}
.italic {
font-style: italic;
}
.active {
letter-spacing: 0.5em;
}
</style>
示例代碼:
<!-- 第一種使用方式,直接傳遞一個數組,注意: 這里的 class 需要使用 v-bind 做數據綁定 -->
<h2 :class="['red','thin']">這是一個很大很大的H2,大到你無法想象!!!</h2>
數組中的元素必須加引號,否則會去Vue實例中尋找對應的變量數據。
示例代碼:
<!-- 第二種使用方式,在數組中使用三元表達式 -->
<h3 :class="['thin', 'italic', flag?'active':'']">這是一個很大很大的H3,大到你無法想象!!!</h3>
這上面使用的data中的數據flag,如果flag為true,則使用active這個class樣式,否則是空樣式。
示例代碼:
<!-- 第三種使用方式,在數組中使用 對象來代替三元表達式,提高代碼的可讀性 -->
<h4 :class="['thin', 'italic', {active:flag} ]">這是一個很大很大的H4,大到你無法想象!!!</h4>
上述示例數組中第三個元素是個對象,對象的鍵是style中已知存在的類名,值是boolean類型,如果為true,則采用該類樣式,否則則不采用。
總結:設置class樣式需要使用v-bind綁定;
1、使用[]設置樣式,中括號里的樣式必須加引號,否則被識別為變量;
2、可以使用對象表示class樣式,鍵是類名,值是Boolean類型
3、使用對象類名可以不加引號,但是為了區別變量,建議加上;
當在一個自定義組件上使用 class property 時,這些 class 將被添加到該組件的根元素上面。這個元素上已經存在的 class 不會被覆蓋。
例如,如果你聲明了這個組件:
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
然后在使用它的時候添加一些 class:
<my-component class="baz boo"></my-component>
HTML 將被渲染為:
<p class="foo bar baz boo">Hi</p>
對于帶數據綁定 class 也同樣適用:
<my-component v-bind:class="{ active: isActive }"></my-component>
當 isActive 為 true時,HTML 將被渲染成為:
<p class="foo bar active">Hi</p>
給第六章的綜合案例,使用本章的知識添加樣式。
小節基本要求:
要點:
1、樣式選擇器class
在實際開發網站中,css規定了兩種選擇器,一種是樣式選擇器class,一種是Id選擇器#。
class翻譯過來有種類的意思,中國人將它弄得好聽點兒,就叫樣式。
描出一個網站輪廓,利用樣式選擇器就夠了。
一個很簡單的例子:
1、寫一個html文件,在body里面寫一個div標簽,標簽中文本寫上一段字:hello world!
2、右鍵run,運行一下看效果
3、我想給這個div標簽變成一個框我該怎么做?很簡單,如下,給div標簽加一個樣式選擇器。
要點:
4、右鍵run,運行一下
可以看到,并沒有框!
為什么沒有?
那是因為我雖然定義了這個名叫haha的css選擇器,但是還沒有去編寫它的代碼!
5、為了省事,我就不新建一個css文件了,直接在這個html文件中寫css代碼。
要點:
注意寫法,我用了一個標識符 . , 這個點是一個標志,標志什么?標志這個haha 就是一個樣式選擇器。
解釋一下haha選擇器中代碼的含義:
boder:1px solid red; 設置邊框:尺寸為1,單線,顏色為紅色。
height:300px; 設置高度為300px
width:300px; 設置寬度為300px
6、右鍵run,運行一下
現在div標簽的邊框就出來了!與在.haha選擇器中規定的樣式是一樣的,紅色邊框,尺寸為1
樣式選擇器class是css文件中的常客,作用是來為html標簽設置顏色、寬高、位置等。
用法:
第一步,在標簽內這樣寫
<div class="hehe">你好</div>
第二步,在css文件中定義我們設置的這個class選擇器,hehe
.hehe{
border:1px solid red; 設置邊框樣式
height:300px; 設置邊框高度
width:300px; 設置邊框寬度
}
下一篇會將上一篇寫好的網站body邊框給描繪出來!
*請認真填寫需求信息,我們會在24小時內與您取得聯系。