1、文件提取

在在线CHM阅读器(1)一文中已提到,CHM其实就是一个结构化存储文件(Structured Storage),如果也阅读CHM文档,就必须将文件,图片等从CHM文件中提取出来,提取需要用到Structured Storage的StgOpenStorage函数以及IStorage和IStream接口,不过这些在.NET中都不能直接使用,需要先“包装”一下。如何使用IStorage和IStream可以参考这篇文章:

CHM Help File Extractor

不过这篇文章提供的源代码是用于反编译出CHM的所有文件的,开发在线CHM阅读器并不需要先反编译出所有的文件,只需要把浏览器当前请求的那个文件提取出来发送到客户端即可。提取文件的代码如下:

public class CHH
{/// <summary>/// 提取CHM中的文件/// </summary>/// <param name="chm">chm文件的路径</param>/// <param name="res">要提取的文件的全路径</param>/// <returns></returns>public static Stream Find(string chm, string res){IStorage storage = ((ITStorage)new ITStorageClass()).StgOpenStorage(chm, IntPtr.Zero, 0x20, IntPtr.Zero, 0);try{DateTime s = DateTime.Now;IStream stream = Find(storage, res.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries), 0);double e = (DateTime.Now - s).TotalMilliseconds;if (stream == null) return null;if (res.ToUpper() == "#SYSTEM"){//#System是二进制文件,javascript无法处理,因此处理成json格式的文本,数据格式为://{//    HomePage:起始页,//    Encoding:编码,//    Title:标题//}using (Stream cs = new ComStream(stream)){try{return (new ChmInfo(cs)).MakeStream();}finally{cs.Close();}}}else{return new ComStream(stream);}}finally{Marshal.ReleaseComObject(storage);}}static IStream Find(IStorage storage, string[] res, int first){if (first == res.Length - 1){IStream stream = null;try{//找到对应的文件stream = storage.OpenStream(res[first], IntPtr.Zero, 0x20, 0);}catch{}if (stream == null && res[first].ToUpper() == ".HHC"){//由于目录文件的文件名一般不确定,因此做特殊处理System.Runtime.InteropServices.ComTypes.STATSTG stats;IEnumSTATSTG enumStats;int i = 0;storage.EnumElements(0, IntPtr.Zero, 0, out enumStats);try{enumStats.Reset();//枚举所有文件查找while (enumStats.Next(1, out stats, out i) == 0){if (System.IO.Path.GetExtension(stats.pwcsName).ToUpper() == ".HHC" && stats.type == 2){stream = storage.OpenStream(stats.pwcsName, IntPtr.Zero, 0x20, 0);return stream;}}}finally{Marshal.ReleaseComObject(enumStats);}}return stream;}else{//在文件夹中递归查找目标文件IStorage next = storage.OpenStorage(res[first], IntPtr.Zero, 0x20, IntPtr.Zero, 0);try{return Find(next, res, first + 1);}finally{Marshal.ReleaseComObject(next);}}}
}

2、处理#SYSTEM文件

上文提取文件的代码已提到,#SYSTEM是一个二进制文件,而javascript是不能处理二进制文件的,因此,必须在后台处理#SYSTEM文件,转换成json格式的文本发送到客户端。#SYSTEM的格式如下图所示:

根据上图所示的规律,既可以处理#SYSTEM文件了,代码如下:

    class ChmInfo{const UInt16 ID_HHC = 0x0000;        //const UInt16 ID_MAIN = 0x0002;        //起始页的IDconst UInt16 ID_Version = 0x0009;   //版本的IDconst UInt16 ID_TITLE = 0x0003;        //标题的IDconst UInt16 ID_PRJNAME = 0x0006;    //工程的IDconst UInt16 ID_LAN = 0x0004;        //区域的IDpublic String MainPage = String.Empty, PrjName = String.Empty, Title = String.Empty, HHA_Version = String.Empty, HHC = String.Empty;public Encoding Encoding = null;Hashtable _session = new Hashtable();private bool ReadSession(BinaryReader reader){if (reader.BaseStream.Position >= reader.BaseStream.Length) return false;UInt16 id = reader.ReadUInt16();UInt16 count = reader.ReadUInt16();if (count + reader.BaseStream.Position <= reader.BaseStream.Length){if (count > 0){_session[id] = reader.ReadBytes(count);}return true;}else{return false;}}public ChmInfo(Stream stream){BinaryReader reader = new BinaryReader(stream);//读取所有数据及其对应的ID并保存到一个Hashtable中while (ReadSession(reader)) ;try{if (_session.ContainsKey(ID_LAN)){Byte[] data = _session[ID_LAN] as Byte[];CultureInfo info = new CultureInfo(data[1] * 0x100 + data[0]);Encoding = Encoding.GetEncoding(info.TextInfo.ANSICodePage);}}catch{}if (Encoding == null) Encoding = Encoding.GetEncoding("GB2312");if (_session.ContainsKey(ID_MAIN)){Byte[] data = _session[ID_MAIN] as Byte[];MainPage = Encoding.GetString(data, 0, data.Length - 1);}if (_session.ContainsKey(ID_TITLE)){Byte[] data = _session[ID_TITLE] as Byte[];Title = Encoding.GetString(data, 0, data.Length - 1);}if (_session.ContainsKey(ID_PRJNAME)){Byte[] data = _session[ID_PRJNAME] as Byte[];PrjName = Encoding.GetString(data, 0, data.Length - 1);}if (_session.ContainsKey(ID_Version)){Byte[] data = _session[ID_Version] as Byte[];HHA_Version = Encoding.GetString(data, 0, data.Length - 1);}if (_session.ContainsKey(ID_HHC)){Byte[] data = _session[ID_HHC] as Byte[];HHC = Encoding.GetString(data, 0, data.Length - 1);}}public Stream MakeStream(){//生成JSON并保存到一个MemoryStream中String json = String.Format("{{\"MainPage\":\"{0}\",\"Title\":\"{1}\",\"HHC\":\"{2}\",\"Encoding\":\"{3}\"}}",TransferCharJavascript(MainPage),TransferCharJavascript(Title),TransferCharJavascript(HHC),TransferCharJavascript(Encoding.HeaderName));Byte[] buffer = Encoding.UTF8.GetBytes(json);Stream stream = new MemoryStream(buffer.Length);stream.Write(buffer, 0, buffer.Length);stream.Seek(0, SeekOrigin.Begin);return stream;}public static string TransferCharJavascript(string s){StringBuilder ret = new StringBuilder();foreach (char c in s){switch (c){case '\r':case '\t':case '\n':case '\f':case '\v':case '\"':case '\\':case '\'':case '<':case '>':case '\0':ret.AppendFormat("\\u{0:X4}", (int)c);break;default:ret.Append(c);break;}}return ret.ToString();}}

3、处理目录(*.hhc)文件

目录文件保存着一个CHM文件的目录结构,它是一个文本文件,为了减轻服务器的负担,将目录文件放到浏览器来处理。在在线CHM阅读器(1)一文中已提到,目录文件大概的规律是:每一个<LI><OBJECT>…<OBJECT>对应着目录树中的一个节点,<OBJECT>…<OBJECT>中的参数记录着该节点的属性(对应的页面,名称等)。如果这个节点有子节点的话,那么<LI>后面会紧跟着一个<UL></UL>,<UL>里面所有的节点都是其子节点。处理的代码如下:

function ChmHHC(buffer)
{var position = 0;var RegxTagName = /(<|<\/)([a-zA-Z]+)(\s[\S\s]*|)>/i;var RegxAttrs = /([a-zA-Z1-9]+)\s*=\s*\x22([^\x22]+)\x22/ig;//读取下一个标志(<标志名>)function ReadTag(){var tag = {Name: "",Type: "",Attrs: {}};var res = null;while (res == null){if (position >= buffer.length) return null;while (position < buffer.length && buffer.charAt(position) != '<') position++;if (position >= buffer.length) return null;var s = position;while (position < buffer.length && buffer.charAt(position) != '>') position++;if (position >= buffer.length) return null;var e = position;position++;var tag_str = buffer.substr(s, e - s + 1);RegxTagName.lastIndex = 0;res = RegxTagName.exec(tag_str);}tag.Name = res[2].toUpperCase();tag.Type = res[1] == '<' ? "Begin": "End";if (tag.Type == "Begin" && res.length > 3 && res[3] != ""){RegxAttrs.lastIndex = 0;var atrr = null;while ((attr = RegxAttrs.exec(res[3])) != null){tag.Attrs[attr[1].toLowerCase()] = attr[2];}}return tag;}var current = null;function IsBeginTag(tag, name){return tag.Type == "Begin" && tag.Name == name;}function IsEndTag(tag, name){return tag.Type == "End" && tag.Name == name;}function RenderTag(){if (current != null && IsBeginTag(current, "LI")){var node = {NodeType: "LI",SubNodes: []};current = ReadTag();if (current != null && IsBeginTag(current, "OBJECT")){node.type = current.Attrs["type"];current = ReadTag();while (current != null && !IsEndTag(current, "OBJECT")){if (IsBeginTag(current, "PARAM")){node[current.Attrs["name"]] = current.Attrs["value"];}current = ReadTag();}if (current != null && IsEndTag(current, "OBJECT")) current = ReadTag();if (current != null && IsEndTag(current, "LI")) current = ReadTag();//尾随着LI的所有UL中的节点均作为该LI的子节点while(current != null && IsBeginTag(current, "UL")){var ul = RenderTag();if (ul != null){for(var ul_index in ul.Nodes) node.SubNodes.push(ul.Nodes[ul_index]);}}return node;}}else if (current != null && IsBeginTag(current, "UL")){var node = {NodeType: "UL",Nodes: []};current = ReadTag();while (current != null && !IsEndTag(current, "UL")){var subNode = RenderTag();if (subNode != null) node.Nodes.push(subNode);}if (current != null){current = ReadTag();return node;}}else{current = ReadTag();}return null;}var roots = [];this.Render = function(){position = 0;current = ReadTag();while (current != null){var node = RenderTag();if (node != null) roots.push(node);}current = null;}this.GetNodes = function(){return roots;}
}

调用ChmHHC的Render方法后,将HHC文件转换成一个数组,保存着所有的节点,其结构与目录的对应关系如下图所示:

在上文中,已经介绍了如何提取出CHM文件中的文件(网页,图片等)以及如何解析目录文件,下一篇文章,将介绍如何使用ISAPI筛选器和IHttpHandler来开发一个在线CHM阅读器。

源代码下载

如果觉得文章不错的话,欢迎点一下右下角的推荐。

转载于:https://www.cnblogs.com/lucc/archive/2010/04/28/1723326.html

在线CHM阅读器(2)——文件提取及关键文件解析相关推荐

  1. rss阅读器保存html文件,4款在线RSS阅读器使用体验

    RSS阅读有以下优点:您可以看到没有广告和图片的标题或文章的概要阅读,这样你不必阅读全文即可知文章讲的一个意思是什么,为您节省时间. RSS阅读器会自动更新你定制的网站内容,保持新闻的及时性.要订阅新 ...

  2. linux下chm阅读器的安装与使用

    Fedora Core 下的chm阅读器有好几个,xchm.chmsee.kchmviewer.gnochm.chmreader.fbreader. 1.gnochm gnochm功能和界面都跟win ...

  3. android 上下滚动文字_计算机毕设项目004之Android系统在线小说阅读器

    计算机毕设项目004之Android系统在线小说阅读器 一. 项目名称 基于Android系统的在线小说阅读器 二. 项目简介 项目中的角色功能: 支持翻页动画:仿真翻页.覆盖翻页.上下滚动翻页等翻页 ...

  4. 基于ASP.NET AJAX技术开发在线RSS阅读器(下篇)

    五.逻辑层设计 (一)添加RSS频道 在展开真正的逻辑层设计之前,先让我们简单地浏览一下下面的草图4.图4展示了我对于两个重要ASP.NET AJAX客户端控件-ListView和DataSource ...

  5. InoReader—— 轻便快捷的在线 RSS 阅读器

    致鲜果RSS阅读器用户       各位鲜果RSS阅读器用户,很遗憾的通知大家,鲜果团队将在2014年12月12日关闭RSS订阅服务.感谢大家长期以来的支持,希望大家理解我们的这一决定. --题记 图 ...

  6. ubuntu 输入法、邮箱迁移、词典、CHM阅读器

    IBUS输入法安装和设置 IBus是一个框架,支持多种输入法. 问题1:Ubuntu系统选择自带的拼音输入法 是错误的,它默认无法输入中文及中文词组,请独立安装其他基于IBus的输入法 问题2:Ubu ...

  7. 在Ubuntu 14.04 64bit上安装CHM阅读器KchmViewer 5.3

    kchmviewer是一款不错的开源软件,你可以使用它在linux系统下阅读chm格式的电子书.KchmViewer 作为一款使用Qt开发的程序,与KDE 桌面环境整合得很好.从语言兼容性上看,Kch ...

  8. 【Java项目实战】在线音乐播放器(从需求到产品完整解析)

    准备工作必看:[Java项目实战]在线音乐播放器(前期准备) 核心功能 登录.注册 上传音乐 删除某一个音乐信息 删除选中的音乐信息 查询音乐(包含查找指定/模糊匹配的音乐) 添加音乐到"喜 ...

  9. iPhone4 用 iTools 的 SHSH.cfg 文件提取通用 shsh 文件(小雨伞、cydia、ifaith),降级5.0.1成功

       帖子里详细描述了如何从SHSH.cfg文件中提取出.shsh文件,并验证其有效性! 详细步骤:[略去其对文件类型.结构等等的描述,直接上步骤] 0.  itools 的shsh保存在SHSH.c ...

  10. 安装adobe阅读器时,报写至.....文件时错误

    1.写至文件......时出错. 解决办法:关闭360等安全软件.

最新文章

  1. ubuntu18.04.1内核升级至5.0.0-25版本
  2. 又一个防火墙的问题——content filtering
  3. pymysql.err.InterfaceError: (0, '')
  4. 详解布隆过滤器的原理、使用场景和注意事项
  5. int * * a[10] int * (*a)[10]和 int(*a[10])() 是什么意思
  6. 【机器视觉】 close_measure算子
  7. 【区块链】GO语言区块链项目——超级账本
  8. 牛客网暑期ACM多校训练营(第一场)
  9. python string转int_我用Python搞资源 [ 02 ]
  10. 权限管理系统系列之序言
  11. iscsi:IO操作流程(三)
  12. LintCode 4.丑数
  13. Xinlinx 7系列FPGA概览
  14. 黑苹果 10.14.6 安装记录
  15. vim编辑器跳转、复制、剪切(2)
  16. RAC 11G ASM磁盘损坏恢复
  17. 手机进行linux编程的 app,手机也能编程?盘点这6个可以用手机编程的App!快收藏...
  18. Codeforces Round #176 (Div. 2) D. Shifting(模拟,STLdeque应用)
  19. python解一元二次方程虚根_怎么用python解一元二次方程
  20. 数码相框(四、使用freetype库实现矢量字体显示)

热门文章

  1. Feb16 小白《Linux就该这么学》学习笔记3
  2. html表格填充空白单元格,快速填充数据表格中的空白单元格
  3. 锋麦4S笔记本英伟达独显驱动安装
  4. scala的交互式图表工具wisp
  5. 何钦铭.c语言程序设计,《C语言程序设计》 - 何钦铭
  6. 超级牛散股神叶健颜专找重组题材股,精准买入,不服不行。
  7. 虚拟机下Ubuntu打开摄像头是黑屏问题
  8. 纯文本,富文本,超文本
  9. mybatis的一级缓存和二级缓存
  10. 如何防范计算机安全,计算机安全风险及防范措施