PageRenderTime 21ms CodeModel.GetById 4ms app.highlight 151ms RepoModel.GetById 1ms app.codeStats 1ms

/src/markdown.cpp

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