打印

[转帖] ASP.Net实现将Word转换PDF格式

本主题由 mwpq 于 2007-11-7 08:58 关闭

ASP.Net实现将Word转换PDF格式

前言:由于一个客户的项目中需要将WORD文档转换成PDF格式,故写了本篇实站教程
* ?/ D2 C# W! O% S1 y, R2 F5 J  需求分析:客户的项目以B/S结构为主,提供一个WORD文件在后台自动转换成PDF,经过实际测试,如果该篇WORD文档有100多页的话,转换需要20分钟左右的时间(环境:CPU是奔腾M 1.6G,512M内存),整个CPU的占用率近乎95%~100%,此结果告诉客户以后,客户提议:到客户下班后,自动转换PDF,同时如果使用人确认要查看该PDF文档,如果没有转换,提供给客户选择,是现在转换成PDF,还是由服务器在客户下班后,自动转换。
, {' y( K1 }, x$ ]* U: `8 q% y  项目功能:按需求分析要写两个功能! f( n @+ n e0 l/ s
  第一为:B/S结构后台转换,要提交给客户选择
, L8 L1 p/ s& C& c1 u; i6 Y6 ~- \" V6 m  第二为:Windows服务自动转换WORD文档到PDF
, f$ a% }! W$ `0 h" V; b- V0 T  这两个分类:核心的转换程序都是采用线程的方式执行,只不过第一个功能是针对一个WORD文件,第二个功能针对所有未转换的WORD文档.
8 M3 d7 h# k$ ~9 Q9 ?* @  分析到现在:我们开始实战转换了!4 T" }! Y! s$ e3 g
  一:必备工具+ u8 V: `! J K& \9 ~
  安装必须的工具MS VS.Net2003,MS Office2003,Adobe Acrobat 7.0 Professional,postscript.exe,gs811w32.exe
/ t, X, n8 X0 m+ V, L$ E  MS VS.Net2003的安装不说明/ O9 _- H8 U' d) c! a/ ?( H# P
  MS Office2003的安装不说明5 k5 i' `7 Y. E( z
  Adobe Acrobat 7.0 Professional安装说明: _9 R+ T/ Y( x9 ?
  运行setup.exe文件,出现输入序列号,就运行注册机,用鼠标在第一行刷下就可以看见序列号,复制粘贴到Adobe Acrobat 7.0 Professional安装程序对话框,安装到最后出现注册时,点击PHONE...将安装程序中显示的第二行序列号(第一行是刚才注册机生成的序列号)复制粘贴到注册机的第二行,点击右边的按钮,再用鼠标刷第三行授权号就出来了,将其复制粘贴到安装程序的最后一行,完成安装注册! ; N3 H5 u8 U# H5 {) d4 z
  postscript.exe默认安装就可以了,它是一个PDF转换时所需要的脚本
, ?6 X }, F% [+ t# E  gs811w32.exe默认安装就可以,它其实是个PDF虚拟打印机的驱动
* C8 T5 L( Z) c3 Z  二:配置虚拟打印机
# U$ i, |" N( z$ _  进入Windows的控制面板,进入打印机,点击"添加打印机"图标.在安装对话框上"按一步",出现选择打印机时,在制造商一栏中选择"Generic",在打印机一栏中,选择"MS Publisher Color Printer",然后一路按下一步,知道安装结束.
! B$ b5 M+ m2 S: U  三:开始写第一个程序(脚本程序)
- E j. _ ?" E5 D0 e  为什么要使用脚本程序进行转换呢,其实实际测试过程中,使用PDF Distiller的对象引用到C#后,转换成功,但整个PDF Distiller对象不能释放,第二次再转换时,就发生了错误,故此处使用脚本程序实现转换.这样我们只要在C#的程序中调用脚本程序就可以实现WORD到PDF的转换。
- Q( B3 T- x+ B. L  宿主脚本文件名:ConvertDoc2PDF.js
" z0 ?7 _- L) Q  脚本文件内容:
e$ t( \$ M3 ]! U. Svar files = WScript.Arguments;& Z2 l' r% x+ T. L7 D% V
var fso = new ActiveXObject("Scripting.FileSystemObject");
( X" n/ T9 b9 _' Rvar word = new ActiveXObject("Word.Application");6 U$ J+ z" L) T; T1 n4 H
var PDF = new ActiveXObject("PDFDistiller.PDFDistiller.1");
, q! [1 U: l" i4 z" x( d1 k4 ~word.ActivePrinter = "MS Publisher Color Printer";
6 l+ ^0 y& Y' v @" e//files(0) 为WORD文档文件名0 D, F" s. `7 t5 O! e& {9 D9 u) r
//files(1) 为,转换后需要保存的路径& |( c* |: v9 A/ H0 n
//调用fso.GetBaseName(files(0))后,为无路径,无扩展名,的文件名5 t, G3 Y, Z+ M8 _ f* C
//files.length为文件参数的个数,使用循环可以支持多个WORD文档的转换
, w+ A# D" }* I9 ~+ {var docfile = files(0);7 a9 Q7 {8 F, _. U
var psfile = files(1) + fso.GetBaseName(files(0)) + ".ps";3 N# V8 r' u; k7 ~% v# z
var pdffile = files(1) + fso.GetBaseName(files(0)) + ".pdf";
j6 {+ ~9 [6 _3 Jvar logfile = files(1) + fso.GetBaseName(files(0)) + ".log";
/ g- q2 |, a( @2 {: |try{
# f( J& P; v4 }; B0 i3 R' evar doc = word.Documents.Open(docfile); o2 N: n" g A% {5 N4 S
//WORD文件转成PS文件;
3 n1 c* `) Y- _' Q) Q2 g8 eword.PrintOut(false, false, 0, psfile);4 y7 X, a. _/ m5 c: E+ k
doc.Close(0);& y4 y8 _; n" Y, ~- k2 Z; \. r
//PS文件转成PDF文件;
2 ~$ @: A: Q# OPDF.FileToPDF(psfile,pdffile,"");
+ R; c+ w/ e4 sfso.GetFile(psfile).Delete();//删除PS脚本文件
. x0 _* X, c) N. N7 N$ s* @" |fso.GetFile(logfile).Delete();//删除转换的日志文件
: X- I' W/ u4 iword.Quit();$ k1 \5 ?8 y. U/ Z; L4 d
WScript.Echo("isuccess");//成功( j3 v9 t4 m# z- S* U1 n
WScript.Quit(0);) }# R. [8 n. _- u( l" \
}; @1 \ {' ~. g l. f6 ~
catch(x), |* e/ B+ u/ ^/ `' a
{
& v3 t1 R* r' V2 Hword.Quit();# N% h9 J4 H5 W0 Y- v( Q0 h6 T7 g
WScript.Echo("isfail");//失败. h! V+ b5 e K. E8 F+ `8 A* Z
WScript.Quit(0);
6 N: k: A. J9 M} : }& L: H8 D/ }9 s$ r, o. G
  然后测试该脚本程序9 @( f- U. X7 Q0 i1 f
  启动MS-DOS,输入如下命令:
$ c4 t( g9 H0 V" W, mc:\>cscript //nologo c:\ConvertDoc2PDF.js c:\test.doc c:\
# X0 S$ M( W' i8 F, v4 c% l  说明:
- ?/ u; w/ H5 d$ J9 K- ^3 O  运行成功后将看到test.pdf文档了
+ B- L( i& B1 c' x  c:\test.doc参数对应的是脚本程序中的files(0)
. j( D3 Z) H! Y& a4 M  c:\参数对应的是脚本程序中的files(1)! p4 N- r' J7 F# Z; a+ V
  你可以安照该脚本改写成,支持多个参数,使用FOR循环,一次转换多个WORD文档,此处没有使用多个文件转换功能,是考虑到,该段脚本放在C#的线程中执行,这样一来也可以转换多个WORD文档.
6 d5 J F% [; l- r- q6 t  四:使用C#调用ConvertDoc2PDF.js脚本( e, Z# L/ a: p; }! c8 f$ ]9 r, B
  新建一个C#的WINDOWS应用程序,添加一个按钮button1# g' `+ ?7 W- N( q @) j9 S
  添加一个函数,函数名StartConvertPDF+ ^6 x, O) a; [8 @& Q) r* B
public void StartConvertPDF()/ z, j: ?5 k8 T0 ]$ |$ ?
{
6 u% v; z$ A2 e! I Process proc = new Process();
4 n" Q W' X+ _# K! V& Q( T proc.StartInfo.FileName = "cmd.exe";
. N6 N* N% H E, R% a) D( p( G! F proc.StartInfo.WorkingDirectory = @"c:\"; I9 i% z2 w! b5 l, v8 ]
 proc.StartInfo.CreateNoWindow = true;
7 R2 r% Z/ {! }; L3 e6 \ proc.StartInfo.UseShellExecute = false;
% Y1 s! [& s+ w* Q, M6 H. @3 ^ proc.StartInfo.RedirectStandardInput = true; //输入重定向2 N( @9 a# ^8 j9 @* J# L
 proc.Start();
* I5 ~) R& w# n9 Z- T% Y& V* i proc.StandardInput.WriteLine(@"cscript //nologo c:\ConvertDoc2PDF.js c:\test.doc c:\");2 E7 [% Q; b, y ?- H: i5 t0 N
 proc.StandardInput.WriteLine("exit");
4 Z* r& }4 t& K0 z' b proc.WaitForExit();8 \3 a9 p6 Y! b( P
} : R. a2 d0 W& p% J& Z
  然后在按钮的CLICK事件中添加调用线程的代码
* Z" ]. T5 G8 ]# x% R. }7 |private void button1_Click(object sender, System.EventArgs e)1 a; o5 A# b$ s
{
H1 Y1 I! R1 Z/ V//定义线程序, f" f6 D( ]5 v2 c
Thread thConvert = new Thread(new ThreadStart(StartConvertData));
5 D1 c# u8 t5 Y X0 C; OthConvert.Start();5 M: k. s4 m+ _$ P3 B, w; a
} " b: H x. u2 _' {( `( g
  注意:在测试上面的C#程序时,必须添加如下命名空间
# e, h; u0 E: [- i% E* Rusing System.Diagnostics;- B9 ^. m9 \+ v( E- D* j" G
using System.Threading;
B9 b" w3 G% G0 k. L  五:健壮的C#调用代码(实际考虑,可放在B/S系统中)
! ]; ~: q5 l; H  完成第4步的C#测试后,细心的读者,可能看到一点问题,那就是如何得到脚本运行后输出的结果,如何给线程中调用的StartConvertData方法传递参数0 A6 }/ [2 F! N+ @
  1:传递参数,此话说来也可用一篇教程告诉大家线程中方法如何来传递参数,现在就讲一个方案,此种方案很多,我采用一个类,初始化这个类,然后调用该类的方法作为线程执行的方法& c. V" X& W+ x, m
  2:得到脚本的输出结果,使用Process对象的输出重定向,就是说改变输出方向,使脚本不输出到控制台(MS-DOS窗口),而是重定向输出到C#程序中,并采用线程的异步回调方法,显示脚本运行结果。0 R+ q/ M: P; f# w7 w4 P8 d
  添加一个新类,类名为ToPdf0 W& {# ~+ q4 M0 W) D# C
using System;
G5 N' C; o+ h% Zusing System.Diagnostics;
5 B G5 x" F/ b2 x0 b6 ?5 z6 rusing System.ComponentModel;
- B f% P9 l% cusing System.Windows.Forms;
. h' }% v3 p. p$ Z& d4 Dusing System.Data;
; m) w+ ~( \; |2 _namespace Doc2Pdf: _* h8 v1 `$ \7 s/ T/ G
{
! N+ k8 \. b! R; Ppublic class ToPdf
* Q) C( V% o) O" s3 |6 G6 \{# }! L0 n0 N+ A; |* \7 r% B G
private string strWord = "";//此处的WORD文件不含路径- |. q: z0 ~. E8 ]9 O
private string sPath = "";" G7 g; w* e8 C
public string sExecResult = "";" z/ v# f( v! K2 G9 M9 U( U
public bool bSuccess = false;; Q( Z6 O4 V# u' z7 M
public ToPdf(string sParamWord,string sParamPath)
. u" O* i/ F* R5 g+ g. X{# P9 k+ W$ `9 {9 I
strWord = sParamWord;' d5 \; ?0 w: b
sPath = sParamPath;
) }6 e) \) ~# d- P5 x}( T$ q! ^' o: {& g$ v4 m6 y
public void StartConvertPDF(), O. n" O% v$ J; ^- v) u
{
1 ?7 B* L3 L. l, i1 ]Process proc = new Process();
! ^9 N6 G7 ]8 \; K; W$ Q   proc.StartInfo.FileName = "cmd.exe";
6 w8 \3 r2 R5 s   proc.StartInfo.WorkingDirectory = sPath; " ` V$ W8 W7 I; Q7 P) z
   proc.StartInfo.CreateNoWindow = true; & ^! O6 q2 [, C# M0 Q9 |3 W' p
   proc.StartInfo.UseShellExecute = false;
: e% A! y, s* N! r' a" w1 j   proc.StartInfo.RedirectStandardInput = true;//标准输入重定向0 z3 s) v! v4 V+ u, x% U0 M! q
proc.StartInfo.RedirectStandardOutput = true;//标准输出重定向; n; l, r; _% d# E+ F) o
   proc.Start();
( w, ?; [0 j5 u& c# m6 e- _proc.StandardInput.WriteLine("cscript //nologo "+sPath+"ConvertDoc2PDF.js "+sPath+strWord+ " "+sPath);& l: S5 C# V7 \
proc.StandardInput.WriteLine("exit");
5 e. R1 ], N s3 Y0 e9 v% R2 Z5 [; ]sExecResult = proc.StandardOutput.ReadToEnd();//返回脚本执行的结果! w5 \5 ~& K$ l1 d# }8 X& ~! T% }
proc.WaitForExit();- Y) I' l8 `! P% l, V
proc.Close();
0 h; i, y& n6 O: y}: ^9 h+ ~& y- Z3 e' W$ R1 M
public void EndConvertPDF(System.IAsyncResult ar)//ar参数必须写,是线程执行完成后的回调函数
. F% ?) R: K! K: N2 x* n{; R1 t |1 I3 `) O$ y2 q; {8 {9 `, z
if(sExecResult.IndexOf("isuccess")!=-1)bSuccess=true;; m( e- G% g& ?2 u3 g8 L
else if(sExecResult.IndexOf("isfail")!=-1)bSuccess=false;; l3 ^2 d$ Q, W4 ]
//如果放在B/S系统,你可以在此处写数据库,是成功还是失败,并用一个WEBService程序不断检查数据库,此WEBService程序不放在该回调用函数中
) f$ j+ _/ d/ s; P: d3 ?, W//如果放在C/S系统,回调函数可以不放在类中,以便在窗体程序中调用结果# q. x8 M( E3 O7 L9 x! x
}8 G3 }) n' \ ? x5 d, G
}
4 ~8 L8 u, M T# p% i' a} ( R0 }& _, p: I" @$ f: B
  改写原来的button1_Click事件中的代码% c2 b0 y4 \" g5 t9 ^4 h* Q
private void button1_Click(object sender, System.EventArgs e)6 R2 b% k# V2 y$ U3 V6 n- X
{
& _5 m2 ?6 T1 a2 c3 eToPdf my2Pdf = new ToPdf("test.doc","c:\\");
0 t; W0 O' E9 n9 k. q2 e gThreadStart thStartConvert = new ThreadStart(my2Pdf.StartConvertPDF); //开始异步调用线程6 S0 E- N+ P1 H
thStartConvert.BeginInvoke(new AsyncCallback(my2Pdf.EndConvertPDF),null);//设置异步线程的回调函数
- f* n; ~+ Q" k" g) r- l S//如果需要转换多个WORD,你可以用循环) `6 B, L' T: r( q; A
//如果是B/S系统,可以将本段代码放在ASPX中,并结合客户端的无刷新显示数据的技术,不断访问WEBService程序,以确定PDF是否转换成功或失败
% f! ^1 W0 v, m( u5 R+ e0 t} / I" ?7 h: `) e6 c: j5 \
  六:编写更加健壮的C#调用代码(实际考虑,可放在WINDOWS的服务程序中)
D% i; z% A( v  实际使用时,由于转化PDF时CPU的占用率很高,考虑只在同一时间转换一篇WORD文档,放弃异步线程的回调函数的使用,考虑一个WINDOWS的服务程序。3 f" I% |# P) x! t5 }! ~( m' X
  写一个函数CheckData2Convert(),不断的检查没有转换的WORD文档,并使用循环调用ToPdf类中执行转换方法StartConvertPDF9 _+ _1 h5 M3 t3 i
//以下给出,泛代码,用户按照自己的需求,填写完整即可/ I* p: W, ^2 X6 U& y7 J
//bool bStart为全局变量,控制循环的进入与退出
' g( \0 f( u$ O/ I, U7 j//例:18:30开始检查并转换,那么18:30时,bStart=true;并启动转换线程' m' c+ M z* \1 a
//6:30停止转换线程,bStart=fasle;
+ g! Y' N& N1 dprivate void CheckData2Convert()
% c" b9 M! l- x/ U! e{9 I8 ]% s. Q4 M& U
//检查指定目录下的没有转换的WORD文档,你同样可以检查数据库中记录的没有转换的WORD文档# J) q: Y5 K `9 z D e5 ?/ ~
string sPath = System.Threading.Thread.GetDomain().BaseDirectory; //当前的路径
) {7 H, |2 h; Awhile(bStart). }+ Y/ u% f! D' _( z
{2 |+ M3 ?$ r z
int iFileCount = CheckWord(); //CheckWord为一个方法,检查当前没有转换的WORD文档,返回没有转换的文件数,该方法的代码由读者自己编写
- h+ @1 ?$ F- {7 Lfor(int i=0;i
1 }: h1 f5 L6 M: k- N. Eif(my2Pdf.sExecResult.IndexOf("isuccess")!=-1)
2 c; E. N' c( w/ C3 m5 a: d{( z5 s: s q& @- Y, Y1 D# f
//成功,写日志,或回写数据库/ \( j' e6 f, ]% M7 o3 Q+ p
}7 {- M% M1 x. X3 N9 }" ^$ I% w9 ^
else if(my2Pdf.sExecResult.IndexOf("isfail")!=-1)+ f+ q7 r3 I3 s/ u# Y
{7 b0 w% D+ z: S, h4 m3 S. Y2 b
//失败,写日志,或回写数据库
4 | m) E+ F5 E8 J3 x2 z. A' U}
! B0 x: d+ ^: C}9 t0 i# B" f0 g
if(!bStart)break;
7 F: C. J, J. l7 _+ x- @# T% q2 v$ r$ D: KThread.Sleep(1000);
5 V P6 {' S1 U" R4 r. K. g}
: P* W" q0 Z! |}
; B( ]. Y# y* u0 J5 z, q& O C  然后在服务的开始事件中,启动线程% Y" _- g- i' N4 s7 v0 {( c2 N; j
protected override void OnStart(string[] args)
4 y A3 @ U3 O9 t. P& ~/ M{6 j+ F3 A6 R% o6 z
//可以使用一个开始定时器,检查是否到开始时间,时间一到,就开始执行线程,此处的开始执行线程可以放在开始定时事件中! N+ B$ \6 C0 ?* i
//可以使用一个结束定时器,检查是否到结束时间,时间一到,就结束线程,结束线程的代码可以放在结束定时事件中
! N8 l+ e! O; M- @# P//注意:应该使用组件中的定时器,而不是Windows的FORMS中的定时器" Y* @& ?. ^& f# A
//该定时器的类名为System.Timers.Timer,千万别搞错,不然执行不会正常的
+ A- n/ l1 f5 V! t) n3 QbStart = true;8 D5 }2 R5 C4 P' l
Thread thConvert = new Thread(new ThreadStart(StartConvertData));( O: f) m% U5 H' ?6 v9 a- N2 Q* V
thConvert.Start();
; u( A1 q3 f3 H, p N! J# f% G}
1 C0 }* D0 |* d; k+ Q" E  然后在服务的结束事件中,设置停止线程的标识bStart= false
' r, O, e/ O. X! ]$ e! O/ f" [protected override void OnStop(), W% Y8 B) E! r |+ p
{! Y/ ]7 ?; V. {/ G1 Y B
bStart = false;
% _3 M; j0 H: F1 H8 i, p2 f# T//为何次处不停止线程呢,因为考虑到,现在线程正在转换WORD文档,但没有结束,所以只设置停止标识,转换完成后,线程也执行结束了.! l" U6 G7 f G5 Z# M7 v3 {$ D
}
8 D$ b! w' V+ u- W# i  结束语:' o+ [; y! n: R. a4 P }
  Adobe Acrobat 7.0 Professional,postscript.exe,gs811w32.exe这三个文件可以在itbaby.jss.cn下载,都包含在同一个RAR的压缩文件中了。
, o8 L' ~3 b- \0 u& f' a  itbaby.jss.cn是动态域名,主机在作者家里,如果网站不能访问,说明电脑没有开,请稍后几天再试。: V" X: }* Z- @8 V

TOP

谢谢分享,顺便说一句Office2007已经自带pdf插件,需要的可以从微软下载,安装之后就可以将文件存为pdf格式了。
《无量寿经·第十八愿》言:
设我得佛,十方众生,至心信乐,欲生我国,乃至十念,若不生者,不取正觉。唯除五逆,诽谤正法。

TOP

引用:
原帖由 garnett_wu 于 2007-7-5 23:43 发表 0 h+ d" N5 g4 G5 s! Z/ b
前言:由于一个客户的项目中需要将WORD文档转换成PDF格式,故写了本篇实站教程* B$ ^8 ?4 K" {2 ~
  需求分析:客户的项目以B/S结构为主,提供一个WORD文件在后台自动转换成PDF,经过实际测试,如果该篇WORD文档有100多页的话,转换需 ...
3 ^7 k; V/ v# N. b: i/ ?
这个到没有注意到,我去搜索下,看看.不过只要装了acrobat的都可以自由转换doc和pdf文档.
# c- |: U1 z/ N' t& K$ E
1 i w9 F# L5 j `; W[ 本帖最后由 etassassin 于 2007-7-10 11:22 编辑 ]

TOP

楼住,这样转换的效果和acrobat的比怎么样?

TOP

本功能由奇虎搜索实现

相关主题

标题 作者 最后发表
[站外] 一个好的总结   [转帖] 郭亲华 2008-08-21
[站外] {精典}LINUX认证教程\VB\VC\C++\SERVER\SQL\Delphi\   [转帖] 314605291 2008-08-24
[站外] 关于ASP.NET页面打印技术的总结   [转帖] 鹰瞰群雄 2008-08-24
点击阅读更多关于的相关帖子  更多相关主题