vc写的chm合并工具(mergechm)


前一篇介绍了写的一个VC工程打包工具,转换打包好之后用QuickCHM编译成CHM帮助文件,但是如果以后又下载了
或者自己又写了一些程序,想把它们也加入到先前的CHM文件之中怎么办呢?总不能每次都把以前转换好的工程重新用
QuickCHM编译一遍,那样太麻烦了。于是想在网上找一个CHM合并工具,找了很久只在VckBase上找到一个Magic CHM Merge
的公具说是可以合并chm,但是不知道是因为我下载的是绿色版还是其他什么原因,这个工具老是用不起来,要么总是提示我
选择的文件不在一个工程文件夹内,要么就是合并中文CHM时莫名其妙的退出,有时候运气好能合并成功(郁闷了一两天)。
于是没办法自己用vc++写了一个CHM合并工具,它能将已经编译好的CHM合并到一个CHM文件中。和Magic CHM Merge
一样,这种合并其实并不是真正意义上的把多个CHM的内容合并到一个chm中,而只是把多个CHM文件中的主题的链接加入
到一个chm中,这样只要打开编译好的那个CHM帮助文件就能访问所有的内容。就像MSDN一样,用这个程序你也能打造自己的
MSDN。如果要真正的把多个CHM中的内容合并到一个CHM中,则需要先把所有需要合并的chm反编译,然后把反编译得到的文
件一起同一编译成一个CHM,这样太费时间也没必要。
工程源代码下载地址:http://download.csdn.net/source/2246858

程序运行图:
 
这个程序实际是利用Microsoft发布的CHM制作工具HTML Help Workshop来合并chm的。所以需要根据用户选择的要合并的CHM
生成相应的hhp工程文件和hhc主题列表文件,然后用HTML Help Workshop的控制台程序hhc.exe来编译生成合并的CHM文件(
hhc.exe运行时需要一个dll文件: itcc.dll,只要是安装了HTML Help Workshop都会有,本程序已经自带)。先说一下hhp工程文件
和hhc主题列表文件的格式:

hhp文件(HTML Help Workshop工程文件)内容:
[OPTIONS]
Compatibility=1.1 or later //编译器版本
Compiled file=VcKbase.chm //编译后的chm文件名
Contents file=vckbase.hhc //主题列表文件名
Default Font=宋体,11,134 //左边面板的字体
Default Window=Main
Default topic=hello.htm  //主页
Display compile progress=Yes 
Enhanced decompilation=Yes
Full-text search=Yes  //全文搜索
Index file=Vckbase.hhk //索引文件,可以没有
Language=0x804 中文(中国) //语言
Title=VcKbase合订本 //编译后的chm文件的标题

[WINDOWS]
Main="VcKbase合订本","vckbase.hhc",,"hello.htm",,,,,,0x21420,180,0x186e,[80,60,720,540],0x10B0000,,,,2,,0

[MERGE FILES]  //要合并的chm文件列表
vckbase01.chm
vckbase02.chm
vckbase03.chm
vckbase04.chm
[INFOTYPES]

hhc文件(主题类表文件)内容格式:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<meta name="GENERATOR" content="Microsoft&reg; HTML Help Workshop 4.1">
<!-- Sitemap 1.0 -->
</HEAD>
<BODY>
<OBJECT type="text/site properties"> 
<param name="FrameName" value="right">
<param name="ImageType" value="Book">
<param name="ExWindow Styles" value="0x800025">
</OBJECT>
<UL>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="vckbase01">
</OBJECT>
<OBJECT type="text/sitemap">
<param name="Merge" value="vckbase01.chm::/Toc1.hhc"> //toc1.hhc是主题列表文件,已经编译压缩到vckbase01.chm中
</OBJECT>
</LI>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="vckbase02">
</OBJECT>
<OBJECT type="text/sitemap">
<param name="Merge" value="vckbase02.chm::/toc.hhc">
</OBJECT>
</LI>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="vckbase03">
</OBJECT>
<OBJECT type="text/sitemap">
<param name="Merge" value="vckbase03.chm::/toc.hhc">
</OBJECT>
</LI>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="vckbase04">
</OBJECT>
<OBJECT type="text/sitemap">
<param name="Merge" value="vckbase04.chm::/toc.hhc">
</OBJECT>
</LI>
</UL>
</BODY>
</HTML>

通过以上这两个文件的例子不难看出,只要按照上面的格式生成了相应的hhp和hhc文件,然后调用hhc.exe就能生成合并的chm了,估计
magic Merge CHM的原理也是如此。但是有一个难点是如何知道已经编译在chm中的主题文件名,例如上面hhc文件里的一句:
vckbase01.chm::/Toc1.hhc,只有知道Toc1.hhc这个文件名才能正确链接合并vckbase01.chm。某个chm中包含的hhc文件到底叫什么,
是看不出来的,只有从vckbase01.chm文件中查找该文件名。通过分析了七八个chm文件,发现每个CHM文件从第0xe6这个字节开始往后
会有一段类似于配置的字符串,这段字符串里面就有我们需要的hhc文件名,只要从0xe6字节开始查找就能找到内部包含的hhc文件名(想了
半天也只想到这个笨办法)。后来又分析了几个中文chm的情况,发现这些字符串是UTF-8编码:
CString CMergeCHMDlg::ReadHHCName(CString strPath)
{
    char  path[255];
    WCharToAChar(strPath.GetBuffer(),path,255);
    FILE* fr=fopen(path,"rb");
    if(!fr)
    {
        return _T("读取错误!");
    }
    fseek(fr,0xe6,SEEK_SET);
    while(!feof(fr))
    {
        char c;
        fread(&c,sizeof(char),1,fr);
        if(c=='.')
        {
            fread(&c,sizeof(char),1,fr);
            if(c=='h'||c=='H')
            {
                fread(&c,sizeof(char),1,fr);
                if(c=='h'||c=='H')
                {
                    fread(&c,sizeof(char),1,fr);
                    if(c=='c'||c=='C')
                    {
                        BYTE be;
                        fread(&be,sizeof(BYTE),1,fr);
                        if(be==0x01)
                        {
                            fseek(fr,-1,SEEK_CUR);
                            long pos=ftell(fr);
                            fseek(fr,-5,SEEK_CUR);
                            fread(&c,sizeof(char),1,fr);
                            while(c!='/' && 0xe6<=ftell(fr))
                            {
                                fseek(fr,-2,SEEK_CUR);
                                fread(&c,sizeof(char),1,fr);
                            }
                            fseek(fr,-2,SEEK_CUR);
                            fread(&be,sizeof(BYTE),1,fr);
                            if(be<=0x20)
                            {
                                fseek(fr,1,SEEK_CUR);
                                long len=pos-ftell(fr);
                                char* hhc=(char*)calloc(len+4,sizeof(char));
                                fread(hhc,sizeof(char),len,fr);
                                wchar_t* whhc=(wchar_t*)calloc(len+4,sizeof(wchar_t));
                                DWORD flag=MultiByteToWideChar(CP_UTF8, 0, hhc, -1, NULL, 0);
                                MultiByteToWideChar (CP_UTF8, 0, hhc, -1, whhc, len+4);
                                CString str(whhc);
                                free(hhc);
                                free(whhc);
                                fclose(fr);
                                return str;
                            }
                            else
                            {
                                fseek(fr,pos,SEEK_SET);
                            }
                        }
                        else
                            fseek(fr,-4,SEEK_CUR);
                    }
                    else
                        fseek(fr,-3,SEEK_CUR);
                }
                else
                    fseek(fr,-2,SEEK_CUR);
            }
            else
                fseek(fr,-1,SEEK_CUR);
        }
    }
    fclose(fr);
    return _T("读取失败");
}

找到了文件名,其他的一切都好办了,接下来生成相应的hhc工程文件和hhc主题列表,调用hhc.exe编译就好了:
#include <locale>//头文件
CString  CMergeCHMDlg::CreateHHPFile()
{
    int n=m_strSavePath.ReverseFind('//');
    CString strFolder=m_strSavePath.Left(n);
    CString strPath=strFolder+_T("//")+m_saveName+_T(".hhp");

    CStdioFile fileW;
    fileW.Open(strPath,CFile::modeCreate | CFile::modeWrite);

    char* old_locale = _strdup( setlocale(LC_CTYPE,NULL) );
    setlocale( LC_CTYPE, "chs" );//设定

    fileW.WriteString(_T("[OPTIONS]/r/n"));
    fileW.WriteString(_T("Compatibility=1.1 or later/r/n"));
    fileW.WriteString(_T("Compiled file=")+m_saveName+_T(".chm/r/n"));
    fileW.WriteString(_T("Contents file=")+m_saveName+_T(".hhc/r/n"));
    fileW.WriteString(_T("Default Font=宋体,10,134/r/n"));
    fileW.WriteString(_T("Default Window=Main/r/n"));
    fileW.WriteString(_T("Default topic=")+m_strTopic+_T("/r/n"));
    fileW.WriteString(_T("Display compile progress=Yes/r/n"));
    fileW.WriteString(_T("Enhanced decompilation=Yes/r/n"));
    fileW.WriteString(_T("Full-text search=Yes/r/n"));
    fileW.WriteString(_T("Language=0x804 中文(中国)/r/n"));
    fileW.WriteString(_T("Title=")+m_strTitle+_T("/r/n/r/n"));
    fileW.WriteString(_T("[WINDOWS]/r/n"));
    fileW.WriteString(_T("Main=/"")+m_strTitle+_T("/",/"")+m_saveName+_T(".hhc/", ,/"")+m_strTopic+_T("/",/"")+m_strTopic+_T("/",,,,,0x21420,250,0x10304E,[80,60,1000,700],0x10B0000,,,,0,,0/r/n/r/n"));
    fileW.WriteString(_T("[MERGE FILES]/r/n"));
    int ncount=m_List.GetItemCount();
    for(int i=0;i<ncount;i++)
    {
        CString strText=m_List.GetItemText(i,2);
        int nsp=strText.ReverseFind('//');
        CString strName=strText.Right(strText.GetLength()-nsp-1);
        fileW.WriteString(strName+_T("/r/n"));
    }
    fileW.WriteString(_T("/r/n[INFOTYPES]"));
    setlocale( LC_CTYPE, old_locale );
    free( old_locale );//还原区域设定
    fileW.Close();
    return strPath;
}

CString CMergeCHMDlg::CreateHHCFile() //生成合并chm的hhc文件
{
    int n=m_strSavePath.ReverseFind('//');
    CString strFolder=m_strSavePath.Left(n);
    CString strPath=strFolder+_T("//")+m_saveName+_T(".hhc");

    CStdioFile fileW;
    fileW.Open(strPath,CFile::modeCreate | CFile::modeWrite);

    char* old_locale = _strdup( setlocale(LC_CTYPE,NULL) );
    setlocale( LC_CTYPE, "chs" );//设定

    fileW.WriteString(_T("<!DOCTYPE HTML PUBLIC /"-//IETF//DTD HTML//EN/">/r/n"));
    fileW.WriteString(_T("<HTML>/r/n<HEAD>/r/n"));
    fileW.WriteString(_T("<meta name=/"GENERATOR/" content=/"Microsoft&reg; HTML Help Workshop 4.1/">/r/n"));
    fileW.WriteString(_T("<!-- Sitemap 1.0 -->/r/n</HEAD>/r/n<BODY>/r/n<OBJECT type=/"text/site properties/">/r/n"));
    fileW.WriteString(_T("<param name=/"FrameName/" value=/"right/">/r/n"));
    fileW.WriteString(_T("<param name=/"ImageType/" value=/"Book/">/r/n"));
    fileW.WriteString(_T("<param name=/"ExWindow Styles/" value=/"0x800025/">/r/n</OBJECT>/r/n<UL>/r/n/r/n"));
    int ncount=m_List.GetItemCount();
    for(int i=0;i<ncount;i++)
    {
        fileW.WriteString(_T("<LI><OBJECT type=/"text/sitemap/">/r/n"));
        CString title=m_List.GetItemText(i,0);
        CString File=m_List.GetItemText(i,2);
        CString hhc=m_List.GetItemText(i,3);
        int nsp=File.ReverseFind('//');
        File=File.Right(File.GetLength()-nsp-1);
        fileW.WriteString(_T("<param name=/"Name/" value=/"")+title+_T("/">/r/n</OBJECT>/r/n<OBJECT type=/"text/sitemap/">/r/n"));
        fileW.WriteString(_T("<param name=/"Merge/" value=/"")+File+_T(":://")+hhc+_T("/">/r/n</OBJECT>/r/n</LI>/r/n/r/n"));
    }
    fileW.WriteString(_T("</UL>/r/n</BODY>/r/n</HTML>/r/n"));
    setlocale( LC_CTYPE, old_locale );
    free( old_locale );//还原区域设定
    fileW.Close();
    return strPath;
}


void CMergeCHMDlg::OnBnClickedMakemerge()  //点击“编译合并”按钮的消息函数
{
    // TODO: 在此添加控件通知处理程序代码
    UpdateData(TRUE);
    if(m_List.GetItemCount()<=0)
    {
        AfxMessageBox(_T("请先添加要合并的帮助文件!"));
        OnBnClickedAdd();
        return;
    }
    if(m_strTitle.IsEmpty())
    {
        AfxMessageBox(_T("请先填写合并后的标题!"));
        return ;
    } 
    if(m_strSavePath.IsEmpty())
    {
        AfxMessageBox(_T("请先选择合并后保存的位置!"));
        return ;
    }
    if(m_strTopic.IsEmpty())
    {
        AfxMessageBox(_T("请先设定主页文件!"));
        return;
    }
    if(!IsInOneDirectory())
    {
        //MessageBox(_T("需要合并的所有子文件和合并以后的帮助文件必须在同一文件夹内,请检查!"),_T("提示"),MB_ICONWARNING|MB_OK);
        return;
    }
    CString strhhp=CreateHHPFile();
    CString strhhc=CreateHHCFile();

    CString cmdline=m_CurrentFolder+_T("//hhc.exe  /"")+strhhp+_T("/""); //启动控制台程序的命令行
    STARTUPINFO si; 
    GetStartupInfo(&si);
    si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
    si.wShowWindow=SW_HIDE;
    PROCESS_INFORMATION ProcessInformation;
    int ret=CreateProcess(NULL, cmdline.GetBuffer(),NULL,NULL,1,0,NULL,NULL, &si,&ProcessInformation);
    
    CDialog* pDlg=new CDialog;
    pDlg->Create(IDD_TIPSDLG,this);
    pDlg->ShowWindow(SW_SHOW);
    WaitForSingleObject(ProcessInformation.hProcess,INFINITE);
    CloseHandle(ProcessInformation.hProcess);
    DeleteFile(strhhp.GetBuffer());
    DeleteFile(strhhc.GetBuffer());
    pDlg->DestroyWindow();
    delete pDlg;
    AfxMessageBox(_T("编译完成!"));
}

另外告诉某些想合并“VCKBase知识库在线杂志”所有chm的朋友,合并时把vckbase20.chm去掉,那个chm帮助文件本身就是
有问题的,所以会导致合并后的chm也会出现问题

可能有某些地方没有考虑到,程序存在某些bug,如果哪位朋友发现了,还请留言告知,谢谢……



前言 1. 什么是 TortoiseSVN? 2. TortoiseSVN 的特性 3. 许可协议 4. 开发 4.1. TortoiseSVN 的历史 4.2. 致谢 5. 阅读指南 6. 本文使用的术语 1. 开始 1.1. 安装 TortoiseSVN 1.1.1. 系统要求 1.1.2. 安装 1.2. 基本概念 1.3. 开始试用 1.3.1. 创建版本库 1.3.2. 导入项目 1.3.3. 检出工作副本 1.3.4. 进行修改 1.3.5. 添加更多的文件 1.3.6. 查看项目历史 1.3.7. 撤消更改 1.4. 继续前进 ... 2. 基本版本控制概念 2.1. 版本库 2.2. 版本模型 2.2.1. 文件共享的问题 2.2.2. 锁定-修改-解锁 方案 2.2.3. 复制-修改-合并 方案 2.2.4. Subversion 怎么做? 2.3. Subversion 实战 2.3.1. 工作副本 2.3.2. 版本库的 URL 2.3.3. 修订版本 2.3.4. 工作副本怎样跟踪版本库 2.4. 摘要 3. 版本库 3.1. 创建版本库 3.1.1. 使用命令行工具创建版本库 3.1.2. 使用 TortoiseSVN 创建版本库 3.1.3. 本地访问版本库 3.1.4. 访问网络共享磁盘上的版本库 3.1.5. 版本库布局 3.2. 版本库备份 3.3. 服务器端钩子脚本 3.4. 检出链接 3.5. 访问版本库 4. 日常使用指南 4.1. 基本特性 4.1.1. 图标重载 4.1.2. 右键菜单 4.1.3. 拖放 4.1.4. 常用快捷方式 4.1.5. 认证 4.1.6. 最大化窗口 4.2. 导入数据到版本库 4.2.1. 导入 4.2.2. 导入适当的位置 4.2.3. 专用文件 4.3. 检出工作副本 4.3.1. 检出深度 4.4. 将你的修改提交到版本库 4.4.1. 提交对话框 4.4.2. 修改列表 4.4.3. 从提交列表中排除项目 4.4.4. 提交日志信息 4.4.5. 提交进程 4.5. 用来自别人的修改更新你的工作副本 4.6. 解决冲突 4.6.1. 文件冲突 4.6.2. 属性冲突 4.6.3. 树冲突 4.6.3.1. 本地删除,当更新时有更改进入 4.6.3.2. 本地更改,当更新时有删除进入 4.6.3.3. 本地删除,当更新时有删除进入 4.6.3.4. 本地缺少,当合并时有更改进入 4.6.3.5. 本地更改,当合并时有删除进入 4.6.3.6. 本地删除,当合并时有删除进入 4.6.3.7. 其它树冲突 4.7. 获得状态信息 4.7.1. 图标重载 4.7.2. 详细状态 4.7.3. 在 Windows 资源管理器中的 TortoiseSVN 列 4.7.4. 本地与远程状态 4.7.5. 查看差别 4.8. 修改列表 4.9. 版本日志对话框 4.9.1. 调用版本日志对话框 4.9.2. 版本日志动作 4.9.3. 获得更多信息 4.9.4. 获取更多的日志信息 4.9.5. 当前工作副本的版本 4.9.6. 合并跟踪特性 4.9.7. 修改日志消息和作者 4.9.8. 过滤日志信息 4.9.9. 统计信息 4.9.9.1. 统计页 4.9.9.2. 作者提交次数统计页 4.9.9.3. 按日期提交统计页 4.9.10. 离线方式 4.9.11. 刷新视图 4.10. 查看差异 4.10.1. 文件差异 4.10.2. 行结束符和空白选项 4.10.3. 比较文件夹 4.10.4. 使用 TortoiseIDiff 进行比较的图像 4.10.5. Diffing Office Documents 4.10.6. 其他的比较/合并工具 4.11. 添加新文件和目录 4.12. 复制/移动/重命名文件和文件夹 4.13. 忽略文件和目录 4.13.1. 忽略列表中的模式匹配 4.14. 删除、移动和改名 4.14.1. 正在删除文件/文件夹 4.14.2. 移动文件和文件夹 4.14.3. 处理文件名称大小冲突 4.14.4. 修复文件改名 4.14.5. 删除未版本控制的文件 4.15. 撤消更改 4.16. 清理 4.17. 项目设置 4.17.1. Subversion 属性 4.17.1.1. svn:keywords 4.17.1.2. 增加和编辑属性 4.17.1.3. 导出和导入属性 4.17.1.4. 二进制属性 4.17.1.5. 自动属性设置 4.17.2. TortoiseSVN 项目属性 4.17.3. 属性编辑器 4.17.3.1. 外部条目 4.17.3.2. SVN 关键字 4.17.3.3. EOL 样式 4.1
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hanjiangying

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值