背景
咱們在開發的過程當中,經常都會想記錄下來一些東西,能夠成文的,則以隨筆的形式發佈,那些不能成文的,例如某bug的解決方案,或者開發中的注意事項,甚至是某個SQL語句,以隻言片語的形式記錄在文章、日記裏,這樣,本身就能在不一樣的設備、終端上查看本身記錄的東西。css
博客園的文章,若是不設置在首頁顯示的話,我的以爲查看起來不是很方便。想到本身曾作了一個數據庫CHM文檔生成工具,因而,不論是隨筆,仍是文章,可否也經過CHM文檔的形式查看呢。想到這裏,個人需求就產生了。html
效果預覽
資源下載
可執行程序正則表達式
示例CHM文檔數據庫
源代碼json
開發思路
1.獲得博客內容。以前想過經過url請求的方式獲取到博客正文部分,可是後臺的文章或日記處理起來相對麻煩,因而採用了博客備份獲得的xml文件,按照xml文件結構,定義數據結構,讀取xml數據。怎麼讀取,這裏就不細講,若有須要,移步至《以讀取博客園隨筆備份爲例 將xml 序列化成json,再序列化成對象》。數據結構
2.遍歷博客的博文,而後將博文的html內容,以html形式的存儲。可是xml裏存儲的僅僅是博客的正文部份內容,整個頁面顯示框架是沒有的。此時,我打開任意個人任意一篇博客,而後將網頁文件所有下載到本地,對應的js或圖片會存儲在files文件夾中。打開下載的html,去掉頁眉,側邊欄等,最後獲得咱們須要的模版,將關鍵地方使用特殊字符串佔位,替換後的模版效果如圖:mvc
利用上述模版,替換後的網頁效果,僅僅包含正文部分了。框架
3.獲得模板化的html博客正文以後,就能夠輕鬆的將其編譯成CHM文件了。具體的編譯方式很簡單,下面貼一下代碼:ide
![](http://static.javashuo.com/static/loading.gif)
#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); } } } } } }
![](http://static.javashuo.com/static/loading.gif)
//編譯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文件。