前言
在许多Web应用中都需要预览文档的功能。而用户可能用不同的设备访问Web应用,可能是装有Windows系统的PC台式机,也有可能是iOS系统的iPad。一般来说,要预览的文档通常是主流的Office文档,包括.doc、.ppt、.docx、.pptx文档,也包括Adobe的.pdf文档。
因此开发一个能够在不同客户端上使用的,支持主流Office文档类型的在线文档预览系统就显得非常重要。
目标
实现基于Web的跨平台的在线预览功能,支持主流Office文档。另外我们的服务器是Linux系统,不能够安装Microsoft Office软件。
我的解决方案
先大致地说一下我是如何实现的。为了实现这个功能,我用了下面的组件:
- PDF.js
- OpenOffice
- PyODConverter
PDF.js 是Mozilla实验室开发的关于HTML5技术的一个开源项目,用于在网页上显示PDF文档,而不需要任何浏览器插件等原生代码。目前这个开源项目放在了GitHub上,网址是https://github.com/andreasgal/pdf.js
演示地址:http://mozilla.github.io/pdf.js/web/viewer.html
OpenOffice是Apache开源软件基金会开发的一款开源软件,是可以在Linux上运行的Office软件。OpenOffice可以打开主流的Microsoft Office的各种文档,例如.doc,.docx,.ppt,.pptx格式的文档。OpenOffice有一个文档转换服务,这个服务是以网络服务的形式运行的。启动这个服务,我就可以在任何类型的代码中通过端口调用这个服务。
PyODConverter是一个Python脚本,用于自动化的文档格式转换,依赖于LibreOffice or OpenOffice。我用它把Office文档转成PDF文档。
PyODConverter代码托管在 https://github.com/jamayka/barberry-plugin-openoffice/tree/master/externals/pyodconverter 上。
实现过程
整个实现分为**“Office文档转PDF”和“PDF文档在Web端显示”**两部分。
,后端负责将各种Office文档通过OpenOffice服务转换成PDF文档,而前端通过pdf.js显示PDF的内容。由于是采用了pdf.js显示PDF文档,浏览器端不需要安装任何插件,但是需要支持部分HTML5的功能特性。因此旧浏览器例如IE8,IE7,IE6就不能够使用pdf.js了.
Office文档转PDF
我利用了OpenOffice服务将各种Office文档转换成了PDF文档。写的脚本是rake task脚本,这样我可以用rake命令运行此脚本。这个脚本作为一个守护进程运行,它不断地检查系统里是否有新的上传文件。如果有新上传的文件,则启动转换过程,步骤如下:
- 启动OpenOffice的服务
- 检查是否有新的Office文档上传
- 调用PyODConverter转换文档
- 更新Model上的字段,指向生成的用于预览的PDF
- 重复步骤2到4
整个rake task脚本的代码如下:
# -*- encoding : utf-8 -*-
PYTHON_PATH = '/opt/openoffice4/program/python'
CONVERTER_PATH = File.expand_path('../DocumentConverter.py', __FILE__)
def gen_preview(beDaemon = false)
begin
no_preview_materials = CoursewareMaterial.where(preview_file_name: nil).where(['doc', 'docx', 'ppt', 'pptx'].map {|x| "upload_file_name LIKE '%.#{x}'"}.join(' or '))
if no_preview_materials.size > 0
puts "Prepare to process #{no_preview_materials.size} items."
no_preview_materials.each do |material|
begin
generate_preview material
rescue
puts "[Exception] #{$!}".red
end
end
end
break if not beDaemon
sleep 30
end while true
end
def generate_preview material
puts "Generating preview PDF for [#{material.upload_file_name}]..."
if material.upload.path.to_s =~ /\.(doc|docx|ppt|pptx|pdf)$/
tmpPdfPath = File.join(Dir.tmpdir, File.basename(material.upload.path, '.*') + '.pdf')
# try to delete existing temporary file if it exists.
begin
File.delete tmpPdfPath
rescue
# do nothing
end
# relaunch openoffice if it quit.
if `pgrep soffice`.size == 0
puts 'Relaunch OpenOffice service.'
spawn 'soffice "-accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager" -norestore -nofirststartwizard -nologo -headless &'
end
out = nil
cmd = "#{PYTHON_PATH} #{CONVERTER_PATH} '#{material.upload.path}' '#{tmpPdfPath}'"
puts "Execute command: \n#{cmd}"
out = `#{cmd}`
if File.exists?(tmpPdfPath)
material.preview = File.open(tmpPdfPath, 'rb')
material.save
File.delete tmpPdfPath
puts "Preview PDF for [#{material.upload_file_name}] is generated.".green
else
puts "An error occured when invoke DocumentConverter.py. #{out}".red
end
else
puts "The material [#{material.upload_file_name}] is not available for generating preview PDF.".yellow
end
end
PDF文档在Web端显示
首先在asset pipeline中添加pdf.js,这一步可以通过引入pdfjs-rails这个Gem实现。然后在页面中引用需要的js和css资源即可,而这些资源包含在pdfjs-rails中。
Gem pdfjs-rails代码托管在https://github.com/concordia-publishing-house/pdfjs-rails中。
然后在view上添加前端代码。我用的是ERB,因此对应的文件是preview.html.erb。此文件中的代码如下所示:
<%= javascript_include_tag "compatibility" %>
<%= javascript_include_tag "l10n" %>
<%= javascript_include_tag "pdf" %>
<%= javascript_include_tag "pdfviewer" %>
<%= stylesheet_link_tag"viewer", :media => "all" %>
......
<div>
<% if not (pdf_url.nil? or pdf_url.empty?) %>
<b>内容预览:</b>
<%= pdf_viewer pdf_url %>
<% end %>
</div>
......
如以上代码所示,pdfjs_rails提供了用于自动生成HTML的pdf_viewer方法,简化了代码。当要显示一个文档时,还需要判断这个文档对应的PDF文档有没有生成,因此需要一个类似于if pdf_url.nil?
的代码。为了简单起见,我就直接把此代码写在view上了。
安装部署
安装OpenOffice到Linux CentOS
这里有详细说明 http://www.if-not-true-then-false.com/2010/install-openoffice-org-on-fedora-centos-red-hat-rhel/
以作为后台服务的方式运行OpenOffice
这里有详细说明 http://*.com/questions/11591643/failed-to-connect-to-openoffice-headless-mode
下载PyODConverter脚本
从 https://github.com/concordia-publishing-house/pdfjs-rails 下载PyODConverter脚本,放在lib/tasks/prevew
目录下面,然后我把它直接上传到我的代码库里。
演示
其它几种方案
我在方案选型的过程中研究了好几种方案,但是除了上面所描述的方案之外的其他所有方案都因为各种原因而不能满足要求。下面一一列举这些未选中的方案,以及解释为何它们满足不了要求。
FlexPaper
FlexPaper 是一个商业软件,但是有采用GPLv3开源协议的免费版。然而,它仅支持ASP.NET、PHP和Java,而且部署相对复杂。我的系统是基于Linux的,开发技术是Ruby on Rails,因此不太容易和它整合在一起。
Accusoft Document Viewers
Accusoft Document Viewers 是一个商业软件,有一些很酷的Demos,下面列举两个Demo的网址。
- http://prizmdemos.accusoft.com/flash.php
- http://prizmdemos.accusoft.com/html5.php
可是这个软件的价格太高,而且不是提供一个组件,而是提供一套解决方案,对于我们的目标来说,这些解决方案太“重”了。
Google的文档预览服务
Google的文档预览服务(Google Docs Viewer) 真心很不错,这里有个应用实例,但是最大的问题是经常被墙,无法稳定地工作。
CUPS-PDF
根据一个*上的解答,有一个叫CUPS-PDF的软件可以安装在Linux系列(包括Mac OS X)的操作系统上。该软件能够为系统添加一个PDF打印机。它是另一种文档转PDF的方法,通过安装PDF打印机将文档“打印”成PDF文档。用OpenOffice的命令行工具可以打印一个文档,并指定要使用的打印机。只要指定使用CUPS-PDF的打印机,就能够输出一个PDF文件到文件系统中的一个位置上。
这种方法可以很精确地将文档转换成PDF文档,可以说是“所打印即所得”。但是转换后的PDF文档都非常的大。比如一个0.5M的Word文档,用这种方法转换后的PDF竟然要300多M。因此这种方法并不可行。
Ahxing1985 发布了16 篇原创文章 · 获赞 1 · 访问量 9789 私信 关注