【項目實戰】從終端到浏覽器:實現ANSI字體在前端頁面的彩色展示

互聯架構唠唠嗑 2024-06-27 17:58:41
前言

在學習和工作中,我們經常需要使用日志來記錄程序的運行狀態和調試信息。而爲了更好地區分不同的日志等級,我們可以使用不同的顔色來呈現,使其更加醒目和易于閱讀。

在下圖運行結果中,我們使用了 colorlog 庫來實現彩色日志輸出。通過定義不同日志等級對應的顔色,我們可以在控制台中以彩色的方式顯示日志信息。例如,DEBUG 級別的日志使用白色,INFO 級別的日志使用綠色,WARNING 級別的日志使用黃色,ERROR 級別的日志使用紅色,CRITICAL 級別的日志使用藍色。

但是在查看日志文件時,我們會發現日志信息是系統默認的字體顔色,並且前後多了一些特殊符號,例如 [32m 等。這是因爲在控制台中使用的是 ANSI 轉義序列來實現彩色文本效果,而這些特殊符號是 ANSI 轉義序列的一部分。如下圖所示:

現在有一個需求,在前端頁面直接查看日志內容並還原彩色文本效果,因此,我們將進行以下內容講解:

什麽是 ANSI 轉義序列?如何在前端頁面直接查看日志內容?如何在前端頁面還原彩色文本效果?

本文代碼點擊此處跳轉,往期系列文章請訪問博主的 項目實戰專欄,博文中的所有代碼全部收集在博主的 GitHub 倉庫中;

ANSI 轉義序列

ANSI 轉義序列是美國國家標准化組織(American National Standards Institute,ANSI)制定的標准,是一種用于控制文本終端顯示的特殊字符序列。它們以 \033[ 開頭,以字母和數字組合的形式表示不同的控制功能。

ANSI 轉義序列可以用于控制文本的顔色、背景色、文本樣式(如粗體、斜體等)、光標位置、清屏等操作。通過在輸出文本中插入適當的 ANSI 轉義序列,可以實現豐富的終端顯示效果。

以下是一些常用的 ANSI 轉義序列示例:

\033[0m:重置所有屬性,恢複默認設置;\033[31m:設置文本顔色爲紅色;\033[42m:設置背景顔色爲綠色;\033[1m:設置文本爲粗體;\033[4m:設置文本爲下劃線;\033[2J:清屏;

需要注意的是,ANSI 轉義序列在不同的終端和操作系統上的支持程度可能會有所不同。在某些終端中,可能無法正確解釋和顯示 ANSI 轉義序列。

我們以 \033[31m 和 \033[42m 爲例,輸出一個綠底紅字的句子 Hello World! --sidiot.,代碼如下所示:

log.debug("\033[42m\033[31mHello World! --sidiot.\033[0m\033[0m")

運行結果:

前端頁面直接查看日志內容

這裏的話,我們使用 Python 的 http.server 模塊來啓動一個簡單的 HTTP 服務器。

比較快捷的方式就是在日志文件夾中打開終端,輸入 python -m http.server 8888 即可,運行結果如下所示:

不過這種方式相對來說還是不太安全的,因此我們可以通過設置白名單的方式,來規避一些潛在的安全隱患,代碼如下所示:

import http.serverimport socketserverclass 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_textif __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 和 \033 都代 表ASCII 碼中的 Escape 字符,用于開始一個轉義序列。

運行結果:

使用 ansiconv 轉換

接下來,我們借助已有的庫函數 ansiconv 進行 ANSI 的轉換。

通過 pip 進行安裝:

pip install ansiconv

根據 ansiconv 的官方文檔使用其中的三個方法 to_plain(),to_html() 和 base_css() 來實現在前端頁面展示 ANSI 字體的彩色效果,代碼如下所示:

import ansiconvansi = "\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 轉義序列,並將其替換爲空字符串,從而得到不包含轉義序列的純文本。

正則表達式的含義如下:

\x1B:匹配 ESCAPE 字符;\[:匹配左方括號;[0-9;]*:匹配零個或多個數字或分號;[ABCDEFGHJKSTfmnsulh]:匹配 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 代碼。

正則表達式的含義:

^:表示匹配字符串的開頭。\[:匹配左方括號 [。(?P<code>\d+(?:;\d+)*)?:這是一個命名捕獲組,用于匹配 ANSI 代碼中的數字部分。它由以下組成: \d+:匹配一個或多個數字。 (?:;\d+)*:這是一個非捕獲組,用于匹配分號 ; 和一個或多個數字的重複出現。(?: ... ) 表示非捕獲組,* 表示重複零次或多次。(?P<command>[Am]):這是另一個命名捕獲組,用于匹配 ANSI 代碼中的命令部分。它由以下組成: [Am]:匹配字符 A 或 m。

我們可以通過運行源碼裏的部分代碼來幫助理解,代碼如下所示:

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>這裏要注意的是,需要設置 CSS 樣式,不然 類是無法進行渲染的。

純文本運行結果:

HTML 運行結果:

後記

在本文中,我們探討了如何實現將 ANSI 字體在前端頁面進行彩色展示的方法。在前端頁面中直接顯示 ANSI 轉義序列是不起作用的,因爲浏覽器不會解析和處理這些轉義序列。

爲了在前端頁面實現彩色展示,我們介紹了一種方法,即將 ANSI 轉義序列轉換爲對應的 HTML 代碼。通過解析 ANSI 轉義序列並將其轉換爲適當的 HTML 標簽和樣式,我們可以在前端頁面上還原彩色文本的效果。

在本文中,我們使用了 Python 中的 ansiconv 庫來實現 ANSI 轉換。該庫提供了 to_plain 和 to_html 兩個方法,分別用于將 ANSI 轉義序列轉換爲純文本和 HTML 代碼。我們還展示了如何使用這些方法來轉換 ANSI 字符串,並在前端頁面上顯示轉換後的結果。

通過本文的介紹,讀者可以了解到如何在前端頁面實現彩色文本的展示,從而提升用戶體驗和可讀性。無論是在日志查看器、終端模擬器還是其他需要展示彩色文本的應用中,這種技術都能發揮重要作用。

以上就是 從終端到浏覽器:實現 ANSI 字體在前端頁面的彩色展示 的所有內容了,希望本篇博文對大家有所幫助!歡迎大家持續關注我的博客,一起分享學習和成長的樂趣!✨

作者:sidiot鏈接:https://juejin.cn/post/7381820436274184202

0 阅读:0

互聯架構唠唠嗑

簡介:感謝大家的關注