Fix for UBSan build
[platform/upstream/doxygen.git] / src / markdown.cpp
1 /******************************************************************************
2  *
3  * Copyright (C) 1997-2012 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 /* Note: part of the code below is inspired by libupskirt written by 
17  * Natacha Porté. Original copyright message follows:
18  *
19  * Copyright (c) 2008, Natacha Porté
20  *
21  * Permission to use, copy, modify, and distribute this software for any
22  * purpose with or without fee is hereby granted, provided that the above
23  * copyright notice and this permission notice appear in all copies.
24  *
25  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
26  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
27  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
28  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
29  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
30  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
31  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
32  */
33
34 #include <stdio.h>
35 #include <qglobal.h>
36 #include <qregexp.h>
37 #include <qfileinfo.h>
38 #include <qdict.h>
39
40 #include "markdown.h"
41 #include "growbuf.h"
42 #include "debug.h"
43 #include "util.h"
44 #include "doxygen.h"
45 #include "commentscan.h"
46 #include "entry.h"
47 #include "bufstr.h"
48 #include "commentcnv.h"
49
50 //-----------
51
52 // is character at position i in data part of an identifier?
53 #define isIdChar(i) \
54   ((data[i]>='a' && data[i]<='z') || \
55    (data[i]>='A' && data[i]<='Z') || \
56    (data[i]>='0' && data[i]<='9'))   \
57
58 // is character at position i in data allowed before an emphasis section
59 #define isOpenEmphChar(i) \
60   (data[i]=='\n' || data[i]==' ' || data[i]=='\'' || data[i]=='<' || \
61    data[i]=='{'  || data[i]=='(' || data[i]=='['  || data[i]==',' || \
62    data[i]==':'  || data[i]==';')
63
64 // is character at position i in data an escape that prevents ending an emphasis section
65 // so for example *bla (*.txt) is cool*
66 #define ignoreCloseEmphChar(i) \
67   (data[i]=='('  || data[i]=='{' || data[i]=='[' || data[i]=='<' || \
68    data[i]=='='  || data[i]=='+' || data[i]=='-' || data[i]=='\\' || \
69    data[i]=='@')
70
71 //----------
72
73 struct LinkRef
74 {
75   LinkRef(const QCString &l,const QCString &t) : link(l), title(t) {}
76   QCString link;
77   QCString title;
78 };
79
80 typedef int (*action_t)(GrowBuf &out,const char *data,int offset,int size);
81
82 enum Alignment { AlignNone, AlignLeft, AlignCenter, AlignRight };
83
84
85 //----------
86
87 static QDict<LinkRef> g_linkRefs(257);
88 static action_t       g_actions[256];
89 static Entry         *g_current;
90 static QCString       g_fileName;
91
92 // In case a markdown page starts with a level1 header, that header is used
93 // as a title of the page, in effect making it a level0 header, so the
94 // level of all other sections needs to be corrected as well.
95 // This flag is TRUE if corrections are needed.
96 //static bool           g_correctSectionLevel;
97
98
99 //----------
100
101 const int codeBlockIndent = 4;
102
103 static void processInline(GrowBuf &out,const char *data,int size);
104
105 // escape characters that have a special meaning later on.
106 static QCString escapeSpecialChars(const QCString &s)
107 {
108   if (s.isEmpty()) return "";
109   GrowBuf growBuf;
110   const char *p=s;
111   char c;
112   while ((c=*p++))
113   {
114     switch (c)
115     {
116       case '<':  growBuf.addStr("\\<");   break;
117       case '>':  growBuf.addStr("\\>");   break;
118       case '\\': growBuf.addStr("\\\\");  break;
119       case '@':  growBuf.addStr("\\@");   break;
120       default:   growBuf.addChar(c);      break;
121     }
122   }
123   growBuf.addChar(0);
124   return growBuf.get();
125 }
126
127 static void convertStringFragment(QCString &result,const char *data,int size)
128 {
129   if (size<0) size=0;
130   result.resize(size+1);
131   memcpy(result.data(),data,size);
132   result.at(size)='\0';
133 }
134
135 /** helper function to convert presence of left and/or right alignment markers
136  *  to a alignment value
137  */
138 static Alignment markersToAlignment(bool leftMarker,bool rightMarker)
139 {
140   //printf("markerToAlignment(%d,%d)\n",leftMarker,rightMarker);
141   if (leftMarker && rightMarker)
142   {
143     return AlignCenter;
144   }
145   else if (leftMarker)
146   {
147     return AlignLeft;
148   }
149   else if (rightMarker)
150   {
151     return AlignRight;
152   }
153   else
154   {
155     return AlignNone;
156   }
157 }
158
159
160 // Check if data contains a block command. If so returned the command
161 // that ends the block. If not an empty string is returned.
162 // Note When offset>0 character position -1 will be inspected.
163 //
164 // Checks for and skip the following block commands:
165 // {@code .. { .. } .. }
166 // \dot .. \enddot
167 // \code .. \endcode
168 // \msc .. \endmsc
169 // \f$..\f$
170 // \f[..\f]
171 // \f{..\f}
172 // \verbatim..\endverbatim
173 // \latexonly..\endlatexonly
174 // \htmlonly..\endhtmlonly
175 // \xmlonly..\endxmlonly
176 // \rtfonly..\endrtfonly
177 // \manonly..\endmanonly
178 static QCString isBlockCommand(const char *data,int offset,int size)
179 {
180   bool openBracket = offset>0 && data[-1]=='{';
181   bool isEscaped = offset>0 && (data[-1]=='\\' || data[-1]=='@');
182   if (isEscaped) return QCString();
183
184   int end=1;
185   while (end<size && (data[end]>='a' && data[end]<='z')) end++;
186   if (end==1) return QCString();
187   QCString blockName;
188   convertStringFragment(blockName,data+1,end-1);
189   if (blockName=="code" && openBracket)
190   {
191     return "}";
192   }
193   else if (blockName=="dot"       || 
194            blockName=="code"      || 
195            blockName=="msc"       ||
196            blockName=="verbatim"  || 
197            blockName=="latexonly" || 
198            blockName=="htmlonly"  ||
199            blockName=="xmlonly"   ||
200            blockName=="rtfonly"   ||
201            blockName=="manonly"
202      )
203   {
204     return "end"+blockName;
205   }
206   else if (blockName=="f" && end<size)
207   {
208     if (data[end]=='$')
209     {
210       return "f$";
211     }
212     else if (data[end]=='[')
213     {
214       return "f]";
215     }
216     else if (data[end]=='}')
217     {
218       return "f}";
219     }
220   }
221   return QCString();
222 }
223
224 /** looks for the next emph char, skipping other constructs, and
225  *  stopping when either it is found, or we are at the end of a paragraph.
226  */
227 static int findEmphasisChar(const char *data, int size, char c)
228 {
229   int i = 1;
230
231   while (i<size)
232   {
233     while (i<size && data[i]!=c    && data[i]!='`' && 
234                      data[i]!='\\' && data[i]!='@' &&
235                      data[i]!='\n') i++;
236     //printf("findEmphasisChar: data=[%s] i=%d c=%c\n",data,i,data[i]);
237
238     // not counting escaped chars or characters that are unlikely 
239     // to appear as the end of the emphasis char
240     if (i>0 && ignoreCloseEmphChar(i-1))
241     {
242       i++;
243       continue;
244     }
245     else if (data[i] == c)
246     {
247       if (i<size-1 && isIdChar(i+1)) // to prevent touching some_underscore_identifier
248       {
249         i++;
250         continue;
251       }
252       return i; // found it
253     }
254
255
256     // skipping a code span
257     if (data[i]=='`')
258     {
259       int snb=0;
260       while (i<size && data[i]=='`') snb++,i++;
261
262       // find same pattern to end the span
263       int enb=0;
264       while (i<size && enb<snb)
265       {
266         if (data[i]=='`') enb++;
267         if (snb==1 && data[i]=='\'') break; // ` ended by '
268         i++;
269       }
270     }
271     else if (data[i]=='@' || data[i]=='\\')
272     { // skip over blocks that should not be processed
273       QCString endBlockName = isBlockCommand(data+i,i,size-i);
274       if (!endBlockName.isEmpty())
275       {
276         i++;
277         int l = endBlockName.length();
278         while (i<size-l)
279         {
280           if ((data[i]=='\\' || data[i]=='@') && // command
281               data[i-1]!='\\' && data[i-1]!='@') // not escaped
282           {
283             if (strncmp(&data[i+1],endBlockName,l)==0)
284             {
285               break;
286             }
287           }
288           i++;
289         }
290       }
291       else
292       {
293         i++;
294       }
295     }
296     else if (data[i]=='\n') // end * or _ at paragraph boundary
297     {
298       i++;
299       while (i<size && data[i]==' ') i++;
300       if (i>=size || data[i]=='\n') return 0; // empty line -> paragraph
301     }
302     else // should not get here!
303     {
304       i++;
305     }
306
307   }
308   return 0;
309 }
310
311 /** process single emphasis */
312 static int processEmphasis1(GrowBuf &out, const char *data, int size, char c)
313 {
314   int i = 0, len;
315
316   /* skipping one symbol if coming from emph3 */
317   if (size>1 && data[0]==c && data[1]==c) { i=1; }
318
319   while (i<size)
320   {
321     len = findEmphasisChar(data+i, size-i, c);
322     if (len==0) return 0; 
323     i+=len;
324     if (i>=size) return 0; 
325
326     if (i+1<size && data[i+1]==c)
327     {
328       i++;
329       continue;
330     }
331     if (data[i]==c && data[i-1]!=' ' && data[i-1]!='\n')
332     {
333       out.addStr("<em>");
334       processInline(out,data,i);
335       out.addStr("</em>");
336       return i+1;
337     }
338   }
339   return 0;
340 }
341
342 /** process double emphasis */
343 static int processEmphasis2(GrowBuf &out, const char *data, int size, char c)
344 {
345   int i = 0, len;
346
347   while (i<size)
348   {
349     len = findEmphasisChar(data+i, size-i, c);
350     if (len==0)
351     {
352       return 0;
353     }
354     i += len;
355     if (i+1<size && data[i]==c && data[i+1]==c && i && data[i-1]!=' ' && 
356         data[i-1]!='\n'
357        )
358     {
359       out.addStr("<strong>");
360       processInline(out,data,i);
361       out.addStr("</strong>");
362       return i + 2;
363     }
364     i++;
365   }
366   return 0;
367 }
368
369 /** Parsing tripple emphasis.
370  *  Finds the first closing tag, and delegates to the other emph 
371  */
372 static int processEmphasis3(GrowBuf &out, const char *data, int size, char c)
373 {
374   int i = 0, len;
375
376   while (i<size)
377   {
378     len = findEmphasisChar(data+i, size-i, c);
379     if (len==0)
380     {
381       return 0;
382     }
383     i+=len;
384
385     /* skip whitespace preceded symbols */
386     if (data[i]!=c || data[i-1]==' ' || data[i-1]=='\n')
387     {
388       continue;
389     }
390
391     if (i+2<size && data[i+1]==c && data[i+2]==c)
392     {
393       out.addStr("<em><strong>");
394       processInline(out,data,i);
395       out.addStr("</strong></em>");
396       return i+3;
397     }
398     else if (i+1<size && data[i+1]==c)
399     {
400       // double symbol found, handing over to emph1
401       len = processEmphasis1(out, data-2, size+2, c);
402       if (len==0)
403       {
404         return 0;
405       }
406       else
407       {
408         return len - 2;
409       }
410     }
411     else
412     {
413       // single symbol found, handing over to emph2
414       len = processEmphasis2(out, data-1, size+1, c);
415       if (len==0)
416       {
417         return 0;
418       }
419       else
420       {
421         return len - 1;
422       }
423     }
424   }
425   return 0;
426 }
427
428 /** Process ndash and mdashes */
429 static int processNmdash(GrowBuf &out,const char *data,int,int size)
430 {
431   // precondition: data[0]=='-'
432   int i=1;
433   int count=1;
434   if (i<size && data[i]=='-') // found --
435   {
436     count++,i++;
437   }
438   if (i<size && data[i]=='-') // found ---
439   {
440     count++,i++;
441   }
442   if (i<size && data[i]=='-') // found ----
443   {
444     count++;
445   }
446   if (count==2) // -- => ndash
447   {
448     out.addStr("&ndash;");
449     return 2;
450   }
451   else if (count==3) // --- => ndash
452   {
453     out.addStr("&mdash;");
454     return 3;
455   }
456   // not an ndash or mdash
457   return 0;
458 }
459
460 /** Process a HTML tag. Note that <pre>..</pre> are treated specially, in
461  *  the sense that all code inside is written unprocessed
462  */
463 static int processHtmlTag(GrowBuf &out,const char *data,int offset,int size)
464 {
465   if (offset>0 && data[-1]=='\\') return 0; // escaped <
466
467   // find the end of the html tag
468   int i=1;
469   int l=0;
470   // compute length of the tag name
471   while (i<size && isIdChar(i)) i++,l++;
472   QCString tagName;
473   convertStringFragment(tagName,data+1,i-1);
474   if (tagName.lower()=="pre") // found <pre> tag
475   {
476     bool insideStr=FALSE;
477     while (i<size-6)
478     {
479       char c=data[i];
480       if (!insideStr && c=='<') // potential start of html tag
481       {
482         if (data[i+1]=='/' &&
483             tolower(data[i+2])=='p' && tolower(data[i+3])=='r' &&
484             tolower(data[i+4])=='e' && tolower(data[i+5])=='>')
485         { // found </pre> tag, copy from start to end of tag
486           out.addStr(data,i+6);
487           //printf("found <pre>..</pre> [%d..%d]\n",0,i+6);
488           return i+6;
489         }
490       }
491       else if (insideStr && c=='"')
492       {
493         if (data[i-1]!='\\') insideStr=FALSE;
494       }
495       else if (c=='"')
496       {
497         insideStr=TRUE;
498       }
499       i++;
500     }
501   }
502   else // some other html tag
503   {
504     if (l>0 && i<size)
505     {
506       if (data[i]=='/' && i<size-1 && data[i+1]=='>') // <bla/>
507       {
508         //printf("Found htmlTag={%s}\n",QCString(data).left(i+2).data());
509         out.addStr(data,i+2);
510         return i+2;
511       }
512       else if (data[i]=='>') // <bla>
513       {
514         //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
515         out.addStr(data,i+1);
516         return i+1;
517       }
518       else if (data[i]==' ') // <bla attr=...
519       {
520         i++;
521         bool insideAttr=FALSE;
522         while (i<size)
523         {
524           if (!insideAttr && data[i]=='"')
525           {
526             insideAttr=TRUE;
527           }
528           else if (data[i]=='"' && data[i-1]!='\\')
529           {
530             insideAttr=FALSE;
531           }
532           else if (!insideAttr && data[i]=='>') // found end of tag
533           {
534             //printf("Found htmlTag={%s}\n",QCString(data).left(i+1).data());
535             out.addStr(data,i+1);
536             return i+1;
537           }
538           i++;
539         }
540       }
541     }
542   }
543   //printf("Not a valid html tag\n");
544   return 0;
545 }
546
547 static int processEmphasis(GrowBuf &out,const char *data,int offset,int size)
548 {
549   if ((offset>0 && !isOpenEmphChar(-1)) || // invalid char before * or _
550       (size>1 && data[0]!=data[1] && !isIdChar(1)) || // invalid char after * or _
551       (size>2 && data[0]==data[1] && !isIdChar(2)))   // invalid char after ** or __
552   {
553     return 0;
554   }
555
556   char c = data[0];
557   int ret;
558   if (size>2 && data[1]!=c) // _bla or *bla
559   {
560     // whitespace cannot follow an opening emphasis
561     if (data[1]==' ' || data[1]=='\n' || 
562         (ret = processEmphasis1(out, data+1, size-1, c)) == 0)
563     {
564       return 0;
565     }
566     return ret+1;
567   }
568   if (size>3 && data[1]==c && data[2]!=c) // __bla or **bla
569   {
570     if (data[2]==' ' || data[2]=='\n' || 
571         (ret = processEmphasis2(out, data+2, size-2, c)) == 0)
572     {
573       return 0;
574     }
575     return ret+2;
576   }
577   if (size>4 && data[1]==c && data[2]==c && data[3]!=c) // ___bla or ***bla
578   {
579     if (data[3]==' ' || data[3]=='\n' || 
580         (ret = processEmphasis3(out, data+3, size-3, c)) == 0)
581     {
582       return 0;
583     }
584     return ret+3;
585   }
586   return 0;
587 }
588
589 static int processLink(GrowBuf &out,const char *data,int,int size)
590 {
591   QCString content;
592   QCString link;
593   QCString title;
594   int contentStart,contentEnd,linkStart,titleStart,titleEnd;
595   bool isImageLink = FALSE;
596   bool isToc = FALSE;
597   int i=1;
598   if (data[0]=='!')
599   {
600     isImageLink = TRUE;
601     if (size<2 || data[1]!='[')
602     {
603       return 0;
604     }
605     i++;
606   }
607   contentStart=i;
608   int level=1;
609   int nl=0;
610   // find the matching ]
611   while (i<size)
612   {
613     if (data[i-1]=='\\') // skip escaped characters
614     {
615     }
616     else if (data[i]=='[')
617     {
618       level++;
619     }
620     else if (data[i]==']')
621     {
622       level--;
623       if (level<=0) break;
624     }
625     else if (data[i]=='\n')
626     {
627       nl++;
628       if (nl>1) return 0; // only allow one newline in the content
629     }
630     i++;
631   }
632   if (i>=size) return 0; // premature end of comment -> no link
633   contentEnd=i;
634   convertStringFragment(content,data+contentStart,contentEnd-contentStart);
635   //printf("processLink: content={%s}\n",content.data());
636   if (!isImageLink && content.isEmpty()) return 0; // no link text
637   i++; // skip over ]
638
639   // skip whitespace
640   while (i<size && data[i]==' ') i++;
641   if (i<size && data[i]=='\n') // one newline allowed here
642   {
643     i++;
644     // skip more whitespace
645     while (i<size && data[i]==' ') i++;
646   }
647
648   bool explicitTitle=FALSE;
649   if (i<size && data[i]=='(') // inline link
650   {
651     i++;
652     while (i<size && data[i]==' ') i++;
653     if (i<size && data[i]=='<') i++;
654     linkStart=i;
655     nl=0;
656     while (i<size && data[i]!='\'' && data[i]!='"' && data[i]!=')') 
657     {
658       if (data[i]=='\n')
659       {
660         nl++;
661         if (nl>1) return 0;
662       }
663       i++;
664     }
665     if (i>=size || data[i]=='\n') return 0;
666     convertStringFragment(link,data+linkStart,i-linkStart);
667     link = link.stripWhiteSpace();
668     //printf("processLink: link={%s}\n",link.data());
669     if (link.isEmpty()) return 0;
670     if (link.at(link.length()-1)=='>') link=link.left(link.length()-1);
671
672     // optional title
673     if (data[i]=='\'' || data[i]=='"')
674     {
675       char c = data[i];
676       i++;
677       titleStart=i;
678       nl=0;
679       while (i<size && data[i]!=')')
680       {
681         if (data[i]=='\n') 
682         {
683           if (nl>1) return 0;
684           nl++;
685         }
686         i++;
687       }
688       if (i>=size)
689       {
690         return 0;
691       }
692       titleEnd = i-1;
693       // search back for closing marker
694       while (titleEnd>titleStart && data[titleEnd]==' ') titleEnd--;
695       if (data[titleEnd]==c) // found it
696       {
697         convertStringFragment(title,data+titleStart,titleEnd-titleStart);
698         //printf("processLink: title={%s}\n",title.data());
699       }
700       else
701       {
702         return 0;
703       }
704     }
705     i++;
706   }
707   else if (i<size && data[i]=='[') // reference link
708   {
709     i++;
710     linkStart=i;
711     nl=0;
712     // find matching ]
713     while (i<size && data[i]!=']')
714     {
715       if (data[i]=='\n')
716       {
717         nl++;
718         if (nl>1) return 0;
719       }
720       i++;
721     }
722     if (i>=size) return 0;
723     // extract link
724     convertStringFragment(link,data+linkStart,i-linkStart);
725     //printf("processLink: link={%s}\n",link.data());
726     link = link.stripWhiteSpace();
727     if (link.isEmpty()) // shortcut link
728     {
729       link=content;
730     }
731     // lookup reference
732     LinkRef *lr = g_linkRefs.find(link.lower());
733     if (lr) // found it
734     {
735       link  = lr->link;
736       title = lr->title;
737       //printf("processLink: ref: link={%s} title={%s}\n",link.data(),title.data());
738     }
739     else // reference not found! 
740     {
741       //printf("processLink: ref {%s} do not exist\n",link.lower().data());
742       return 0;
743     }
744     i++;
745   }
746   else if (i<size && data[i]!=':' && !content.isEmpty()) // minimal link ref notation [some id]
747   {
748     LinkRef *lr = g_linkRefs.find(content.lower());
749     //printf("processLink: minimal link {%s} lr=%p",content.data(),lr);
750     if (lr) // found it
751     {
752       link  = lr->link;
753       title = lr->title;
754       explicitTitle=TRUE;
755       i=contentEnd;
756     }
757     else if (content=="TOC")
758     {
759       isToc=TRUE;
760       i=contentEnd;
761     }
762     else
763     {
764       return 0;
765     }
766     i++;
767   }
768   else
769   {
770     return 0;
771   }
772   static QRegExp re("^[@\\]ref ");
773   if (isToc) // special case for [TOC]
774   {
775     if (g_current) g_current->stat=TRUE;
776   }
777   else if (isImageLink) 
778   {
779     bool ambig;
780     FileDef *fd=0;
781     if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1 ||
782         (fd=findFileDef(Doxygen::imageNameDict,link,ambig))) 
783         // assume doxygen symbol link or local image link
784     {
785       out.addStr("@image html ");
786       out.addStr(link.mid(fd ? 0 : 5));
787       if (!explicitTitle && !content.isEmpty())
788       { 
789         out.addStr(" \"");
790         out.addStr(content);
791         out.addStr("\"");
792       }
793       else if ((content.isEmpty() || explicitTitle) && !title.isEmpty())
794       {
795         out.addStr(" \"");
796         out.addStr(title);
797         out.addStr("\"");
798       }
799     }
800     else
801     {
802       out.addStr("<img src=\"");
803       out.addStr(link);
804       out.addStr("\" alt=\"");
805       out.addStr(content);
806       out.addStr("\"");
807       if (!title.isEmpty())
808       {
809         out.addStr(" title=\"");
810         out.addStr(substitute(title.simplifyWhiteSpace(),"\"","&quot;"));
811         out.addStr("\"");
812       }
813       out.addStr("/>");
814     }
815   }
816   else
817   {
818     if (link.find("@ref ")!=-1 || link.find("\\ref ")!=-1) 
819         // assume doxygen symbol link
820     {
821       out.addStr(link);
822       out.addStr(" \"");
823       if (explicitTitle && !title.isEmpty())
824       {
825         out.addStr(title);
826       }
827       else
828       {
829         out.addStr(content);
830       }
831       out.addStr("\"");
832     }
833     else if (link.find('/')!=-1 || link.find('.')!=-1 || link.find('#')!=-1) 
834     { // file/url link
835       out.addStr("<a href=\"");
836       out.addStr(link);
837       out.addStr("\"");
838       if (!title.isEmpty())
839       {
840         out.addStr(" title=\"");
841         out.addStr(substitute(title.simplifyWhiteSpace(),"\"","&quot;"));
842         out.addStr("\"");
843       }
844       out.addStr(">");
845       out.addStr(content.simplifyWhiteSpace());
846       out.addStr("</a>");
847     }
848     else // avoid link to e.g. F[x](y)
849     {
850       //printf("no link for '%s'\n",link.data());
851       return 0;
852     }
853   }
854   return i;
855 }
856
857 /** '`' parsing a code span (assuming codespan != 0) */
858 static int processCodeSpan(GrowBuf &out, const char *data, int /*offset*/, int size)
859 {
860   int end, nb = 0, i, f_begin, f_end;
861
862   /* counting the number of backticks in the delimiter */
863   while (nb<size && data[nb]=='`')
864   {
865     nb++;
866   }
867
868   /* finding the next delimiter */
869   i = 0;
870   int nl=0;
871   for (end=nb; end<size && i<nb && nl<2; end++)
872   {
873     if (data[end]=='`') 
874     {
875       i++; 
876     }
877     else if (data[end]=='\n')
878     {
879       i=0;
880       nl++;
881     }
882     else
883     {
884       i=0; 
885     }
886   }
887   if (i < nb && end >= size)
888   {
889     return 0;  // no matching delimiter
890   }
891   if (nl==2) // too many newlines inside the span
892   {
893     return 0;
894   }
895
896   // trimming outside whitespaces
897   f_begin = nb;
898   while (f_begin < end && data[f_begin]==' ')
899   {
900     f_begin++;
901   }
902   f_end = end - nb;
903   while (f_end > nb && data[f_end-1]==' ')
904   {
905     f_end--;
906   }
907
908   if (nb==1) // check for closing ' followed by space within f_begin..f_end
909   {
910     i=f_begin;
911     while (i<f_end-1)
912     {
913       if (data[i]=='\'' && !isIdChar(i+1)) // reject `some word' and not `it's cool`
914       {
915         return 0;
916       }
917       i++;
918     }
919   }
920   //printf("found code span '%s'\n",QCString(data+f_begin).left(f_end-f_begin).data());
921
922   /* real code span */
923   if (f_begin < f_end)
924   {
925     QCString codeFragment;
926     convertStringFragment(codeFragment,data+f_begin,f_end-f_begin);
927     out.addStr("<tt>");
928     //out.addStr(convertToHtml(codeFragment,TRUE));
929     out.addStr(escapeSpecialChars(codeFragment));
930     out.addStr("</tt>");
931   }
932   return end;
933 }
934
935
936 static int processSpecialCommand(GrowBuf &out, const char *data, int offset, int size)
937 {
938   int i=1;
939   QCString endBlockName = isBlockCommand(data,offset,size);
940   if (!endBlockName.isEmpty())
941   {
942     int l = endBlockName.length();
943     while (i<size-l)
944     {
945       if ((data[i]=='\\' || data[i]=='@') && // command
946           data[i-1]!='\\' && data[i-1]!='@') // not escaped
947       {
948         if (strncmp(&data[i+1],endBlockName,l)==0)
949         {
950           //printf("found end at %d\n",i);
951           out.addStr(data,i+1+l);
952           return i+1+l;
953         }
954       }
955       i++;
956     }
957   }
958   if (size>1 && data[0]=='\\')
959   {
960     char c=data[1];
961     if (c=='[' || c==']' || c=='*' || c=='+' || c=='-' ||
962         c=='!' || c=='(' || c==')' || c=='.' || c=='`' || c=='_') 
963     {
964       out.addStr(&data[1],1);
965       return 2;
966     }
967   }
968   return 0;
969 }
970
971 static void processInline(GrowBuf &out,const char *data,int size)
972 {
973   int i=0, end=0;
974   action_t action = 0;
975   while (i<size)
976   {
977     while (end<size && ((action=g_actions[(uchar)data[end]])==0)) end++;
978     out.addStr(data+i,end-i);
979     if (end>=size) break;
980     i=end;
981     end = action(out,data+i,i,size-i);
982     if (!end)
983     {
984       end=i+1;
985     }
986     else
987     {
988       i+=end;
989       end=i;
990     }
991   }
992 }
993
994 /** returns whether the line is a setext-style hdr underline */
995 static int isHeaderline(const char *data, int size)
996 {
997   int i=0, c=0;
998   while (i<size && data[i]==' ') i++;
999
1000   // test of level 1 header
1001   if (data[i]=='=')
1002   {
1003     while (i<size && data[i]=='=') i++,c++;
1004     while (i<size && data[i]==' ') i++;
1005     return (c>1 && (i>=size || data[i]=='\n')) ? 1 : 0;
1006   }
1007   // test of level 2 header
1008   if (data[i]=='-')
1009   {
1010     while (i<size && data[i]=='-') i++,c++;
1011     while (i<size && data[i]==' ') i++;
1012     return (c>1 && (i>=size || data[i]=='\n')) ? 2 : 0;
1013   }
1014   return 0;
1015 }
1016
1017 /** returns TRUE if this line starts a block quote */
1018 static bool isBlockQuote(const char *data,int size,int indent)
1019 {
1020   int i = 0;
1021   while (i<size && data[i]==' ') i++;
1022   if (i<indent+codeBlockIndent) // could be a quotation
1023   {
1024     // count >'s and skip spaces
1025     int level=0;
1026     while (i<size && (data[i]=='>' || data[i]==' ')) 
1027     {
1028       if (data[i]=='>') level++;
1029       i++;
1030     }
1031     // last characters should be a space or newline, 
1032     // so a line starting with >= does not match
1033     return level>0 && i<size && ((data[i-1]==' ') || data[i]=='\n'); 
1034   }
1035   else // too much indentation -> code block
1036   {
1037     return FALSE;
1038   }
1039   //return i<size && data[i]=='>' && i<indent+codeBlockIndent;
1040 }
1041
1042 /** returns end of the link ref if this is indeed a link reference. */
1043 static int isLinkRef(const char *data,int size,
1044             QCString &refid,QCString &link,QCString &title)
1045 {
1046   //printf("isLinkRef data={%s}\n",data);
1047   // format: start with [some text]:
1048   int i = 0;
1049   while (i<size && data[i]==' ') i++;
1050   if (i>=size || data[i]!='[') return 0;
1051   i++;
1052   int refIdStart=i;
1053   while (i<size && data[i]!='\n' && data[i]!=']') i++;
1054   if (i>=size || data[i]!=']') return 0;
1055   convertStringFragment(refid,data+refIdStart,i-refIdStart);
1056   if (refid.isEmpty()) return 0;
1057   //printf("  isLinkRef: found refid='%s'\n",refid.data());
1058   i++;
1059   if (i>=size || data[i]!=':') return 0;
1060   i++;
1061
1062   // format: whitespace* \n? whitespace* (<url> | url)
1063   while (i<size && data[i]==' ') i++;
1064   if (i<size && data[i]=='\n')
1065   {
1066     i++;
1067     while (i<size && data[i]==' ') i++;
1068   }
1069   if (i>=size) return 0;
1070
1071   if (i<size && data[i]=='<') i++;
1072   int linkStart=i;
1073   while (i<size && data[i]!=' ' && data[i]!='\n') i++;
1074   int linkEnd=i;
1075   if (i<size && data[i]=='>') i++;
1076   if (linkStart==linkEnd) return 0; // empty link
1077   convertStringFragment(link,data+linkStart,linkEnd-linkStart);
1078   //printf("  isLinkRef: found link='%s'\n",link.data());
1079   if (link=="@ref" || link=="\\ref")
1080   {
1081     int argStart=i;
1082     while (i<size && data[i]!='\n' && data[i]!='"') i++;
1083     QCString refArg;
1084     convertStringFragment(refArg,data+argStart,i-argStart);
1085     link+=refArg;
1086   }
1087
1088   title.resize(0);
1089
1090   // format: (whitespace* \n? whitespace* ( 'title' | "title" | (title) ))?
1091   int eol=0;
1092   while (i<size && data[i]==' ') i++;
1093   if (i<size && data[i]=='\n')
1094   {
1095     i++;
1096     eol=i;
1097     while (i<size && data[i]==' ') i++;
1098   }
1099   if (i>=size) 
1100   {
1101     //printf("end of isLinkRef while looking for title! i=%d\n",i);
1102     return i; // end of buffer while looking for the optional title
1103   }
1104
1105   char c = data[i];
1106   if (c=='\'' || c=='"' || c=='(') // optional title present?
1107   {
1108     //printf("  start of title found! char='%c'\n",c);
1109     i++;
1110     if (c=='(') c=')'; // replace c by end character
1111     int titleStart=i;
1112     // search for end of the line
1113     while (i<size && data[i]!='\n') i++;
1114
1115     // search back to matching character
1116     int end=i-1;
1117     while (end>titleStart && data[end]!=c) end--;
1118     if (end>titleStart)
1119     {
1120       convertStringFragment(title,data+titleStart,end-titleStart);
1121     }
1122     //printf("  title found: '%s'\n",title.data());
1123   }
1124   while (i<size && data[i]==' ') i++;
1125   //printf("end of isLinkRef: i=%d size=%d data[i]='%c' eol=%d\n",
1126   //    i,size,data[i],eol);
1127   if      (i>=size)       return i;    // end of buffer while ref id was found
1128   else if (data[i]=='\n') return i+1;  // end of line while ref id was found
1129   else if (eol)           return eol;  // no optional title found
1130   return 0;                            // invalid link ref
1131 }
1132
1133 static int isHRuler(const char *data,int size)
1134 {
1135   int i=0;
1136   if (size>0 && data[size-1]=='\n') size--; // ignore newline character
1137   while (i<size && data[i]==' ') i++;
1138   if (i>=size) return 0; // empty line
1139   char c=data[i];
1140   if (c!='*' && c!='-' && c!='_') 
1141   {
1142     return 0; // not a hrule character
1143   }
1144   int n=0;
1145   while (i<size)
1146   {
1147     if (data[i]==c)
1148     {
1149       n++; // count rule character
1150     }
1151     else if (data[i]!=' ')
1152     {
1153       return 0; // line contains non hruler characters
1154     }
1155     i++;
1156   }
1157   return n>=3; // at least 3 characters needed for a hruler
1158 }
1159
1160 static QCString extractTitleId(QCString &title)
1161 {
1162   //static QRegExp r1("^[a-z_A-Z][a-z_A-Z0-9\\-]*:");
1163   static QRegExp r2("\\{#[a-z_A-Z][a-z_A-Z0-9\\-]*\\}$");
1164   int l=0;
1165   int i = r2.match(title,0,&l);
1166   if (i!=-1) // found {#id} style id
1167   {
1168     QCString id = title.mid(i+2,l-3);
1169     title = title.left(i)+title.mid(i+l);
1170     //printf("found id='%s' title='%s'\n",id.data(),title.data());
1171     return id;
1172   }
1173   //printf("no id found in title '%s'\n",title.data());
1174   return "";
1175 }
1176
1177
1178 static int isAtxHeader(const char *data,int size,
1179                        QCString &header,QCString &id)
1180 {
1181   int i = 0, end;
1182   int level = 0, blanks=0;
1183
1184   // find start of header text and determine heading level
1185   while (i<size && data[i]==' ') i++;
1186   if (i>=size || data[i]!='#') 
1187   {
1188     return 0;
1189   }
1190   while (i<size && level<6 && data[i]=='#') i++,level++;
1191   while (i<size && data[i]==' ') i++,blanks++;
1192   if (level==1 && blanks==0) 
1193   {
1194     return 0; // special case to prevent #someid seen as a header (see bug 671395)
1195   }
1196
1197   // find end of header text
1198   end=i;
1199   while (end<size && data[end]!='\n') end++;
1200   while (end>i && (data[end-1]=='#' || data[end-1]==' ')) end--;
1201
1202   // store result
1203   convertStringFragment(header,data+i,end-i);
1204   id = extractTitleId(header);
1205   if (!id.isEmpty()) // strip #'s between title and id
1206   {
1207     i=header.length()-1;
1208     while (i>=0 && (header.at(i)=='#' || header.at(i)==' ')) i--;
1209     header=header.left(i+1);
1210   }
1211
1212   return level;
1213 }
1214
1215 static int isEmptyLine(const char *data,int size)
1216 {
1217   int i=0;
1218   while (i<size)
1219   {
1220     if (data[i]=='\n') return TRUE;
1221     if (data[i]!=' ') return FALSE;
1222     i++;
1223   }
1224   return TRUE;
1225 }
1226
1227 #define isLiTag(i) \
1228    (data[(i)]=='<' && \
1229    (data[(i)+1]=='l' || data[(i)+1]=='L') && \
1230    (data[(i)+2]=='i' || data[(i)+2]=='I') && \
1231    (data[(i)+3]=='>'))
1232
1233 // compute the indent from the start of the input, excluding list markers
1234 // such as -, -#, *, +, 1., and <li>
1235 static int computeIndentExcludingListMarkers(const char *data,int size)
1236 {
1237   int i=0;
1238   int indent=0;
1239   bool isDigit=FALSE;
1240   bool isLi=FALSE;
1241   bool listMarkerSkipped=FALSE;
1242   while (i<size && 
1243          (data[i]==' ' ||                                    // space
1244           (!listMarkerSkipped &&                             // first list marker
1245            (data[i]=='+' || data[i]=='-' || data[i]=='*' ||  // unordered list char
1246             (data[i]=='#' && i>0 && data[i-1]=='-') ||       // -# item
1247             (isDigit=(data[i]>='1' && data[i]<='9')) ||      // ordered list marker?
1248             (isLi=(i<size-3 && isLiTag(i)))                  // <li> tag
1249            )
1250           )
1251          )
1252         ) 
1253   {
1254     if (isDigit) // skip over ordered list marker '10. '
1255     {
1256       int j=i+1;
1257       while (j<size && ((data[j]>='0' && data[j]<='9') || data[j]=='.'))
1258       {
1259         if (data[j]=='.') // should be end of the list marker
1260         {
1261           if (j<size-1 && data[j+1]==' ') // valid list marker
1262           {
1263             listMarkerSkipped=TRUE;
1264             indent+=j+1-i;
1265             i=j+1;
1266             break;
1267           }
1268           else // not a list marker
1269           {
1270             break;
1271           }
1272         }
1273         j++;
1274       }
1275     }
1276     else if (isLi)
1277     {
1278       i+=3; // skip over <li>
1279       indent+=3;
1280       listMarkerSkipped=TRUE;
1281     }
1282     else if (data[i]=='-' && i<size-2 && data[i+1]=='#' && data[i+2]==' ')
1283     { // case "-# "
1284       listMarkerSkipped=TRUE; // only a single list marker is accepted
1285       i++; // skip over #
1286       indent++;
1287     }
1288     else if (data[i]!=' ' && i<size-1 && data[i+1]==' ')
1289     { // case "- " or "+ " or "* "
1290       listMarkerSkipped=TRUE; // only a single list marker is accepted
1291     }
1292     if (data[i]!=' ' && !listMarkerSkipped)
1293     { // end of indent
1294       break;
1295     }
1296     indent++,i++;
1297   }
1298   //printf("{%s}->%d\n",QCString(data).left(size).data(),indent);
1299   return indent;
1300 }
1301
1302 static bool isFencedCodeBlock(const char *data,int size,int refIndent,
1303                              QCString &lang,int &start,int &end,int &offset)
1304 {
1305   // rules: at least 3 ~~~, end of the block same amount of ~~~'s, otherwise
1306   // return FALSE
1307   int i=0;
1308   int indent=0;
1309   int startTildes=0;
1310   while (i<size && data[i]==' ') indent++,i++;
1311   if (indent>=refIndent+4) return FALSE; // part of code block
1312   while (i<size && data[i]=='~') startTildes++,i++;
1313   if (startTildes<3) return FALSE; // not enough tildes
1314   if (i<size && data[i]=='{') i++; // skip over optional {
1315   int startLang=i;
1316   while (i<size && (data[i]!='\n' && data[i]!='}' && data[i]!=' ')) i++;
1317   convertStringFragment(lang,data+startLang,i-startLang);
1318   while (i<size && data[i]!='\n') i++; // proceed to the end of the line
1319   start=i;
1320   while (i<size)
1321   {
1322     if (data[i]=='~')
1323     {
1324       end=i-1;
1325       int endTildes=0;
1326       while (i<size && data[i]=='~') endTildes++,i++; 
1327       while (i<size && data[i]==' ') i++;
1328       if (i==size || data[i]=='\n') 
1329       {
1330         offset=i;
1331         return endTildes==startTildes;
1332       }
1333     }
1334     i++;
1335   }
1336   return FALSE;
1337 }
1338
1339 static bool isCodeBlock(const char *data,int offset,int size,int &indent)
1340 {
1341   //printf("<isCodeBlock(offset=%d,size=%d,indent=%d)\n",offset,size,indent);
1342   // determine the indent of this line
1343   int i=0;
1344   int indent0=0;
1345   while (i<size && data[i]==' ') indent0++,i++;
1346
1347   if (indent0<codeBlockIndent)
1348   {
1349     //printf(">isCodeBlock: line is not indented enough %d<4\n",indent0);
1350     return FALSE;
1351   }
1352   if (indent0>=size || data[indent0]=='\n') // empty line does not start a code block
1353   {
1354     //printf("only spaces at the end of a comment block\n");
1355     return FALSE;
1356   }
1357     
1358   i=offset;
1359   int nl=0;
1360   int nl_pos[3];
1361   // search back 3 lines and remember the start of lines -1 and -2
1362   while (i>0 && nl<3)
1363   {
1364     if (data[i-offset-1]=='\n') nl_pos[nl++]=i-offset;
1365     i--;
1366   }
1367
1368   // if there are only 2 preceding lines, then line -2 starts at -offset
1369   if (i==0 && nl==2) nl_pos[nl++]=-offset;
1370   //printf("  nl=%d\n",nl);
1371
1372   if (nl==3) // we have at least 2 preceding lines
1373   {
1374     //printf("  positions: nl_pos=[%d,%d,%d] line[-2]='%s' line[-1]='%s'\n",
1375     //    nl_pos[0],nl_pos[1],nl_pos[2],
1376     //    QCString(data+nl_pos[1]).left(nl_pos[0]-nl_pos[1]-1).data(),
1377     //    QCString(data+nl_pos[2]).left(nl_pos[1]-nl_pos[2]-1).data());
1378
1379     // check that line -1 is empty
1380     if (!isEmptyLine(data+nl_pos[1],nl_pos[0]-nl_pos[1]-1))
1381     {
1382       return FALSE;
1383     }
1384
1385     // determine the indent of line -2
1386     indent=computeIndentExcludingListMarkers(data+nl_pos[2],nl_pos[1]-nl_pos[2]);
1387     
1388     //printf(">isCodeBlock local_indent %d>=%d+4=%d\n",
1389     //    indent0,indent2,indent0>=indent2+4);
1390     // if the difference is >4 spaces -> code block
1391     return indent0>=indent+codeBlockIndent;
1392   }
1393   else // not enough lines to determine the relative indent, use global indent
1394   {
1395     // check that line -1 is empty
1396     if (nl==1 && !isEmptyLine(data-offset,offset-1))
1397     {
1398       return FALSE;
1399     }
1400     //printf(">isCodeBlock global indent %d>=%d+4=%d nl=%d\n",
1401     //    indent0,indent,indent0>=indent+4,nl);
1402     return indent0>=indent+codeBlockIndent;
1403   }
1404 }
1405
1406 /** Finds the location of the table's contains in the string \a data.
1407  *  Only one line will be inspected.
1408  *  @param[in] data pointer to the string buffer.
1409  *  @param[in] size the size of the buffer.
1410  *  @param[out] start offset of the first character of the table content
1411  *  @param[out] end   offset of the last character of the table content
1412  *  @param[out] columns number of table columns found
1413  *  @returns The offset until the next line in the buffer.
1414  */
1415 int findTableColumns(const char *data,int size,int &start,int &end,int &columns)
1416 {
1417   int i=0;
1418   int eol;
1419   // find start character of the table line
1420   while (i<size && data[i]==' ') i++;
1421   if (i<size && data[i]=='|') i++; // leading | does not count
1422   start = i;
1423
1424   // find end character of the table line
1425   while (i<size && data[i]!='\n') i++;
1426   eol=i+1;
1427   i--;
1428   while (i>0 && data[i]==' ') i--;
1429   if (i>0 && data[i]=='|') i--; // trailing | does not count
1430   end = i;
1431
1432   // count columns between start and end
1433   columns=1;
1434   if (end>start)
1435   {
1436     i=start;
1437     while (i<=end)
1438     {
1439       if (data[i]=='|' && (i==0 || data[i-1]!='\\')) columns++;
1440       i++;
1441     }
1442   }
1443   //printf("findTableColumns(start=%d,end=%d,columns=%d) eol=%d\n",
1444   //    start,end,columns,eol);
1445   return eol;
1446 }
1447
1448 /** Returns TRUE iff data points to the start of a table block */
1449 static bool isTableBlock(const char *data,int size)
1450 {
1451   int cc0,start,end;
1452
1453   // the first line should have at least two columns separated by '|'
1454   int i = findTableColumns(data,size,start,end,cc0);
1455   if (i>=size || cc0<2) 
1456   {
1457     //printf("isTableBlock: no |'s in the header\n");
1458     return FALSE;
1459   }
1460
1461   int cc1;
1462   int ret = findTableColumns(data+i,size-i,start,end,cc1);
1463   int j=i+start;
1464   // separator line should consist of |, - and : and spaces only
1465   while (j<=end+i)
1466   {
1467     if (data[j]!=':' && data[j]!='-' && data[j]!='|' && data[j]!=' ')
1468     {
1469       //printf("isTableBlock: invalid character '%c'\n",data[j]);
1470       return FALSE; // invalid characters in table separator
1471     }
1472     j++;
1473   }
1474   if (cc1!=cc0) // number of columns should be same as previous line
1475   {
1476     return FALSE;
1477   }
1478
1479   i+=ret; // goto next line
1480   int cc2;
1481   ret = findTableColumns(data+i,size-i,start,end,cc2);
1482
1483   //printf("isTableBlock: %d\n",cc1==cc2);
1484   return cc1==cc2;
1485 }
1486
1487 static int writeTableBlock(GrowBuf &out,const char *data,int size)
1488 {
1489   int i=0,j,k;
1490   int columns,start,end,cc;
1491
1492   i = findTableColumns(data,size,start,end,columns);
1493   
1494   out.addStr("<table>");
1495
1496   // write table header, in range [start..end]
1497   out.addStr("<tr>");
1498
1499   int headerStart = start;
1500   int headerEnd = end;
1501
1502   // read cell alignments
1503   int ret = findTableColumns(data+i,size-i,start,end,cc);
1504   k=0;
1505   Alignment *columnAlignment = new Alignment[columns];
1506
1507   bool leftMarker=FALSE,rightMarker=FALSE;
1508   bool startFound=FALSE;
1509   j=start+i;
1510   while (j<=end+i)
1511   {
1512     if (!startFound)
1513     {
1514       if (data[j]==':') { leftMarker=TRUE; startFound=TRUE; }
1515       if (data[j]=='-') startFound=TRUE; 
1516       //printf("  data[%d]=%c startFound=%d\n",j,data[j],startFound);
1517     }
1518     if      (data[j]=='-') rightMarker=FALSE;
1519     else if (data[j]==':') rightMarker=TRUE;
1520     if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\'))) 
1521     {
1522       if (k<columns)
1523       {
1524         columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
1525         //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
1526         leftMarker=FALSE;
1527         rightMarker=FALSE;
1528         startFound=FALSE;
1529       }
1530       k++;
1531     }
1532     j++;
1533   }
1534   if (k<columns)
1535   {
1536     columnAlignment[k] = markersToAlignment(leftMarker,rightMarker);
1537     //printf("column[%d] alignment=%d\n",k,columnAlignment[k]);
1538   }
1539   // proceed to next line
1540   i+=ret;
1541
1542   int m=headerStart;
1543   for (k=0;k<columns;k++)
1544   {
1545     out.addStr("<th");
1546     switch (columnAlignment[k])
1547     {
1548       case AlignLeft:   out.addStr(" align=left"); break;
1549       case AlignRight:  out.addStr(" align=right"); break;
1550       case AlignCenter: out.addStr(" align=center"); break;
1551       case AlignNone:   break;
1552     }
1553     out.addStr(">");
1554     while (m<=headerEnd && (data[m]!='|' || (m>0 && data[m-1]=='\\')))
1555     {
1556       out.addChar(data[m++]);
1557     }
1558     m++;
1559   }
1560
1561   // write table cells
1562   while (i<size)
1563   {
1564     int ret = findTableColumns(data+i,size-i,start,end,cc);
1565     //printf("findTableColumns cc=%d\n",cc);
1566     if (cc!=columns) break; // end of table
1567
1568     out.addStr("<tr>");
1569     j=start+i;
1570     int columnStart=j;
1571     k=0;
1572     while (j<=end+i)
1573     {
1574       if (j==columnStart)
1575       {
1576         out.addStr("<td");
1577         switch (columnAlignment[k])
1578         {
1579           case AlignLeft:   out.addStr(" align=left"); break;
1580           case AlignRight:  out.addStr(" align=right"); break;
1581           case AlignCenter: out.addStr(" align=center"); break;
1582           case AlignNone:   break;
1583         }
1584         out.addStr(">");
1585       }
1586       if (j<=end+i && (data[j]=='|' && (j==0 || data[j-1]!='\\'))) 
1587       {
1588         columnStart=j+1;
1589         k++;
1590       }
1591       else
1592       {
1593         out.addChar(data[j]);
1594       }
1595       j++;
1596     }
1597     out.addChar('\n');
1598
1599     // proceed to next line
1600     i+=ret;
1601   }
1602
1603   out.addStr("</table>\n");
1604
1605   delete[] columnAlignment;
1606   return i;
1607 }
1608
1609
1610 void writeOneLineHeaderOrRuler(GrowBuf &out,const char *data,int size)
1611 {
1612   int level;
1613   QCString header;
1614   QCString id;
1615   if (isHRuler(data,size))
1616   {
1617     out.addStr("<hr>\n");
1618   }
1619   else if ((level=isAtxHeader(data,size,header,id)))
1620   {
1621     //if (level==1) g_correctSectionLevel=FALSE;
1622     //if (g_correctSectionLevel) level--;
1623     QCString hTag;
1624     if (level<5 && !id.isEmpty())
1625     {
1626       SectionInfo::SectionType type = SectionInfo::Anchor;
1627       switch(level)
1628       {
1629         case 1:  out.addStr("@section ");       
1630                  type=SectionInfo::Section; 
1631                  break;
1632         case 2:  out.addStr("@subsection ");    
1633                  type=SectionInfo::Subsection; 
1634                  break;
1635         case 3:  out.addStr("@subsubsection "); 
1636                  type=SectionInfo::Subsubsection;
1637                  break;
1638         default: out.addStr("@paragraph "); 
1639                  type=SectionInfo::Paragraph;
1640                  break;
1641       }
1642       out.addStr(id);
1643       out.addStr(" ");
1644       out.addStr(header);
1645       out.addStr("\n");
1646       SectionInfo *si = new SectionInfo(g_fileName,id,header,type,level);
1647       if (g_current)
1648       {
1649         g_current->anchors->append(si);
1650       }
1651       Doxygen::sectionDict.append(header,si);
1652     }
1653     else
1654     {
1655       if (!id.isEmpty())
1656       {
1657         out.addStr("\\anchor "+id+"\n");
1658       }
1659       hTag.sprintf("h%d",level);
1660       out.addStr("<"+hTag+">");
1661       out.addStr(header); 
1662       out.addStr("</"+hTag+">\n");
1663     }
1664   }
1665   else // nothing interesting -> just output the line
1666   {
1667     out.addStr(data,size);
1668   }
1669 }
1670
1671 static int writeBlockQuote(GrowBuf &out,const char *data,int size)
1672 {
1673   int l;
1674   int i=0;
1675   int curLevel=0;
1676   int end=0;
1677   while (i<size)
1678   {
1679     // find end of this line
1680     end=i+1;
1681     while (end<size && data[end-1]!='\n') end++;
1682     int j=i;
1683     int level=0;
1684     int indent=i;
1685     // compute the quoting level
1686     while (j<end && (data[j]==' ' || data[j]=='>'))
1687     {
1688       if (data[j]=='>') { level++; indent=j+1; }
1689       else if (j>0 && data[j-1]=='>') indent=j+1;
1690       j++;
1691     }
1692     if (j>0 && data[j-1]=='>') // disqualify last > if not followed by space
1693     {
1694       indent--;
1695       j--;
1696     }
1697     if (level>curLevel) // quote level increased => add start markers
1698     {
1699       for (l=curLevel;l<level;l++)
1700       {
1701         out.addStr("<blockquote>\n");
1702       }
1703     }
1704     else if (level<curLevel) // quote level descreased => add end markers
1705     {
1706       for (l=level;l<curLevel;l++)
1707       {
1708         out.addStr("\n</blockquote>\n");
1709       }
1710     }
1711     curLevel=level;
1712     if (level==0) break; // end of quote block
1713     // copy line without quotation marks
1714     out.addStr(data+indent,end-indent);
1715     // proceed with next line
1716     i=end;
1717   }
1718   // end of comment within blockquote => add end markers
1719   for (l=0;l<curLevel;l++)
1720   {
1721     out.addStr("\n</blockquote>\n");
1722   }
1723   return i;
1724 }
1725
1726 static int writeCodeBlock(GrowBuf &out,const char *data,int size,int refIndent)
1727 {
1728   int i=0,end;
1729   //printf("writeCodeBlock: data={%s}\n",QCString(data).left(size).data());
1730   out.addStr("@verbatim\n");
1731   int emptyLines=0;
1732   while (i<size)
1733   {
1734     // find end of this line
1735     end=i+1;
1736     while (end<size && data[end-1]!='\n') end++;
1737     int j=i;
1738     int indent=0;
1739     while (j<end && data[j]==' ') j++,indent++;
1740     //printf("j=%d end=%d indent=%d refIndent=%d tabSize=%d data={%s}\n",
1741     //    j,end,indent,refIndent,Config_getInt("TAB_SIZE"),QCString(data+i).left(end-i-1).data());
1742     if (j==end-1) // empty line 
1743     {
1744       emptyLines++;
1745       i=end;
1746     }
1747     else if (indent>=refIndent+codeBlockIndent) // enough indent to contine the code block
1748     {
1749       while (emptyLines>0) // write skipped empty lines
1750       {
1751         // add empty line
1752         out.addStr("\n");
1753         emptyLines--;
1754       }
1755       // add code line minus the indent
1756       out.addStr(data+i+refIndent+codeBlockIndent,end-i-refIndent-codeBlockIndent);
1757       i=end;
1758     }
1759     else // end of code block
1760     {
1761       break;
1762     }
1763   }
1764   out.addStr("@endverbatim\n"); 
1765   while (emptyLines>0) // write skipped empty lines
1766   {
1767     // add empty line
1768     out.addStr("\n");
1769     emptyLines--;
1770   }
1771   //printf("i=%d\n",i);
1772   return i;
1773 }
1774
1775 // start searching for the end of the line start at offset \a i
1776 // keeping track of possible blocks that need to to skipped.
1777 static void findEndOfLine(GrowBuf &out,const char *data,int size,
1778                           int &pi,int&i,int &end)
1779 {
1780   // find end of the line
1781   int nb=0;
1782   end=i+1;
1783   while (end<size && data[end-1]!='\n')
1784   {
1785     // while looking for the end of the line we might encounter a block
1786     // that needs to be passed unprocessed.
1787     if ((data[end-1]=='\\' || data[end-1]=='@') &&          // command
1788         (end<=1 || (data[end-2]!='\\' && data[end-2]!='@')) // not escaped
1789        )
1790     {
1791       QCString endBlockName = isBlockCommand(data+end-1,end-1,size-(end-1));
1792       if (!endBlockName.isEmpty())
1793       {
1794         int l = endBlockName.length();
1795         for (;end<size-l;end++) // search for end of block marker
1796         {
1797           if ((data[end]=='\\' || data[end]=='@') &&
1798               data[end-1]!='\\' && data[end-1]!='@'
1799              )
1800           {
1801             if (strncmp(&data[end+1],endBlockName,l)==0)
1802             {
1803               if (pi!=-1) // output previous line if available
1804               {
1805                 //printf("feol out={%s}\n",QCString(data+pi).left(i-pi).data());
1806                 out.addStr(data+pi,i-pi);
1807               }
1808               // found end marker, skip over this block
1809               //printf("feol.block out={%s}\n",QCString(data+i).left(end+l+1-i).data());
1810               out.addStr(data+i,end+l+1-i);
1811               pi=-1;
1812               i=end+l+1; // continue after block
1813               end=i+1;
1814               break;
1815             }
1816           }
1817         }
1818       }
1819       else
1820       {
1821         end++;
1822       }
1823     }
1824     else if (nb==0 && data[end-1]=='<' && end<size-6 &&
1825              (end<=1 || (data[end-2]!='\\' && data[end-2]!='@'))
1826             )
1827     {
1828       if (tolower(data[end])=='p' && tolower(data[end+1])=='r' &&
1829           tolower(data[end+2])=='e' && data[end+3]=='>') // <pre> tag
1830       {
1831         if (pi!=-1) // output previous line if available
1832         {
1833           out.addStr(data+pi,i-pi);
1834         }
1835         // output part until <pre>
1836         out.addStr(data+i,end-1-i); 
1837         // output part until </pre>
1838         i = end-1 + processHtmlTag(out,data+end-1,end-1,size-end+1);
1839         pi=-1;
1840         end = i+1;
1841         break;
1842       }
1843       else
1844       {
1845         end++;
1846       }
1847     }
1848     else if (nb==0 && data[end-1]=='`') 
1849     {
1850       while (end<size && data[end-1]=='`') end++,nb++;
1851     }
1852     else if (nb>0 && data[end-1]=='`')
1853     {
1854       int enb=0;
1855       while (end<size && data[end-1]=='`') end++,enb++;
1856       if (enb==nb) nb=0;
1857     }
1858     else
1859     {
1860       end++;
1861     }
1862   }
1863   //printf("findEndOfLine pi=%d i=%d end=%d {%s}\n",pi,i,end,QCString(data+i).left(end-i).data());
1864 }
1865
1866 static QCString processQuotations(const QCString &s,int refIndent)
1867 {
1868   GrowBuf out;
1869   const char *data = s.data();
1870   int size = s.length();
1871   int i=0,end=0,pi=-1;
1872   while (i<size)
1873   {
1874     findEndOfLine(out,data,size,pi,i,end);
1875     // line is now found at [i..end)
1876
1877     if (pi!=-1)
1878     {
1879       if (isBlockQuote(data+pi,i-pi,refIndent))
1880       {
1881         i = pi+writeBlockQuote(out,data+pi,size-pi);
1882         pi=-1;
1883         end=i+1;
1884         continue;
1885       }
1886       else
1887       {
1888         //printf("quote out={%s}\n",QCString(data+pi).left(i-pi).data());
1889         out.addStr(data+pi,i-pi);
1890       }
1891     }
1892     pi=i;
1893     i=end;
1894   }
1895   if (pi!=-1 && pi<size) // deal with the last line
1896   {
1897     if (isBlockQuote(data+pi,size-pi,refIndent))
1898     {
1899       writeBlockQuote(out,data+pi,size-pi);
1900     }
1901     else
1902     {
1903       out.addStr(data+pi,size-pi);
1904     }
1905   }
1906   out.addChar(0);
1907
1908   //printf("Process quotations\n---- input ----\n%s\n---- output ----\n%s\n------------\n",
1909   //    s.data(),out.get());
1910
1911   return out.get();
1912 }
1913
1914 static QCString processBlocks(const QCString &s,int indent)
1915 {
1916   GrowBuf out;
1917   const char *data = s.data();
1918   int size = s.length();
1919   int i=0,end=0,pi=-1,ref,level;
1920   QCString id,link,title;
1921   int blockIndent = indent;
1922
1923   // get indent for the first line
1924   end = i+1;
1925   int sp=0;
1926   while (end<size && data[end-1]!='\n') 
1927   {
1928     if (data[end-1]==' ') sp++;
1929     end++;
1930   }
1931
1932   // special case when the documentation starts with a code block
1933   // since the first line is skipped when looking for a code block later on.
1934   if (end>codeBlockIndent && isCodeBlock(data,0,end,blockIndent))
1935   {
1936     i=writeCodeBlock(out,data,size,blockIndent);
1937     end=i+1;
1938     pi=-1;
1939   }
1940
1941   // process each line
1942   while (i<size)
1943   {
1944     findEndOfLine(out,data,size,pi,i,end);
1945     // line is now found at [i..end)
1946
1947     //printf("findEndOfLine: pi=%d i=%d end=%d\n",pi,i,end);
1948
1949     if (pi!=-1)
1950     {
1951       int blockStart,blockEnd,blockOffset;
1952       QCString lang;
1953       blockIndent = indent;
1954       //printf("isHeaderLine(%s)=%d\n",QCString(data+i).left(size-i).data(),level);
1955       if ((level=isHeaderline(data+i,size-i))>0)
1956       {
1957         //if (level==1) g_correctSectionLevel=FALSE;
1958         //if (g_correctSectionLevel) level--;
1959         //printf("Found header at %d-%d\n",i,end);
1960         while (pi<size && data[pi]==' ') pi++;
1961         QCString header,id;
1962         convertStringFragment(header,data+pi,i-pi-1);
1963         id = extractTitleId(header);
1964         if (!header.isEmpty())
1965         {
1966           if (!id.isEmpty())
1967           {
1968             out.addStr(level==1?"@section ":"@subsection ");
1969             out.addStr(id);
1970             out.addStr(" ");
1971             out.addStr(header);
1972             out.addStr("\n");
1973             SectionInfo *si = new SectionInfo(g_fileName,id,header,
1974                 level==1 ? SectionInfo::Section : SectionInfo::Subsection,level);
1975             if (g_current)
1976             {
1977               g_current->anchors->append(si);
1978             }
1979             Doxygen::sectionDict.append(header,si);
1980           }
1981           else
1982           {
1983             out.addStr(level==1?"<h1>":"<h2>");
1984             out.addStr(header);
1985             out.addStr(level==1?"</h1>\n":"</h2>\n");
1986           }
1987         }
1988         else
1989         {
1990           out.addStr("<hr>\n");
1991         }
1992         pi=-1;
1993         i=end;
1994         end=i+1;
1995         continue;
1996       }
1997       else if ((ref=isLinkRef(data+pi,size-pi,id,link,title)))
1998       {
1999         //printf("found link ref: id='%s' link='%s' title='%s'\n",
2000         //       id.data(),link.data(),title.data());
2001         g_linkRefs.insert(id.lower(),new LinkRef(link,title));
2002         i=ref+pi;
2003         pi=-1;
2004         end=i+1;
2005       }
2006       else if (isFencedCodeBlock(data+pi,size-pi,indent,lang,blockStart,blockEnd,blockOffset))
2007       {
2008         //printf("Found FencedCodeBlock lang='%s' start=%d end=%d code={%s}\n",
2009         //       lang.data(),blockStart,blockEnd,QCString(data+pi+blockStart).left(blockEnd-blockStart).data());
2010         if (!lang.isEmpty() && lang.at(0)=='.') lang=lang.mid(1);
2011         out.addStr("@code");
2012         if (!lang.isEmpty())
2013         {
2014           out.addStr("{"+lang+"}");
2015         }
2016         out.addStr(data+pi+blockStart,blockEnd-blockStart);       
2017         out.addStr("\n");
2018         out.addStr("@endcode");
2019         i=pi+blockOffset;
2020         pi=-1;
2021         end=i+1;
2022         continue;
2023       }
2024       else if (isCodeBlock(data+i,i,end-i,blockIndent))
2025       {
2026         // skip previous line (it is empty anyway)
2027         i+=writeCodeBlock(out,data+i,size-i,blockIndent);
2028         pi=-1;
2029         end=i+1;
2030         continue;
2031       }
2032       else if (isTableBlock(data+pi,size-pi))
2033       {
2034         i=pi+writeTableBlock(out,data+pi,size-pi);
2035         pi=-1;
2036         end=i+1;
2037         continue;
2038       }
2039       else
2040       {
2041         writeOneLineHeaderOrRuler(out,data+pi,i-pi);
2042       }
2043     }
2044     pi=i;
2045     i=end;
2046   }
2047   //printf("last line %d size=%d\n",i,size);
2048   if (pi!=-1 && pi<size) // deal with the last line
2049   {
2050     if (isLinkRef(data+pi,size-pi,id,link,title))
2051     {
2052       //printf("found link ref: id='%s' link='%s' title='%s'\n",
2053       //    id.data(),link.data(),title.data());
2054       g_linkRefs.insert(id,new LinkRef(link,title));
2055     }
2056     else
2057     {
2058       writeOneLineHeaderOrRuler(out,data+pi,size-pi);
2059     }
2060   }
2061
2062   out.addChar(0);
2063   return out.get();
2064 }
2065
2066 static QCString extractPageTitle(QCString &docs,QCString &id)
2067 {
2068   // first first non-empty line
2069   QCString title;
2070   const char *data = docs.data();
2071   int i=0;
2072   int size=docs.size();
2073   while (i<size && (data[i]==' ' || data[i]=='\n')) i++;
2074   if (i>=size) return "";
2075   int end1=i+1;
2076   while (end1<size && data[end1-1]!='\n') end1++;
2077   //printf("i=%d end1=%d size=%d line='%s'\n",i,end1,size,docs.mid(i,end1-i).data());
2078   // first line from i..end1
2079   if (end1<size)
2080   {
2081     // second line form end1..end2
2082     int end2=end1+1;
2083     while (end2<size && data[end2-1]!='\n') end2++;
2084     if (isHeaderline(data+end1,size-end1))
2085     {
2086       convertStringFragment(title,data+i,end1-i-1);
2087       docs=docs.mid(end2);
2088       id = extractTitleId(title);
2089       //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
2090       return title;
2091     }
2092   }
2093   if (i<end1 && isAtxHeader(data+i,end1-i,title,id)>0)
2094   {
2095     docs=docs.mid(end1);
2096   }
2097   id = extractTitleId(title);
2098   //printf("extractPageTitle(title='%s' docs='%s' id='%s')\n",title.data(),docs.data(),id.data());
2099   return title;
2100 }
2101
2102 static QCString detab(const QCString &s,int &refIndent)
2103 {
2104   static int tabSize = Config_getInt("TAB_SIZE");
2105   GrowBuf out;
2106   int size = s.length();
2107   const char *data = s.data();
2108   int i=0;
2109   int col=0;
2110   const int maxIndent=1000000; // value representing infinity
2111   int minIndent=maxIndent;
2112   while (i<size)
2113   {
2114     char c = data[i++];
2115     switch(c)
2116     {
2117       case '\t': // expand tab
2118         {
2119           int stop = tabSize - (col%tabSize);
2120           //printf("expand at %d stop=%d\n",col,stop);
2121           col+=stop;
2122           while (stop--) out.addChar(' '); 
2123         }
2124         break;
2125       case '\n': // reset colomn counter
2126         out.addChar(c);
2127         col=0;
2128         break;
2129       case ' ': // increment column counter
2130         out.addChar(c);
2131         col++;
2132         break;
2133       default: // non-whitespace => update minIndent
2134         out.addChar(c);
2135         if (col<minIndent) minIndent=col;
2136         col++;
2137     }
2138   }
2139   if (minIndent!=maxIndent) refIndent=minIndent; else refIndent=0;
2140   out.addChar(0);
2141   //printf("detab refIndent=%d\n",refIndent);
2142   return out.get();
2143 }
2144
2145 //---------------------------------------------------------------------------
2146
2147 QCString processMarkdown(const QCString &fileName,Entry *e,const QCString &input)
2148 {
2149   static bool init=FALSE;
2150   if (!init)
2151   {
2152     // setup callback table for special characters
2153     g_actions[(unsigned int)'_']=processEmphasis;
2154     g_actions[(unsigned int)'*']=processEmphasis;
2155     g_actions[(unsigned int)'`']=processCodeSpan;
2156     g_actions[(unsigned int)'\\']=processSpecialCommand;
2157     g_actions[(unsigned int)'@']=processSpecialCommand;
2158     g_actions[(unsigned int)'[']=processLink;
2159     g_actions[(unsigned int)'!']=processLink;
2160     g_actions[(unsigned int)'<']=processHtmlTag;
2161     g_actions[(unsigned int)'-']=processNmdash;
2162     init=TRUE;
2163   }
2164
2165   g_linkRefs.setAutoDelete(TRUE);
2166   g_linkRefs.clear();
2167   g_current = e;
2168   g_fileName = fileName;
2169   static GrowBuf out;
2170   if (input.isEmpty()) return input;
2171   out.clear();
2172   int refIndent;
2173   // for replace tabs by spaces
2174   QCString s = detab(input,refIndent);
2175   //printf("======== DeTab =========\n---- output -----\n%s\n---------\n",s.data());
2176   // then process quotation blocks (as these may contain other blocks)
2177   s = processQuotations(s,refIndent);
2178   //printf("======== Quotations =========\n---- output -----\n%s\n---------\n",s.data());
2179   // then process block items (headers, rules, and code blocks, references)
2180   s = processBlocks(s,refIndent);
2181   //printf("======== Blocks =========\n---- output -----\n%s\n---------\n",s.data());
2182   // finally process the inline markup (links, emphasis and code spans)
2183   processInline(out,s,s.length());
2184   out.addChar(0);
2185   Debug::print(Debug::Markdown,0,"======== Markdown =========\n---- input ------- \n%s\n---- output -----\n%s\n---------\n",input.data(),out.get());
2186   return out.get();
2187 }
2188
2189 //---------------------------------------------------------------------------
2190
2191 void MarkdownFileParser::parseInput(const char *fileName, 
2192                 const char *fileBuf, 
2193                 Entry *root)
2194 {
2195   Entry *current = new Entry;
2196   current->lang = SrcLangExt_Markdown;
2197   current->fileName = fileName;
2198   current->docFile  = fileName;
2199   int len = strlen(fileBuf);
2200   BufStr input(len);
2201   BufStr output(len);
2202   input.addArray(fileBuf,strlen(fileBuf));
2203   input.addChar('\0');
2204   convertCppComments(&input,&output,fileName);
2205   output.addChar('\0');
2206   QCString docs = output.data();
2207   QCString id;
2208   QCString title=extractPageTitle(docs,id).stripWhiteSpace();
2209   //g_correctSectionLevel = !title.isEmpty();
2210   QCString baseName = substitute(QFileInfo(fileName).baseName().utf8()," ","_");
2211   if (id.isEmpty()) id = "md_"+baseName;
2212   if (title.isEmpty()) title = baseName;
2213   if (id=="mainpage" || id=="index")
2214   {
2215     docs.prepend("@mainpage "+title+"\n");
2216   }
2217   else
2218   {
2219     docs.prepend("@page "+id+" "+title+"\n");
2220   }
2221   int lineNr=1;
2222   int position=0;
2223
2224   // even without markdown support enabled, we still 
2225   // parse markdown files as such
2226   bool markdownEnabled = Doxygen::markdownSupport;
2227   Doxygen::markdownSupport = TRUE;
2228
2229   bool needsEntry;
2230   Protection prot;
2231   while (parseCommentBlock(
2232         this,
2233         current,
2234         docs,
2235         fileName,
2236         lineNr,
2237         FALSE,     // isBrief
2238         FALSE,     // javadoc autobrief
2239         FALSE,     // inBodyDocs
2240         prot,      // protection
2241         position,
2242         needsEntry))
2243   {
2244     if (needsEntry)
2245     {
2246       QCString docFile = current->docFile;
2247       root->addSubEntry(current);
2248       current = new Entry;
2249       current->lang = SrcLangExt_Markdown;
2250       current->docFile = docFile;
2251       current->docLine = lineNr;
2252     }
2253   }
2254   if (needsEntry)
2255   {
2256     root->addSubEntry(current);
2257   }
2258
2259   // restore setting
2260   Doxygen::markdownSupport = markdownEnabled;
2261   //g_correctSectionLevel = FALSE;
2262 }
2263
2264 void MarkdownFileParser::parseCode(CodeOutputInterface &codeOutIntf,
2265                const char *scopeName,
2266                const QCString &input,
2267                bool isExampleBlock,
2268                const char *exampleName,
2269                FileDef *fileDef,
2270                int startLine,
2271                int endLine,
2272                bool inlineFragment,
2273                MemberDef *memberDef,
2274                bool showLineNumbers,
2275                Definition *searchCtx
2276               )
2277 {
2278   ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2279   if (pIntf!=this)
2280   {
2281     pIntf->parseCode(
2282        codeOutIntf,scopeName,input,isExampleBlock,exampleName,
2283        fileDef,startLine,endLine,inlineFragment,memberDef,showLineNumbers,searchCtx);
2284   }
2285 }
2286
2287 void MarkdownFileParser::resetCodeParserState()
2288 {
2289   ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2290   if (pIntf!=this)
2291   {
2292     pIntf->resetCodeParserState();
2293   }
2294 }
2295
2296 void MarkdownFileParser::parsePrototype(const char *text)
2297 {
2298   ParserInterface *pIntf = Doxygen::parserManager->getParser("*.cpp");
2299   if (pIntf!=this)
2300   {
2301     pIntf->parsePrototype(text);
2302   }
2303 }
2304