使用CHM文档 阅读随笔

简介: 原文 http://www.cnblogs.com/codealone/archive/2013/04/19/3029055.html 背景   我们在开发的过程中,常常都会想记录下来一些东西,可以成文的,则以随笔的形式发布,那些不能成文的,例如某bug的解决方案,或者开发中的注意事项,甚至是某个SQL语句,以只言片语的形式记录在文章、日记里,这样,自己就能在不同的设备、终端上查看自己记录的东西。

原文 http://www.cnblogs.com/codealone/archive/2013/04/19/3029055.html

背景

  我们在开发的过程中,常常都会想记录下来一些东西,可以成文的,则以随笔的形式发布,那些不能成文的,例如某bug的解决方案,或者开发中的注意事项,甚至是某个SQL语句,以只言片语的形式记录在文章、日记里,这样,自己就能在不同的设备、终端上查看自己记录的东西。

  博客园的文章,如果不设置在首页显示的话,个人觉得查看起来不是很方便。想到自己曾做了一个数据库CHM文档生成工具,于是,不管是随笔,还是文章,能否也通过CHM文档的形式查看呢。想到这里,我的需求就产生了。

效果预览

资源下载

  可执行程序

  示例CHM文档

  源代码

开发思路

  1.得到博客内容。之前想过通过url请求的方式获取到博客正文部分,但是后台的文章或日记处理起来相对麻烦,于是采用了博客备份得到的xml文件,按照xml文件结构,定义数据结构,读取xml数据。怎么读取,这里就不细讲,如有需要,移步至《以读取博客园随笔备份为例 将xml 序列化成json,再序列化成对象》

  2.遍历博客的博文,然后将博文的html内容,以html形式的存储。但是xml里存储的仅仅是博客的正文部分内容,整个页面显示框架是没有的。此时,我打开任意我的任意一篇博客,然后将网页文件全部下载到本地,对应的js或图片会存储在files文件夹中。打开下载的html,去掉页眉,侧边栏等,最后得到我们需要的模版,将关键地方使用特殊字符串占位,替换后的模版效果如图:

  利用上述模版,替换后的网页效果,仅仅包含正文部分了。

  3.得到模板化的html博客正文之后,就可以轻松的将其编译成CHM文件了。具体的编译方式很简单,下面贴一下代码:

复制代码
#region 版权信息
/* ==============================================================================
   * 文 件 名: ChmHelp
   * 功能描述:Chm编译封装类
   * Copyright (c) 2013 武汉经纬视通科技有限公司
   * 创 建 人: alone
   * 创建时间: 2012/12/05 20:53:29
   * 修 改 人: alone
   * 修改时间: 2013/3/01 18:22:03
   * 修改描述: 使用hha.dll接口方法
   * 版    本: v1.0.0.0
   * ==============================================================================*/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Chen.Ext;
/* 变量含义 rootPath:待编译的文件夹 rootParent:rootPath的父目录
 * 1.将rootPath目录下的文件编译成CHM。
 * 2.将编译过程中产生的hhp、hhc、hhk文件均放在rootParent下。
 * 3.基于rootParent,获取待编译文件的相对路径(相对rootParent)。
 */
namespace Chen.Common
{
    /// <summary>
    /// Chm辅助类
    /// </summary>
    public class ChmHelp
    {
        #region 成员定义
        //Chm文件保存路径
        public string ChmFileName { get; set; }
        //Chm文件Titie
        public string Title { get; set; }
        //编译文件夹路径
        public string RootPath
        {
            get { return rootPath; }
            set
            {
                rootPath = Path.GetFullPath(value);
                //获取上级目录的完整路径
                //指定文件夹的父目录是作为关键字被文件的完整路径替换的,此时目录必须携带\\
                DirectoryInfo di = new DirectoryInfo(rootPath);
                rootParent = di.Parent.FullName + "\\";
            }
        }
        //默认页面 相对编译文件夹的路径
        public string DefaultPage
        {
            get//编译时路径是相对rootParent
            {
                var rootName = Path.GetFileName(rootPath);
                return rootName + "\\" + defaluePage;
            }
            set //赋值时路径是相对rootPath。
            {
                defaluePage = value;
            }
        }
        public int FileCount { get { return fileCount; } }
        //私有变量
        private string rootParent;
        private string rootPath;
        private string defaluePage;
        private int fileCount = 0;
        //CHM相关文件内容
        private StringBuilder hhcBody = new StringBuilder();
        private StringBuilder hhpBody = new StringBuilder();
        private StringBuilder hhkBody = new StringBuilder();
        private bool deleteTmp = true;
        //日志信息
        private StringBuilder sbMessage = new StringBuilder();
        public event Action<string> logHandle;
        #endregion

        #region hha 方法引入
        [DllImport("hha.dll")]
        private extern static void HHA_CompileHHP(string hhpFile, CompileLog g1, CompileLog g2, int stack);
        delegate bool CompileLog(string log);
        //编译信息
        private bool CompileLoging(string log)
        {
            if (logHandle != null) logHandle(log);
            return true;
        }
        private bool CompileProcess(string log)
        {
            return true;
        }
        #endregion

        #region 构造所需要的文件
        private void Create(string path)
        {
            //获取文件
            var strFileNames = Directory.GetFiles(path);
            //获取子目录
            var strDirectories = Directory.GetDirectories(path);
            //给该目录添加UL标记
            if (strFileNames.Length > 0 || strDirectories.Length > 0)
                hhcBody.AppendLine("    <UL>");
            //处理获取的文件
            foreach (string filename in strFileNames)
            {
                var fileItem = new StringBuilder();
                fileItem.AppendLine("    <LI> <OBJECT type=\"text/sitemap\">");
                fileItem.AppendLine("        <param name=\"Name\" value=\"{0}\">".FormatString(Path.GetFileNameWithoutExtension(filename)));
                fileItem.AppendLine("        <param name=\"Local\" value=\"{0}\">".FormatString(filename.Replace(rootParent, string.Empty)));
                fileItem.AppendLine("        <param name=\"ImageNumber\" value=\"11\">");
                fileItem.AppendLine("        </OBJECT>");
                //添加文件列表到hhp
                hhpBody.AppendLine(filename);
                hhcBody.Append(fileItem.ToString());
                hhkBody.Append(fileItem.ToString());
                //记录待编译文件总数
                fileCount++;
            }
            //遍历获取的目录
            foreach (string dirname in strDirectories)
            {
                if (dirname.Contains("content") || dirname.Contains("image")) continue;
                hhcBody.AppendLine("    <LI> <OBJECT type=\"text/sitemap\">");
                hhcBody.AppendLine("        <param name=\"Name\" value=\"{0}\">".FormatString(Path.GetFileName(dirname)));
                hhcBody.AppendLine("        <param name=\"ImageNumber\" value=\"1\">");
                hhcBody.AppendLine("        </OBJECT>");
                //递归遍历子文件夹
                Create(dirname);
            }
            //给该目录添加/UL标记
            if (strFileNames.Length > 0 || strDirectories.Length > 0)
            {
                hhcBody.AppendLine("    </UL>");
            }
        }
        private void CreateHHC()
        {
            var code = new StringBuilder();
            code.AppendLine("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">");
            code.AppendLine("<HTML>");
            code.AppendLine("<HEAD>");
            code.AppendLine("<meta name=\"GENERATOR\" content=\"EasyCHM.exe  www.zipghost.com\">");
            code.AppendLine("<!-- Sitemap 1.0 -->");
            code.AppendLine("</HEAD><BODY>");
            code.AppendLine("<OBJECT type=\"text/site properties\">");
            code.AppendLine("    <param name=\"ExWindow Styles\" value=\"0x200\">");
            code.AppendLine("    <param name=\"Window Styles\" value=\"0x800025\">");
            code.AppendLine("    <param name=\"Font\" value=\"MS Sans Serif,9,0\">");
            code.AppendLine("</OBJECT>");
            //遍历文件夹 构建hhc文件内容
            code.Append(hhcBody.ToString());
            code.AppendLine("</BODY></HTML>");
            //File.WriteAllText(Path.Combine(SourcePath, "chm.hhc"), code.ToString(), Encoding.GetEncoding("gb2312"));
            File.WriteAllText(".//chm.hhc", code.ToString(), Encoding.GetEncoding("gb2312"));
        }
        private void CreateHHP()
        {
            var code = new StringBuilder();
            code.AppendLine("[OPTIONS]");
            code.AppendLine("CITATION=Made by Chen");//制作人
            code.AppendLine("Compatibility=1.1 or later");//版本
            code.AppendLine(@"Compiled file=" + ChmFileName);//生成chm文件路径
            code.AppendLine("Contents file=chm.HHC");//hhc文件路径
            code.AppendLine("COPYRIGHT=www.jinwin.com");//版权所有
            code.AppendLine(@"Default topic={1}");//CHM文件的首页
            code.AppendLine("Default Window=Main");//目标文件窗体控制参数,这里跳转到Windows小节中,与其一致即可
            code.AppendLine("Display compile notes=Yes");//显示编译信息
            code.AppendLine("Display compile progress=Yes");//显示编译进度
            //code.AppendLine("Error log file=error.Log");//错误日志文件
            code.AppendLine("Full-text search=Yes");//是否支持全文检索信息
            code.AppendLine("Index file=chm.HHK");//hhk文件路径
            code.AppendLine("Title={0}");//CHM文件标题
            //code.AppendLine("Flat=NO");//编译文件不包括文件夹
            code.AppendLine("Enhanced decompilation=yes");//编译文件不包括文件夹
            code.AppendLine();
            code.AppendLine("[WINDOWS]");
            //例子中使用的参数 0x20 表示只显示目录和索引
            code.AppendLine("Main=\"{0}\",\"chm.hhc\",\"chm.hhk\",\"{1}\",\"{1}\",,,,,0x63520,180,0x104E, [0,0,745,509],0x0,0x0,,,,,0");
            //Easy Chm使用的参数 0x63520 表示目录索引搜索收藏夹
            //code.AppendLine("Main=\"{0}\",\"chm.HHC\",\"chm.HHK\",\"{1}\",\"{1}\",,,,,0x63520,271,0x304E,[0,0,745,509],,,,,,,0");
            code.AppendLine();
            code.AppendLine("[MERGE FILES]");
            code.AppendLine();
            code.AppendLine("[FILES]");
            code.Append(hhpBody.ToString());

            // File.WriteAllText(Path.Combine(SourcePath, "chm.hhp"), code.ToString().FormatString(Title, DefaultPage), Encoding.GetEncoding("gb2312"));
            File.WriteAllText(".//chm.hhp", code.ToString().FormatString(Title, DefaultPage), Encoding.GetEncoding("gb2312"));
        }
        private void CreateHHK()
        {
            var code = new StringBuilder();
            code.AppendLine("<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">");
            code.AppendLine("<HTML>");
            code.AppendLine("<HEAD>");
            code.AppendLine("<meta name=\"GENERATOR\" content=\"EasyCHM.exe  www.zipghost.com\">");
            code.AppendLine("<!-- Sitemap 1.0 -->");
            code.AppendLine("</HEAD><BODY>");
            code.AppendLine("<OBJECT type=\"text/site properties\">");
            code.AppendLine("    <param name=\"ExWindow Styles\" value=\"0x200\">");
            code.AppendLine("    <param name=\"Window Styles\" value=\"0x800025\">");
            code.AppendLine("    <param name=\"Font\" value=\"MS Sans Serif,9,0\">");
            code.AppendLine("</OBJECT>");
            code.AppendLine("<UL>");
            //遍历文件夹 构建hhc文件内容
            code.Append(hhkBody.ToString());
            code.AppendLine("</UL>");
            code.AppendLine("</BODY></HTML>");
            //File.WriteAllText(Path.Combine(SourcePath, "chm.hhk"), code.ToString(), Encoding.GetEncoding("gb2312"));
            File.WriteAllText(".//chm.hhk", code.ToString(), Encoding.GetEncoding("gb2312"));
        }
        #endregion

        /// <summary>
        /// 编译
        /// </summary>
        /// <returns></returns>
        public void Compile()
        {
            //准备hhp hhc hhk文件
            Create(RootPath);
            CreateHHC();
            CreateHHK();
            CreateHHP();
            var path = ".//chm.hhp";
            HHA_CompileHHP(path, CompileLoging, CompileProcess, 0);
            DeleteTmpFile();
        }
        /// <summary>
        /// 使用hhc.exe进行编译 暂时不使用该方式 局限性较大
        /// </summary>
        /// <param name="hhpPath"></param>
        private void CompileByHHC(string hhpPath)
        {
            var hhcPath = string.Empty;
            var program = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
            if (File.Exists(".//hhc.exe"))
            {
                hhcPath = ".//hhc.exe";
            }
            else if (File.Exists(program + @"\HTML Help Workshop\hhc.exe"))
            {
                hhcPath = program + @"\HTML Help Workshop\hhc.exe";
            }
            else if (File.Exists(program + @" (x86)\HTML Help Workshop\hhc.exe"))
            {
                hhcPath = program + @" (x86)\HTML Help Workshop\hhc.exe";
            }
            else
            {
                throw new Exception("未找到编译核心文件hhc.exe");
            }
            var process = new Process();//创建新的进程,用Process启动HHC.EXE来Compile一个CHM文件
            try
            {
                ProcessStartInfo processInfo = new ProcessStartInfo();
                processInfo.WindowStyle = ProcessWindowStyle.Hidden;
                processInfo.FileName = hhcPath;  //调入HHC.EXE文件 
                processInfo.Arguments = hhpPath;
                processInfo.UseShellExecute = false;
                processInfo.CreateNoWindow = false;
                process.StartInfo = processInfo;
                process.Start();
                process.WaitForExit(); //组件无限期地等待关联进程退出
                if (process.ExitCode == 0)
                {
                    throw new Exception("编译发生异常!");
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                process.Close();
            }
        }
        /// <summary>
        /// 反编译
        /// </summary>
        /// <returns></returns>
        public bool DeCompile()
        {
            //反编译时,Path作为CHM文件路径
            //得到chm文件的绝对路径
            string ExtportPath = Path.GetDirectoryName(ChmFileName);
            //命令参数含义
            //Path:导出的文件保存的路径
            //ChmPath:Chm文件所在的路径
            string cmd = " -decompile " + ExtportPath + " " + ChmFileName;//反编译命令
            Process p = Process.Start("hh.exe", cmd);//调用hh.exe进行反编译
            p.WaitForExit();
            return true;
        }
        //删除临时文件
        private void DeleteTmpFile()
        {
            if (deleteTmp)
            {
                var arr = new string[] { ".//chm.hhc", ".//chm.hhp", ".//chm.hhk" };
                foreach (var a in arr)
                {
                    if (File.Exists(a))
                    {
                        File.Delete(a);
                    }
                }
            }
        }
    }
}
复制代码
复制代码
            //编译CHM文档
            ChmHelp chm = new ChmHelp();
            chm.RootPath = ".//cnblogs";
            chm.ChmFileName =Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop ),channel.title+".chm");
            chm.DefaultPage = "博文目录.html";
            chm.Title = channel.title;
            chm.Compile();
复制代码

  到这里基本上就已经完成了,但是有一点需要改进。博客中插入的图片,是url地址,如果阅读该chm文件时,电脑没有联网,此时是影响阅读的。此时我们的工作就应该是下载博客正文所使用的图片,存储到本地,编译到CHM文件中。

  4.首先提取博客正文的url链接,并下载图片。图片分两种,手动插入的图片和博客园显示代码时使用的图片(展开代码,折叠代码,复制代码的图标),前者下载时需防止图片命名重复导致覆盖,后者如果已经下载,则无需重复下载。下载成功后,对博客正文进行替换,将引用图片的url链接替换成本地的相对途径。

复制代码
        /// <summary>
        /// 提取网页文件中的图片链接
        /// </summary>
        /// <param name="sHtmlText">html</param>
        /// <returns></returns>
        public static string[] GetHtmlImageUrls(string sHtmlText)
        {
            // 定义正则表达式用来匹配 img 标签     
            Regex regImg = new Regex(@"<img\b[^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<imgUrl>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>", RegexOptions.IgnoreCase);
            // 搜索匹配的字符串      
            MatchCollection matches = regImg.Matches(sHtmlText);
            int i = 0;
            string[] sUrlList = new string[matches.Count];
            // 取得匹配项列表        
            foreach (Match match in matches)
                sUrlList[i++] = match.Groups["imgUrl"].Value;
            return sUrlList;
        }
复制代码

  到这里,基本上全部完成了。

  程序使用的模版文件,全在content文件夹下,如果有朋友使用到自定义css,可以手动的更改模版。

下节预告

  我们既然能将自己的博客备份得到的xml文件,转换成chm文件,我们也同样可以将某个人的博客随笔备份成chm文件,参考《一键构造你的博客目录》,它可以得到某个博客下的所有随笔链接,既然能够得到链接,我们就可以等到博客的正文,因此我们同样将其转换成CHM文件。

  以啊汉的博客为例,示例文档下载,效果图预览:

  



   如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐】按钮。

   感谢阅读,希望这篇文章能给你带来帮助!

目录
相关文章
|
JavaScript API
markdown-it 插件如何写(二)
「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。
255 0
markdown-it 插件如何写(二)
|
1月前
|
存储 Docker 容器
Star 8.3k!强烈推荐这款强大的 PDF 文件处理工具,PDF处理它全包了!
Star 8.3k!强烈推荐这款强大的 PDF 文件处理工具,PDF处理它全包了!
|
11月前
|
PHP
thinkphp在富文本中获取简介
thinkphp在富文本中获取简介
77 0
|
前端开发 Linux Docker
NetCore实战:基于html生成pdf文件案例讲解
WkHtmlToPdfDotNet是基于本地 wkhtmltopdf封装的.NET Core类库,主要通过webkit引擎实现html页面转换为pdf文件。并且支持在Windows、Docker、Linux、MacOSX运行。
NetCore实战:基于html生成pdf文件案例讲解
|
C# Windows
C#编程学习15:应用程序帮助文档(chm/pdf)制作与C#调用
C#编程学习15:应用程序帮助文档(chm/pdf)制作与C#调用
C#编程学习15:应用程序帮助文档(chm/pdf)制作与C#调用
|
JavaScript API 索引
markdown-it 插件如何写(一)
「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」。
273 0
markdown-it 插件如何写(一)
|
程序员
分享几篇文章(PDF版)
后天就要回老家了,除了团团年估计也没有其他好玩的,就琢磨下点经典文章带回去看看。 Google一番找到了老赵很早以前的文章: http://www.cnblogs.com/JeffreyZhao/archive/2009/03/02/1401259.html 说来惭愧,里面的十余篇文章我只看过一篇,正好就补补知识。
628 0

相关实验场景

更多