html jquery pdf预览插件 js pdf文件预览_js预览pdf


项目结构 本项目现在已支持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 http.server 8081

或者:

python -m SimpleHTTPServer 8081
python -m SimpleHTTPServer 8081

接着介绍HTML文件,index.html是首页代码,主要实现文件上传功能,代码如下:

文件上传    



    
    文件上传


文件上传

提交 页面如下(有点儿过于简单,还好本项目是注重文档预览功能):


html jquery pdf预览插件 js pdf文件预览_json预览_02

文件上传页面

markdown.html主要用于展示Markdown文件中的内容,代码如下:

Markdown文件展示                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;        }




    
    Markdown文件展示
    
    
        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;
        }

注意,我们在head部分引用了showdown.js的CDN地址,这样就不用下载该项目文件了。

最后是后端部分,采用Python的Tornado模块实现。tornado_file_receiver.py主要用于文档的上传和保存,并展示文档内容,完整代码如下:

# -*- coding: utf-8 -*-import osimport loggingimport tracebackimport tornado.ioloopimport tornado.webfrom tornado import optionsfrom 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", "
"))            except Exception:                logging.error(traceback.format_exc())                self.write('')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()
# -*- 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", "
"))
            except Exception:
                logging.error(traceback.format_exc())
                self.write('')


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 jsonimport base64import loggingimport tracebackfrom json import JSONDecodeError# 解析text/plain或者text/csv文件格式def parse_text_plain(text):    return "%s" % text.replace("\n", "
")# 解析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", "
").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", "
").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 '' % \           (mtype, str(base64.b64encode(text), "utf-8"))# 解析Python文件def parse_python(text):    # indent和换行    text = text.replace("\n", "
").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, '' % (color, word))    colors = ["peru"] * 7    punctuations = list("[](){}#")    for punctuation, color in zip(punctuations, colors):        text = text.replace(punctuation, '' % (color, punctuation))    html = "%s" % text    return 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 "%s" % text.replace("\n", "
")


# 解析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", "
").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", "
").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 '' % \
           (mtype, str(base64.b64encode(text), "utf-8"))


# 解析Python文件
def parse_python(text):
    # indent和换行
    text = text.replace("\n", "
").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, '' % (color, word))

    colors = ["peru"] * 7
    punctuations = list("[](){}#")
    for punctuation, color in zip(punctuations, colors):
        text = text.replace(punctuation, '' % (color, punctuation))

    html = "%s" % text

    return html


实现方式

下面将进一步介绍各种格式实现预览的机制。


text/html: 如html文件等

html文件的MIMETYPE为text/html,由于本项目采用HTML展示,因此对于text/html的文档,直接返回其内容就可以了。

从Tornado的代码中我们可以看出,filename变量为文档名称,text为文档内容,bytes字符串。在前端展示的时候,我们返回其文档内容:

self.write(str(text, encoding="utf-8"))
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 "%s" % text.replace("\n","
")
# 解析text/plain或者text/csv文件格式
def parse_text_plain(text):
    return "%s" % text.replace("\n","
")


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", "
").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", "
").replace(" ", " ")        except JSONDecodeError:            logging.error(traceback.format_exc())            return "JSON文件格式解析错误"        except Exception as err:            logging.error(traceback.format_exc())            return "未知错误: %s" % err
# 解析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", "
").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", "
").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)
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", "
").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, '' % (color, word))    colors = ["peru"] * 7    punctuations = list("[](){}#")    for punctuation, color in zip(punctuations, colors):        text = text.replace(punctuation, '' % (color, punctuation))    html = "%s" % text    return html
# 解析Python文件
def parse_python(text):
    # indent和换行
    text = text.replace("\n", "
").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, '' % (color, word))

    colors = ["peru"] * 7
    punctuations = list("[](){}#")
    for punctuation, color in zip(punctuations, colors):
        text = text.replace(punctuation, '' % (color, punctuation))

    html = "%s" % text

    return html

根据笔者的了解,其实有更好的Python脚本内容的预览方式,可以借助handout模块实现,这点笔者将会在后续加上。


image/*: 各种图片文件,比如jpg, png等

图片文件在HTML上的展示有很多中,笔者采用的方式为:

就是对图片读取后的字符串进行base64编码即可,因此实现代码如下(parse_file.py中的代码):

import base64# 解析image/*文件格式def parse_image(mtype, text):    return '' % \           (mtype, str(base64.b64encode(text), "utf-8"))
import base64
# 解析image/*文件格式
def parse_image(mtype, text):
    return '' % \
           (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"))
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,代码如下:

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;        }

        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;
        }


效果demo

下面将给出上述8中文档格式在本系统中的预览效果。

text/html: 如html文件等

上传文件为reponse.html,预览效果如下:


html jquery pdf预览插件 js pdf文件预览_js预览pdf_03

html文件预览示例


text/plain: txt/log文件等

上传文件为info.log,预览效果如下:


html jquery pdf预览插件 js pdf文件预览_html jquery pdf预览插件_04

info.log文件预览


text/csv: csv文件

上传文件为iris.csv,预览效果如下:


html jquery pdf预览插件 js pdf文件预览_js pdf预览_05

iris.csv文件预览


application/json: json文件

上传文件为test1.json,预览效果如下:


html jquery pdf预览插件 js pdf文件预览_json预览_06

test1.json文件预览


application/pdf: pdf文件

上传文件为,预览效果如下:


html jquery pdf预览插件 js pdf文件预览_js预览pdf_07

PDF文档预览


text/x-python: Python脚本文件

上传文件为test.py,预览效果如下:


html jquery pdf预览插件 js pdf文件预览_json文件预览_08

Python脚本预览


image/*: 各种图片文件,比如jpg, png等

上传图片为ffe3d40029eae71ccf8587e5dc21d58d.jpg,预览效果如下:


html jquery pdf预览插件 js pdf文件预览_js pdf预览_09

jpg图片预览


markdown文件
上传文件为Scrapy爬取动态网页.md,预览效果如下:


html jquery pdf预览插件 js pdf文件预览_html jquery pdf预览插件_10

markdown文件预览


文件上传记录

为了证明上面的预览确实是笔者已经实现的,而不是从哪搬来的图片,特放上程序运行记录以及files目录下的文件,如下:


html jquery pdf预览插件 js pdf文件预览_html jquery pdf预览插件_11

文件上传记录


html jquery pdf预览插件 js pdf文件预览_js pdf预览_12

上传文件


总结

本项目已经开源至Github,网址为

https://github.com/percent4/document_reviewer  本项目现在支持的文档格式还比较少,后续可以增加更好文档格式的支持,另外,现在的文档格式的预览有些可以做得更好,后续也会进行优化~

感谢大家阅读,希望读者多多批评指正~

参考文档

  1. PDF.js官方网址:http://mozilla.github.io/pdf.js/
  2. showdown.js官方网址:https://github.com/showdownjs/showdown
  3. 让你的站点也支持Markdown——showdown.js使用教程:https://www.jianshu.com/p/747d6f8dddb0


-END-