1 /******************************************************************************
2  *
3  * Copyright (C) 1997-2021 by Dimitri van Heesch.
4  *
5  * Permission to use, copy, modify, and distribute this software and its
6  * documentation under the terms of the GNU General Public License is hereby
7  * granted. No representations are made about the suitability of this software
8  * for any purpose. It is provided "as is" without express or implied warranty.
9  * See the GNU General Public License for more details.
10  *
11  * Documents produced by Doxygen are derivative works derived from the
12  * input used in their production; they are not affected by this license.
13  *
14  */
15 
16 #include <set>
17 #include <stack>
18 #include <fstream>
19 
20 #include "docsets.h"
21 #include "config.h"
22 #include "message.h"
23 #include "doxygen.h"
24 #include "groupdef.h"
25 #include "classdef.h"
26 #include "filedef.h"
27 #include "memberdef.h"
28 #include "namespacedef.h"
29 #include "util.h"
30 #include "textstream.h"
31 
32 struct DocSets::Private
33 {
34   QCString indent();
35   std::ofstream ntf;
36   TextStream    nts;
37   std::ofstream ttf;
38   TextStream    tts;
39   std::stack<bool> indentStack;
40   std::set<std::string> scopes;
41 };
42 
43 
DocSets()44 DocSets::DocSets() : p(std::make_unique<Private>())
45 {
46 }
47 
~DocSets()48 DocSets::~DocSets()
49 {
50 }
51 
initialize()52 void DocSets::initialize()
53 {
54   // -- get config options
55   QCString projectName = Config_getString(PROJECT_NAME);
56   if (projectName.isEmpty()) projectName="root";
57   QCString bundleId = Config_getString(DOCSET_BUNDLE_ID);
58   if (bundleId.isEmpty()) bundleId="org.doxygen.Project";
59   QCString feedName = Config_getString(DOCSET_FEEDNAME);
60   if (feedName.isEmpty()) feedName="FeedName";
61   QCString feedURL = Config_getString(DOCSET_FEEDURL);
62   if (feedURL.isEmpty()) feedURL="FeedUrl";
63   QCString publisherId = Config_getString(DOCSET_PUBLISHER_ID);
64   if (publisherId.isEmpty()) publisherId="PublisherId";
65   QCString publisherName = Config_getString(DOCSET_PUBLISHER_NAME);
66   if (publisherName.isEmpty()) publisherName="PublisherName";
67   QCString projectNumber = Config_getString(PROJECT_NUMBER);
68   if (projectNumber.isEmpty()) projectNumber="ProjectNumber";
69 
70   // -- write Makefile
71   {
72     QCString mfName = Config_getString(HTML_OUTPUT) + "/Makefile";
73     std::ofstream ts(mfName.str(),std::ofstream::out | std::ofstream::binary);
74     if (!ts.is_open())
75     {
76       term("Could not open file %s for writing\n",qPrint(mfName));
77     }
78 
79     ts << "DOCSET_NAME=" << bundleId << ".docset\n"
80           "DOCSET_CONTENTS=$(DOCSET_NAME)/Contents\n"
81           "DOCSET_RESOURCES=$(DOCSET_CONTENTS)/Resources\n"
82           "DOCSET_DOCUMENTS=$(DOCSET_RESOURCES)/Documents\n"
83           "DESTDIR=~/Library/Developer/Shared/Documentation/DocSets\n"
84           "XCODE_INSTALL=\"$(shell xcode-select -print-path)\"\n"
85           "\n"
86           "all: docset\n"
87           "\n"
88           "docset:\n"
89           "\tmkdir -p $(DOCSET_DOCUMENTS)\n"
90           "\tcp Nodes.xml $(DOCSET_RESOURCES)\n"
91           "\tcp Tokens.xml $(DOCSET_RESOURCES)\n"
92           "\tcp Info.plist $(DOCSET_CONTENTS)\n"
93           "\ttar --exclude $(DOCSET_NAME) \\\n"
94           "\t    --exclude Nodes.xml \\\n"
95           "\t    --exclude Tokens.xml \\\n"
96           "\t    --exclude Info.plist \\\n"
97           "\t    --exclude Makefile -c -f - . \\\n"
98           "\t    | (cd $(DOCSET_DOCUMENTS); tar xvf -)\n"
99           "\t$(XCODE_INSTALL)/usr/bin/docsetutil index $(DOCSET_NAME)\n"
100           "\trm -f $(DOCSET_DOCUMENTS)/Nodes.xml\n"
101           "\trm -f $(DOCSET_DOCUMENTS)/Info.plist\n"
102           "\trm -f $(DOCSET_DOCUMENTS)/Makefile\n"
103           "\trm -f $(DOCSET_RESOURCES)/Nodes.xml\n"
104           "\trm -f $(DOCSET_RESOURCES)/Tokens.xml\n"
105           "\n"
106           "clean:\n"
107           "\trm -rf $(DOCSET_NAME)\n"
108           "\n"
109           "install: docset\n"
110           "\tmkdir -p $(DESTDIR)\n"
111           "\tcp -R $(DOCSET_NAME) $(DESTDIR)\n"
112           "\n"
113           "uninstall:\n"
114           "\trm -rf $(DESTDIR)/$(DOCSET_NAME)\n"
115           "\n"
116           "always:\n";
117   }
118 
119   // -- write Info.plist
120   {
121     QCString plName = Config_getString(HTML_OUTPUT) + "/Info.plist";
122     std::ofstream ts(plName.str(),std::ofstream::out | std::ofstream::binary);
123     if (!ts.is_open())
124     {
125       term("Could not open file %s for writing\n",qPrint(plName));
126     }
127 
128     ts << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
129           "<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\"\n"
130           "\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
131           "<plist version=\"1.0\">\n"
132           "<dict>\n"
133           "     <key>CFBundleName</key>\n"
134           "     <string>" << projectName << "</string>\n"
135           "     <key>CFBundleIdentifier</key>\n"
136           "     <string>" << bundleId << "</string>\n"
137           "     <key>CFBundleVersion</key>\n"
138           "     <string>" << projectNumber << "</string>\n"
139           "     <key>DocSetFeedName</key>\n"
140           "     <string>" << feedName << "</string>\n"
141           "     <key>DocSetFeedUrl</key>\n"
142           "     <string>" << feedURL << "</string>\n"
143           "     <key>DocSetPublisherIdentifier</key>\n"
144           "     <string>" << publisherId << "</string>\n"
145           "     <key>DocSetPublisherName</key>\n"
146           "     <string>" << publisherName << "</string>\n"
147           // markers for Dash
148           "     <key>DashDocSetFamily</key>\n"
149           "     <string>doxy</string>\n"
150           "     <key>DocSetPlatformFamily</key>\n"
151           "     <string>doxygen</string>\n"
152           "</dict>\n"
153           "</plist>\n";
154   }
155 
156   // -- start Nodes.xml
157   QCString notes = Config_getString(HTML_OUTPUT) + "/Nodes.xml";
158   p->ntf.open(notes.str(),std::ofstream::out | std::ofstream::binary);
159   if (!p->ntf.is_open())
160   {
161     term("Could not open file %s for writing\n",qPrint(notes));
162   }
163   p->nts.setStream(&p->ntf);
164   //QCString indexName=Config_getBool(GENERATE_TREEVIEW)?"main":"index";
165   QCString indexName="index";
166   p->nts << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
167   p->nts << "<DocSetNodes version=\"1.0\">\n";
168   p->nts << "  <TOC>\n";
169   p->nts << "    <Node>\n";
170   p->nts << "      <Name>Root</Name>\n";
171   p->nts << "      <Path>" << indexName << Doxygen::htmlFileExtension << "</Path>\n";
172   p->nts << "      <Subnodes>\n";
173   p->indentStack.push(true);
174 
175   QCString tokens = Config_getString(HTML_OUTPUT) + "/Tokens.xml";
176   p->ttf.open(tokens.str(),std::ofstream::out | std::ofstream::binary);
177   if (!p->ttf.is_open())
178   {
179     term("Could not open file %s for writing\n",qPrint(tokens));
180   }
181   p->tts.setStream(&p->ttf);
182   p->tts << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
183   p->tts << "<Tokens version=\"1.0\">\n";
184 }
185 
finalize()186 void DocSets::finalize()
187 {
188   if (!p->indentStack.top())
189   {
190     p->nts << p->indent() << " </Node>\n";
191   }
192   p->indentStack.pop();
193   p->nts << "      </Subnodes>\n";
194   p->nts << "    </Node>\n";
195   p->nts << "  </TOC>\n";
196   p->nts << "</DocSetNodes>\n";
197   p->nts.flush();
198   p->ntf.close();
199 
200   p->tts << "</Tokens>\n";
201   p->tts.flush();
202   p->ttf.close();
203 }
204 
indent()205 QCString DocSets::Private::indent()
206 {
207   QCString result;
208   result.fill(' ',static_cast<int>(indentStack.size()+2)*2);
209   return result;
210 }
211 
incContentsDepth()212 void DocSets::incContentsDepth()
213 {
214   //printf("DocSets::incContentsDepth() depth=%zu\n",p->indentStack.size());
215   p->nts << p->indent() << "<Subnodes>\n";
216   p->indentStack.push(true);
217 }
218 
decContentsDepth()219 void DocSets::decContentsDepth()
220 {
221   if (!p->indentStack.top())
222   {
223     p->nts << p->indent() << " </Node>\n";
224   }
225   p->nts << p->indent() << "</Subnodes>\n";
226   p->indentStack.pop();
227   //printf("DocSets::decContentsDepth() depth=%zu\n",p->indentStack.size());
228 }
229 
addContentsItem(bool isDir,const QCString & name,const QCString & ref,const QCString & file,const QCString & anchor,bool,bool,const Definition *)230 void DocSets::addContentsItem(bool isDir,
231                               const QCString &name,
232                               const QCString &ref,
233                               const QCString &file,
234                               const QCString &anchor,
235                               bool /* separateIndex */,
236                               bool /* addToNavIndex */,
237                               const Definition * /*def*/)
238 {
239   (void)isDir;
240   //printf("DocSets::addContentsItem(%s) depth=%zu\n",name,p->indentStack.size());
241   if (ref==0)
242   {
243     if (!p->indentStack.top())
244     {
245       p->nts << p->indent() << " </Node>\n";
246     }
247     p->indentStack.top()=false;
248     p->nts << p->indent() << " <Node>\n";
249     p->nts << p->indent() << "  <Name>" << convertToXML(name) << "</Name>\n";
250     if (!file.isEmpty() && file[0]=='^') // URL marker
251     {
252       p->nts << p->indent() << "  <URL>" << convertToXML(&file[1])
253             << "</URL>\n";
254     }
255     else // relative file
256     {
257       p->nts << p->indent() << "  <Path>";
258       if (!file.isEmpty() && file[0]=='!') // user specified file
259       {
260         p->nts << convertToXML(&file[1]);
261       }
262       else if (!file.isEmpty()) // doxygen generated file
263       {
264         p->nts << addHtmlExtensionIfMissing(file);
265       }
266       p->nts << "</Path>\n";
267       if (!file.isEmpty() && !anchor.isEmpty())
268       {
269         p->nts << p->indent() << "  <Anchor>" << anchor << "</Anchor>\n";
270       }
271     }
272   }
273 }
274 
addIndexItem(const Definition * context,const MemberDef * md,const QCString &,const QCString &)275 void DocSets::addIndexItem(const Definition *context,const MemberDef *md,
276                            const QCString &,const QCString &)
277 {
278   if (md==0 && context==0) return;
279 
280   const FileDef *fd      = 0;
281   const ClassDef *cd     = 0;
282   const NamespaceDef *nd = 0;
283 
284   if (md)
285   {
286     fd = md->getFileDef();
287     cd = md->getClassDef();
288     nd = md->getNamespaceDef();
289     if (!md->isLinkable()) return; // internal symbol
290   }
291 
292   QCString scope;
293   QCString type;
294   QCString decl;
295 
296   // determine language
297   QCString lang;
298   SrcLangExt langExt = SrcLangExt_Cpp;
299   if (md)
300   {
301     langExt = md->getLanguage();
302   }
303   else if (context)
304   {
305     langExt = context->getLanguage();
306   }
307   switch (langExt)
308   {
309     case SrcLangExt_Cpp:
310     case SrcLangExt_ObjC:
311       {
312         if (md && (md->isObjCMethod() || md->isObjCProperty()))
313           lang="occ";  // Objective C/C++
314         else if (fd && fd->name().right(2).lower()==".c")
315           lang="c";    // Plain C
316         else if (cd==0 && nd==0)
317           lang="c";    // Plain C symbol outside any class or namespace
318         else
319           lang="cpp";  // C++
320       }
321       break;
322     case SrcLangExt_IDL:     lang="idl"; break;        // IDL
323     case SrcLangExt_CSharp:  lang="csharp"; break;     // C#
324     case SrcLangExt_PHP:     lang="php"; break;        // PHP4/5
325     case SrcLangExt_D:       lang="d"; break;          // D
326     case SrcLangExt_Java:    lang="java"; break;       // Java
327     case SrcLangExt_JS:      lang="javascript"; break; // JavaScript
328     case SrcLangExt_Python:  lang="python"; break;     // Python
329     case SrcLangExt_Fortran: lang="fortran"; break;    // Fortran
330     case SrcLangExt_VHDL:    lang="vhdl"; break;       // VHDL
331     case SrcLangExt_XML:     lang="xml"; break;        // DBUS XML
332     case SrcLangExt_SQL:     lang="sql"; break;        // Sql
333     case SrcLangExt_Markdown:lang="markdown"; break;   // Markdown
334     case SrcLangExt_Slice:   lang="slice"; break;      // Slice
335     case SrcLangExt_Lex:     lang="lex"; break;        // Lex
336     case SrcLangExt_Unknown: lang="unknown"; break;    // should not happen!
337   }
338 
339   if (md)
340   {
341     if (context==0)
342     {
343       if (md->getGroupDef())
344         context = md->getGroupDef();
345       else if (md->getFileDef())
346         context = md->getFileDef();
347     }
348     if (context==0) return; // should not happen
349 
350     switch (md->memberType())
351     {
352       case MemberType_Define:
353         type="macro"; break;
354       case MemberType_Function:
355         if (cd && (cd->compoundType()==ClassDef::Interface ||
356               cd->compoundType()==ClassDef::Class))
357         {
358           if (md->isStatic())
359             type="clm";         // class member
360           else
361             type="instm";       // instance member
362         }
363         else if (cd && cd->compoundType()==ClassDef::Protocol)
364         {
365           if (md->isStatic())
366             type="intfcm";     // interface class member
367           else
368             type="intfm";      // interface member
369         }
370         else
371           type="func";
372         break;
373       case MemberType_Variable:
374         type="data"; break;
375       case MemberType_Typedef:
376         type="tdef"; break;
377       case MemberType_Enumeration:
378         type="enum"; break;
379       case MemberType_EnumValue:
380         type="econst"; break;
381         //case MemberDef::Prototype:
382         //  type="prototype"; break;
383       case MemberType_Signal:
384         type="signal"; break;
385       case MemberType_Slot:
386         type="slot"; break;
387       case MemberType_Friend:
388         type="ffunc"; break;
389       case MemberType_DCOP:
390         type="dcop"; break;
391       case MemberType_Property:
392         if (cd && cd->compoundType()==ClassDef::Protocol)
393           type="intfp";         // interface property
394         else
395           type="instp";         // instance property
396         break;
397       case MemberType_Event:
398         type="event"; break;
399       case MemberType_Interface:
400         type="ifc"; break;
401       case MemberType_Service:
402         type="svc"; break;
403       case MemberType_Sequence:
404         type="sequence"; break;
405       case MemberType_Dictionary:
406         type="dictionary"; break;
407     }
408     cd = md->getClassDef();
409     nd = md->getNamespaceDef();
410     if (cd)
411     {
412       scope = cd->qualifiedName();
413     }
414     else if (nd)
415     {
416       scope = nd->name();
417     }
418     const MemberDef *declMd = md->memberDeclaration();
419     if (declMd==0) declMd = md;
420     {
421       fd = md->getFileDef();
422       if (fd)
423       {
424         decl = fd->name();
425       }
426     }
427     writeToken(p->tts,md,type,lang,scope,md->anchor(),decl);
428   }
429   else if (context && context->isLinkable())
430   {
431     if (fd==0 && context->definitionType()==Definition::TypeFile)
432     {
433       fd = toFileDef(context);
434     }
435     if (cd==0 && context->definitionType()==Definition::TypeClass)
436     {
437       cd = toClassDef(context);
438     }
439     if (nd==0 && context->definitionType()==Definition::TypeNamespace)
440     {
441       nd = toNamespaceDef(context);
442     }
443     if (fd)
444     {
445       type="file";
446     }
447     else if (cd)
448     {
449       scope = cd->qualifiedName();
450       if (cd->isTemplate())
451       {
452         type="tmplt";
453       }
454       else if (cd->compoundType()==ClassDef::Protocol)
455       {
456         type="intf";
457         if (scope.right(2)=="-p") scope=scope.left(scope.length()-2);
458       }
459       else if (cd->compoundType()==ClassDef::Interface)
460       {
461         type="cl";
462       }
463       else if (cd->compoundType()==ClassDef::Category)
464       {
465         type="cat";
466       }
467       else
468       {
469         type = "cl";
470       }
471       const IncludeInfo *ii = cd->includeInfo();
472       if (ii)
473       {
474         decl=ii->includeName;
475       }
476     }
477     else if (nd)
478     {
479       scope = nd->name();
480       type = "ns";
481     }
482     if (p->scopes.find(context->getOutputFileBase().str())==p->scopes.end())
483     {
484       writeToken(p->tts,context,type,lang,scope,QCString(),decl);
485       p->scopes.insert(context->getOutputFileBase().str());
486     }
487   }
488 }
489 
writeToken(TextStream & t,const Definition * d,const QCString & type,const QCString & lang,const QCString & scope,const QCString & anchor,const QCString & decl)490 void DocSets::writeToken(TextStream &t,
491                          const Definition *d,
492                          const QCString &type,
493                          const QCString &lang,
494                          const QCString &scope,
495                          const QCString &anchor,
496                          const QCString &decl)
497 {
498   t << "  <Token>\n";
499   t << "    <TokenIdentifier>\n";
500   QCString name = d->name();
501   if (name.right(2)=="-p")  name=name.left(name.length()-2);
502   t << "      <Name>" << convertToXML(name) << "</Name>\n";
503   if (!lang.isEmpty())
504   {
505     t << "      <APILanguage>" << lang << "</APILanguage>\n";
506   }
507   if (!type.isEmpty())
508   {
509     t << "      <Type>" << type << "</Type>\n";
510   }
511   if (!scope.isEmpty())
512   {
513     t << "      <Scope>" << convertToXML(scope) << "</Scope>\n";
514   }
515   t << "    </TokenIdentifier>\n";
516   t << "    <Path>" << addHtmlExtensionIfMissing(d->getOutputFileBase()) << "</Path>\n";
517   if (!anchor.isEmpty())
518   {
519     t << "    <Anchor>" << anchor << "</Anchor>\n";
520   }
521   QCString tooltip = d->briefDescriptionAsTooltip();
522   if (!tooltip.isEmpty())
523   {
524     t << "    <Abstract>" << convertToXML(tooltip) << "</Abstract>\n";
525   }
526   if (!decl.isEmpty())
527   {
528     t << "    <DeclaredIn>" << convertToXML(decl) << "</DeclaredIn>\n";
529   }
530   t << "  </Token>\n";
531 }
532 
addIndexFile(const QCString & name)533 void DocSets::addIndexFile(const QCString &name)
534 {
535   (void)name;
536 }
537 
538