在上篇《C#生成CHM文件(入门篇)》中,我们利用微软自带的hhc.exe以编程的方式创建一个CHM文件,而且调用的是一个静态的HMTL文件。

在中篇中,实现以下几个目标
 1.将在线的网页保存为CHM文件
 2.我们将对我们进行编译的CHM文件进行反编译,使用的还是微软自带的一个exe(hh.exe)。
 3.以编程的方式将CHM文件转换为Word

在中篇中,把界面稍微调整了下,如下图

一、将在线的网页保存为CHM文件

曾尝试直接使用网址来编译html文件,结果一直报错,于是就放弃了。现在实现的方法的思想是这样的:先将输入的url地址的网页保存到本地,然后利用上一篇中的方法生成CHM文件。不过经测试,这样的效率还是比较低的,主要的花费在将htm文件下载到本地,如果带宽不够的话,将会很慢,不过总归是种方法,大家如果有更好的解决方案,希望能告诉我。


   
   
  1. HttpWebRequest myReq = (HttpWebRequest)WebRequest.Create(url);  
  2.  HttpWebResponse myResp = (HttpWebResponse)myReq.GetResponse();  
  3.  StreamReader respStream = new StreamReader(myResp.GetResponseStream(), Encoding.Default);  
  4.  string respStr = respStream.ReadToEnd();  
  5.  respStream.Close();  
  6.  FileStream fs = new FileStream(startPath+@"\test.htm", FileMode.Create, FileAccess.Write);  
  7.  StreamWriter sw = new StreamWriter(fs, Encoding.Default);  
  8.  sw.Write(respStr);  
  9.  sw.Close(); 

思路是将网页保存在本地的,startPath为项目所在路径。 大家可以以http://www.baidu.com/index.htm为例(注意要以htm或html为结尾),测试下,看看能不能正常将百度的首页保存到CHM文件中。效果图是这样的:

二、反编译CHM

反编译CHM的方法同初篇中的利用Process类来进行。

 


   
   
  1. /// <summary>  
  2.         /// 反编译CHM文件  
  3.         /// </summary>  
  4.         /// <param name="CHMFile">CHM文件名</param>  
  5.         /// <returns>返回hhc文件名</returns>  
  6.         /// <remarks>uses the <see cref="DecompileChm"></see></remarks>  
  7.         public string DecompileChm(string CHMFile)  
  8.         {  
  9.             string pathDir = Path.GetDirectoryName(CHMFile);//得到chm文件的绝对路径  
  10.             pathDir = Path.Combine(pathDir, Path.GetFileNameWithoutExtension(CHMFile));  
  11.             return DecompileChm(CHMFile, ref pathDir);  
  12.         }  
  13.         /// <summary>  
  14.         /// 反编译CHM文件  
  15.         /// </summary>  
  16.         /// <param name="CHMFile">CHM文件名</param>  
  17.         /// <param name="FolderToPut">反编译后的文件存放路径</param>  
  18.         /// <returns>返回反编译后的hhc文件名</returns>  
  19.         /// <remarks>使用hh.exe反编译</remarks>  
  20.         public string DecompileChm(string CHMFile, ref string FolderToPut)  
  21.         {  
  22.             if ((!System.IO.File.Exists(CHMFile)))  
  23.             {  
  24.                 throw new ArgumentException(CHMFile+"文件不存在");  
  25.             }  
  26.             if ((!Directory.Exists(FolderToPut)))  
  27.             {  
  28.                 FolderToPut = FolderToPut.Replace(" ""_");  
  29.                 Directory.CreateDirectory(FolderToPut);  
  30.             }  
  31.             DirectoryInfo di = new DirectoryInfo(FolderToPut);  
  32.             if ((di.Name.Contains(" ")))  
  33.             {  
  34.                 throw new ArgumentException("反编译的文件夹名不能包含空格");  
  35.             }  
  36.             string strD = null;  
  37.             strD = " -decompile " + di.FullName + " " + CHMFile;//反编译命令  
  38.             Console.WriteLine(strD);  
  39.             Process p = Process.Start("hh.exe", strD);//调用hh.exe进行反编译  
  40.  
  41.             p.WaitForExit();  
  42.             return Directory.GetFiles(FolderToPut, "*.HHC")[0];  
  43.         } 

三、CHM文件转换为Word

接下来,我们来延伸下,利用反编译的文件,将CHM转换成Word文件。思路是这样的:利用反编译,得到hhc文件(hhc文件中包含htm或html文件的文件名)和一大堆web页面(如果一开始编译进去的是一大堆的话,呵呵),创建一个word文件,将html文件插入到word中,下面以实例的方式来实现。
为了方便代码管理,我创建了一个类库项目,命名为CHM2Word,里面主要实现将CHM文件反编译并将反编译的文件整合为Word。在CreateCHM项目中调用代码即可,另需要你的机器安装Office2003(对应,添加引用 ->COM->Microsoft Word 11.0 Object Library)或2007(对应,添加引用->COM->Microsoft Word 12.0 Object Library)。


   
   
  1. /// <summary>  
  2.         /// 添加到word中  
  3.         /// </summary>  
  4.         /// <param name="pathFileHHC"></param>  
  5.         /// <param name="saveAs"></param>  
  6.         public void AddToWord(string pathFileHHC, string saveAs)  
  7.         {  
  8.             if (File.Exists(saveAs))  
  9.             {  
  10.                 throw new Exception("word文件已经存在!");  
  11.             }  
  12.             Object Nothing = System.Reflection.Missing.Value;  
  13.             Microsoft.Office.Interop.Word.Application wApp = (Microsoft.Office.Interop.Word.Application)this.Word();  
  14.             Document wDoc = wApp.Documents.Add(ref  Nothing, ref  Nothing, ref  Nothing, ref  Nothing);  
  15.  
  16.             if (wApp == null)  
  17.             {  
  18.                 throw new Exception("转换失败");  
  19.             }  
  20.  
  21.             try 
  22.             {  
  23.                 string dirfile = "";//目录位置  
  24.                 dirfile = Path.GetDirectoryName(pathFileHHC);//目录的绝对路径  
  25.                 string[] lines = File.ReadAllLines(pathFileHHC);//读取hhc所有的行,这是为了找出里面的htm或html文件  
  26.  
  27.                 string quote = "" + (char)34;  
  28.                 long filenumber = 0;  
  29.  
  30.                 //遍历每一行  
  31.                 foreach (string TextLine in lines)  
  32.                 {  
  33.                     string htmFile = null;  
  34.  
  35.                     if (TextLine.IndexOf(".html", 0) > 0 || TextLine.IndexOf(".htm", 0) > 0)//如果这一行里面有.htm或者html.的字符串  
  36.                     {  
  37.  
  38.                         #region 以下代码是获取htm或者html文件名  
  39.  
  40.                         int endQuote = 0;  
  41.                         if (TextLine.IndexOf(".html", 0) > 0)  
  42.                         {  
  43.                             endQuote = TextLine.IndexOf(quote, TextLine.IndexOf(".html", 0));  
  44.                         }  
  45.                         else 
  46.                         {  
  47.                             endQuote = TextLine.IndexOf(quote, TextLine.IndexOf(".htm", 0));  
  48.                         }  
  49.                         int quoteLoop = 0;  
  50.                         quoteLoop = endQuote - 1;  
  51.                         while (TextLine.Substring(quoteLoop, 1) != quote)  
  52.                         {  
  53.                             quoteLoop = quoteLoop - 1;  
  54.                         }  
  55.                         htmFile = TextLine.Substring(quoteLoop + 1, endQuote - quoteLoop - 1);//获取html文件的名字  
  56.  
  57.                         #endregion  
  58.                           
  59.                           
  60.                         htmFile = dirfile + "\\" + htmFile;  
  61.  
  62.                         bool b = false;//是否存在html文件  
  63.                         try 
  64.                         {  
  65.                             b = File.Exists(htmFile);  
  66.                         }  
  67.                         catch (Exception ex)  
  68.                         {  
  69.                         }  
  70.                         if ((!b))  
  71.                         {  
  72.                             continue;  
  73.                         }  
  74.                         //将文件插入到word中  
  75.                         wApp.Selection.InsertParagraphAfter();  
  76.                         filenumber += 1;  
  77.                         if (ProcessFile != null)  
  78.                         {  
  79.                             ProcessFile(thisnew ProcessFileEventArgs(htmFile, filenumber));  
  80.                         }  
  81.                         //InsertFile参数说明  
  82.                         //文件名: 必选的 String. 要被插入的文件名和路径。如果没有指定路径,Word默认为当前文件夹  
  83.                         //Range: 可选的 Object. 如果指定的文件时word, 参数为bookmark(书签). 如果文件为其他类型(如Excel工作表), 参数为指定的一个单元或区域,如 R1C1:R3C4  
  84.                         //确定是否转换 可选 Object.如果值为 True,则 word 应用程序将在插入非“ Word 文档”格式的文档时提示对转换进行确认。.  
  85.                         //链接:  可选 Object. 如果值为 True,则可用 INCLUDETEXT 域插入该文档。  
  86.                         //附件: 可选 Object. 为 True 时将该文件作为附件插入电子邮件消息中。  
  87.                         wApp.Selection.InsertFile(htmFile, ref Nothing, ref Nothing, ref Nothing, ref Nothing);  
  88.                         if ((filenumber % 10 == 0))  
  89.                             wDoc.Save();  
  90.                     }  
  91.                 }  
  92.                 wDoc.Save();//保存word  
  93.                 wDoc.Close(ref Nothing, ref Nothing, ref Nothing);//关闭  
  94.                 wApp.Quit(ref Nothing, ref Nothing, ref Nothing);//释放  
  95.  
  96.             }  
  97.             catch (Exception ex)  
  98.             {  
  99.             }  
  100.             finally//释放对象  
  101.             {  
  102.                 if ((wDoc != null))  
  103.                 {  
  104.                     Marshal.ReleaseComObject(wDoc);  
  105.                     wDoc = null;  
  106.                 }  
  107.  
  108.                 Marshal.ReleaseComObject(wApp);  
  109.                 wApp = null;  
  110.             }  
  111.         } 

创建word对象


   
   
  1. /// <summary>  
  2.         /// 创建word对象  
  3.         /// </summary>  
  4.         /// <returns></returns>  
  5.         public object Word()  
  6.         {  
  7.             Microsoft.Office.Interop.Word.Application WordApp;  
  8.             try 
  9.             {  
  10.                   
  11.                 //WordApp = new Microsoft.Office.Interop.Word.ApplicationClass();//如果是office2003和office2007用这样方法  
  12.                 WordApp = new Microsoft.Office.Interop.Word.Application();//如果是office2010,使用这个方法  
  13.             }  
  14.             catch (Exception e)  
  15.             {  
  16.                 WordApp = null;  
  17.             }  
  18.             return WordApp;  
  19.         } 

反编译导出类主要方法


   
   
  1. /// <summary>  
  2.         /// feedback about processing   
  3.         /// </summary>  
  4.         public event EventHandler<ProcessFileEventArgs> ProcessFileIntoWord;//定义一个事件属性  
  5.         private WordClass withEventsField_w = new WordClass();  
  6.         /// <summary>  
  7.         /// 通过这个类,我们可以转换为word,并且把事件传给调用者  
  8.         /// </summary>      
  9.         public WordClass w  
  10.         {  
  11.             get { return withEventsField_w; }  
  12.             set 
  13.             {  
  14.                 if (withEventsField_w != null)//如果不为null,撤销事件  
  15.                 {  
  16.                     withEventsField_w.ProcessFile -= w_ProcessFile;  
  17.                 }  
  18.                 withEventsField_w = value;  
  19.                 if (withEventsField_w != null)//如果不为null,注册  
  20.                 {  
  21.                     withEventsField_w.ProcessFile += w_ProcessFile;  
  22.                 }  
  23.             }  
  24.         }  
  25.         /// <summary>  
  26.         /// 主要函数:反编译、导出  
  27.         /// </summary>  
  28.         /// <param name="ChmFile">待反编译的CHM文件</param>  
  29.         /// <param name="DocFile">word文件名</param>  
  30.         /// <remarks>word文件一定不存在</remarks>  
  31.         public void DecompileAndExport(string ChmFile, string DocFile)  
  32.         {  
  33.             try 
  34.             {  
  35.                 Decompile d = new Decompile();//实例化一个反编译类  
  36.                 string strHHC = d.DecompileChm(ChmFile);//获取hhc文件  
  37.                 w.AddToWord(strHHC, DocFile);//调用word类的添加到word中方法  
  38.             }  
  39.             catch (System.Runtime.InteropServices.COMException ex)  
  40.             {  
  41.                 //throw new clsError("Com exception:" + ex.Message, ErrorsOcurred.ComError);  
  42.             }  
  43.  
  44.         } 

我利用刚刚生成的baidu的CHM导出的word如图:

效果还是不错的,呵呵。如果你的CHM文件大的话,导出的时间可能会比较长一些。

 PS:
1.如果你使用的是office2003或者office2007,需要修改类库项目下的WordClass类下Word方法,因为office2010的
Microsoft.Office.Interop.Word.ApplicationClass不再提供构造方法,而是提供Microsoft.Office.Interop.Word.Application()接口


2.如果在转换的工程中,始终没有反应,可以调试下,如果出现这样的错误,“因为没有打开的文档,所以这一命令无效”。

调试中不会弹出异常,但是将鼠标放到wApp对象中,查看的会发现那样的错误,原因是因为权限不够,可以采用如下方法解决:

运行dcomcnfg打开组件服务,依次展开"组件服务"->"计算机"->"我的电脑"->"DCOM配置"

找到"Microsoft Word应用程序",右键打开属性对话框, 
点击"标识"选项卡,点击"标识"标签,选择"交互式用户"(此设置可能对计算机安全存在威胁,如不设置可以解决问题就不设置,点"下列用户",把管理员的用户administrator密码....正确填写进去也行)
点击"安全"选项卡,依次把"启动和激活权限","访问权限","配置权限",都选择为自定义,然后依次点击它们的编辑,把everyone添加进去,并加入所有的权限...
OK,解决此问题!
如果你的office是2010或者你的系统版本较高的话,很有可能遇到这样的问题。我的电脑是windows7+office2010,就遇到了这样的问题。
 

3.在反编译和在线生成CHM的时候会生成一些临时文件,如果不及时删掉的话,会造成空间的浪费。我们自己可以写一个简单的删除程序,这个应该很简单,如果不会的,可以参考我以前项目中的代码,http://alexis.blog.51cto.com/2621421/573630

 PS:汗...(2010-09-30 12:40),忘了附源代码了 C#生成CHM(中级篇)

在下篇(应用篇)中,我将说说如何将这些技术运用到实际中。