使用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 HdhCmsTestzipghost测试数据\"> " );
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=HdhCmsTestjinwin测试数据 " ); // 版权所有
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 HdhCmsTestzipghost测试数据\"> " );
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文件。
以 啊汉 的博客为例, 示例文档下载 ,效果图预览:
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的 【 推荐 】按钮。
感谢阅读,希望这篇文章能给你带来帮助!
分类: C#
作者: Leo_wl
出处: http://HdhCmsTestcnblogs测试数据/Leo_wl/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
版权信息