1 /******************************************************************************
5 * Copyright (C) 1997-2015 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.
27 #include "qtextcodec.h"
36 #include "memberdef.h"
40 //----------------------------------------------------------------------------
42 /** Class representing a field in the HTML help index. */
52 /** Sorted dictionary of IndexField objects. */
53 class IndexFieldSDict : public SDict<IndexField>
56 IndexFieldSDict() : SDict<IndexField>(17) {}
59 int compareValues(const IndexField *item1, const IndexField *item2) const
61 return qstricmp(item1->name,item2->name);
65 /** A helper class for HtmlHelp that manages a two level index in
71 HtmlHelpIndex(HtmlHelp *help);
73 void addItem(const char *first,const char *second,
74 const char *url, const char *anchor,
75 bool hasLink,bool reversed);
76 void writeFields(FTextStream &t);
78 IndexFieldSDict *dict;
82 /*! Constructs a new HtmlHelp index */
83 HtmlHelpIndex::HtmlHelpIndex(HtmlHelp *help) : m_help(help)
85 dict = new IndexFieldSDict;
86 dict->setAutoDelete(TRUE);
89 /*! Destroys the HtmlHelp index */
90 HtmlHelpIndex::~HtmlHelpIndex()
95 /*! Stores an item in the index if it is not already present.
96 * Items are stored in alphetical order, by sorting on the
97 * concatenation of \a level1 and \a level2 (if present).
99 * \param level1 the string at level 1 in the index.
100 * \param level2 the string at level 2 in the index (or 0 if not applicable).
101 * \param url the url of the documentation (without .html extension).
102 * \param anchor the anchor of the documentation within the page.
103 * \param hasLink if true, the url (without anchor) can be used in the
104 * level1 item, when writing the header of a list of level2 items.
105 * \param reversed TRUE if level1 is the member name and level2 the compound
108 void HtmlHelpIndex::addItem(const char *level1,const char *level2,
109 const char *url,const char *anchor,bool hasLink,
112 QCString key = level1;
113 if (level2) key+= (QCString)"?" + level2;
114 if (key.find(QRegExp("@[0-9]+"))!=-1) // skip anonymous stuff
118 if (dict->find(key+anchor)==0) // new key
120 //printf(">>>>>>>>> HtmlHelpIndex::addItem(%s,%s,%s,%s)\n",
121 // level1,level2,url,anchor);
122 IndexField *f = new IndexField;
127 f->reversed = reversed;
128 dict->append(key+anchor,f);
132 static QCString field2URL(const IndexField *f,bool checkReversed)
134 QCString result = f->url + Doxygen::htmlFileExtension;
135 if (!f->anchor.isEmpty() && (!checkReversed || f->reversed))
137 // HTML Help needs colons in link anchors to be escaped in the .hhk file.
138 result+="#"+substitute(f->anchor,":","%3A");
143 /*! Writes the sorted list of index items into a html like list.
145 * An list of calls with <code>name = level1,level2</code> as follows:
155 * Will result in the following list:
158 * a1 -> link to url if hasLink==TRUE
159 * b1 -> link to url#anchor
160 * b2 -> link to url#anchor
161 * a2 -> link to url if hasLink==TRUE
162 * b1 -> link to url#anchor
163 * b2 -> link to url#anchor
164 * a3 -> link to url if hasLink==TRUE
165 * a4 -> link to url if hasLink==TRUE
166 * b1 -> link to url#anchor
169 void HtmlHelpIndex::writeFields(FTextStream &t)
172 IndexFieldSDict::Iterator ifli(*dict);
175 bool level2Started=FALSE;
176 for (;(f=ifli.current());++ifli)
178 QCString level1,level2;
180 if ((i=f->name.find('?'))!=-1)
182 level1 = f->name.left(i);
183 level2 = f->name.right(f->name.length()-i-1);
187 level1 = f->name.copy();
190 //if (level1!=lastLevel1)
191 { // finish old list at level 2
192 if (level2Started) t << " </UL>" << endl;
196 // Added this code so that an item with only one subitem is written
197 // without any subitem.
199 // a1, b1 -> will create only a1, not separate subitem for b1
203 IndexField* fnext = ++ifli;
206 nextLevel1 = fnext->name.left(fnext->name.find('?'));
209 if (level1 != nextLevel1)
215 if (level2.isEmpty())
217 t << " <LI><OBJECT type=\"text/sitemap\">";
218 t << "<param name=\"Local\" value=\"" << field2URL(f,FALSE);
220 t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
227 t << " <LI><OBJECT type=\"text/sitemap\">";
228 t << "<param name=\"Local\" value=\"" << field2URL(f,TRUE);
230 t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
235 t << " <LI><OBJECT type=\"text/sitemap\">";
236 t << "<param name=\"See Also\" value=\"" << m_help->recode(level1) << "\">";
237 t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
242 if (!level2Started && !level2.isEmpty())
243 { // start new list at level 2
244 t << " <UL>" << endl;
247 else if (level2Started && level2.isEmpty())
248 { // end list at level 2
249 t << " </UL>" << endl;
254 t << " <LI><OBJECT type=\"text/sitemap\">";
255 t << "<param name=\"Local\" value=\"" << field2URL(f,FALSE);
257 t << "<param name=\"Name\" value=\"" << m_help->recode(level2) << "\">"
260 lastLevel1 = level1.copy();
262 if (level2Started) t << " </UL>" << endl;
265 //----------------------------------------------------------------------------
267 HtmlHelp *HtmlHelp::theInstance = 0;
269 /*! Constructs an html object.
270 * The object has to be \link initialize() initialized\endlink before it can
273 HtmlHelp::HtmlHelp() : indexFileDict(1009)
278 index = new HtmlHelpIndex(this);
279 m_fromUtf8 = (void *)(-1);
282 HtmlHelp::~HtmlHelp()
284 if (m_fromUtf8!=(void *)(-1)) portable_iconv_close(m_fromUtf8);
288 /*! return a reference to the one and only instance of this class.
290 HtmlHelp *HtmlHelp::getInstance()
292 if (theInstance==0) theInstance = new HtmlHelp;
297 static QDict<QCString> s_languageDict;
299 /*! This will create a contents file (index.hhc) and a index file (index.hhk)
300 * and write the header of those files.
301 * It also creates a project file (index.hhp)
304 void HtmlHelp::initialize()
306 const char *str = Config_getString(CHM_INDEX_ENCODING);
307 if (!str) str = "CP1250"; // use safe and likely default
308 m_fromUtf8 = portable_iconv_open(str,"UTF-8");
309 if (m_fromUtf8==(void *)(-1))
311 err("unsupported character conversion for CHM_INDEX_ENCODING: '%s'->'UTF-8'\n", str);
315 /* open the contents file */
316 QCString fName = Config_getString(HTML_OUTPUT) + "/index.hhc";
317 cf = new QFile(fName);
318 if (!cf->open(IO_WriteOnly))
320 err("Could not open file %s for writing\n",fName.data());
323 /* Write the header of the contents file */
325 cts << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
326 "<HTML><HEAD></HEAD><BODY>\n"
327 "<OBJECT type=\"text/site properties\">\n"
328 "<param name=\"FrameName\" value=\"right\">\n"
332 /* open the contents file */
333 fName = Config_getString(HTML_OUTPUT) + "/index.hhk";
334 kf = new QFile(fName);
335 if (!kf->open(IO_WriteOnly))
337 err("Could not open file %s for writing\n",fName.data());
340 /* Write the header of the contents file */
342 kts << "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
343 "<HTML><HEAD></HEAD><BODY>\n"
344 "<OBJECT type=\"text/site properties\">\n"
345 "<param name=\"FrameName\" value=\"right\">\n"
349 /* language codes for Html help
353 0xC09 English (Australia)
354 0x809 English (Britain)
355 0x1009 English (Canada)
356 0x1809 English (Ireland)
357 0x1409 English (New Zealand)
358 0x1C09 English (South Africa)
359 0x409 English (United States)
368 0x816 Portuguese(Portugal)
369 0x416 Portuguese(Brazil)
371 0x80A Spanish(Mexico)
372 0xC0A Spanish(Modern Sort)
373 0x40A Spanish(Traditional Sort)
379 0x404 Chinese (Taiwan)
388 0x81A Serbian (Serbia, Latin)
395 0xC01 Arabic (Egypt) - I don't know which version of arabic is used inside translator_ar.h ,
396 so I have chosen Egypt at random
399 s_languageDict.setAutoDelete(TRUE);
400 s_languageDict.clear();
401 s_languageDict.insert("czech", new QCString("0x405 Czech"));
402 s_languageDict.insert("danish", new QCString("0x406 Danish"));
403 s_languageDict.insert("dutch", new QCString("0x413 Dutch"));
404 s_languageDict.insert("finnish", new QCString("0x40B Finnish"));
405 s_languageDict.insert("french", new QCString("0x40C French"));
406 s_languageDict.insert("german", new QCString("0x407 German"));
407 s_languageDict.insert("greek", new QCString("0x408 Greece"));
408 s_languageDict.insert("hungarian", new QCString("0x40E Hungarian"));
409 s_languageDict.insert("italian", new QCString("0x410 Italian"));
410 s_languageDict.insert("norwegian", new QCString("0x814 Norwegian"));
411 s_languageDict.insert("polish", new QCString("0x415 Polish"));
412 s_languageDict.insert("portuguese", new QCString("0x816 Portuguese(Portugal)"));
413 s_languageDict.insert("brazilian", new QCString("0x416 Portuguese(Brazil)"));
414 s_languageDict.insert("russian", new QCString("0x419 Russian"));
415 s_languageDict.insert("spanish", new QCString("0x40A Spanish(Traditional Sort)"));
416 s_languageDict.insert("swedish", new QCString("0x41D Swedish"));
417 s_languageDict.insert("turkish", new QCString("0x41F Turkey"));
418 s_languageDict.insert("japanese", new QCString("0x411 Japanese"));
419 s_languageDict.insert("japanese-en", new QCString("0x411 Japanese"));
420 s_languageDict.insert("korean", new QCString("0x412 Korean"));
421 s_languageDict.insert("korean-en", new QCString("0x412 Korean"));
422 s_languageDict.insert("chinese", new QCString("0x804 Chinese (PRC)"));
423 s_languageDict.insert("chinese-traditional", new QCString("0x404 Chinese (Taiwan)"));
426 s_languageDict.insert("indonesian", new QCString("0x412 Indonesian"));
427 s_languageDict.insert("croatian", new QCString("0x41A Croatian"));
428 s_languageDict.insert("romanian", new QCString("0x418 Romanian"));
429 s_languageDict.insert("slovene", new QCString("0x424 Slovenian"));
430 s_languageDict.insert("slovak", new QCString("0x41B Slovak"));
431 s_languageDict.insert("ukrainian", new QCString("0x422 Ukrainian"));
432 s_languageDict.insert("serbian", new QCString("0x81A Serbian (Serbia, Latin)"));
433 s_languageDict.insert("catalan", new QCString("0x403 Catalan"));
434 s_languageDict.insert("lithuanian", new QCString("0x427 Lithuanian"));
435 s_languageDict.insert("afrikaans", new QCString("0x436 Afrikaans"));
436 s_languageDict.insert("vietnamese", new QCString("0x42A Vietnamese"));
437 s_languageDict.insert("persian", new QCString("0x429 Persian (Iran)"));
438 s_languageDict.insert("arabic", new QCString("0xC01 Arabic (Egypt)"));
439 s_languageDict.insert("latvian", new QCString("0x426 Latvian"));
440 s_languageDict.insert("macedonian", new QCString("0x042f Macedonian (Former Yugoslav Republic of Macedonia)"));
441 s_languageDict.insert("armenian", new QCString("0x42b Armenian"));
442 //Code for Esperanto should be as shown below but the htmlhelp compiler 1.3 does not support this
443 // (and no newer version is available).
444 //So do a fallback to the default language (see getLanguageString())
445 //s_languageDict.insert("esperanto", new QCString("0x48f Esperanto"));
446 s_languageDict.insert("serbian-cyrillic", new QCString("0xC1A Serbian (Serbia, Cyrillic)"));
450 QCString HtmlHelp::getLanguageString()
452 if (!theTranslator->idLanguage().isEmpty())
454 QCString *s = s_languageDict[theTranslator->idLanguage()];
461 return "0x409 English (United States)";
466 void HtmlHelp::createProjectFile()
468 /* Write the project file */
469 QCString fName = Config_getString(HTML_OUTPUT) + "/index.hhp";
471 if (f.open(IO_WriteOnly))
475 QCString indexName="index"+Doxygen::htmlFileExtension;
477 if (!Config_getString(CHM_FILE).isEmpty())
479 t << "Compiled file=" << Config_getString(CHM_FILE) << "\n";
481 t << "Compatibility=1.1\n"
482 "Full-text search=Yes\n"
483 "Contents file=index.hhc\n"
484 "Default Window=main\n"
485 "Default topic=" << indexName << "\n"
486 "Index file=index.hhk\n"
487 "Language=" << getLanguageString() << endl;
488 if (Config_getBool(BINARY_TOC)) t << "Binary TOC=YES\n";
489 if (Config_getBool(GENERATE_CHI)) t << "Create CHI file=YES\n";
490 t << "Title=" << recode(Config_getString(PROJECT_NAME)) << endl << endl;
492 t << "[WINDOWS]" << endl;
494 // NOTE: the 0x10387e number is a set of bits specifying the buttons
495 // which should appear in the CHM viewer; that specific value
496 // means "show all buttons including the font-size one";
497 // the font-size one is not normally settable by the HTML Help Workshop
498 // utility but the way to set it is described here:
499 // http://support.microsoft.com/?scid=kb%3Ben-us%3B240062&x=17&y=18
500 // NOTE: the 0x70387e number in addition to the above the Next and Prev button
501 // are shown. They can only be shown in case of a binary toc.
502 // dee http://www.mif2go.com/xhtml/htmlhelp_0016_943addingtabsandtoolbarbuttonstohtmlhelp.htm#Rz108x95873
503 // Value has been taken from htmlhelp.h file of the HTML Help Workshop
504 if (Config_getBool(BINARY_TOC))
506 t << "main=\"" << recode(Config_getString(PROJECT_NAME)) << "\",\"index.hhc\","
507 "\"index.hhk\",\"" << indexName << "\",\"" <<
508 indexName << "\",,,,,0x23520,,0x70387e,,,,,,,,0" << endl << endl;
512 t << "main=\"" << recode(Config_getString(PROJECT_NAME)) << "\",\"index.hhc\","
513 "\"index.hhk\",\"" << indexName << "\",\"" <<
514 indexName << "\",,,,,0x23520,,0x10387e,,,,,,,,0" << endl << endl;
517 t << "[FILES]" << endl;
518 char *s = indexFiles.first();
522 s = indexFiles.next();
525 for (i=0;i<imageFiles.count();i++)
527 t << imageFiles.at(i) << endl;
533 err("Could not open file %s for writing\n",fName.data());
537 void HtmlHelp::addIndexFile(const char *s)
539 if (indexFileDict.find(s)==0)
541 indexFiles.append(s);
542 indexFileDict.insert(s,(void *)0x8);
546 /*! Finalizes the HTML help. This will finish and close the
547 * contents file (index.hhc) and the index file (index.hhk).
550 void HtmlHelp::finalize()
552 // end the contents file
560 index->writeFields(kts);
562 // end the index file
571 s_languageDict.clear();
574 /*! Increase the level of the contents hierarchy.
575 * This will start a new unnumbered HTML list in contents file.
576 * \sa decContentsDepth()
578 void HtmlHelp::incContentsDepth()
580 int i; for (i=0;i<dc+1;i++) cts << " ";
585 /*! Decrease the level of the contents hierarchy.
586 * This will end the unnumber HTML list.
587 * \sa incContentsDepth()
589 void HtmlHelp::decContentsDepth()
591 int i; for (i=0;i<dc;i++) cts << " ";
596 QCString HtmlHelp::recode(const QCString &s)
598 int iSize = s.length();
599 int oSize = iSize*4+1;
600 QCString output(oSize);
601 size_t iLeft = iSize;
602 size_t oLeft = oSize;
603 char *iPtr = s.rawData();
604 char *oPtr = output.rawData();
605 if (!portable_iconv(m_fromUtf8,&iPtr,&iLeft,&oPtr,&oLeft))
608 output.resize(oSize+1);
609 output.at(oSize)='\0';
618 /*! Add an list item to the contents file.
619 * \param isDir boolean indicating if this is a dir or file entry
620 * \param name the name of the item.
621 * \param ref the URL of to the item.
622 * \param file the file in which the item is defined.
623 * \param anchor the anchor of the item.
624 * \param separateIndex not used.
625 * \param addToNavIndex not used.
626 * \param def not used.
628 void HtmlHelp::addContentsItem(bool isDir,
630 const char * /*ref*/,
633 bool /* separateIndex */,
634 bool /* addToNavIndex */,
635 Definition * /* def */)
637 // If we're using a binary toc then folders cannot have links.
638 // Tried this and I didn't see any problems, when not using
639 // the resetting of file and anchor the TOC works better
640 // (prev / next button)
641 //if(Config_getBool(BINARY_TOC) && isDir)
646 int i; for (i=0;i<dc;i++) cts << " ";
647 cts << "<LI><OBJECT type=\"text/sitemap\">";
648 cts << "<param name=\"Name\" value=\"" << convertToHtml(recode(name),TRUE) << "\">";
649 if (file) // made file optional param - KPW
651 if (file && (file[0]=='!' || file[0]=='^')) // special markers for user defined URLs
653 cts << "<param name=\"";
654 if (file[0]=='^') cts << "URL"; else cts << "Local";
655 cts << "\" value=\"";
660 cts << "<param name=\"Local\" value=\"";
661 cts << file << Doxygen::htmlFileExtension;
662 if (anchor) cts << "#" << anchor;
666 cts << "<param name=\"ImageNumber\" value=\"";
667 if (isDir) // added - KPW
669 cts << (int)BOOK_CLOSED ;
676 cts << "</OBJECT>\n";
680 void HtmlHelp::addIndexItem(Definition *context,MemberDef *md,
681 const char *sectionAnchor,const char *word)
685 static bool separateMemberPages = Config_getBool(SEPARATE_MEMBER_PAGES);
686 if (context==0) // global member
688 if (md->getGroupDef())
689 context = md->getGroupDef();
690 else if (md->getFileDef())
691 context = md->getFileDef();
693 if (context==0) return; // should not happen
695 QCString cfname = md->getOutputFileBase();
696 QCString cfiname = context->getOutputFileBase();
697 QCString level1 = context->name();
698 QCString level2 = md->name();
699 QCString contRef = separateMemberPages ? cfname : cfiname;
700 QCString memRef = cfname;
701 QCString anchor = sectionAnchor ? QCString(sectionAnchor) : md->anchor();
702 index->addItem(level1,level2,contRef,anchor,TRUE,FALSE);
703 index->addItem(level2,level1,memRef,anchor,TRUE,TRUE);
707 QCString level1 = word ? QCString(word) : context->name();
708 index->addItem(level1,0,context->getOutputFileBase(),sectionAnchor,TRUE,FALSE);
712 void HtmlHelp::addImageFile(const char *fileName)
714 if (!imageFiles.contains(fileName)) imageFiles.append(fileName);