在许多 Web 应用中都需要预览文档的功能。而用户可能用不同的设备访问 Web 应用,可能是装有 Windows 系统的 PC 台式机,也有可能是 iOS 系统的 iPad。一般来说,要预览的文档通常是主流的 Office 文档,包括.doc、.ppt、.docx、.pptx 文档,也包括 Adobe 的.pdf 文档。
因此开发一个能够在不同客户端上使用的,支持主流 Office 文档类型的在线文档预览系统就显得非常重要。
实现基于 Web 的跨平台的在线预览功能,支持主流 Office 文档。另外我们的服务器是 Linux 系统,不能够安装 Microsoft Office 软件。
先大致地说一下我是如何实现的。为了实现这个功能,我用了下面的组件:
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 了。
我利用了 OpenOffice 服务将各种 Office 文档转换成了 PDF 文档。写的脚本是rake task脚本,这样我可以用rake命令运行此脚本。这个脚本作为一个守护进程运行,它不断地检查系统里是否有新的上传文件。如果有新上传的文件,则启动转换过程,步骤如下:
整个 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
首先在 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 上了。
这里有详细说明 http://www.if-not-true-then-false.com/2010/install-openoffice-org-on-fedora-centos-red-hat-rhel/
这里有详细说明 http://stackoverflow.com/questions/11591643/failed-to-connect-to-openoffice-headless-mode
从 https://github.com/concordia-publishing-house/pdfjs-rails 下载 PyODConverter 脚本,放在lib/tasks/prevew
目录下面,然后我把它直接上传到我的代码库里。
我在方案选型的过程中研究了好几种方案,但是除了上面所描述的方案之外的其他所有方案都因为各种原因而不能满足要求。下面一一列举这些未选中的方案,以及解释为何它们满足不了要求。
FlexPaper 是一个商业软件,但是有采用 GPLv3 开源协议的免费版。然而,它仅支持 ASP.NET、PHP 和 Java,而且部署相对复杂。我的系统是基于 Linux 的,开发技术是 Ruby on Rails,因此不太容易和它整合在一起。
Accusoft Document Viewers 是一个商业软件,有一些很酷的 Demos,下面列举两个 Demo 的网址。
可是这个软件的价格太高,而且不是提供一个组件,而是提供一套解决方案,对于我们的目标来说,这些解决方案太“重”了。
Google 的文档预览服务 (Google Docs Viewer) 真心很不错,这里有个应用实例,但是最大的问题是经常被墙,无法稳定地工作。
根据一个 StackOverflow 上的解答,有一个叫 CUPS-PDF 的软件可以安装在 Linux 系列(包括 Mac OS X)的操作系统上。该软件能够为系统添加一个 PDF 打印机。它是另一种文档转 PDF 的方法,通过安装 PDF 打印机将文档“打印”成 PDF 文档。用 OpenOffice 的命令行工具可以打印一个文档,并指定要使用的打印机。只要指定使用 CUPS-PDF 的打印机,就能够输出一个 PDF 文件到文件系统中的一个位置上。
这种方法可以很精确地将文档转换成 PDF 文档,可以说是“所打印即所得”。但是转换后的 PDF 文档都非常的大。比如一个 0.5M 的 Word 文档,用这种方法转换后的 PDF 竟然要 300 多 M。因此这种方法并不可行。