My Project
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
htmlhelp.cpp
Go to the documentation of this file.
1 /******************************************************************************
2  *
3  *
4  *
5  * Copyright (C) 1997-2015 by Dimitri van Heesch.
6  *
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.
12  *
13  * Documents produced by Doxygen are derivative works derived from the
14  * input used in their production; they are not affected by this license.
15  *
16  * The original version of this file is largely based on a contribution from
17  * Harm van der Heijden.
18  */
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <qlist.h>
23 #include <qdict.h>
24 #include <qregexp.h>
25 #include <qfile.h>
26 
27 #include "qtextcodec.h"
28 #include "sortdict.h"
29 #include "htmlhelp.h"
30 #include "config.h"
31 #include "message.h"
32 #include "doxygen.h"
33 #include "language.h"
34 #include "portable.h"
35 #include "groupdef.h"
36 #include "memberdef.h"
37 #include "filedef.h"
38 #include "util.h"
39 
40 //----------------------------------------------------------------------------
41 
43 struct IndexField
44 {
45  QCString name;
46  QCString url;
47  QCString anchor;
48  bool link;
49  bool reversed;
50 };
51 
53 class IndexFieldSDict : public SDict<IndexField>
54 {
55  public:
58  private:
59  int compareValues(const IndexField *item1, const IndexField *item2) const
60  {
61  return qstricmp(item1->name,item2->name);
62  }
63 };
64 
69 {
70  public:
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);
77  private:
80 };
81 
84 {
85  dict = new IndexFieldSDict;
86  dict->setAutoDelete(TRUE);
87 }
88 
91 {
92  delete dict;
93 }
94 
108 void HtmlHelpIndex::addItem(const char *level1,const char *level2,
109  const char *url,const char *anchor,bool hasLink,
110  bool reversed)
111 {
112  QCString key = level1;
113  if (level2) key+= (QCString)"?" + level2;
114  if (key.find(QRegExp("@[0-9]+"))!=-1) // skip anonymous stuff
115  {
116  return;
117  }
118  if (dict->find(key)==0) // new key
119  {
120  //printf(">>>>>>>>> HtmlHelpIndex::addItem(%s,%s,%s,%s)\n",
121  // level1,level2,url,anchor);
122  IndexField *f = new IndexField;
123  f->name = key;
124  f->url = url;
125  f->anchor = anchor;
126  f->link = hasLink;
127  f->reversed = reversed;
128  dict->append(key,f);
129  }
130 }
131 
132 static QCString field2URL(const IndexField *f,bool checkReversed)
133 {
134  QCString result = f->url + Doxygen::htmlFileExtension;
135  if (!f->anchor.isEmpty() && (!checkReversed || f->reversed))
136  {
137  // HTML Help needs colons in link anchors to be escaped in the .hhk file.
138  result+="#"+substitute(f->anchor,":","%3A");
139  }
140  return result;
141 }
142 
170 {
171  dict->sort();
173  IndexField *f;
174  QCString lastLevel1;
175  bool level2Started=FALSE;
176  for (;(f=ifli.current());++ifli)
177  {
178  QCString level1,level2;
179  int i;
180  if ((i=f->name.find('?'))!=-1)
181  {
182  level1 = f->name.left(i);
183  level2 = f->name.right(f->name.length()-i-1);
184  }
185  else
186  {
187  level1 = f->name.copy();
188  }
189 
190  if (level1!=lastLevel1)
191  { // finish old list at level 2
192  if (level2Started) t << " </UL>" << endl;
193  level2Started=FALSE;
194 
195  // <Antony>
196  // Added this code so that an item with only one subitem is written
197  // without any subitem.
198  // For example:
199  // a1, b1 -> will create only a1, not separate subitem for b1
200  // a2, b2
201  // a2, b3
202  QCString nextLevel1;
203  IndexField* fnext = ++ifli;
204  if (fnext)
205  {
206  nextLevel1 = fnext->name.left(fnext->name.find('?'));
207  --ifli;
208  }
209  if (level1 != nextLevel1)
210  {
211  level2 = "";
212  }
213  // </Antony>
214 
215  if (level2.isEmpty())
216  {
217  t << " <LI><OBJECT type=\"text/sitemap\">";
218  t << "<param name=\"Local\" value=\"" << field2URL(f,FALSE);
219  t << "\">";
220  t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
221  "</OBJECT>\n";
222  }
223  else
224  {
225  if (f->link)
226  {
227  t << " <LI><OBJECT type=\"text/sitemap\">";
228  t << "<param name=\"Local\" value=\"" << field2URL(f,TRUE);
229  t << "\">";
230  t << "<param name=\"Name\" value=\"" << m_help->recode(level1) << "\">"
231  "</OBJECT>\n";
232  }
233  else
234  {
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) << "\">"
238  "</OBJECT>\n";
239  }
240  }
241  }
242  if (!level2Started && !level2.isEmpty())
243  { // start new list at level 2
244  t << " <UL>" << endl;
245  level2Started=TRUE;
246  }
247  else if (level2Started && level2.isEmpty())
248  { // end list at level 2
249  t << " </UL>" << endl;
250  level2Started=FALSE;
251  }
252  if (level2Started)
253  {
254  t << " <LI><OBJECT type=\"text/sitemap\">";
255  t << "<param name=\"Local\" value=\"" << field2URL(f,FALSE);
256  t << "\">";
257  t << "<param name=\"Name\" value=\"" << m_help->recode(level2) << "\">"
258  "</OBJECT>\n";
259  }
260  lastLevel1 = level1.copy();
261  }
262  if (level2Started) t << " </UL>" << endl;
263 }
264 
265 //----------------------------------------------------------------------------
266 
268 
273 HtmlHelp::HtmlHelp() : indexFileDict(1009)
274 {
275  /* initial depth */
276  dc = 0;
277  cf = kf = 0;
278  index = new HtmlHelpIndex(this);
279  m_fromUtf8 = (void *)(-1);
280 }
281 
283 {
284  if (m_fromUtf8!=(void *)(-1)) portable_iconv_close(m_fromUtf8);
285  delete index;
286 }
287 #if 0
288 
290 HtmlHelp *HtmlHelp::getInstance()
291 {
292  if (theInstance==0) theInstance = new HtmlHelp;
293  return theInstance;
294 }
295 #endif
296 
297 static QDict<QCString> s_languageDict;
298 
305 {
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))
310  {
311  err("unsupported character conversion for CHM_INDEX_ENCODING: '%s'->'UTF-8'\n", str);
312  exit(1);
313  }
314 
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))
319  {
320  err("Could not open file %s for writing\n",fName.data());
321  exit(1);
322  }
323  /* Write the header of the contents file */
324  cts.setDevice(cf);
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"
329  "</OBJECT>\n"
330  "<UL>\n";
331 
332  /* open the contents file */
333  fName = Config_getString(HTML_OUTPUT) + "/index.hhk";
334  kf = new QFile(fName);
335  if (!kf->open(IO_WriteOnly))
336  {
337  err("Could not open file %s for writing\n",fName.data());
338  exit(1);
339  }
340  /* Write the header of the contents file */
341  kts.setDevice(kf);
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"
346  "</OBJECT>\n"
347  "<UL>\n";
348 
349  /* language codes for Html help
350  0x405 Czech
351  0x406 Danish
352  0x413 Dutch
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)
360  0x40B Finnish
361  0x40C French
362  0x407 German
363  0x408 Greece
364  0x40E Hungarian
365  0x410 Italian
366  0x814 Norwegian
367  0x415 Polish
368  0x816 Portuguese(Portugal)
369  0x416 Portuguese(Brazil)
370  0x419 Russian
371  0x80A Spanish(Mexico)
372  0xC0A Spanish(Modern Sort)
373  0x40A Spanish(Traditional Sort)
374  0x41D Swedish
375  0x41F Turkey
376  0x411 Japanese
377  0x412 Korean
378  0x804 Chinese (PRC)
379  0x404 Chinese (Taiwan)
380 
381  New LCIDs:
382  0x421 Indonesian
383  0x41A Croatian
384  0x418 Romanian
385  0x424 Slovenian
386  0x41B Slovak
387  0x422 Ukrainian
388  0x81A Serbian (Serbia, Latin)
389  0x403 Catalan
390  0x426 Latvian
391  0x427 Lithuanian
392  0x436 Afrikaans
393  0x42A Vietnamese
394  0x429 Persian (Iran)
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
397 
398  */
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)"));
424 
425  // new LCIDs
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)"));
447 }
448 
449 
451 {
452  if (!theTranslator->idLanguage().isEmpty())
453  {
454  QCString *s = s_languageDict[theTranslator->idLanguage()];
455  if (s)
456  {
457  return *s;
458  }
459  }
460  // default language
461  return "0x409 English (United States)";
462 }
463 
464 
465 
467 {
468  /* Write the project file */
469  QCString fName = Config_getString(HTML_OUTPUT) + "/index.hhp";
470  QFile f(fName);
471  if (f.open(IO_WriteOnly))
472  {
473  FTextStream t(&f);
474 
475  QCString indexName="index"+Doxygen::htmlFileExtension;
476  t << "[OPTIONS]\n";
477  if (!Config_getString(CHM_FILE).isEmpty())
478  {
479  t << "Compiled file=" << Config_getString(CHM_FILE) << "\n";
480  }
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;
491 
492  t << "[WINDOWS]" << endl;
493 
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))
505  {
506  t << "main=\"" << recode(Config_getString(PROJECT_NAME)) << "\",\"index.hhc\","
507  "\"index.hhk\",\"" << indexName << "\",\"" <<
508  indexName << "\",,,,,0x23520,,0x70387e,,,,,,,,0" << endl << endl;
509  }
510  else
511  {
512  t << "main=\"" << recode(Config_getString(PROJECT_NAME)) << "\",\"index.hhc\","
513  "\"index.hhk\",\"" << indexName << "\",\"" <<
514  indexName << "\",,,,,0x23520,,0x10387e,,,,,,,,0" << endl << endl;
515  }
516 
517  t << "[FILES]" << endl;
518  char *s = indexFiles.first();
519  while (s)
520  {
521  t << s << endl;
522  s = indexFiles.next();
523  }
524  uint i;
525  for (i=0;i<imageFiles.count();i++)
526  {
527  t << imageFiles.at(i) << endl;
528  }
529  f.close();
530  }
531  else
532  {
533  err("Could not open file %s for writing\n",fName.data());
534  }
535 }
536 
537 void HtmlHelp::addIndexFile(const char *s)
538 {
539  if (indexFileDict.find(s)==0)
540  {
541  indexFiles.append(s);
542  indexFileDict.insert(s,(void *)0x8);
543  }
544 }
545 
551 {
552  // end the contents file
553  cts << "</UL>\n";
554  cts << "</BODY>\n";
555  cts << "</HTML>\n";
556  cts.unsetDevice();
557  cf->close();
558  delete cf;
559 
561 
562  // end the index file
563  kts << "</UL>\n";
564  kts << "</BODY>\n";
565  kts << "</HTML>\n";
566  kts.unsetDevice();
567  kf->close();
568  delete kf;
569 
571  s_languageDict.clear();
572 }
573 
579 {
580  int i; for (i=0;i<dc+1;i++) cts << " ";
581  cts << "<UL>\n";
582  ++dc;
583 }
584 
590 {
591  int i; for (i=0;i<dc;i++) cts << " ";
592  cts << "</UL>\n";
593  --dc;
594 }
595 
596 QCString HtmlHelp::recode(const QCString &s)
597 {
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))
606  {
607  oSize -= (int)oLeft;
608  output.resize(oSize+1);
609  output.at(oSize)='\0';
610  return output;
611  }
612  else
613  {
614  return s;
615  }
616 }
617 
629  const char *name,
630  const char * /*ref*/,
631  const char *file,
632  const char *anchor,
633  bool /* separateIndex */,
634  bool /* addToNavIndex */,
635  Definition * /* def */)
636 {
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)
642  //{
643  //file = 0;
644  //anchor = 0;
645  //}
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
650  {
651  if (file && (file[0]=='!' || file[0]=='^')) // special markers for user defined URLs
652  {
653  cts << "<param name=\"";
654  if (file[0]=='^') cts << "URL"; else cts << "Local";
655  cts << "\" value=\"";
656  cts << &file[1];
657  }
658  else
659  {
660  cts << "<param name=\"Local\" value=\"";
661  cts << file << Doxygen::htmlFileExtension;
662  if (anchor) cts << "#" << anchor;
663  }
664  cts << "\">";
665  }
666  cts << "<param name=\"ImageNumber\" value=\"";
667  if (isDir) // added - KPW
668  {
669  cts << (int)BOOK_CLOSED ;
670  }
671  else
672  {
673  cts << (int)TEXT;
674  }
675  cts << "\">";
676  cts << "</OBJECT>\n";
677 }
678 
679 
681  const char *sectionAnchor,const char *word)
682 {
683  if (md)
684  {
685  static bool separateMemberPages = Config_getBool(SEPARATE_MEMBER_PAGES);
686  if (context==0) // global member
687  {
688  if (md->getGroupDef())
689  context = md->getGroupDef();
690  else if (md->getFileDef())
691  context = md->getFileDef();
692  }
693  if (context==0) return; // should not happen
694 
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);
704  }
705  else if (context)
706  {
707  QCString level1 = word ? QCString(word) : context->name();
708  index->addItem(level1,0,context->getOutputFileBase(),sectionAnchor,TRUE,FALSE);
709  }
710 }
711 
712 void HtmlHelp::addImageFile(const char *fileName)
713 {
714  if (!imageFiles.contains(fileName)) imageFiles.append(fileName);
715 }
716