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,如果哪位朋友发现了,还请留言告知,谢谢……



  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

hanjiangying

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

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

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

打赏作者

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

抵扣说明:

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

余额充值