1 /******************************************************************************
5 * Copyright (C) 1997-2012 by Dimitri van Heesch.
7 * Permission to use, copy, modify, and distribute this software and its
8 * documentation under the terms of the GNU General Public License is hereby
9 * granted. No representations are made about the suitability of this software
10 * for any purpose. It is provided "as is" without express or implied warranty.
11 * See the GNU General Public License for more details.
13 * Documents produced by Doxygen are derivative works derived from the
14 * input used in their production; they are not affected by this license.
16 * The original version of this file is largely based on a contribution from
17 * Harm van der Heijden.
25 #include "qtextcodec.h"
35 //----------------------------------------------------------------------------
37 /** Class representing a field in the HTML help index. */
47 /** Sorted dictionary of IndexField objects. */
48 class IndexFieldSDict : public SDict<IndexField>
51 IndexFieldSDict() : SDict<IndexField>(17) {}
53 int compareItems(GCI item1, GCI item2)
55 return stricmp(((IndexField *)item1)->name,((IndexField *)item2)->name);
59 /** A helper class for HtmlHelp that manages a two level index in
65 HtmlHelpIndex(HtmlHelp *help);
67 void addItem(const char *first,const char *second,
68 const char *url, const char *anchor,
69 bool hasLink,bool reversed);
70 void writeFields(FTextStream &t);
72 IndexFieldSDict *dict;
76 /*! Constructs a new HtmlHelp index */
77 HtmlHelpIndex::HtmlHelpIndex(HtmlHelp *help) : m_help(help)
79 dict = new IndexFieldSDict;
80 dict->setAutoDelete(TRUE);
83 /*! Destroys the HtmlHelp index */
84 HtmlHelpIndex::~HtmlHelpIndex()
89 /*! Stores an item in the index if it is not already present.
90 * Items are stored in alphetical order, by sorting on the
91 * concatenation of \a level1 and \a level2 (if present).
93 * \param level1 the string at level 1 in the index.
94 * \param level2 the string at level 2 in the index (or 0 if not applicable).
95 * \param url the url of the documentation (without .html extension).
96 * \param anchor the anchor of the documentation within the page.
97 * \param hasLink if true, the url (without anchor) can be used in the
98 * level1 item, when writing the header of a list of level2 items.
99 * \param reversed TRUE if level1 is the member name and level2 the compound
102 void HtmlHelpIndex::addItem(const char *level1,const char *level2,
103 const char *url,const char *anchor,bool hasLink,
106 QCString key = level1;
107 if (level2) key+= (QCString)"?" + level2;
108 if (key.find(QRegExp("@[0-9]+"))!=-1) // skip anonymous stuff
112 if (dict->find(key)==0) // new key
114 //printf(">>>>>>>>> HtmlHelpIndex::addItem(%s,%s,%s,%s)\n",
115 // level1,level2,url,anchor);
116 IndexField *f = new IndexField;
121 f->reversed = reversed;
126 static QCString field2URL(const IndexField *f,bool checkReversed)
128 QCString result = f->url + Doxygen::htmlFileExtension;
129 if (!f->anchor.isEmpty() && (!checkReversed || f->reversed))
131 result+="#"+f->anchor;
136 /*! Writes the sorted list of index items into a html like list.
138 * An list of calls with <code>name = level1,level2</code> as follows:
148 * Will result in the following list:
151 * a1 -> link to url if hasLink==TRUE
152 * b1 -> link to url#anchor
153 * b2 -> link to url#anchor
154 * a2 -> link to url if hasLink==TRUE
155 * b1 -> link to url#anchor
156 * b2 -> link to url#anchor
157 * a3 -> link to url if hasLink==TRUE
158 * a4 -> link to url if hasLink==TRUE
159 * b1 -> link to url#anchor
162 void HtmlHelpIndex::writeFields(FTextStream &t)
165 IndexFieldSDict::Iterator ifli(*dict);
168 bool level2Started=FALSE;
169 for (;(f=ifli.current());++ifli)
171 QCString level1,level2;
173 if ((i=f->name.find('?'))!=-1)
175 level1 = f->name.left(i);
176 level2 = f->name.right(f->name.length()-i-1);
180 level1 = f->name.copy();
183 if (level1!=lastLevel1)
184 { // finish old list at level 2
185 if (level2Started) t << " </UL>" << endl;
189 // Added this code so that an item with only one subitem is written
190 // without any subitem.
192 // a1, b1 -> will create only a1, not separate subitem for b1
196 IndexField* fnext = ++ifli;
199 nextLevel1 = fnext->name.left(fnext->name.find('?'));
202 if (level1 != nextLevel1)
208 if (level2.isEmpty())
210 t << " <LI><OBJECT type=\"text/sitemap\">";
211 t << "<param name=\"Local\" value=\"" << field2URL(f,TRUE);
213 t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
220 t << " <LI><OBJECT type=\"text/sitemap\">";
221 t << "<param name=\"Local\" value=\"" << field2URL(f,TRUE);
223 t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
228 t << " <LI><OBJECT type=\"text/sitemap\">";
229 t << "<param name=\"See Also\" value=\"" << m_help->recode(level1) << "\">";
230 t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
235 if (!level2Started && !level2.isEmpty())
236 { // start new list at level 2
237 t << " <UL>" << endl;
240 else if (level2Started && level2.isEmpty())
241 { // end list at level 2
242 t << " </UL>" << endl;
247 t << " <LI><OBJECT type=\"text/sitemap\">";
248 t << "<param name=\"Local\" value=\"" << field2URL(f,FALSE);
250 t << "<param name=\"Name\" value=\"" << m_help->recode(level2) << "\">"
253 lastLevel1 = level1.copy();
255 if (level2Started) t << " </UL>" << endl;
258 //----------------------------------------------------------------------------
260 HtmlHelp *HtmlHelp::theInstance = 0;
262 /*! Constructs an html object.
263 * The object has to be \link initialize() initialized\endlink before it can
266 HtmlHelp::HtmlHelp() : indexFileDict(1009)
271 index = new HtmlHelpIndex(this);
272 m_fromUtf8 = (void *)(-1);
275 HtmlHelp::~HtmlHelp()
277 if (m_fromUtf8!=(void *)(-1)) portable_iconv_close(m_fromUtf8);
280 /*! return a reference to the one and only instance of this class.
282 HtmlHelp *HtmlHelp::getInstance()
284 if (theInstance==0) theInstance = new HtmlHelp;
289 static QDict<QCString> s_languageDict;
291 /*! This will create a contents file (index.hhc) and a index file (index.hhk)
292 * and write the header of those files.
293 * It also creates a project file (index.hhp)
296 void HtmlHelp::initialize()
298 const char *str = Config_getString("CHM_INDEX_ENCODING");
299 if (!str) str = "CP1250"; // use safe and likely default
300 m_fromUtf8 = portable_iconv_open(str,"UTF-8");
301 if (m_fromUtf8==(void *)(-1))
303 err("Error: unsupported character conversion for CHM_INDEX_ENCODING: '%s'->'UTF-8'\n", str);
307 /* open the contents file */
308 QCString fName = Config_getString("HTML_OUTPUT") + "/index.hhc";
309 cf = new QFile(fName);
310 if (!cf->open(IO_WriteOnly))
312 err("Could not open file %s for writing\n",fName.data());
315 /* Write the header of the contents file */
317 cts << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
318 "<HTML><HEAD></HEAD><BODY>\n"
319 "<OBJECT type=\"text/site properties\">\n"
320 "<param name=\"FrameName\" value=\"right\">\n"
324 /* open the contents file */
325 fName = Config_getString("HTML_OUTPUT") + "/index.hhk";
326 kf = new QFile(fName);
327 if (!kf->open(IO_WriteOnly))
329 err("Could not open file %s for writing\n",fName.data());
332 /* Write the header of the contents file */
334 kts << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
335 "<HTML><HEAD></HEAD><BODY>\n"
336 "<OBJECT type=\"text/site properties\">\n"
337 "<param name=\"FrameName\" value=\"right\">\n"
341 /* language codes for Html help
345 0xC09 English (Australia)
346 0x809 English (Britain)
347 0x1009 English (Canada)
348 0x1809 English (Ireland)
349 0x1409 English (New Zealand)
350 0x1C09 English (South Africa)
351 0x409 English (United States)
360 0x816 Portuguese(Portugal)
361 0x416 Portuguese(Brazil)
363 0x80A Spanish(Mexico)
364 0xC0A Spanish(Modern Sort)
365 0x40A Spanish(Traditional Sort)
371 0x404 Chinese (Taiwan)
380 0x81A Serbian (Serbia, Latin)
386 0xC01 Arabic (Egypt) - I don't know which version of arabic is used inside translator_ar.h ,
387 so I have chosen Egypt at random
390 s_languageDict.setAutoDelete(TRUE);
391 s_languageDict.clear();
392 s_languageDict.insert("czech", new QCString("0x405 Czech"));
393 s_languageDict.insert("danish", new QCString("0x406 Danish"));
394 s_languageDict.insert("dutch", new QCString("0x413 Dutch"));
395 s_languageDict.insert("finnish", new QCString("0x40B Finnish"));
396 s_languageDict.insert("french", new QCString("0x40C French"));
397 s_languageDict.insert("german", new QCString("0x407 German"));
398 s_languageDict.insert("greek", new QCString("0x408 Greece"));
399 s_languageDict.insert("hungarian", new QCString("0x40E Hungarian"));
400 s_languageDict.insert("italian", new QCString("0x410 Italian"));
401 s_languageDict.insert("norwegian", new QCString("0x814 Norwegian"));
402 s_languageDict.insert("polish", new QCString("0x415 Polish"));
403 s_languageDict.insert("portuguese", new QCString("0x816 Portuguese(Portugal)"));
404 s_languageDict.insert("brazil", new QCString("0x416 Portuguese(Brazil)"));
405 s_languageDict.insert("russian", new QCString("0x419 Russian"));
406 s_languageDict.insert("spanish", new QCString("0x40A Spanish(Traditional Sort)"));
407 s_languageDict.insert("swedish", new QCString("0x41D Swedish"));
408 s_languageDict.insert("turkish", new QCString("0x41F Turkey"));
409 s_languageDict.insert("japanese", new QCString("0x411 Japanese"));
410 s_languageDict.insert("japanese-en", new QCString("0x411 Japanese"));
411 s_languageDict.insert("korean", new QCString("0x412 Korean"));
412 s_languageDict.insert("korean-en", new QCString("0x412 Korean"));
413 s_languageDict.insert("chinese", new QCString("0x804 Chinese (PRC)"));
414 s_languageDict.insert("chinese-traditional", new QCString("0x404 Chinese (Taiwan)"));
417 s_languageDict.insert("indonesian", new QCString("0x412 Indonesian"));
418 s_languageDict.insert("croatian", new QCString("0x41A Croatian"));
419 s_languageDict.insert("romanian", new QCString("0x418 Romanian"));
420 s_languageDict.insert("slovene", new QCString("0x424 Slovenian"));
421 s_languageDict.insert("slovak", new QCString("0x41B Slovak"));
422 s_languageDict.insert("ukrainian", new QCString("0x422 Ukrainian"));
423 s_languageDict.insert("serbian", new QCString("0x81A Serbian (Serbia, Latin)"));
424 s_languageDict.insert("catalan", new QCString("0x403 Catalan"));
425 s_languageDict.insert("lithuanian", new QCString("0x427 Lithuanian"));
426 s_languageDict.insert("afrikaans", new QCString("0x436 Afrikaans"));
427 s_languageDict.insert("vietnamese", new QCString("0x42A Vietnamese"));
428 s_languageDict.insert("persian", new QCString("0x429 Persian (Iran)"));
429 s_languageDict.insert("arabic", new QCString("0xC01 Arabic (Egypt)"));
433 static QCString getLanguageString()
435 if (!theTranslator->idLanguage().isEmpty())
437 QCString *s = s_languageDict[theTranslator->idLanguage()];
444 return "0x409 English (United States)";
449 void HtmlHelp::createProjectFile()
451 /* Write the project file */
452 QCString fName = Config_getString("HTML_OUTPUT") + "/index.hhp";
454 if (f.open(IO_WriteOnly))
458 QCString indexName="index"+Doxygen::htmlFileExtension;
459 //if (Config_getBool("GENERATE_TREEVIEW")) indexName="main"+Doxygen::htmlFileExtension;
461 if (!Config_getString("CHM_FILE").isEmpty())
463 t << "Compiled file=" << Config_getString("CHM_FILE") << "\n";
465 t << "Compatibility=1.1\n"
466 "Full-text search=Yes\n"
467 "Contents file=index.hhc\n"
468 "Default Window=main\n"
469 "Default topic=" << indexName << "\n"
470 "Index file=index.hhk\n"
471 "Language=" << getLanguageString() << endl;
472 if (Config_getBool("BINARY_TOC")) t << "Binary TOC=YES\n";
473 if (Config_getBool("GENERATE_CHI")) t << "Create CHI file=YES\n";
474 t << "Title=" << recode(Config_getString("PROJECT_NAME")) << endl << endl;
476 t << "[WINDOWS]" << endl;
478 // NOTE: the 0x10387e number is a set of bits specifying the buttons
479 // which should appear in the CHM viewer; that specific value
480 // means "show all buttons including the font-size one";
481 // the font-size one is not normally settable by the HTML Help Workshop
482 // utility but the way to set it is described here:
483 // http://support.microsoft.com/?scid=kb%3Ben-us%3B240062&x=17&y=18
484 t << "main=\"" << recode(Config_getString("PROJECT_NAME")) << "\",\"index.hhc\","
485 "\"index.hhk\",\"" << indexName << "\",\"" <<
486 indexName << "\",,,,,0x23520,,0x10387e,,,,,,,,0" << endl << endl;
488 t << "[FILES]" << endl;
489 char *s = indexFiles.first();
493 s = indexFiles.next();
496 // items not found by the html help compiler scan.
497 t << "tabs.css" << endl;
498 t << "tab_a.png" << endl;
499 t << "tab_b.png" << endl;
500 t << "tab_h.png" << endl;
501 t << "tab_s.png" << endl;
502 t << "nav_h.png" << endl;
503 t << "nav_f.png" << endl;
504 t << "bc_s.png" << endl;
505 if (Config_getBool("HTML_DYNAMIC_SECTIONS"))
507 t << "open.png" << endl;
508 t << "closed.png" << endl;
510 if (Config_getBool("GENERATE_HTMLHELP"))
512 t << "ftv2blank.png" << endl;
513 t << "ftv2doc.png" << endl;
514 t << "ftv2folderclosed.png" << endl;
515 t << "ftv2folderopen.png" << endl;
516 t << "ftv2lastnode.png" << endl;
517 t << "ftv2link.png" << endl;
518 t << "ftv2mlastnode.png" << endl;
519 t << "ftv2mnode.png" << endl;
520 t << "ftv2node.png" << endl;
521 t << "ftv2plastnode.png" << endl;
522 t << "ftv2pnode.png" << endl;
523 t << "ftv2vertline.png" << endl;
525 if (Config_getBool("SEARCHENGINE"))
527 t << "search_l.png" << endl;
528 t << "search_m.png" << endl;
529 t << "search_r.png" << endl;
530 if (Config_getBool("SERVER_BASED_SEARCH"))
532 t << "mag.png" << endl;
536 t << "mag_sel.png" << endl;
537 t << "close.png" << endl;
542 for (i=0;i<imageFiles.count();i++)
544 t << imageFiles.at(i) << endl;
550 err("Could not open file %s for writing\n",fName.data());
554 void HtmlHelp::addIndexFile(const char *s)
556 if (indexFileDict.find(s)==0)
558 indexFiles.append(s);
559 indexFileDict.insert(s,(void *)0x8);
563 /*! Finalizes the HTML help. This will finish and close the
564 * contents file (index.hhc) and the index file (index.hhk).
567 void HtmlHelp::finalize()
569 // end the contents file
577 index->writeFields(kts);
579 // end the index file
588 s_languageDict.clear();
591 /*! Increase the level of the contents hierarchy.
592 * This will start a new unnumbered HTML list in contents file.
593 * \sa decContentsDepth()
595 void HtmlHelp::incContentsDepth()
597 int i; for (i=0;i<dc+1;i++) cts << " ";
602 /*! Decrease the level of the contents hierarchy.
603 * This will end the unnumber HTML list.
604 * \sa incContentsDepth()
606 void HtmlHelp::decContentsDepth()
608 int i; for (i=0;i<dc;i++) cts << " ";
613 QCString HtmlHelp::recode(const QCString &s)
615 int iSize = s.length();
616 int oSize = iSize*4+1;
617 QCString output(oSize);
618 size_t iLeft = iSize;
619 size_t oLeft = oSize;
620 char *iPtr = s.data();
621 char *oPtr = output.data();
622 if (!portable_iconv(m_fromUtf8,&iPtr,&iLeft,&oPtr,&oLeft))
625 output.resize(oSize+1);
626 output.at(oSize)='\0';
635 /*! Add an list item to the contents file.
636 * \param isDir boolean indicating if this is a dir or file entry
637 * \param name the name of the item.
638 * \param ref the URL of to the item.
639 * \param file the file in which the item is defined.
640 * \param anchor the anchor of the item.
641 * \param separateIndex not used.
642 * \param addToNavIndex not used.
643 * \param def not used.
645 void HtmlHelp::addContentsItem(bool isDir,
647 const char * /*ref*/,
650 bool /* separateIndex */,
651 bool /* addToNavIndex */,
652 Definition * /* def */)
654 // If we're using a binary toc then folders cannot have links.
655 if(Config_getBool("BINARY_TOC") && isDir)
660 int i; for (i=0;i<dc;i++) cts << " ";
661 cts << "<LI><OBJECT type=\"text/sitemap\">";
662 cts << "<param name=\"Name\" value=\"" << recode(name) << "\">";
663 if (file) // made file optional param - KPW
665 if (file && (file[0]=='!' || file[0]=='^')) // special markers for user defined URLs
667 cts << "<param name=\"";
668 if (file[0]=='^') cts << "URL"; else cts << "Local";
669 cts << "\" value=\"";
674 cts << "<param name=\"Local\" value=\"";
675 cts << file << Doxygen::htmlFileExtension;
676 if (anchor) cts << "#" << anchor;
680 cts << "<param name=\"ImageNumber\" value=\"";
681 if (isDir) // added - KPW
683 cts << (int)BOOK_CLOSED ;
690 cts << "</OBJECT>\n";
694 void HtmlHelp::addIndexItem(Definition *context,MemberDef *md,
699 static bool separateMemberPages = Config_getBool("SEPARATE_MEMBER_PAGES");
700 if (context==0) // global member
702 if (md->getGroupDef())
703 context = md->getGroupDef();
704 else if (md->getFileDef())
705 context = md->getFileDef();
707 if (context==0) return; // should not happen
709 QCString cfname = md->getOutputFileBase();
710 QCString cfiname = context->getOutputFileBase();
711 QCString level1 = context->name();
712 QCString level2 = md->name();
713 QCString contRef = separateMemberPages ? cfname : cfiname;
714 QCString memRef = cfname;
715 QCString anchor = md->anchor();
716 index->addItem(level1,level2,contRef,anchor,TRUE,FALSE);
717 index->addItem(level2,level1,memRef,anchor,TRUE,TRUE);
721 QCString level1 = word ? QCString(word) : context->name();
722 index->addItem(level1,0,context->getOutputFileBase(),0,TRUE,FALSE);
726 void HtmlHelp::addImageFile(const char *fileName)
728 imageFiles.append(fileName);