在平時的工作或學習中,我們經常會接觸不同格式的文檔類型,比如txt,log,Offices文檔,編程代碼腳本,圖片,視頻等。本文將會介紹筆者的一個朴素想法,即把不同格式的文檔都放在同一個平台中進行預覽,這樣既方便查看常見文檔,又能提升工作和學習效率。
本項目的工程結構如下:
本項目現在已支持8種文檔格式的格式,分別為:
- text/html: 如html文件等;
- text/plain: 如txt/log文件等;
- text/csv: csv文件;
- application/json: json文件;
- application/pdf: pdf文件;
- text/x-python: Python腳本文件;
- image/*: 各種圖片文件,比如jpg, png等;
- markdown文件
准備工作
首先,我們需要下載前端的PDF預覽JS框架PDF.js
,它是一個網頁端的PDF文件解析和預覽框架,下載網址為:http://mozilla.github.io/pdf.js/ 。
接着,本項目還用到了showdown.js
,該JS框架用於渲染Markdown文檔。
用Python做后端,tornado為web框架,筆者使用的版本為5.1.1
。
項目代碼
我們下載PDF.js
項目代碼,並在/pdfjs/web
目錄下新建files
文件夾,用於存放上傳的文件。為了能夠用PDF.js
實現PDF文件預覽,需要切換至pdfjs文件夾,運行搭建文件服務器命令:
python -m http.server 8081
或者:
python -m SimpleHTTPServer 8081
接着介紹HTML文件,index.html
是首頁代碼,主要實現文件上傳功能,代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>文件上傳</title>
</head>
<body>
<div align="center">
<br><br>
<h1>文件上傳</h1>
<form action='file' enctype="multipart/form-data" method='post'>
<div class="am-form-group am-form-file">
<input id="doc-form-file" type="file" name="file" multiple>
</div>
<div id="file-list"></div>
<p>
<button type="submit" class="am-btn am-btn-default">提交</button>
</p>
</form>
</div>
</body>
</html>
頁面如下(有點兒過於簡單,還好本項目是注重文檔預覽功能):
markdown.html
主要用於展示Markdown文件中的內容,代碼如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Markdown文件展示</title>
<script src="https://cdn.bootcss.com/showdown/1.9.0/showdown.min.js"></script>
<script>
function convert(){
var converter = new showdown.Converter();
var text = "{{ md_content }}";
var html = converter.makeHtml(text.replace(/newline/g, "\n"));
document.getElementById("result").innerHTML = html;
}
</script>
</head>
<body onload="convert()">
<div id="result" ></div>
</body>
</html>
注意,我們在head部分引用了showdown.js
的CDN地址,這樣就不用下載該項目文件了。
最后是后端部分,采用Python的Tornado模塊實現。tornado_file_receiver.py
主要用於文檔的上傳和保存,並展示文檔內容,完整代碼如下:
# -*- coding: utf-8 -*-
import os
import logging
import traceback
import tornado.ioloop
import tornado.web
from tornado import options
from parse_file import *
# 文檔上傳與解析
class UploadFileHandler(tornado.web.RequestHandler):
# get函數
def get(self):
self.render('upload.html')
def post(self):
# 文件的存放路徑
upload_path = os.path.join(os.path.dirname(__file__), 'pdfjs/web/files')
# 提取表單中‘name’為‘file’的文件元數據
# 暫時只支持單文檔的上傳
file_meta = self.request.files['file'][0]
filename = file_meta['filename']
# 保存文件
with open(os.path.join(upload_path, filename), 'wb') as up:
up.write(file_meta['body'])
text = file_meta["body"]
# 解析文件的內容
mtype = file_meta["content_type"]
logging.info('POST "%s" "%s" %d bytes', filename, mtype, len(text))
if mtype in ["text/x-python", "text/x-python-script"]:
self.write(parse_python(str(text, encoding="utf-8")))
elif mtype in ["text/plain", "text/csv"]:
self.write(parse_text_plain(str(text, encoding="utf-8")))
elif mtype == "text/html":
self.write(str(text, encoding="utf-8"))
elif mtype.startswith("image"):
self.write(parse_image(mtype, text))
elif mtype == "application/json":
self.write(parse_application_json(str(text, encoding="utf-8")))
elif mtype == "application/pdf":
self.redirect("http://127.0.0.1:8081/web/viewer.html?file=files/%s" % filename)
elif mtype == "application/octet-stream" and filename.endswith(".md"):
self.render("markdown.html", md_content=r"%s" % str(text, encoding="utf-8").replace("\n", "newline"))
else: # 其余文件格式
try:
self.write(str(text, encoding="utf-8").replace("\n", "<br>"))
except Exception:
logging.error(traceback.format_exc())
self.write('<font color=red>系統不支持的文件解析格式!</font>')
def make_app():
return tornado.web.Application([(r"/file", UploadFileHandler)],
template_path=os.path.join(os.path.dirname(__file__), "templates")) # 模板路徑
if __name__ == "__main__":
# Tornado configures logging.
options.parse_command_line()
app = make_app()
app.listen(8888)
tornado.ioloop.IOLoop.current().start()
parse_file.py
用於解析各種格式的文檔,並返回HTML展示的格式,完整代碼如下:
# -*- coding: utf-8 -*-
# author: Jclian91
# place: Pudong Shanghai
# time: 2020/6/5 1:05 下午
# filename: parse_file.py
# 用於解析各種文件類型的數據
import json
import base64
import logging
import traceback
from json import JSONDecodeError
# 解析text/plain或者text/csv文件格式
def parse_text_plain(text):
return "<html><head></head><body>%s</body></html>" % text.replace("\n", "<br>")
# 解析application/json文件格式
def parse_application_json(text):
try:
data_dict = json.loads(text)
return json.dumps(data_dict, ensure_ascii=False, indent=2).replace("\n", "<br>").replace(" ", " ")
except JSONDecodeError:
try:
data_list = [json.loads(_) for _ in text.split("\n") if _]
return json.dumps(data_list, ensure_ascii=False, indent=2).replace("\n", "<br>").replace(" ", " ")
except JSONDecodeError:
logging.error(traceback.format_exc())
return "JSON文件格式解析錯誤"
except Exception as err:
logging.error(traceback.format_exc())
return "未知錯誤: %s" % err
# 解析image/*文件格式
def parse_image(mtype, text):
return '<html><head></head><body><img src="data:%s;base64,%s"></body></html>' % \
(mtype, str(base64.b64encode(text), "utf-8"))
# 解析Python文件
def parse_python(text):
# indent和換行
text = text.replace("\n", "<br>").replace(" ", " ").replace("\t", " " * 4)
# 關鍵字配色
color_list = ["gray", "red", "green", "blue", "orange", "purple", "pink", "brown", "wheat", "seagreen", "orchid", "olive"]
key_words = ["self", "from", "import", "def", ":", "return", "open", "class", "try", "except", '"', "print"]
for word, color in zip(key_words, color_list):
text = text.replace(word, '<font color=%s>%s</font>' % (color, word))
colors = ["peru"] * 7
punctuations = list("[](){}#")
for punctuation, color in zip(punctuations, colors):
text = text.replace(punctuation, '<font color=%s>%s</font>' % (color, punctuation))
html = "<html><head></head><body>%s</body></html>" % text
return html
實現方式
下面將進一步介紹各種格式實現預覽的機制。
text/html: 如html文件等
html文件的MIMETYPE為text/html
,由於本項目采用HTML展示,因此對於text/html
的文檔,直接返回其內容就可以了。
從Tornado的代碼中我們可以看出,filename變量為文檔名稱,text為文檔內容,bytes字符串。在前端展示的時候,我們返回其文檔內容:
self.write(str(text, encoding="utf-8"))
其中,str(text, encoding="utf-8")
是將bytes字符串轉化為UTF-8編碼的字符串。
text/plain: txt/log文件等
txt/log等文件的MIMETYPE為text/plain
,其與HTML文檔的不同之處在於,如果需要前端展示,需要在返回的字符中添加HTML代碼,如下(parse_file.py
中的代碼):
# 解析text/plain或者text/csv文件格式
def parse_text_plain(text):
return "<html><head></head><body>%s</body></html>" % text.replace("\n", "<br>")
text/csv: csv文件
csv格式文件的MIMETYPE為text/csv
,其預覽的方式與txt/log等格式的文檔一致。
但csv是逗號分隔文件,數據格式是表格形式,因此在前端展示上應該有更好的效果。關於這一格式的文檔,其前端預覽的更好方式可以參考文章: 利用tornado實現表格文件預覽 。
application/json: json文件
關於json文件的預覽,筆者更關注的是json文件的讀取。這里處理兩種情況,一種是整個json文件就是json字符串,另一種情況是json文件的每一行都是json字符串。在前端展示的時候,采用json.dumps中的indent參數實現縮進,並轉化為html中的空格,實現方式如下(parse_file.py
中的代碼):
# 解析application/json文件格式
def parse_application_json(text):
try:
data_dict = json.loads(text)
return json.dumps(data_dict, ensure_ascii=False, indent=2).replace("\n", "<br>").replace(" ", " ")
except JSONDecodeError:
try:
data_list = [json.loads(_) for _ in text.split("\n") if _]
return json.dumps(data_list, ensure_ascii=False, indent=2).replace("\n", "<br>").replace(" ", " ")
except JSONDecodeError:
logging.error(traceback.format_exc())
return "JSON文件格式解析錯誤"
except Exception as err:
logging.error(traceback.format_exc())
return "未知錯誤: %s" % err
筆者相信一定有json文件更好的前端展示方式,這里沒有采用專門處理json的JS框架,這以后作為后續的改進措施。
application/pdf: pdf文件
PDF文檔的展示略顯復雜,本項目借助了PDF.js
的幫助,我們需要它來搭建PDF預覽服務,這點在上面的項目代碼
部分的開頭已經講了。
搭建好PDF預覽服務后,由於上傳的文件都會進入pdfjs/web/files
目錄下,因此PDF文檔預覽的網址為:http://127.0.0.1:8081/web/viewer.html?file=files/pdf_name ,其中pdf_name為上傳的PDF文檔名稱。
有了這個PDF預覽服務后,我們展示PDF文檔的代碼就很簡單了(tornado_file_receiver.py
中的代碼):
elif mtype == "application/pdf":
self.redirect("http://127.0.0.1:8081/web/viewer.html?file=files/%s" % filename)
text/x-python: Python腳本文件
Python腳本的處理方式並不復雜,無非是在把Python文檔轉化為HTML文件格式的時候,加入縮進、換行處理,以及對特定的Python關鍵字進行配色,因此代碼如下(parse_file.py
中的代碼):
# 解析Python文件
def parse_python(text):
# indent和換行
text = text.replace("\n", "<br>").replace(" ", " ").replace("\t", " " * 4)
# 關鍵字配色
color_list = ["gray", "red", "green", "blue", "orange", "purple", "pink", "brown", "wheat", "seagreen", "orchid", "olive"]
key_words = ["self", "from", "import", "def", ":", "return", "open", "class", "try", "except", '"', "print"]
for word, color in zip(key_words, color_list):
text = text.replace(word, '<font color=%s>%s</font>' % (color, word))
colors = ["peru"] * 7
punctuations = list("[](){}#")
for punctuation, color in zip(punctuations, colors):
text = text.replace(punctuation, '<font color=%s>%s</font>' % (color, punctuation))
html = "<html><head></head><body>%s</body></html>" % text
return html
根據筆者的了解,其實有更好的Python腳本內容的預覽方式,可以借助handout
模塊實現,這點筆者將會在后續加上。
image/*: 各種圖片文件,比如jpg, png等
圖片文件在HTML上的展示有很多中,筆者采用的方式為:
<img src="data:image/png;base64,ABKAMNDKSJFHVCJSNVOIEJHVUEHVUV==">
就是對圖片讀取后的字符串進行base64編碼即可,因此實現代碼如下(parse_file.py
中的代碼):
import base64
# 解析image/*文件格式
def parse_image(mtype, text):
return '<html><head></head><body><img src="data:%s;base64,%s"></body></html>' % \
(mtype, str(base64.b64encode(text), "utf-8"))
markdown文件
markdown文件的預覽稍顯復雜,借助showdown.js
和不斷的嘗試探索,由於markdown在讀取后的換行符\n
在轉化為JavaScript字符串時並不需要轉義,這是實現預覽的難點。筆者的做法是把Python讀取的markdown中的換行符\n
轉化為newline
,並在JS渲染的時候才把newline
替換成\n
,這就解決了不需要轉移的難題。具體的實現可以參考markdown.html
,現在Python后端代碼中把Python讀取的markdown中的換行符\n
轉化為newline
,代碼如下:
elif mtype == "application/octet-stream" and filename.endswith(".md"):
self.render("markdown.html", md_content=r"%s" % str(text, encoding="utf-8").replace("\n", "newline"))
接着在markdown.html
中的JS部分把Python讀取的markdown中的換行符\n
轉化為newline
,代碼如下:
<script>
function convert(){
var converter = new showdown.Converter();
var text = "{{ md_content }}";
var html = converter.makeHtml(text.replace(/newline/g, "\n"));
document.getElementById("result").innerHTML = html;
}
</script>
效果demo
下面將給出上述8中文檔格式在本系統中的預覽效果。
text/html: 如html文件等
上傳文件為reponse.html
,預覽效果如下:
text/plain: txt/log文件等
上傳文件為info.log,預覽效果如下:
text/csv: csv文件
上傳文件為iris.csv,預覽效果如下:
application/json: json文件
上傳文件為test1.json,預覽效果如下:
application/pdf: pdf文件
上傳文件為,預覽效果如下:
text/x-python: Python腳本文件
上傳文件為test.py,預覽效果如下:
image/*: 各種圖片文件,比如jpg, png等
上傳圖片為ffe3d40029eae71ccf8587e5dc21d58d.jpg,預覽效果如下:
markdown文件
上傳文件為Scrapy爬取動態網頁.md,預覽效果如下:
文件上傳記錄
為了證明上面的預覽確實是筆者已經實現的,而不是從哪搬來的圖片,特放上程序運行記錄以及files目錄下的文件,如下:
總結
本項目已經開源至Github,網址為https://github.com/percent4/document_reviewer 。
本項目現在支持的文檔格式還比較少,后續可以增加更好文檔格式的支持,另外,現在的文檔格式的預覽有些可以做得更好,后續也會進行優化~
感謝大家閱讀,希望讀者多多批評指正~
參考文檔
- PDF.js官方網址:http://mozilla.github.io/pdf.js/
- showdown.js官方網址:https://github.com/showdownjs/showdown
- 讓你的站點也支持Markdown——showdown.js使用教程: https://www.jianshu.com/p/747d6f8dddb0