zoukankan      html  css  js  c++  java
  • C#把博客园编译成CHM文档阅读

    使用CHM文档 阅读随笔

    背景

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

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

    效果预览

    资源下载

      可执行程序

      示例CHM文档

      源代码

    开发思路

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

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

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

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

    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 Workshophhc.exe"))
                {
                    hhcPath = program + @"HTML Help Workshophhc.exe";
                }
                else if (File.Exists(program + @" (x86)HTML Help Workshophhc.exe"))
                {
                    hhcPath = program + @" (x86)HTML Help Workshophhc.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[^<>]*?src[s	
    ]*=[s	
    ]*[""']?[s	
    ]*(?<imgUrl>[^s	
    ""'<>]*)[^<>]*?/?[s	
    ]*>", 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文件。

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

      

    出处:https://www.cnblogs.com/codealone/archive/2013/04/19/3029055.html

    =======================================================================================

    使用CHM文档 采集随笔(续)

    背景

      上篇说到我们可以将自己的博客内随笔/文章/日记备份得到的xml 转换成CHM文档,如果我们希望将某个大牛的博客随笔全部导出,这个能不能实现呢?写在这里算是废话了,既然有了这篇博客,那么这个问题,一定是可以解决的。

    资源下载

      可执行程序

      源代码

      示例文档截图(路过秋天):

          

    开发思路

      1.根据博客园ID得到随笔类别,如地址为 http://www.cnblogs.com/cyq1162/,则博客园ID为cyq1162,请求页面http://www.cnblogs.com/cyq1162/mvc/blog/sidecolumn.aspx。请求结果如下:

         

      通过正则匹配到该页面的链接,以某个链接为例,http://www.cnblogs.com/cyq1162/category/268820.html,其中包含cyq1162/category,基于这样的规则,我们可以得到所有的随笔分类链接。

      2.得到随笔分类链接之后,则请求该链接的内容,得到该随笔下的所有文章链接。文章链接,以某个链接为例,http://www.cnblogs.com/cyq1162/archive/2013/03/17/2964746.html,其中包含cyq1162/archive,基于这样的规则,我们可以得到所有的文章链接。

      3.得到文章的链接,这样就能得到文章正文。我们要获取的有文章标题、文章正文、发布时间。这里没有去尝试获取文章作者,不好获取。前面指的需要获取的3个内容,在某个明确id的节点里。这里使用了HtmlAgilityPack进行HTML解析,感觉非常方便,可以直接根据ID得到元素,然后获取它的内容。解析代码如下:

    复制代码
                                //下载随笔内容 替换后保存本地
                                var contentCode = GetContent(articleUrl);//获取随笔内容
                                HtmlDocument htmlCode = new HtmlDocument();
                                htmlCode.LoadHtml(contentCode);
                                var titleNode = htmlCode.GetElementbyId("cb_post_title_url");
                                var postBody = htmlCode.GetElementbyId("cnblogs_post_body");
                                var postDate = htmlCode.GetElementbyId("post-date");
                                //var topics = htmlCode.GetElementbyId("topics");
                                var localHtml = template
                           .Replace("{channelTitle}", titleNode.InnerText)//博文标题
                           .Replace("{preContent}", DownImage(postBody.InnerHtml))//博文内容
                           .Replace("{channelHref}", titleNode.GetAttributeValue("href", "#"))//博文地址
                           .Replace("{channelLink}", userId + ".cnblogs.com")//博客地址
                           .Replace("{channelAuthor}", userId);//博文作者
    复制代码

      4.下一步则是编译CHM了,这里就不重复介绍了。

      其中参考啊汉的博文 《一键构造你的博客目录》 构造了随笔目录。

      代码逻辑就这么多,比较简单,希望大伙喜欢。如果设置首页不显示随笔分类的话,是无法采集的,若是博客引用了自定义样式,需要手动添加该样式在。有其他的问题,可以向我反馈,也可以自己下载代码调试看看。

    出处:https://www.cnblogs.com/codealone/archive/2013/04/23/3037780.html

  • 相关阅读:
    Spring mvc配置
    css选择器
    网页全屏背景设计
    2018年的第一篇
    总结2016,规划2017
    Jenkins上Git ssh登陆配置
    Jenkins构建项目,JAVA_HOME is not defined correctly
    运行Jmeter.bat出错:Not able to find java executor or version. Please check your installation. errorlevel=2
    Selenium获取input值的两种方法:WebElement.getAttribute("value")和WebElement.getText()
    Jmeter
  • 原文地址:https://www.cnblogs.com/mq0036/p/12652992.html
Copyright © 2011-2022 走看看