1. 准备工作 首先,我们需要下载前端的PDF预览JS框架PDF.js
,它是一个网页端的PDF文件解析和预览框架,下载网址为:http://mozilla.github.io/pdf.js/ 。 接着,本项目还用到了showdown.js
,该JS框架用于渲染Markdown文档。 用Python做后端,tornado为web框架,笔者使用的版本为5.1.1
。
2.项目代码 我们下载PDF.js
项目代码,并在/pdfjs/web
目录下新建files
文件夹,用于存放上传的文件。为了能够用PDF.js
实现PDF文件预览,需要切换至pdfjs文件夹,运行搭建文件服务器命令:
1 python -m http.server 8081
或者
1 python -m SimpleHTTPServer 8081
接着介绍HTML文件,index.html
是首页代码,主要实现文件上传功能,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <!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文件中的内容,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <!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
主要用于文档的上传和保存,并展示文档内容,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 import osimport loggingimport tracebackimport tornado.ioloopimport tornado.webfrom tornado import optionsfrom parse_file import *class UploadFileHandler (tornado.web.RequestHandler) : def get (self) : self.render('upload.html' ) def post (self) : upload_path = os.path.join(os.path.dirname(__file__), 'pdfjs/web/files' ) 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__" : options.parse_command_line() app = make_app() app.listen(8888 ) tornado.ioloop.IOLoop.current().start()
parse_file.py
用于解析各种格式的文档,并返回HTML展示的格式,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 import jsonimport base64import loggingimport tracebackfrom json import JSONDecodeErrordef parse_text_plain (text) : return "<html><head></head><body>%s</body></html>" % text.replace("\n" , "<br>" ) 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 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" )) def parse_python (text) : 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
3.实现方式 下面将进一步介绍各种格式实现预览的机制。
text/html: 如html文件等 html文件的MIMETYPE为text/html
,由于本项目采用HTML展示,因此对于text/html
的文档,直接返回其内容就可以了。 从Tornado的代码中我们可以看出,filename变量为文档名称,text为文档内容,bytes字符串。在前端展示的时候,我们返回其文档内容:
1 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
中的代码):
1 2 3 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
中的代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
中的代码):
1 2 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
中的代码):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def parse_python (text) : 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上的展示有很多中,笔者采用的方式为:
1 <img src ="data:image/png;base64,ABKAMNDKSJFHVCJSNVOIEJHVUEHVUV==" >
就是对图片读取后的字符串进行base64编码即可,因此实现代码如下(parse_file.py
中的代码):
1 2 3 4 5 import base64def 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
,代码如下:
1 2 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
,代码如下:
1 2 3 4 5 6 7 8 <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>