/*****************************************************************************
 *
 * 
 *
 * Copyright (C) 1997-2012 by Dimitri van Heesch.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation under the terms of the GNU General Public License is hereby 
 * granted. No representations are made about the suitability of this software 
 * for any purpose. It is provided "as is" without express or implied warranty.
 * See the GNU General Public License for more details.
 *
 * Documents produced by Doxygen are derivative works derived from the
 * input used in their production; they are not affected by this license.
 *
 */

#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <math.h>

#include "md5.h"

#include "qtbc.h"
#include <qregexp.h>
#include <qfileinfo.h>
#include <qdir.h>
#include <qdatetime.h>
#include <qcache.h>

#include "util.h"
#include "message.h"
#include "classdef.h"
#include "filedef.h"
#include "doxygen.h"
#include "outputlist.h"
#include "defargs.h"
#include "language.h"
#include "config.h"
#include "htmlhelp.h"
#include "example.h"
#include "version.h"
#include "groupdef.h"
#include "reflist.h"
#include "pagedef.h"
#include "debug.h"
#include "searchindex.h"
#include "doxygen.h"
#include "textdocvisitor.h"
#include "portable.h"
#include "parserintf.h"
#include "bufstr.h"
#include "image.h"
#include "growbuf.h"
#include "entry.h"
#include "arguments.h"

#define ENABLE_TRACINGSUPPORT 0

#if defined(_OS_MAC_) && ENABLE_TRACINGSUPPORT
#define TRACINGSUPPORT
#endif

#ifdef TRACINGSUPPORT
#include <execinfo.h>
#include <unistd.h>
#endif


//------------------------------------------------------------------------

// selects one of the name to sub-dir mapping algorithms that is used
// to select a sub directory when CREATE_SUBDIRS is set to YES.

#define ALGO_COUNT 1
#define ALGO_CRC16 2
#define ALGO_MD5   3
    
//#define MAP_ALGO ALGO_COUNT
//#define MAP_ALGO ALGO_CRC16
#define MAP_ALGO ALGO_MD5

#define REL_PATH_TO_ROOT "../../"

//------------------------------------------------------------------------
// TextGeneratorOLImpl implementation
//------------------------------------------------------------------------

TextGeneratorOLImpl::TextGeneratorOLImpl(OutputDocInterface &od) : m_od(od) 
{
}

void TextGeneratorOLImpl::writeString(const char *s,bool keepSpaces) const
{ 
  if (s==0) return;
  //printf("TextGeneratorOlImpl::writeString('%s',%d)\n",s,keepSpaces);
  if (keepSpaces)
  {
    const char *p=s;
    if (p)
    {
      char cs[2];
      char c;
      cs[1]='\0';
      while ((c=*p++))
      {
        if (c==' ') m_od.writeNonBreakableSpace(1); 
        else cs[0]=c,m_od.docify(cs);
      }
    }
  }
  else
  {
    m_od.docify(s); 
  }
}

void TextGeneratorOLImpl::writeBreak(int indent) const
{ 
  m_od.lineBreak("typebreak");
  int i;
  for (i=0;i<indent;i++)
  {
    m_od.writeNonBreakableSpace(3);
  }
}

void TextGeneratorOLImpl::writeLink(const char *extRef,const char *file,
                                    const char *anchor,const char *text
                                   ) const
{
  //printf("TextGeneratorOlImpl::writeLink('%s')\n",text);
  m_od.writeObjectLink(extRef,file,anchor,text);
}

//------------------------------------------------------------------------
//------------------------------------------------------------------------

// an inheritance tree of depth of 100000 should be enough for everyone :-)
const int maxInheritanceDepth = 100000; 

/*! 
  Removes all anonymous scopes from string s
  Possible examples:
\verbatim
   "bla::@10::blep"      => "bla::blep"
   "bla::@10::@11::blep" => "bla::blep"
   "@10::blep"           => "blep"
   " @10::blep"          => "blep"
   "@9::@10::blep"       => "blep"
   "bla::@1"             => "bla"
   "bla::@1::@2"         => "bla"
   "bla @1"              => "bla"
\endverbatim
 */
QCString removeAnonymousScopes(const QCString &s)
{
  QCString result;
  if (s.isEmpty()) return result;
  static QRegExp re("[ :]*@[0-9]+[: ]*");
  int i,l,sl=s.length();
  int p=0;
  while ((i=re.match(s,p,&l))!=-1)
  {
    result+=s.mid(p,i-p);
    int c=i;
    bool b1=FALSE,b2=FALSE;
    while (c<i+l && s.at(c)!='@') if (s.at(c++)==':') b1=TRUE;
    c=i+l-1;
    while (c>=i && s.at(c)!='@') if (s.at(c--)==':') b2=TRUE;
    if (b1 && b2) 
    { 
      result+="::"; 
    }
    p=i+l;
  }
  result+=s.right(sl-p);
  //printf("removeAnonymousScopes(`%s')=`%s'\n",s.data(),result.data());
  return result;
}

// replace anonymous scopes with __anonymous__ or replacement if provided
QCString replaceAnonymousScopes(const QCString &s,const char *replacement)
{
  QCString result;
  if (s.isEmpty()) return result;
  static QRegExp re("@[0-9]+");
  int i,l,sl=s.length();
  int p=0;
  while ((i=re.match(s,p,&l))!=-1)
  {
    result+=s.mid(p,i-p);
    if (replacement)
    {
      result+=replacement;
    }
    else
    {
      result+="__anonymous__";
    }
    p=i+l;
  }
  result+=s.right(sl-p);
  //printf("replaceAnonymousScopes(`%s')=`%s'\n",s.data(),result.data());
  return result;
}


// strip anonymous left hand side part of the scope
QCString stripAnonymousNamespaceScope(const QCString &s)
{
  int i,p=0,l;
  QCString newScope;
  while ((i=getScopeFragment(s,p,&l))!=-1)
  {
    //printf("Scope fragment %s\n",s.mid(i,l).data());
    if (Doxygen::namespaceSDict->find(s.left(i+l))!=0)
    {
      if (s.at(i)!='@')
      {
        if (!newScope.isEmpty()) newScope+="::";
        newScope+=s.mid(i,l);
      }
    }
    else
    {
      if (!newScope.isEmpty()) newScope+="::";
      newScope+=s.right(s.length()-i);
      goto done;
    }
    p=i+l;
  }
done:
  //printf("stripAnonymousNamespaceScope(`%s')=`%s'\n",s.data(),newScope.data());
  return newScope;
}

void writePageRef(OutputDocInterface &od,const char *cn,const char *mn)
{
  od.pushGeneratorState();
  
  od.disable(OutputGenerator::Html);
  od.disable(OutputGenerator::Man);
  if (Config_getBool("PDF_HYPERLINKS")) od.disable(OutputGenerator::Latex);
  if (Config_getBool("RTF_HYPERLINKS")) od.disable(OutputGenerator::RTF);
  od.startPageRef();
  od.docify(theTranslator->trPageAbbreviation());
  od.endPageRef(cn,mn);

  od.popGeneratorState();
}

/*! Generate a place holder for a position in a list. Used for
 *  translators to be able to specify different elements orders
 *  depending on whether text flows from left to right or visa versa.
 */
QCString generateMarker(int id)
{
  QCString result;
  result.sprintf("@%d",id);
  return result;
}

static QCString stripFromPath(const QCString &path,QStrList &l)
{
  // look at all the strings in the list and strip the longest match  
  const char *s=l.first();
  QCString potential;
  unsigned int length = 0;
  while (s)
  {
    QCString prefix = s;
    if (prefix.length() > length &&
        stricmp(path.left(prefix.length()),prefix)==0) // case insensitive compare
    {
      length = prefix.length();
      potential = path.right(path.length()-prefix.length());
    }
    s = l.next();
  }
  if (length) return potential;
  return path;
}

/*! strip part of \a path if it matches
 *  one of the paths in the Config_getList("STRIP_FROM_PATH") list
 */
QCString stripFromPath(const QCString &path)
{
  return stripFromPath(path,Config_getList("STRIP_FROM_PATH"));
}

/*! strip part of \a path if it matches
 *  one of the paths in the Config_getList("INCLUDE_PATH") list
 */
QCString stripFromIncludePath(const QCString &path)
{
  return stripFromPath(path,Config_getList("STRIP_FROM_INC_PATH"));
}

/*! try to determine if \a name is a source or a header file name by looking
 * at the extension. A number of variations is allowed in both upper and 
 * lower case) If anyone knows or uses another extension please let me know :-)
 */
int guessSection(const char *name)
{
  QCString n=((QCString)name).lower();
  if (n.right(2)==".c"    || // source
      n.right(3)==".cc"   ||
      n.right(4)==".cxx"  ||
      n.right(4)==".cpp"  ||
      n.right(4)==".c++"  ||
      n.right(5)==".java" ||
      n.right(2)==".m"    ||
      n.right(2)==".M"    ||
      n.right(3)==".mm"   ||
      n.right(3)==".ii"   || // inline
      n.right(4)==".ixx"  ||
      n.right(4)==".ipp"  ||
      n.right(4)==".i++"  ||
      n.right(4)==".inl"  ||
      n.right(4)==".xml" 
     ) return Entry::SOURCE_SEC;
  if (n.right(2)==".h"   || // header
      n.right(3)==".hh"  ||
      n.right(4)==".hxx" ||
      n.right(4)==".hpp" ||
      n.right(4)==".h++" ||
      n.right(4)==".idl" ||
      n.right(4)==".ddl" ||
      n.right(5)==".pidl"
     ) return Entry::HEADER_SEC;
  return 0;
}

QCString resolveTypeDef(Definition *context,const QCString &qualifiedName,
                        Definition **typedefContext)
{
  //printf("<<resolveTypeDef(%s,%s)\n",
  //          context ? context->name().data() : "<none>",qualifiedName.data());
  QCString result;
  if (qualifiedName.isEmpty()) 
  {
    //printf("  qualified name empty!\n");
    return result;
  }

  Definition *mContext=context;
  if (typedefContext) *typedefContext=context;

  // see if the qualified name has a scope part
  int scopeIndex = qualifiedName.findRev("::");
  QCString resName=qualifiedName;
  if (scopeIndex!=-1) // strip scope part for the name
  {
    resName=qualifiedName.right(qualifiedName.length()-scopeIndex-2);
    if (resName.isEmpty())
    {
      // qualifiedName was of form A:: !
      //printf("  qualified name of form A::!\n");
      return result;
    }
  }
  MemberDef *md=0;
  while (mContext && md==0)
  {
    // step 1: get the right scope
    Definition *resScope=mContext;
    if (scopeIndex!=-1) 
    {
      // split-off scope part
      QCString resScopeName = qualifiedName.left(scopeIndex);
      //printf("resScopeName=`%s'\n",resScopeName.data());

      // look-up scope in context
      int is,ps=0;
      int l;
      while ((is=getScopeFragment(resScopeName,ps,&l))!=-1)
      {
        QCString qualScopePart = resScopeName.mid(is,l);
        QCString tmp = resolveTypeDef(mContext,qualScopePart);
        if (!tmp.isEmpty()) qualScopePart=tmp;
        resScope = resScope->findInnerCompound(qualScopePart);
        //printf("qualScopePart=`%s' resScope=%p\n",qualScopePart.data(),resScope);
        if (resScope==0) break;
        ps=is+l;
      }
    }
    //printf("resScope=%s\n",resScope?resScope->name().data():"<none>");
    
    // step 2: get the member
    if (resScope) // no scope or scope found in the current context 
    {
      //printf("scope found: %s, look for typedef %s\n",
      //     resScope->qualifiedName().data(),resName.data());
      MemberNameSDict *mnd=0;
      if (resScope->definitionType()==Definition::TypeClass)
      {
        mnd=Doxygen::memberNameSDict;
      }
      else
      {
        mnd=Doxygen::functionNameSDict;
      }
      MemberName *mn=mnd->find(resName);
      if (mn)
      {
        MemberNameIterator mni(*mn);
        MemberDef *tmd=0;
        int minDist=-1;
        for (;(tmd=mni.current());++mni)
        {
          //printf("Found member %s resScope=%s outerScope=%s mContext=%p\n",
          //    tmd->name().data(), resScope->name().data(), 
          //    tmd->getOuterScope()->name().data(), mContext);
          if (tmd->isTypedef() /*&& tmd->getOuterScope()==resScope*/)
          {
            int dist=isAccessibleFrom(resScope,0,tmd);
            if (dist!=-1 && (md==0 || dist<minDist))
            {
              md = tmd;
              minDist = dist;
            }
          }
        }
      }
    }
    mContext=mContext->getOuterScope();
  }

  // step 3: get the member's type
  if (md)
  {
    //printf(">>resolveTypeDef: Found typedef name `%s' in scope `%s' value=`%s' args='%s'\n",
    //    qualifiedName.data(),context->name().data(),md->typeString(),md->argsString()
    //    );
    result=md->typeString();
    QString args = md->argsString();
    if (args.find(")(")!=-1) // typedef of a function/member pointer
    {
      result+=args;
    }
    else if (args.find('[')!=-1) // typedef of an array
    {
      result+=args;
    }
    if (typedefContext) *typedefContext=md->getOuterScope();
  }
  else
  {
    //printf(">>resolveTypeDef: Typedef `%s' not found in scope `%s'!\n",
    //    qualifiedName.data(),context ? context->name().data() : "<global>");
  }
  return result;
  
}


/*! Get a class definition given its name. 
 *  Returns 0 if the class is not found.
 */
ClassDef *getClass(const char *name)
{
  if (name==0 || name[0]=='\0') return 0;
  return Doxygen::classSDict->find(name);
}

NamespaceDef *getResolvedNamespace(const char *name)
{
  if (name==0 || name[0]=='\0') return 0;
  QCString *subst = Doxygen::namespaceAliasDict[name];
  if (subst)
  {
    int count=0; // recursion detection guard
    QCString *newSubst;
    while ((newSubst=Doxygen::namespaceAliasDict[*subst]) && count<10)
    {
      subst=newSubst;
      count++;
    }
    if (count==10)
    {
      err("warning: possible recursive namespace alias detected for %s!\n",name);
    }
    return Doxygen::namespaceSDict->find(subst->data());
  }
  else
  {
    return Doxygen::namespaceSDict->find(name);
  }
}

static QDict<MemberDef> g_resolvedTypedefs;
static QDict<Definition> g_visitedNamespaces;

// forward declaration
static ClassDef *getResolvedClassRec(Definition *scope,
                              FileDef *fileScope,
                              const char *n,
                              MemberDef **pTypeDef,
                              QCString *pTemplSpec,
                              QCString *pResolvedType
                             );
int isAccessibleFromWithExpScope(Definition *scope,FileDef *fileScope,Definition *item,
                     const QCString &explicitScopePart);

/*! Returns the class representing the value of the typedef represented by \a md
 *  within file \a fileScope.
 *
 *  Example: typedef A T; will return the class representing A if it is a class.
 * 
 *  Example: typedef int T; will return 0, since "int" is not a class.
 */
ClassDef *newResolveTypedef(FileDef *fileScope,MemberDef *md,
                            MemberDef **pMemType,QCString *pTemplSpec,
                            QCString *pResolvedType,
                            ArgumentList *actTemplParams)
{
  //printf("newResolveTypedef(md=%p,cachedVal=%p)\n",md,md->getCachedTypedefVal());
  bool isCached = md->isTypedefValCached(); // value already cached
  if (isCached)
  {
    //printf("Already cached %s->%s [%s]\n",
    //    md->name().data(),
    //    md->getCachedTypedefVal()?md->getCachedTypedefVal()->name().data():"<none>",
    //    md->getCachedResolvedTypedef()?md->getCachedResolvedTypedef().data():"<none>");

    if (pTemplSpec)    *pTemplSpec    = md->getCachedTypedefTemplSpec();
    if (pResolvedType) *pResolvedType = md->getCachedResolvedTypedef();
    return md->getCachedTypedefVal();
  }
  //printf("new typedef\n");
  QCString qname = md->qualifiedName();
  if (g_resolvedTypedefs.find(qname)) return 0; // typedef already done

  g_resolvedTypedefs.insert(qname,md); // put on the trace list
  
  ClassDef *typeClass = md->getClassDef();
  QCString type = md->typeString(); // get the "value" of the typedef
  if (typeClass && typeClass->isTemplate() && 
      actTemplParams && actTemplParams->count()>0)
  {
    type = substituteTemplateArgumentsInString(type,
            typeClass->templateArguments(),actTemplParams);
  }
  QCString typedefValue = type;
  int tl=type.length();
  int ip=tl-1; // remove * and & at the end
  while (ip>=0 && (type.at(ip)=='*' || type.at(ip)=='&' || type.at(ip)==' ')) 
  {
    ip--;
  }
  type=type.left(ip+1);
  type.stripPrefix("const ");  // strip leading "const"
  type.stripPrefix("struct "); // strip leading "struct"
  type.stripPrefix("union ");  // strip leading "union"
  int sp=0;
  tl=type.length(); // length may have been changed
  while (sp<tl && type.at(sp)==' ') sp++;
  MemberDef *memTypeDef = 0;
  ClassDef  *result = getResolvedClassRec(md->getOuterScope(),
                                  fileScope,type,&memTypeDef,0,pResolvedType);
  // if type is a typedef then return what it resolves to.
  if (memTypeDef && memTypeDef->isTypedef()) 
  {
    result=newResolveTypedef(fileScope,memTypeDef,pMemType,pTemplSpec);
    goto done;
  }
  else if (memTypeDef && memTypeDef->isEnumerate() && pMemType)
  {
    *pMemType = memTypeDef;
  }

  //printf("type=%s result=%p\n",type.data(),result);
  if (result==0)
  {
    // try unspecialized version if type is template
    int si=type.findRev("::");
    int i=type.find('<');
    if (si==-1 && i!=-1) // typedef of a template => try the unspecialized version
    {
      if (pTemplSpec) *pTemplSpec = type.mid(i);
      result = getResolvedClassRec(md->getOuterScope(),fileScope,
                                   type.left(i),0,0,pResolvedType);
      //printf("result=%p pRresolvedType=%s sp=%d ip=%d tl=%d\n",
      //    result,pResolvedType?pResolvedType->data():"<none>",sp,ip,tl);
    }
    else if (si!=-1) // A::B
    {
      i=type.find('<',si);
      if (i==-1) // Something like A<T>::B => lookup A::B
      {
        i=type.length();
      }
      else // Something like A<T>::B<S> => lookup A::B, spec=<S>
      {
        if (pTemplSpec) *pTemplSpec = type.mid(i);
      }
      result = getResolvedClassRec(md->getOuterScope(),fileScope,
           stripTemplateSpecifiersFromScope(type.left(i),FALSE),0,0,
           pResolvedType);
    }

    //if (result) ip=si+sp+1;
  }

done:
  if (pResolvedType)
  {
    if (result)
    {
      *pResolvedType=result->qualifiedName();
      //printf("*pResolvedType=%s\n",pResolvedType->data());
      if (sp>0)    pResolvedType->prepend(typedefValue.left(sp));
      if (ip<tl-1) pResolvedType->append(typedefValue.right(tl-ip-1));
    }
    else
    {
      *pResolvedType=typedefValue;
    }
  }

  // remember computed value for next time
  if (result && result->getDefFileName()!="<code>") 
    // this check is needed to prevent that temporary classes that are 
    // introduced while parsing code fragments are being cached here.
  {
    //printf("setting cached typedef %p in result %p\n",md,result);
    //printf("==> %s (%s,%d)\n",result->name().data(),result->getDefFileName().data(),result->getDefLine());
    //printf("*pResolvedType=%s\n",pResolvedType?pResolvedType->data():"<none>");
    md->cacheTypedefVal(result,
        pTemplSpec ? *pTemplSpec : QCString(),
        pResolvedType ? *pResolvedType : QCString()
       );
  }
  
  g_resolvedTypedefs.remove(qname); // remove from the trace list
  
  return result;
}

/*! Substitutes a simple unqualified \a name within \a scope. Returns the
 *  value of the typedef or \a name if no typedef was found.
 */
static QCString substTypedef(Definition *scope,FileDef *fileScope,const QCString &name,
            MemberDef **pTypeDef=0)
{
  QCString result=name;
  if (name.isEmpty()) return result;

  // lookup scope fragment in the symbol map
  DefinitionIntf *di = Doxygen::symbolMap->find(name);
  if (di==0) return result; // no matches

  MemberDef *bestMatch=0;
  if (di->definitionType()==DefinitionIntf::TypeSymbolList) // multi symbols
  {
    // search for the best match
    DefinitionListIterator dli(*(DefinitionList*)di);
    Definition *d;
    int minDistance=10000; // init at "infinite"
    for (dli.toFirst();(d=dli.current());++dli) // foreach definition
    {
      // only look at members
      if (d->definitionType()==Definition::TypeMember)
      {
        // that are also typedefs
        MemberDef *md = (MemberDef *)d;
        if (md->isTypedef()) // d is a typedef
        {
          // test accessibility of typedef within scope.
          int distance = isAccessibleFromWithExpScope(scope,fileScope,d,"");
          if (distance!=-1 && distance<minDistance) 
            // definition is accessible and a better match
          {
            minDistance=distance;
            bestMatch = md; 
          }
        }
      }
    }
  }
  else if (di->definitionType()==DefinitionIntf::TypeMember) // single symbol
  {
    Definition *d = (Definition*)di;
    // that are also typedefs
    MemberDef *md = (MemberDef *)di;
    if (md->isTypedef()) // d is a typedef
    {
      // test accessibility of typedef within scope.
      int distance = isAccessibleFromWithExpScope(scope,fileScope,d,"");
      if (distance!=-1) // definition is accessible 
      {
        bestMatch = md; 
      }
    }
  }
  if (bestMatch) 
  {
    result = bestMatch->typeString();
    if (pTypeDef) *pTypeDef=bestMatch;
  }
  
  //printf("substTypedef(%s,%s)=%s\n",scope?scope->name().data():"<global>",
  //                                  name.data(),result.data());
  return result;
}

static Definition *endOfPathIsUsedClass(SDict<Definition> *cl,const QCString &localName)
{
  if (cl)
  {
    SDict<Definition>::Iterator cli(*cl);
    Definition *cd;
    for (cli.toFirst();(cd=cli.current());++cli)
    {
      if (cd->localName()==localName)
      {
        return cd;
      }
    }
  }
  return 0;
}

/*! Starting with scope \a start, the string \a path is interpreted as
 *  a part of a qualified scope name (e.g. A::B::C), and the scope is 
 *  searched. If found the scope definition is returned, otherwise 0 
 *  is returned.
 */
static Definition *followPath(Definition *start,FileDef *fileScope,const QCString &path)
{
  int is,ps;
  int l;
  Definition *current=start;
  ps=0;
  //printf("followPath: start='%s' path='%s'\n",start?start->name().data():"<none>",path.data());
  // for each part of the explicit scope
  while ((is=getScopeFragment(path,ps,&l))!=-1)
  {
    // try to resolve the part if it is a typedef
    MemberDef *typeDef=0;
    QCString qualScopePart = substTypedef(current,fileScope,path.mid(is,l),&typeDef);
    //printf("      qualScopePart=%s\n",qualScopePart.data());
    if (typeDef)
    {
      ClassDef *type = newResolveTypedef(fileScope,typeDef);
      if (type)
      {
        //printf("Found type %s\n",type->name().data());
        return type;
      }
    }
    Definition *next = current->findInnerCompound(qualScopePart);
    //printf("++ Looking for %s inside %s result %s\n",
    //     qualScopePart.data(),
    //     current->name().data(),
    //     next?next->name().data():"<null>");
    if (next==0) // failed to follow the path 
    {
      //printf("==> next==0!\n");
      if (current->definitionType()==Definition::TypeNamespace)
      {
        next = endOfPathIsUsedClass(
            ((NamespaceDef *)current)->getUsedClasses(),qualScopePart);
      }
      else if (current->definitionType()==Definition::TypeFile)
      {
        next = endOfPathIsUsedClass(
            ((FileDef *)current)->getUsedClasses(),qualScopePart);
      }
      current = next;
      if (current==0) break;
    }
    else // continue to follow scope
    {
      current = next;
      //printf("==> current = %p\n",current);
    }
    ps=is+l;
  }
  //printf("followPath(start=%s,path=%s) result=%s\n",
  //    start->name().data(),path.data(),current?current->name().data():"<null>");
  return current; // path could be followed
}

bool accessibleViaUsingClass(const SDict<Definition> *cl,
                             FileDef *fileScope,
                             Definition *item,
                             const QCString &explicitScopePart=""
                            )
{
  //printf("accessibleViaUsingClass(%p)\n",cl);
  if (cl) // see if the class was imported via a using statement 
  {
    SDict<Definition>::Iterator cli(*cl);
    Definition *ucd;
    bool explicitScopePartEmpty = explicitScopePart.isEmpty();
    for (cli.toFirst();(ucd=cli.current());++cli)
    {
      //printf("Trying via used class %s\n",ucd->name().data());
      Definition *sc = explicitScopePartEmpty ? ucd : followPath(ucd,fileScope,explicitScopePart);
      if (sc && sc==item) return TRUE; 
      //printf("Try via used class done\n");
    }
  }
  return FALSE;
}

bool accessibleViaUsingNamespace(const NamespaceSDict *nl,
                                 FileDef *fileScope,
                                 Definition *item,
                                 const QCString &explicitScopePart="")
{
  static QDict<void> visitedDict;
  if (nl) // check used namespaces for the class
  {
    NamespaceSDict::Iterator nli(*nl);
    NamespaceDef *und;
    int count=0;
    for (nli.toFirst();(und=nli.current());++nli,count++)
    {
      //printf("[Trying via used namespace %s: count=%d/%d\n",und->name().data(),
      //    count,nl->count());
      Definition *sc = explicitScopePart.isEmpty() ? und : followPath(und,fileScope,explicitScopePart);
      if (sc && item->getOuterScope()==sc) 
      {
        //printf("] found it\n");
        return TRUE; 
      }
      QCString key=und->name();
      if (und->getUsedNamespaces() && visitedDict.find(key)==0)
      {
        visitedDict.insert(key,(void *)0x08);

        if (accessibleViaUsingNamespace(und->getUsedNamespaces(),fileScope,item,explicitScopePart))
        {
          //printf("] found it via recursion\n");
          return TRUE;
        }

        visitedDict.remove(key);
      }
      //printf("] Try via used namespace done\n");
    }
  }
  return FALSE;
}


/* Returns the "distance" (=number of levels up) from item to scope, or -1
 * if item in not inside scope. 
 */
int isAccessibleFrom(Definition *scope,FileDef *fileScope,Definition *item)
{
  //printf("<isAccesibleFrom(scope=%s,item=%s itemScope=%s)\n",
  //    scope->name().data(),item->name().data(),item->getOuterScope()->name().data());

  QCString key(40);
  key.sprintf("%p:%p:%p",scope,fileScope,item);
  static QDict<void> visitedDict;
  if (visitedDict.find(key)) 
  {
    //printf("> already found\n");
    return -1; // already looked at this
  }
  visitedDict.insert(key,(void *)0x8);

  int result=0; // assume we found it
  int i;

  Definition *itemScope=item->getOuterScope();

  if ( 
      itemScope==scope ||                                                  // same thing
      (item->definitionType()==Definition::TypeMember &&                   // a member
       itemScope && itemScope->definitionType()==Definition::TypeClass  && // of a class
       scope->definitionType()==Definition::TypeClass &&                   // accessible
       ((ClassDef*)scope)->isAccessibleMember((MemberDef *)item)           // from scope
      ) ||
      (item->definitionType()==Definition::TypeClass &&                    // a nested class
       itemScope && itemScope->definitionType()==Definition::TypeClass &&  // inside a base 
       scope->definitionType()==Definition::TypeClass &&                   // class of scope
       ((ClassDef*)scope)->isBaseClass((ClassDef*)itemScope,TRUE)          
      )
     ) 
  {
    //printf("> found it\n");
  }
  else if (scope==Doxygen::globalScope)
  {
    if (fileScope)
    {
      SDict<Definition> *cl = fileScope->getUsedClasses();
      if (accessibleViaUsingClass(cl,fileScope,item)) 
      {
        //printf("> found via used class\n");
        goto done;
      }
      NamespaceSDict *nl = fileScope->getUsedNamespaces();
      if (accessibleViaUsingNamespace(nl,fileScope,item)) 
      {
        //printf("> found via used namespace\n");
        goto done;
      }
    }
    //printf("> reached global scope\n");
    result=-1; // not found in path to globalScope
  }
  else // keep searching
  {
    // check if scope is a namespace, which is using other classes and namespaces
    if (scope->definitionType()==Definition::TypeNamespace)
    {
      NamespaceDef *nscope = (NamespaceDef*)scope;
      //printf("  %s is namespace with %d used classes\n",nscope->name().data(),nscope->getUsedClasses());
      SDict<Definition> *cl = nscope->getUsedClasses();
      if (accessibleViaUsingClass(cl,fileScope,item)) 
      {
        //printf("> found via used class\n");
        goto done;
      }
      NamespaceSDict *nl = nscope->getUsedNamespaces();
      if (accessibleViaUsingNamespace(nl,fileScope,item)) 
      {
        //printf("> found via used namespace\n");
        goto done;
      }
    }
    // repeat for the parent scope
    i=isAccessibleFrom(scope->getOuterScope(),fileScope,item);
    //printf("> result=%d\n",i);
    result= (i==-1) ? -1 : i+2;
  }
done:
  visitedDict.remove(key);
  //Doxygen::lookupCache.insert(key,new int(result));
  return result;
}


/* Returns the "distance" (=number of levels up) from item to scope, or -1
 * if item in not in this scope. The explicitScopePart limits the search
 * to scopes that match \a scope (or its parent scope(s)) plus the explicit part.
 * Example:
 *
 * class A { public: class I {}; };
 * class B { public: class J {}; };
 *
 * - Looking for item=='J' inside scope=='B' will return 0.
 * - Looking for item=='I' inside scope=='B' will return -1 
 *   (as it is not found in B nor in the global scope).
 * - Looking for item=='A::I' inside scope=='B', first the match B::A::I is tried but 
 *   not found and then A::I is searched in the global scope, which matches and 
 *   thus the result is 1.
 */
int isAccessibleFromWithExpScope(Definition *scope,FileDef *fileScope,
                     Definition *item,const QCString &explicitScopePart)
{
  if (explicitScopePart.isEmpty())
  {
    // handle degenerate case where there is no explicit scope.
    return isAccessibleFrom(scope,fileScope,item);
  }

  QCString key(40+explicitScopePart.length());
  key.sprintf("%p:%p:%p:%s",scope,fileScope,item,explicitScopePart.data());
  static QDict<void> visitedDict;
  if (visitedDict.find(key)) 
  {
    //printf("Already visited!\n");
    return -1; // already looked at this
  }
  visitedDict.insert(key,(void *)0x8);

  //printf("  <isAccessibleFromWithExpScope(%s,%s,%s)\n",scope?scope->name().data():"<global>",
  //                                      item?item->name().data():"<none>",
  //                                      explicitScopePart.data());
  int result=0; // assume we found it
  Definition *newScope = followPath(scope,fileScope,explicitScopePart);
  if (newScope)  // explicitScope is inside scope => newScope is the result
  {
    Definition *itemScope = item->getOuterScope();
    //printf("    scope traversal successful %s<->%s!\n",itemScope->name().data(),newScope->name().data());
    //if (newScope && newScope->definitionType()==Definition::TypeClass)
    //{
    //  ClassDef *cd = (ClassDef *)newScope;
    //  printf("---> Class %s: bases=%p\n",cd->name().data(),cd->baseClasses());
    //}
    if (itemScope==newScope)  // exact match of scopes => distance==0
    {
      //printf("> found it\n");
    }
    else if (itemScope && newScope &&
             itemScope->definitionType()==Definition::TypeClass &&
             newScope->definitionType()==Definition::TypeClass &&
             ((ClassDef*)newScope)->isBaseClass((ClassDef*)itemScope,TRUE,0)
            )
    {
      // inheritance is also ok. Example: looking for B::I, where 
      // class A { public: class I {} };
      // class B : public A {}
      // but looking for B::I, where
      // class A { public: class I {} };
      // class B { public: class I {} };
      // will find A::I, so we still prefer a direct match and give this one a distance of 1
      result=1;

      //printf("scope(%s) is base class of newScope(%s)\n",
      //    scope->name().data(),newScope->name().data());
    }
    else
    {
      int i=-1;
      if (newScope->definitionType()==Definition::TypeNamespace)
      {
        g_visitedNamespaces.insert(newScope->name(),newScope);
        // this part deals with the case where item is a class
        // A::B::C but is explicit referenced as A::C, where B is imported
        // in A via a using directive.
        //printf("newScope is a namespace: %s!\n",newScope->name().data());
        NamespaceDef *nscope = (NamespaceDef*)newScope;
        SDict<Definition> *cl = nscope->getUsedClasses();
        if (cl)
        {
          SDict<Definition>::Iterator cli(*cl);
          Definition *cd;
          for (cli.toFirst();(cd=cli.current());++cli)
          {
            //printf("Trying for class %s\n",cd->name().data());
            if (cd==item)
            {
              //printf("> class is used in this scope\n");
              goto done;
            }
          }
        }
        NamespaceSDict *nl = nscope->getUsedNamespaces();
        if (nl)
        {
          NamespaceSDict::Iterator nli(*nl);
          NamespaceDef *nd;
          for (nli.toFirst();(nd=nli.current());++nli)
          {
            if (g_visitedNamespaces.find(nd->name())==0)
            {
              //printf("Trying for namespace %s\n",nd->name().data());
              i = isAccessibleFromWithExpScope(scope,fileScope,item,nd->name());
              if (i!=-1)
              {
                //printf("> found via explicit scope of used namespace\n");
                goto done;
              }
            }
          }
        }
      }
      // repeat for the parent scope
      if (scope!=Doxygen::globalScope)
      {
        i = isAccessibleFromWithExpScope(scope->getOuterScope(),fileScope,
            item,explicitScopePart);
      }
      //printf("  | result=%d\n",i);
      result = (i==-1) ? -1 : i+2;
    }
  }
  else // failed to resolve explicitScope
  {
    //printf("    failed to resolve: scope=%s\n",scope->name().data());
    if (scope->definitionType()==Definition::TypeNamespace)
    {
      NamespaceDef *nscope = (NamespaceDef*)scope;
      NamespaceSDict *nl = nscope->getUsedNamespaces();
      if (accessibleViaUsingNamespace(nl,fileScope,item,explicitScopePart)) 
      {
        //printf("> found in used namespace\n");
        goto done;
      }
    }
    if (scope==Doxygen::globalScope)
    {
      if (fileScope)
      {
        NamespaceSDict *nl = fileScope->getUsedNamespaces();
        if (accessibleViaUsingNamespace(nl,fileScope,item,explicitScopePart)) 
        {
          //printf("> found in used namespace\n");
          goto done;
        }
      }
      //printf("> not found\n");
      result=-1;
    }
    else // continue by looking into the parent scope
    {
      int i=isAccessibleFromWithExpScope(scope->getOuterScope(),fileScope,
          item,explicitScopePart);
      //printf("> result=%d\n",i);
      result= (i==-1) ? -1 : i+2;
    }
  }
done:
  //printf("  > result=%d\n",result);
  visitedDict.remove(key);
  //Doxygen::lookupCache.insert(key,new int(result));
  return result;
}

int computeQualifiedIndex(const QCString &name)
{
  int i = name.find('<');
  return name.findRev("::",i==-1 ? name.length() : i);
}

static void getResolvedSymbol(Definition *scope,
                       FileDef *fileScope,
                       Definition *d, 
                       const QCString &explicitScopePart,
                       ArgumentList *actTemplParams,
                       int &minDistance,
                       ClassDef *&bestMatch,
                       MemberDef *&bestTypedef,
                       QCString &bestTemplSpec,
                       QCString &bestResolvedType
                      )
{
  //printf("  => found type %x name=%s d=%p\n",
  //       d->definitionType(),d->name().data(),d);

  // only look at classes and members that are enums or typedefs
  if (d->definitionType()==Definition::TypeClass ||
      (d->definitionType()==Definition::TypeMember && 
       (((MemberDef*)d)->isTypedef() || ((MemberDef*)d)->isEnumerate()) 
      )
     )
  {
    g_visitedNamespaces.clear();
    // test accessibility of definition within scope.
    int distance = isAccessibleFromWithExpScope(scope,fileScope,d,explicitScopePart);
    //printf("  %s; distance %s (%p) is %d\n",scope->name().data(),d->name().data(),d,distance);
    if (distance!=-1) // definition is accessible
    {
      // see if we are dealing with a class or a typedef
      if (d->definitionType()==Definition::TypeClass) // d is a class
      {
        ClassDef *cd = (ClassDef *)d;
        //printf("cd=%s\n",cd->name().data());
        if (!cd->isTemplateArgument()) // skip classes that
          // are only there to 
          // represent a template 
          // argument
        {
          //printf("is not a templ arg\n");
          if (distance<minDistance) // found a definition that is "closer"
          {
            minDistance=distance;
            bestMatch = cd; 
            bestTypedef = 0;
            bestTemplSpec.resize(0);
            bestResolvedType = cd->qualifiedName();
          }
          else if (distance==minDistance &&
              fileScope && bestMatch &&
              fileScope->getUsedNamespaces() && 
              d->getOuterScope()->definitionType()==Definition::TypeNamespace && 
              bestMatch->getOuterScope()==Doxygen::globalScope
              )
          {
            // in case the distance is equal it could be that a class X
            // is defined in a namespace and in the global scope. When searched
            // in the global scope the distance is 0 in both cases. We have
            // to choose one of the definitions: we choose the one in the
            // namespace if the fileScope imports namespaces and the definition
            // found was in a namespace while the best match so far isn't.
            // Just a non-perfect heuristic but it could help in some situations
            // (kdecore code is an example).
            minDistance=distance;
            bestMatch = cd; 
            bestTypedef = 0;
            bestTemplSpec.resize(0);
            bestResolvedType = cd->qualifiedName();
          }
        }
        else
        {
          //printf("  is a template argument!\n");
        }
      }
      else if (d->definitionType()==Definition::TypeMember)
      {
        MemberDef *md = (MemberDef *)d;
        //printf("  member isTypedef()=%d\n",md->isTypedef());
        if (md->isTypedef()) // d is a typedef
        {
          QCString args=md->argsString();
          if (args.isEmpty()) // do not expand "typedef t a[4];"
          {
            //printf("    found typedef!\n");

            // we found a symbol at this distance, but if it didn't
            // resolve to a class, we still have to make sure that
            // something at a greater distance does not match, since
            // that symbol is hidden by this one.
            if (distance<minDistance)
            {
              QCString spec;
              QCString type;
              minDistance=distance;
              MemberDef *enumType = 0;
              ClassDef *cd = newResolveTypedef(fileScope,md,&enumType,&spec,&type,actTemplParams);
              if (cd)  // type resolves to a class
              {
                //printf("      bestTypeDef=%p spec=%s type=%s\n",md,spec.data(),type.data());
                bestMatch = cd;
                bestTypedef = md;
                bestTemplSpec = spec;
                bestResolvedType = type;
              }
              else if (enumType) // type resolves to a enum
              {
                //printf("      is enum\n");
                bestMatch = 0;
                bestTypedef = enumType;
                bestTemplSpec = "";
                bestResolvedType = enumType->qualifiedName();
              }
              else if (md->isReference()) // external reference
              {
                bestMatch = 0;
                bestTypedef = md;
                bestTemplSpec = spec;
                bestResolvedType = type;
              }
              else
              {
                bestMatch = 0;
                bestTypedef = md;
                bestTemplSpec.resize(0);
                bestResolvedType.resize(0);
                //printf("      no match\n");
              }
            }
            else
            {
              //printf("      not the best match %d min=%d\n",distance,minDistance);
            }
          }
          else
          {
            //printf("     not a simple typedef\n")
          }
        }
        else if (md->isEnumerate())
        {
          if (distance<minDistance)
          {
            minDistance=distance;
            bestMatch = 0;
            bestTypedef = md;
            bestTemplSpec = "";
            bestResolvedType = md->qualifiedName();
          }
        }
      }
    } // if definition accessible
    else
    {
      //printf("  Not accessible!\n");
    }
  } // if definition is a class or member
  //printf("  bestMatch=%p bestResolvedType=%s\n",bestMatch,bestResolvedType.data());
}

/* Find the fully qualified class name referred to by the input class
 * or typedef name against the input scope.
 * Loops through scope and each of its parent scopes looking for a
 * match against the input name. Can recursively call itself when 
 * resolving typedefs.
 */
static ClassDef *getResolvedClassRec(Definition *scope,
    FileDef *fileScope,
    const char *n,
    MemberDef **pTypeDef,
    QCString *pTemplSpec,
    QCString *pResolvedType
    )
{
  //printf("[getResolvedClassRec(%s,%s)\n",scope?scope->name().data():"<global>",n);
  QCString name;
  QCString explicitScopePart;
  QCString strippedTemplateParams;
  name=stripTemplateSpecifiersFromScope
                     (removeRedundantWhiteSpace(n),TRUE,
                      &strippedTemplateParams);
  ArgumentList actTemplParams;
  if (!strippedTemplateParams.isEmpty()) // template part that was stripped
  {
    stringToArgumentList(strippedTemplateParams,&actTemplParams);
  }

  int qualifierIndex = computeQualifiedIndex(name);
  //printf("name=%s qualifierIndex=%d\n",name.data(),qualifierIndex);
  if (qualifierIndex!=-1) // qualified name
  {
    // split off the explicit scope part
    explicitScopePart=name.left(qualifierIndex);
    // todo: improve namespace alias substitution
    replaceNamespaceAliases(explicitScopePart,explicitScopePart.length());
    name=name.mid(qualifierIndex+2);
  }

  if (name.isEmpty()) 
  {
    //printf("] empty name\n");
    return 0; // empty name
  }

  //printf("Looking for symbol %s\n",name.data());
  DefinitionIntf *di = Doxygen::symbolMap->find(name);
  if (di==0) 
  {
    di = Doxygen::symbolMap->find(name+"-g");
    if (di==0)
    {
      di = Doxygen::symbolMap->find(name+"-p");
      if (di==0)
      {
        //printf("no such symbol!\n");
        return 0;
      }
    }
  }
  //printf("found symbol!\n");

  bool hasUsingStatements = 
    (fileScope && ((fileScope->getUsedNamespaces() && 
                    fileScope->getUsedNamespaces()->count()>0) ||
                   (fileScope->getUsedClasses() && 
                    fileScope->getUsedClasses()->count()>0)) 
    );
  //printf("hasUsingStatements=%d\n",hasUsingStatements);
  // Since it is often the case that the same name is searched in the same
  // scope over an over again (especially for the linked source code generation)
  // we use a cache to collect previous results. This is possible since the
  // result of a lookup is deterministic. As the key we use the concatenated
  // scope, the name to search for and the explicit scope prefix. The speedup
  // achieved by this simple cache can be enormous.
  int scopeNameLen = scope->name().length()+1;
  int nameLen = name.length()+1;
  int explicitPartLen = explicitScopePart.length();
  int fileScopeLen = hasUsingStatements ? 1+fileScope->absFilePath().length() : 0;

  // below is a more efficient coding of
  // QCString key=scope->name()+"+"+name+"+"+explicitScopePart;
  QCString key(scopeNameLen+nameLen+explicitPartLen+fileScopeLen+1);
  char *p=key.data();
  qstrcpy(p,scope->name()); *(p+scopeNameLen-1)='+';
  p+=scopeNameLen;
  qstrcpy(p,name); *(p+nameLen-1)='+';
  p+=nameLen;
  qstrcpy(p,explicitScopePart);
  p+=explicitPartLen;

  // if a file scope is given and it contains using statements we should
  // also use the file part in the key (as a class name can be in
  // two different namespaces and a using statement in a file can select 
  // one of them).
  if (hasUsingStatements)
  {
    // below is a more efficient coding of
    // key+="+"+fileScope->name();
    *p++='+';
    qstrcpy(p,fileScope->absFilePath());
    p+=fileScopeLen-1;
  }
  *p='\0';

  LookupInfo *pval=Doxygen::lookupCache->find(key);
  //printf("Searching for %s result=%p\n",key.data(),pval);
  if (pval)
  {
    //printf("LookupInfo %p %p '%s' %p\n", 
    //    pval->classDef, pval->typeDef, pval->templSpec.data(), 
    //    pval->resolvedType.data()); 
    if (pTemplSpec)    *pTemplSpec=pval->templSpec;
    if (pTypeDef)      *pTypeDef=pval->typeDef;
    if (pResolvedType) *pResolvedType=pval->resolvedType;
    //printf("] cachedMatch=%s\n",
    //    pval->classDef?pval->classDef->name().data():"<none>");
    //if (pTemplSpec) 
    //  printf("templSpec=%s\n",pTemplSpec->data());
    return pval->classDef; 
  }
  else // not found yet; we already add a 0 to avoid the possibility of 
    // endless recursion.
  {
    Doxygen::lookupCache->insert(key,new LookupInfo);
  }

  ClassDef *bestMatch=0;
  MemberDef *bestTypedef=0;
  QCString bestTemplSpec;
  QCString bestResolvedType;
  int minDistance=10000; // init at "infinite"

  if (di->definitionType()==DefinitionIntf::TypeSymbolList) // not a unique name
  {
    //printf("  name is not unique\n");
    DefinitionListIterator dli(*(DefinitionList*)di);
    Definition *d;
    int count=0;
    for (dli.toFirst();(d=dli.current());++dli,++count) // foreach definition
    {
      getResolvedSymbol(scope,fileScope,d,explicitScopePart,&actTemplParams,
                        minDistance,bestMatch,bestTypedef,bestTemplSpec,
                        bestResolvedType);
    }
  }
  else // unique name
  {
    //printf("  name is unique\n");
    Definition *d = (Definition *)di;
    getResolvedSymbol(scope,fileScope,d,explicitScopePart,&actTemplParams,
                      minDistance,bestMatch,bestTypedef,bestTemplSpec,
                      bestResolvedType);
  }

  if (pTypeDef) 
  {
    *pTypeDef = bestTypedef;
  }
  if (pTemplSpec)
  {
    *pTemplSpec = bestTemplSpec;
  }
  if (pResolvedType)
  {
    *pResolvedType = bestResolvedType;
  }
  //printf("getResolvedClassRec: bestMatch=%p pval->resolvedType=%s\n",
  //    bestMatch,bestResolvedType.data());

  pval=Doxygen::lookupCache->find(key);
  if (pval)
  {
    pval->classDef     = bestMatch;
    pval->typeDef      = bestTypedef;
    pval->templSpec    = bestTemplSpec;
    pval->resolvedType = bestResolvedType;
  }
  else
  {
    Doxygen::lookupCache->insert(key,new LookupInfo(bestMatch,bestTypedef,bestTemplSpec,bestResolvedType));
  }
  //printf("] bestMatch=%s distance=%d\n",
  //    bestMatch?bestMatch->name().data():"<none>",minDistance);
  //if (pTemplSpec) 
  //  printf("templSpec=%s\n",pTemplSpec->data());
  return bestMatch;
}

/* Find the fully qualified class name referred to by the input class
 * or typedef name against the input scope.
 * Loops through scope and each of its parent scopes looking for a
 * match against the input name. 
 */
ClassDef *getResolvedClass(Definition *scope,
    FileDef *fileScope,
    const char *n,
    MemberDef **pTypeDef,
    QCString *pTemplSpec,
    bool mayBeUnlinkable,
    bool mayBeHidden,
    QCString *pResolvedType
    )
{
  g_resolvedTypedefs.clear();
  if (scope==0 ||
      (scope->definitionType()!=Definition::TypeClass && 
       scope->definitionType()!=Definition::TypeNamespace
      ) ||
      (scope->getLanguage()==SrcLangExt_Java && QCString(n).find("::")!=-1)
     )
  {
    scope=Doxygen::globalScope;
  }
  //printf("------------ getResolvedClass(scope=%s,file=%s,name=%s,mayUnlinkable=%d)\n",
  //    scope?scope->name().data():"<global>",
  //    fileScope?fileScope->name().data():"<none>",
  //    n,
  //    mayBeUnlinkable
  //   );
  ClassDef *result = getResolvedClassRec(scope,fileScope,n,pTypeDef,pTemplSpec,pResolvedType);
  if (!mayBeUnlinkable && result && !result->isLinkable()) 
  {
    if (!mayBeHidden || !result->isHidden())
    {
      //printf("result was %s\n",result?result->name().data():"<none>");
      result=0; // don't link to artificial/hidden classes unless explicitly allowed
    }
  }
  //printf("getResolvedClass(%s,%s)=%s\n",scope?scope->name().data():"<global>",
  //                                  n,result?result->name().data():"<none>");
  return result;
}

//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------

static bool findOperator(const QCString &s,int i)
{
  int b = s.findRev("operator",i);
  if (b==-1) return FALSE; // not found
  b+=8;
  while (b<i) // check if there are only spaces in between 
    // the operator and the >
  {
    if (!isspace((uchar)s.at(b))) return FALSE;
    b++;
  }
  return TRUE;
}

static bool findOperator2(const QCString &s,int i)
{
  int b = s.findRev("operator",i);
  if (b==-1) return FALSE; // not found
  b+=8;
  while (b<i) // check if there are only non-ascii
              // characters in front of the operator
  {
    if (isId((uchar)s.at(b))) return FALSE;
    b++;
  }
  return TRUE;
}

static const char constScope[]   = { 'c', 'o', 'n', 's', 't', ':' };
static const char virtualScope[] = { 'v', 'i', 'r', 't', 'u', 'a', 'l', ':' };

// Note: this function is not reentrant due to the use of static buffer!
QCString removeRedundantWhiteSpace(const QCString &s)
{
  static bool cliSupport = Config_getBool("CPP_CLI_SUPPORT");
  if (s.isEmpty()) return s;
  static GrowBuf growBuf;
  //int resultLen = 1024;
  //int resultPos = 0;
  //QCString result(resultLen);
  // we use growBuf.addChar(c) instead of result+=c to 
  // improve the performance of this function
  growBuf.clear();
  uint i;
  uint l=s.length();
  uint csp=0;
  uint vsp=0;
  for (i=0;i<l;i++)
  {
nextChar:
    char c=s.at(i);

    // search for "const"
    if (csp<6 && c==constScope[csp] && // character matches substring "const"
         (csp>0 ||                     // if it is the first character 
          i==0  ||                     // the previous may not be a digit
          !isId(s.at(i-1))
         )
       )
      csp++; 
    else // reset counter
      csp=0;

    // search for "virtual"
    if (vsp<8 && c==virtualScope[vsp] && // character matches substring "virtual"
         (vsp>0 ||                       // if it is the first character
          i==0  ||                       // the previous may not be a digit 
          !isId(s.at(i-1))
         )
       )
      vsp++;
    else // reset counter
      vsp=0;

    if (c=='"') // quoted string
    {
      i++;
      growBuf.addChar(c);
      while (i<l)
      {
        char cc=s.at(i);
        growBuf.addChar(cc);
        if (cc=='\\') // escaped character
        { 
          growBuf.addChar(s.at(i+1));
          i+=2; 
        }
        else if (cc=='"') // end of string
        { i++; goto nextChar; }
        else // any other character
        { i++; }
      }
    }
    else if (i<l-2 && c=='<' &&  // current char is a <
        (isId(s.at(i+1)) || isspace((uchar)s.at(i+1))) && // next char is an id char or space
        (i<8 || !findOperator(s,i)) // string in front is not "operator"
        )
    {
      growBuf.addChar('<');
      growBuf.addChar(' ');
    }
    else if (i>0 && c=='>' && // current char is a >
        (isId(s.at(i-1)) || isspace((uchar)s.at(i-1)) || s.at(i-1)=='*' || s.at(i-1)=='&') && // prev char is an id char or space
        (i<8 || !findOperator(s,i)) // string in front is not "operator"
        )
    {
      growBuf.addChar(' ');
      growBuf.addChar('>');
    }
    else if (i>0 && c==',' && !isspace((uchar)s.at(i-1))
        && ((i<l-1 && isId(s.at(i+1)))
          || (i<l-2 && s.at(i+1)=='$' && isId(s.at(i+2)))  // for PHP
          || (i<l-3 && s.at(i+1)=='&' && s.at(i+2)=='$' && isId(s.at(i+3)))))  // for PHP
    {
      growBuf.addChar(',');
      growBuf.addChar(' ');
    }
    else if (i>0 && 
         ((isId(s.at(i)) && s.at(i-1)==')') || 
          (s.at(i)=='\''  && s.at(i-1)==' ')
         )
        )
    {
      growBuf.addChar(' ');
      growBuf.addChar(s.at(i));
    }
    else if (c=='t' && csp==5 /*&& (i<5 || !isId(s.at(i-5)))*/ &&
             !(isId(s.at(i+1)) /*|| s.at(i+1)==' '*/ || 
               s.at(i+1)==')' || 
               s.at(i+1)==',' || 
               s.at(i+1)=='\0'
              )
            ) 
      // prevent const ::A from being converted to const::A
    {
      growBuf.addChar('t');
      growBuf.addChar(' ');
      if (s.at(i+1)==' ') i++;
      csp=0;
    }
    else if (c==':' && csp==6 /*&& (i<6 || !isId(s.at(i-6)))*/) 
      // replace const::A by const ::A
    {
      growBuf.addChar(' ');
      growBuf.addChar(':');
      csp=0;
    }
    else if (c=='l' && vsp==7 /*&& (i<7 || !isId(s.at(i-7)))*/ &&
             !(isId(s.at(i+1)) /*|| s.at(i+1)==' '*/ || 
               s.at(i+1)==')' || 
               s.at(i+1)==',' || 
               s.at(i+1)=='\0'
              )
            ) 
      // prevent virtual ::A from being converted to virtual::A
    {
      growBuf.addChar('l');
      growBuf.addChar(' ');
      if (s.at(i+1)==' ') i++;
      vsp=0;
    }
    else if (c==':' && vsp==8 /*&& (i<8 || !isId(s.at(i-8)))*/) 
      // replace virtual::A by virtual ::A
    {
      growBuf.addChar(' ');
      growBuf.addChar(':');
      vsp=0;
    }
    else if (!isspace((uchar)c) || // not a space
        ( i>0 && i<l-1 &&          // internal character
          (isId(s.at(i-1)) || s.at(i-1)==')' || s.at(i-1)==',' || s.at(i-1)=='>' || s.at(i-1)==']')
          && (isId(s.at(i+1)) || (i<l-2 && s.at(i+1)=='$' && isId(s.at(i+2)))
            || (i<l-3 && s.at(i+1)=='&' && s.at(i+2)=='$' && isId(s.at(i+3))))
        ) 
        )
    {
      if (c=='*' || c=='&' || c=='@' || c=='$')
      {  
        //uint rl=result.length();
        uint rl=growBuf.getPos();
        if ((rl>0 && (isId(growBuf.at(rl-1)) || growBuf.at(rl-1)=='>')) &&
            ((c!='*' && c!='&') || !findOperator2(s,i)) // avoid splitting operator* and operator->* and operator&
           ) 
        {
          growBuf.addChar(' ');
        }
      }
      growBuf.addChar(c);
      if (cliSupport &&
          (c=='^' || c=='%') && i>1 && isId(s.at(i-1)) &&
          !findOperator(s,i)
         ) 
      {
        growBuf.addChar(' '); // C++/CLI: Type^ name and Type% name
      }
    }
  }
  //printf("removeRedundantWhiteSpace(`%s')=`%s'\n",s.data(),result.data());
  growBuf.addChar(0);
  //result.resize(resultPos);
  return growBuf.get();
}  

bool rightScopeMatch(const QCString &scope, const QCString &name)
{
  return (name==scope || // equal 
      (scope.right(name.length())==name && // substring 
       scope.at(scope.length()-name.length()-1)==':' // scope
      ) 
      );
}

bool leftScopeMatch(const QCString &scope, const QCString &name)
{
  return (name==scope || // equal 
      (scope.left(name.length())==name && // substring 
       scope.at(name.length())==':' // scope
      ) 
      );
}


void linkifyText(const TextGeneratorIntf &out,Definition *scope,
    FileDef *fileScope,const char *,
    const char *text, bool autoBreak,bool external,
    bool keepSpaces,int indentLevel)
{
  //printf("linkify=`%s'\n",text);
  static QRegExp regExp("[a-z_A-Z\\x80-\\xFF][~!a-z_A-Z0-9$\\\\.:\\x80-\\xFF]*");
  static QRegExp regExpSplit("(?!:),");
  QCString txtStr=text;
  int strLen = txtStr.length();
  //printf("linkifyText scope=%s fileScope=%s strtxt=%s strlen=%d\n",
  //    scope?scope->name().data():"<none>",
  //    fileScope?fileScope->name().data():"<none>",
  //    txtStr.data(),strLen);
  int matchLen;
  int index=0;
  int newIndex;
  int skipIndex=0;
  int floatingIndex=0;
  if (strLen==0) return;
  // read a word from the text string
  while ((newIndex=regExp.match(txtStr,index,&matchLen))!=-1 && 
      (newIndex==0 || !(txtStr.at(newIndex-1)>='0' && txtStr.at(newIndex-1)<='9')) // avoid matching part of hex numbers
      )
  {
    // add non-word part to the result
    floatingIndex+=newIndex-skipIndex+matchLen;
    bool insideString=FALSE; 
    int i;
    for (i=index;i<newIndex;i++) 
    { 
      if (txtStr.at(i)=='"') insideString=!insideString; 
    }

    //printf("floatingIndex=%d strlen=%d autoBreak=%d\n",floatingIndex,strLen,autoBreak);
    if (strLen>35 && floatingIndex>30 && autoBreak) // try to insert a split point
    {
      QCString splitText = txtStr.mid(skipIndex,newIndex-skipIndex);
      int splitLength = splitText.length();
      int offset=1;
      i=splitText.find(regExpSplit,0);
      if (i==-1) { i=splitText.find('<'); if (i!=-1) offset=0; }
      if (i==-1) i=splitText.find('>');
      if (i==-1) i=splitText.find(' ');
      //printf("splitText=[%s] len=%d i=%d offset=%d\n",splitText.data(),splitLength,i,offset);
      if (i!=-1) // add a link-break at i in case of Html output
      {
        out.writeString(splitText.left(i+offset),keepSpaces);
        out.writeBreak(indentLevel==0 ? 0 : indentLevel+1);
        out.writeString(splitText.right(splitLength-i-offset),keepSpaces);
        floatingIndex=splitLength-i-offset+matchLen;
      } 
      else
      {
        out.writeString(splitText,keepSpaces); 
      }
    }
    else
    {
      //ol.docify(txtStr.mid(skipIndex,newIndex-skipIndex)); 
      out.writeString(txtStr.mid(skipIndex,newIndex-skipIndex),keepSpaces); 
    }
    // get word from string
    QCString word=txtStr.mid(newIndex,matchLen);
    QCString matchWord = substitute(substitute(word,"\\","::"),".","::");
    //printf("linkifyText word=%s matchWord=%s scope=%s\n",
    //    word.data(),matchWord.data(),scope?scope->name().data():"<none>");
    bool found=FALSE;
    if (!insideString)
    {
      ClassDef     *cd=0;
      FileDef      *fd=0;
      MemberDef    *md=0;
      NamespaceDef *nd=0;
      GroupDef     *gd=0;
      //printf("** Match word '%s'\n",matchWord.data());

      MemberDef *typeDef=0;
      cd=getResolvedClass(scope,fileScope,matchWord,&typeDef);
      if (typeDef) // First look at typedef then class, see bug 584184.
      {
        //printf("Found typedef %s\n",typeDef->name().data());
        if (external ? typeDef->isLinkable() : typeDef->isLinkableInProject())
        {
          out.writeLink(typeDef->getReference(),
              typeDef->getOutputFileBase(),
              typeDef->anchor(),
              word);
          found=TRUE;
        }
      }
      if (!found && cd) 
      {
        //printf("Found class %s\n",cd->name().data());
        // add link to the result
        if (external ? cd->isLinkable() : cd->isLinkableInProject())
        {
          out.writeLink(cd->getReference(),cd->getOutputFileBase(),cd->anchor(),word);
          found=TRUE;
        }
      }
      else if ((cd=getClass(matchWord+"-p"))) // search for Obj-C protocols as well
      {
        // add link to the result
        if (external ? cd->isLinkable() : cd->isLinkableInProject())
        {
          out.writeLink(cd->getReference(),cd->getOutputFileBase(),cd->anchor(),word);
          found=TRUE;
        }
      }
      else if ((cd=getClass(matchWord+"-g"))) // C# generic as well
      {
        // add link to the result
        if (external ? cd->isLinkable() : cd->isLinkableInProject())
        {
          out.writeLink(cd->getReference(),cd->getOutputFileBase(),cd->anchor(),word);
          found=TRUE;
        }
      }
      else
      {
        //printf("   -> nothing\n");
      }

      int m = matchWord.findRev("::");
      QCString scopeName;
      if (scope && 
          (scope->definitionType()==Definition::TypeClass || 
           scope->definitionType()==Definition::TypeNamespace
          ) 
         )
      {
        scopeName=scope->name();
      }
      else if (m!=-1)
      {
        scopeName = matchWord.left(m);
        matchWord = matchWord.mid(m+2);
      }

      //printf("ScopeName=%s\n",scopeName.data());
      //if (!found) printf("Trying to link %s in %s\n",word.data(),scopeName.data()); 
      if (!found && 
          getDefs(scopeName,matchWord,0,md,cd,fd,nd,gd) && 
          //(md->isTypedef() || md->isEnumerate() || 
          // md->isReference() || md->isVariable()
          //) && 
          (external ? md->isLinkable() : md->isLinkableInProject()) 
         )
      {
        //printf("Found ref scope=%s\n",d?d->name().data():"<global>");
        //ol.writeObjectLink(d->getReference(),d->getOutputFileBase(),
        //                       md->anchor(),word);
        out.writeLink(md->getReference(),md->getOutputFileBase(),
            md->anchor(),word);
        //printf("found symbol %s\n",matchWord.data());
        found=TRUE;
      }
    }

    if (!found) // add word to the result
    {
      out.writeString(word,keepSpaces);
    }
    // set next start point in the string
    //printf("index=%d/%d\n",index,txtStr.length());
    skipIndex=index=newIndex+matchLen;
  }
  // add last part of the string to the result.
  //ol.docify(txtStr.right(txtStr.length()-skipIndex));
  out.writeString(txtStr.right(txtStr.length()-skipIndex),keepSpaces);
}


void writeExample(OutputList &ol,ExampleSDict *ed)
{
  QCString exampleLine=theTranslator->trWriteList(ed->count());

  //bool latexEnabled = ol.isEnabled(OutputGenerator::Latex);
  //bool manEnabled   = ol.isEnabled(OutputGenerator::Man);
  //bool htmlEnabled  = ol.isEnabled(OutputGenerator::Html);
  QRegExp marker("@[0-9]+");
  int index=0,newIndex,matchLen;
  // now replace all markers in inheritLine with links to the classes
  while ((newIndex=marker.match(exampleLine,index,&matchLen))!=-1)
  {
    bool ok;
    ol.parseText(exampleLine.mid(index,newIndex-index));
    uint entryIndex = exampleLine.mid(newIndex+1,matchLen-1).toUInt(&ok);
    Example *e=ed->at(entryIndex);
    if (ok && e) 
    {
      ol.pushGeneratorState();
      //if (latexEnabled) ol.disable(OutputGenerator::Latex);
      ol.disable(OutputGenerator::Latex);
      ol.disable(OutputGenerator::RTF);
      // link for Html / man
      //printf("writeObjectLink(file=%s)\n",e->file.data());
      ol.writeObjectLink(0,e->file,e->anchor,e->name);
      ol.popGeneratorState();

      ol.pushGeneratorState();
      //if (latexEnabled) ol.enable(OutputGenerator::Latex);
      ol.disable(OutputGenerator::Man);
      ol.disable(OutputGenerator::Html);
      // link for Latex / pdf with anchor because the sources
      // are not hyperlinked (not possible with a verbatim environment).
      ol.writeObjectLink(0,e->file,0,e->name);
      //if (manEnabled) ol.enable(OutputGenerator::Man);
      //if (htmlEnabled) ol.enable(OutputGenerator::Html);
      ol.popGeneratorState();
    }
    index=newIndex+matchLen;
  } 
  ol.parseText(exampleLine.right(exampleLine.length()-index));
  ol.writeString(".");
}


QCString argListToString(ArgumentList *al,bool useCanonicalType,bool showDefVals)
{
  QCString result;
  if (al==0) return result;
  Argument *a=al->first();
  result+="(";
  while (a)
  {
    QCString type1 = useCanonicalType && !a->canType.isEmpty() ? 
      a->canType : a->type;
    QCString type2;
    int i=type1.find(")("); // hack to deal with function pointers
    if (i!=-1)
    {
      type2=type1.mid(i);
      type1=type1.left(i);
    }
    if (!a->attrib.isEmpty())
    {
      result+=a->attrib+" ";
    }
    if (!a->name.isEmpty() || !a->array.isEmpty())
    {
      result+= type1+" "+a->name+type2+a->array;
    }
    else
    {
      result+= type1+type2;
    }
    if (!a->defval.isEmpty() && showDefVals)
    {
      result+="="+a->defval;
    }
    a = al->next();
    if (a) result+=", "; 
  }
  result+=")";
  if (al->constSpecifier) result+=" const";
  if (al->volatileSpecifier) result+=" volatile";
  return removeRedundantWhiteSpace(result);
}

QCString tempArgListToString(ArgumentList *al)
{
  QCString result;
  if (al==0) return result;
  result="<";
  Argument *a=al->first();
  while (a)
  {
    if (!a->name.isEmpty()) // add template argument name
    {
      result+=a->name;
    }
    else // extract name from type
    {
      int i=a->type.length()-1;
      while (i>=0 && isId(a->type.at(i))) i--;
      if (i>0)
      {
        result+=a->type.right(a->type.length()-i-1);
      }
      else // nothing found -> take whole name
      {
        result+=a->type;
      }
    }
    a=al->next();
    if (a) result+=", ";
  }
  result+=">";
  return removeRedundantWhiteSpace(result);
}


// compute the HTML anchors for a list of members
void setAnchors(ClassDef *cd,char id,MemberList *ml,int groupId)
{
  int count=0;
  if (ml==0) return;
  MemberListIterator mli(*ml);
  MemberDef *md;
  for (;(md=mli.current());++mli)
  {
    if (!md->isReference())
    {
      QCString anchor;
      if (groupId==-1)
        anchor.sprintf("%c%d",id,count++);
      else
        anchor.sprintf("%c%d_%d",id,groupId,count++);
      if (cd) anchor.prepend(escapeCharsInString(cd->name(),FALSE));
      md->setAnchor(anchor);
      //printf("setAnchors(): Member %s outputFileBase=%s anchor %s result %s\n",
      //    md->name().data(),md->getOutputFileBase().data(),anchor.data(),md->anchor().data());
    }
  }
}

//----------------------------------------------------------------------------

/*! takes the \a buf of the given length \a len and converts CR LF (DOS)
 * or CR (MAC) line ending to LF (Unix).  Returns the length of the
 * converted content (i.e. the same as \a len (Unix, MAC) or
 * smaller (DOS).
 */
int filterCRLF(char *buf,int len)
{
  int src = 0;    // source index
  int dest = 0;   // destination index
  char c;         // current character

  while (src<len)
  {
    c = buf[src++];            // Remember the processed character.
    if (c == '\r')             // CR to be solved (MAC, DOS)
    {
      c = '\n';                // each CR to LF
      if (src<len && buf[src] == '\n')
        ++src;                 // skip LF just after CR (DOS) 
    }
    else if ( c == '\0' && src<len-1) // filter out internal \0 characters, as it will confuse the parser
    {
      c = ' ';                 // turn into a space
    }
    buf[dest++] = c;           // copy the (modified) character to dest
  }
  return dest;                 // length of the valid part of the buf
}

static QCString getFilterFromList(const char *name,const QStrList &filterList,bool &found)
{
  found=FALSE;
  // compare the file name to the filter pattern list
  QStrListIterator sli(filterList);
  char* filterStr;
  for (sli.toFirst(); (filterStr = sli.current()); ++sli)
  {
    QCString fs = filterStr;
    int i_equals=fs.find('=');
    if (i_equals!=-1)
    {
      QCString filterPattern = fs.left(i_equals);
      QRegExp fpat(filterPattern,portable_fileSystemIsCaseSensitive(),TRUE); 
      if (fpat.match(name)!=-1) 
      {
        // found a match!
        QCString filterName = fs.mid(i_equals+1);
        if (filterName.find(' ')!=-1)
        { // add quotes if the name has spaces
          filterName="\""+filterName+"\"";
        }
        found=TRUE;
        return filterName;
      }
    }
  }

  // no match
  return "";
}

/*! looks for a filter for the file \a name.  Returns the name of the filter
 *  if there is a match for the file name, otherwise an empty string.
 *  In case \a inSourceCode is TRUE then first the source filter list is
 *  considered.
 */
QCString getFileFilter(const char* name,bool isSourceCode)
{
  // sanity check
  if (name==0) return "";

  QStrList& filterSrcList = Config_getList("FILTER_SOURCE_PATTERNS");
  QStrList& filterList    = Config_getList("FILTER_PATTERNS");

  QCString filterName;
  bool found=FALSE;
  if (isSourceCode && !filterSrcList.isEmpty())
  { // first look for source filter pattern list
    filterName = getFilterFromList(name,filterSrcList,found);
  }
  if (!found && filterName.isEmpty())
  { // then look for filter pattern list
    filterName = getFilterFromList(name,filterList,found);
  }
  if (!found)
  { // then use the generic input filter
    return Config_getString("INPUT_FILTER");
  }
  else
  {
    return filterName;
  }
}


QCString transcodeCharacterStringToUTF8(const QCString &input)
{
  bool error=FALSE;
  static QCString inputEncoding = Config_getString("INPUT_ENCODING");
  const char *outputEncoding = "UTF-8";
  if (inputEncoding.isEmpty() || qstricmp(inputEncoding,outputEncoding)==0) return input;
  int inputSize=input.length();
  int outputSize=inputSize*4+1;
  QCString output(outputSize);
  void *cd = portable_iconv_open(outputEncoding,inputEncoding);
  if (cd==(void *)(-1)) 
  {
    err("error: unsupported character conversion: '%s'->'%s'\n",
        inputEncoding.data(),outputEncoding);
    error=TRUE;
  }
  if (!error)
  {
    size_t iLeft=inputSize;
    size_t oLeft=outputSize;
    const char *inputPtr = input.data();
    char *outputPtr = output.data();
    if (!portable_iconv(cd, &inputPtr, &iLeft, &outputPtr, &oLeft))
    {
      outputSize-=oLeft;
      output.resize(outputSize+1);
      output.at(outputSize)='\0';
      //printf("iconv: input size=%d output size=%d\n[%s]\n",size,newSize,srcBuf.data());
    }
    else
    {
      err("error: failed to translate characters from %s to %s: check INPUT_ENCODING\ninput=[%s]\n",
          inputEncoding.data(),outputEncoding,input.data());
      error=TRUE;
    }
  }
  portable_iconv_close(cd);
  return error ? input : output;
}

/*! reads a file with name \a name and returns it as a string. If \a filter
 *  is TRUE the file will be filtered by any user specified input filter.
 *  If \a name is "-" the string will be read from standard input. 
 */
QCString fileToString(const char *name,bool filter,bool isSourceCode)
{
  if (name==0 || name[0]==0) return 0;
  QFile f;

  bool fileOpened=FALSE;
  if (name[0]=='-' && name[1]==0) // read from stdin
  {
    fileOpened=f.open(IO_ReadOnly,stdin);
    if (fileOpened)
    {
      const int bSize=4096;
      QCString contents(bSize);
      int totalSize=0;
      int size;
      while ((size=f.readBlock(contents.data()+totalSize,bSize))==bSize)
      {
        totalSize+=bSize;
        contents.resize(totalSize+bSize); 
      }
      totalSize = filterCRLF(contents.data(),totalSize+size)+2;
      contents.resize(totalSize);
      contents.at(totalSize-2)='\n'; // to help the scanner
      contents.at(totalSize-1)='\0';
      return contents;
    }
  }
  else // read from file
  {
    QFileInfo fi(name);
    if (!fi.exists() || !fi.isFile())
    {
      err("error: file `%s' not found\n",name);
      return "";
    }
    QCString filterName = getFileFilter(name,isSourceCode);
    if (filterName.isEmpty() || !filter)
    {
      f.setName(name);
      fileOpened=f.open(IO_ReadOnly);
      if (fileOpened)
      {
        int fsize=f.size();
        QCString contents(fsize+2);
        f.readBlock(contents.data(),fsize);
        if (fsize==0 || contents[fsize-1]=='\n') 
          contents[fsize]='\0';
        else
          contents[fsize]='\n'; // to help the scanner
        contents[fsize+1]='\0';
        f.close();
        int newSize = filterCRLF(contents.data(),fsize+2);
        if (newSize!=fsize+2) 
        {
          contents.resize(newSize);
        }
        return transcodeCharacterStringToUTF8(contents);
      }
    }
    else // filter the input
    {
      QCString cmd=filterName+" \""+name+"\"";
      Debug::print(Debug::ExtCmd,0,"Executing popen(`%s`)\n",cmd.data());
      FILE *f=portable_popen(cmd,"r");
      if (!f)
      {
        err("error: could not execute filter %s\n",filterName.data());
        return "";
      }
      const int bSize=4096;
      QCString contents(bSize);
      int totalSize=0;
      int size;
      while ((size=fread(contents.data()+totalSize,1,bSize,f))==bSize)
      {
        totalSize+=bSize;
        contents.resize(totalSize+bSize); 
      }
      totalSize = filterCRLF(contents.data(),totalSize+size)+2;
      contents.resize(totalSize);
      contents.at(totalSize-2)='\n'; // to help the scanner
      contents.at(totalSize-1)='\0';
      portable_pclose(f);
      Debug::print(Debug::FilterOutput, 0, "Filter output\n");
      Debug::print(Debug::FilterOutput,0,"-------------\n%s\n-------------\n",contents.data());
      return transcodeCharacterStringToUTF8(contents);
    }
  }
  if (!fileOpened)  
  {
    err("error: cannot open file `%s' for reading\n",name);
  }
  return "";
}

QCString dateToString(bool includeTime)
{
  QDateTime current = QDateTime::currentDateTime();
  return theTranslator->trDateTime(current.date().year(),
                                   current.date().month(),
                                   current.date().day(),
                                   current.date().dayOfWeek(),
                                   current.time().hour(),
                                   current.time().minute(),
                                   current.time().second(),
                                   includeTime);
}

QCString yearToString()
{
  const QDate &d=QDate::currentDate();
  QCString result;
  result.sprintf("%d", d.year());
  return result;
}

//----------------------------------------------------------------------
// recursive function that returns the number of branches in the 
// inheritance tree that the base class `bcd' is below the class `cd'

int minClassDistance(const ClassDef *cd,const ClassDef *bcd,int level)
{
  if (bcd->categoryOf()) // use class that is being extended in case of 
    // an Objective-C category
  {
    bcd=bcd->categoryOf();
  }
  if (cd==bcd) return level; 
  if (level==256)
  {
    err("error: Internal inconsistency: found class %s seem to have a recursive "
        "inheritance relation! Please send a bug report to dimitri@stack.nl\n",cd->name().data());
    return -1;
  }
  int m=maxInheritanceDepth; 
  if (cd->baseClasses())
  {
#if 0    
    BaseClassListIterator bcli(*cd->baseClasses());
    for ( ; bcli.current() ; ++bcli)
    {
      //printf("class %s base class %s\n",cd->name().data(),bcli.current()->classDef->name().data());
      int mc=minClassDistance(bcli.current()->classDef,bcd,level+1);
      if (mc<m) m=mc;
      if (m<0) break;
    }
#endif
    BaseClassDef *bcdi = cd->baseClasses()->first();
    while (bcdi)
    {
      int mc=minClassDistance(bcdi->classDef,bcd,level+1);
      if (mc<m) m=mc;
      if (m<0) break;
      bcdi = cd->baseClasses()->next();
    }
  }
  return m;
}

//static void printArgList(ArgumentList *al)
//{
//  if (al==0) return;
//  ArgumentListIterator ali(*al);
//  Argument *a;
//  printf("(");
//  for (;(a=ali.current());++ali)
//  {
//    printf("t=`%s' n=`%s' v=`%s' ",a->type.data(),!a->name.isEmpty()>0?a->name.data():"",!a->defval.isEmpty()>0?a->defval.data():""); 
//  }
//  printf(")");
//}

#ifndef NEWMATCH
// strip any template specifiers that follow className in string s
static QCString trimTemplateSpecifiers(
    const QCString &namespaceName,
    const QCString &className,
    const QCString &s
    )
{
  //printf("trimTemplateSpecifiers(%s,%s,%s)\n",namespaceName.data(),className.data(),s.data());
  QCString scopeName=mergeScopes(namespaceName,className);
  ClassDef *cd=getClass(scopeName);
  if (cd==0) return s; // should not happen, but guard anyway.

  QCString result=s;

  int i=className.length()-1;
  if (i>=0 && className.at(i)=='>') // template specialization
  {
    // replace unspecialized occurrences in s, with their specialized versions.
    int count=1;
    int cl=i+1;
    while (i>=0)
    {
      char c=className.at(i);
      if (c=='>') count++,i--;
      else if (c=='<') { count--; if (count==0) break; }
      else i--;
    }
    QCString unspecClassName=className.left(i);
    int l=i;
    int p=0;
    while ((i=result.find(unspecClassName,p))!=-1)
    {
      if (result.at(i+l)!='<') // unspecialized version
      {
        result=result.left(i)+className+result.right(result.length()-i-l);
        l=cl;
      }
      p=i+l;
    }
  }

  //printf("result after specialization: %s\n",result.data());

  QCString qualName=cd->qualifiedNameWithTemplateParameters();
  //printf("QualifiedName = %s\n",qualName.data());
  // We strip the template arguments following className (if any)
  if (!qualName.isEmpty()) // there is a class name
  {
    int is,ps=0;
    int p=0,l,i;

    while ((is=getScopeFragment(qualName,ps,&l))!=-1)
    {
      QCString qualNamePart = qualName.right(qualName.length()-is);
      //printf("qualNamePart=%s\n",qualNamePart.data());
      while ((i=result.find(qualNamePart,p))!=-1)
      {
        int ql=qualNamePart.length();
        result=result.left(i)+cd->name()+result.right(result.length()-i-ql);
        p=i+cd->name().length();
      }
      ps=is+l;
    }
  }
  //printf("result=%s\n",result.data());

  return result.stripWhiteSpace();
}

/*!
 * @param pattern pattern to look for
 * @param s string to search in
 * @param p position to start
 * @param len resulting pattern length
 * @returns position on which string is found, or -1 if not found
 */
static int findScopePattern(const QCString &pattern,const QCString &s,
    int p,int *len)
{
  int sl=s.length();
  int pl=pattern.length();
  int sp=0; 
  *len=0;
  while (p<sl)
  {
    sp=p; // start of match
    int pp=0; // pattern position
    while (p<sl && pp<pl)
    {
      if (s.at(p)=='<') // skip template arguments while matching
      {
        int bc=1;
        //printf("skipping pos=%d c=%c\n",p,s.at(p));
        p++;
        while (p<sl)
        {
          if (s.at(p)=='<') bc++;
          else if (s.at(p)=='>') 
          {
            bc--;
            if (bc==0) 
            {
              p++;
              break;
            }
          }
          //printf("skipping pos=%d c=%c\n",p,s.at(p));
          p++;
        }
      }
      else if (s.at(p)==pattern.at(pp))
      {
        //printf("match at position p=%d pp=%d c=%c\n",p,pp,s.at(p));
        p++;
        pp++;
      }
      else // no match
      {
        //printf("restarting at %d c=%c pat=%s\n",p,s.at(p),pattern.data());
        p=sp+1;
        break;
      }
    }
    if (pp==pl) // whole pattern matches
    {
      *len=p-sp;
      return sp;
    }
  }
  return -1;
}

static QCString trimScope(const QCString &name,const QCString &s)
{
  int scopeOffset=name.length();
  QCString result=s;
  do // for each scope
  {
    QCString tmp;
    QCString scope=name.left(scopeOffset)+"::";
    //printf("Trying with scope=`%s'\n",scope.data());

    int i,p=0,l;
    while ((i=findScopePattern(scope,result,p,&l))!=-1) // for each occurrence
    {
      tmp+=result.mid(p,i-p); // add part before pattern
      p=i+l;
    }
    tmp+=result.right(result.length()-p); // add trailing part

    scopeOffset=name.findRev("::",scopeOffset-1);
    result = tmp;
  } while (scopeOffset>0);   
  //printf("trimScope(name=%s,scope=%s)=%s\n",name.data(),s.data(),result.data());
  return result;
}
#endif

void trimBaseClassScope(BaseClassList *bcl,QCString &s,int level=0)
{
  //printf("trimBaseClassScope level=%d `%s'\n",level,s.data());
  BaseClassListIterator bcli(*bcl);
  BaseClassDef *bcd;
  for (;(bcd=bcli.current());++bcli)
  {
    ClassDef *cd=bcd->classDef;
    //printf("Trying class %s\n",cd->name().data());
    int spos=s.find(cd->name()+"::");
    if (spos!=-1)
    {
      s = s.left(spos)+s.right(
          s.length()-spos-cd->name().length()-2
          );
    }
    //printf("base class `%s'\n",cd->name().data());
    if (cd->baseClasses())
      trimBaseClassScope(cd->baseClasses(),s,level+1); 
  }
}

#if 0
/*! if either t1 or t2 contains a namespace scope, then remove that
 *  scope. If neither or both have a namespace scope, t1 and t2 remain
 *  unchanged.
 */
static void trimNamespaceScope(QCString &t1,QCString &t2,const QCString &nsName)
{
  int p1=t1.length();
  int p2=t2.length();
  for (;;)
  {
    int i1=p1==0 ? -1 : t1.findRev("::",p1);
    int i2=p2==0 ? -1 : t2.findRev("::",p2);
    if (i1==-1 && i2==-1)
    {
      return;
    }
    if (i1!=-1 && i2==-1) // only t1 has a scope
    {
      QCString scope=t1.left(i1);
      replaceNamespaceAliases(scope,i1);

      int so=nsName.length();
      do
      {
        QCString fullScope=nsName.left(so);
        if (!fullScope.isEmpty() && !scope.isEmpty()) fullScope+="::";
        fullScope+=scope;
        if (!fullScope.isEmpty() && Doxygen::namespaceSDict[fullScope]!=0) // scope is a namespace
        {
          t1 = t1.right(t1.length()-i1-2);
          return;
        }
        if (so==0)
        {
          so=-1;
        }
        else if ((so=nsName.findRev("::",so-1))==-1)
        {
          so=0;
        }
      }
      while (so>=0);
    }
    else if (i1==-1 && i2!=-1) // only t2 has a scope
    {
      QCString scope=t2.left(i2);
      replaceNamespaceAliases(scope,i2);

      int so=nsName.length();
      do
      {
        QCString fullScope=nsName.left(so);
        if (!fullScope.isEmpty() && !scope.isEmpty()) fullScope+="::";
        fullScope+=scope;
        if (!fullScope.isEmpty() && Doxygen::namespaceSDict[fullScope]!=0) // scope is a namespace
        {
          t2 = t2.right(t2.length()-i2-2);
          return;
        }
        if (so==0)
        {
          so=-1;
        }
        else if ((so=nsName.findRev("::",so-1))==-1)
        {
          so=0;
        }
      }
      while (so>=0);
    }
    p1 = QMAX(i1-2,0);
    p2 = QMAX(i2-2,0);
  }
}
#endif

static void stripIrrelevantString(QCString &target,const QCString &str)
{
  if (target==str) { target.resize(0); return; }
  int i,p=0;
  int l=str.length();
  bool changed=FALSE;
  while ((i=target.find(str,p))!=-1)
  {
    bool isMatch = (i==0 || !isId(target.at(i-1))) && // not a character before str
      (i+l==(int)target.length() || !isId(target.at(i+l))); // not a character after str
    if (isMatch)
    {
      int i1=target.find('*',i+l);
      int i2=target.find('&',i+l);
      if (i1==-1 && i2==-1)
      {
        // strip str from target at index i
        target=target.left(i)+target.right(target.length()-i-l); 
        changed=TRUE;
        i-=l;
      }
      else if ((i1!=-1 && i<i1) || (i2!=-1 && i<i2)) // str before * or &
      {
        // move str to front
        target=str+" "+target.left(i)+target.right(target.length()-i-l);
        changed=TRUE;
        i++;
      }
    }
    p = i+l;
  }
  if (changed) target=target.stripWhiteSpace();
}

/*! According to the C++ spec and Ivan Vecerina:

  Parameter declarations  that differ only in the presence or absence
  of const and/or volatile are equivalent.

  So the following example, show what is stripped by this routine
  for const. The same is done for volatile.

  \code
  const T param     ->   T param          // not relevant
  const T& param    ->   const T& param   // const needed               
  T* const param    ->   T* param         // not relevant                   
  const T* param    ->   const T* param   // const needed
  \endcode
 */
void stripIrrelevantConstVolatile(QCString &s)
{
  //printf("stripIrrelevantConstVolatile(%s)=",s.data());
  stripIrrelevantString(s,"const");
  stripIrrelevantString(s,"volatile");
  //printf("%s\n",s.data());
}


// a bit of debug support for matchArguments
#define MATCH
#define NOMATCH
//#define MATCH printf("Match at line %d\n",__LINE__);
//#define NOMATCH printf("Nomatch at line %d\n",__LINE__);

#ifndef NEWMATCH
static bool matchArgument(const Argument *srcA,const Argument *dstA,
    const QCString &className,
    const QCString &namespaceName,
    NamespaceSDict *usingNamespaces,
    SDict<Definition> *usingClasses)
{
  //printf("match argument start `%s|%s' <-> `%s|%s' using nsp=%p class=%p\n",
  //    srcA->type.data(),srcA->name.data(),
  //    dstA->type.data(),dstA->name.data(),
  //    usingNamespaces,
  //    usingClasses);

  // TODO: resolve any typedefs names that are part of srcA->type
  //       before matching. This should use className and namespaceName
  //       and usingNamespaces and usingClass to determine which typedefs
  //       are in-scope, so it will not be very efficient :-(

  QCString srcAType=trimTemplateSpecifiers(namespaceName,className,srcA->type);
  QCString dstAType=trimTemplateSpecifiers(namespaceName,className,dstA->type);
  QCString srcAName=srcA->name.stripWhiteSpace();
  QCString dstAName=dstA->name.stripWhiteSpace();
  srcAType.stripPrefix("class ");
  dstAType.stripPrefix("class ");

  // allow distinguishing "const A" from "const B" even though 
  // from a syntactic point of view they would be two names of the same 
  // type "const". This is not fool prove of course, but should at least 
  // catch the most common cases.
  if ((srcAType=="const" || srcAType=="volatile") && !srcAName.isEmpty())
  {
    srcAType+=" ";
    srcAType+=srcAName;
  } 
  if ((dstAType=="const" || dstAType=="volatile") && !dstAName.isEmpty())
  {
    dstAType+=" ";
    dstAType+=dstAName;
  }
  if (srcAName=="const" || srcAName=="volatile")
  {
    srcAType+=srcAName;
    srcAName.resize(0);
  }
  else if (dstA->name=="const" || dstA->name=="volatile")
  {
    dstAType+=dstA->name;
    dstAName.resize(0);
  }

  stripIrrelevantConstVolatile(srcAType);
  stripIrrelevantConstVolatile(dstAType);

  // strip typename keyword
  if (strncmp(srcAType,"typename ",9)==0)
  {
    srcAType = srcAType.right(srcAType.length()-9); 
  }
  if (strncmp(dstAType,"typename ",9)==0)
  {
    dstAType = dstAType.right(dstAType.length()-9); 
  }

  srcAType = removeRedundantWhiteSpace(srcAType);
  dstAType = removeRedundantWhiteSpace(dstAType);

  //srcAType=stripTemplateSpecifiersFromScope(srcAType,FALSE);
  //dstAType=stripTemplateSpecifiersFromScope(dstAType,FALSE);

  //printf("srcA=`%s|%s' dstA=`%s|%s'\n",srcAType.data(),srcAName.data(),
  //      dstAType.data(),dstAName.data());

  if (srcA->array!=dstA->array) // nomatch for char[] against char
  {
    NOMATCH
      return FALSE;
  }
  if (srcAType!=dstAType) // check if the argument only differs on name 
  {

    // remove a namespace scope that is only in one type 
    // (assuming a using statement was used)
    //printf("Trimming %s<->%s: %s\n",srcAType.data(),dstAType.data(),namespaceName.data());
    //trimNamespaceScope(srcAType,dstAType,namespaceName);
    //printf("After Trimming %s<->%s\n",srcAType.data(),dstAType.data());

    //QCString srcScope;
    //QCString dstScope;

    // strip redundant scope specifiers
    if (!className.isEmpty())
    {
      srcAType=trimScope(className,srcAType);
      dstAType=trimScope(className,dstAType);
      //printf("trimScope: `%s' <=> `%s'\n",srcAType.data(),dstAType.data());
      ClassDef *cd;
      if (!namespaceName.isEmpty())
        cd=getClass(namespaceName+"::"+className);
      else
        cd=getClass(className);
      if (cd && cd->baseClasses())
      {
        trimBaseClassScope(cd->baseClasses(),srcAType); 
        trimBaseClassScope(cd->baseClasses(),dstAType); 
      }
      //printf("trimBaseClassScope: `%s' <=> `%s'\n",srcAType.data(),dstAType.data());
    }
    if (!namespaceName.isEmpty())
    {
      srcAType=trimScope(namespaceName,srcAType);
      dstAType=trimScope(namespaceName,dstAType);
    }
    //printf("#usingNamespace=%d\n",usingNamespaces->count());
    if (usingNamespaces && usingNamespaces->count()>0)
    {
      NamespaceSDict::Iterator nli(*usingNamespaces);
      NamespaceDef *nd;
      for (;(nd=nli.current());++nli)
      {
        srcAType=trimScope(nd->name(),srcAType);
        dstAType=trimScope(nd->name(),dstAType);
      }
    }
    //printf("#usingClasses=%d\n",usingClasses->count());
    if (usingClasses && usingClasses->count()>0)
    {
      SDict<Definition>::Iterator cli(*usingClasses);
      Definition *cd;
      for (;(cd=cli.current());++cli)
      {
        srcAType=trimScope(cd->name(),srcAType);
        dstAType=trimScope(cd->name(),dstAType);
      }
    }

    //printf("2. srcA=%s|%s dstA=%s|%s\n",srcAType.data(),srcAName.data(),
    //    dstAType.data(),dstAName.data());

    if (!srcAName.isEmpty() && !dstA->type.isEmpty() &&
        (srcAType+" "+srcAName)==dstAType)
    {
      MATCH
      return TRUE;
    }
    else if (!dstAName.isEmpty() && !srcA->type.isEmpty() &&
        (dstAType+" "+dstAName)==srcAType)
    {
      MATCH
      return TRUE;
    }


    uint srcPos=0,dstPos=0; 
    bool equal=TRUE;
    while (srcPos<srcAType.length() && dstPos<dstAType.length() && equal)
    {
      equal=srcAType.at(srcPos)==dstAType.at(dstPos);
      if (equal) srcPos++,dstPos++; 
    }
    uint srcATypeLen=srcAType.length();
    uint dstATypeLen=dstAType.length();
    if (srcPos<srcATypeLen && dstPos<dstATypeLen)
    {
      // if nothing matches or the match ends in the middle or at the
      // end of a string then there is no match
      if (srcPos==0 || dstPos==0) 
      {
        NOMATCH
        return FALSE;
      }
      if (isId(srcAType.at(srcPos)) && isId(dstAType.at(dstPos)))
      {
        //printf("partial match srcPos=%d dstPos=%d!\n",srcPos,dstPos);
        // check if a name if already found -> if no then there is no match
        if (!srcAName.isEmpty() || !dstAName.isEmpty()) 
        {
          NOMATCH
          return FALSE;
        }
        // types only
        while (srcPos<srcATypeLen && isId(srcAType.at(srcPos))) srcPos++;
        while (dstPos<dstATypeLen && isId(dstAType.at(dstPos))) dstPos++;
        if (srcPos<srcATypeLen || 
            dstPos<dstATypeLen ||
            (srcPos==srcATypeLen && dstPos==dstATypeLen)
           ) 
        {
          NOMATCH
          return FALSE;
        }
      }
      else
      {
        // otherwise we assume that a name starts at the current position.
        while (srcPos<srcATypeLen && isId(srcAType.at(srcPos))) srcPos++;
        while (dstPos<dstATypeLen && isId(dstAType.at(dstPos))) dstPos++;

        // if nothing more follows for both types then we assume we have
        // found a match. Note that now `signed int' and `signed' match, but
        // seeing that int is not a name can only be done by looking at the
        // semantics.

        if (srcPos!=srcATypeLen || dstPos!=dstATypeLen) 
        { 
          NOMATCH
          return FALSE; 
        }
      }
    }
    else if (dstPos<dstAType.length())
    {
      if (!isspace((uchar)dstAType.at(dstPos))) // maybe the names differ
      {
        if (!dstAName.isEmpty()) // dst has its name separated from its type
        {
          NOMATCH
          return FALSE;
        }
        while (dstPos<dstAType.length() && isId(dstAType.at(dstPos))) dstPos++;
        if (dstPos!=dstAType.length()) 
        {
          NOMATCH
          return FALSE; // more than a difference in name -> no match
        }
      }
      else  // maybe dst has a name while src has not
      {
        dstPos++;
        while (dstPos<dstAType.length() && isId(dstAType.at(dstPos))) dstPos++;
        if (dstPos!=dstAType.length() || !srcAName.isEmpty()) 
        {
          NOMATCH
          return FALSE; // nope not a name -> no match
        }
      }
    }
    else if (srcPos<srcAType.length())
    {
      if (!isspace((uchar)srcAType.at(srcPos))) // maybe the names differ
      {
        if (!srcAName.isEmpty()) // src has its name separated from its type
        {
          NOMATCH
          return FALSE;
        }
        while (srcPos<srcAType.length() && isId(srcAType.at(srcPos))) srcPos++;
        if (srcPos!=srcAType.length()) 
        {
          NOMATCH
          return FALSE; // more than a difference in name -> no match
        }
      }
      else // maybe src has a name while dst has not
      {
        srcPos++;
        while (srcPos<srcAType.length() && isId(srcAType.at(srcPos))) srcPos++;
        if (srcPos!=srcAType.length() || !dstAName.isEmpty()) 
        {
          NOMATCH
          return FALSE; // nope not a name -> no match
        }
      }
    }
  }
  MATCH
  return TRUE;
}


/*!
 * Matches the arguments list srcAl with the argument list dstAl
 * Returns TRUE if the argument lists are equal. Two argument list are 
 * considered equal if the number of arguments is equal and the types of all 
 * arguments are equal. Furthermore the const and volatile specifiers 
 * stored in the list should be equal.
 */
bool matchArguments(ArgumentList *srcAl,ArgumentList *dstAl,
    const char *cl,const char *ns,bool checkCV,
    NamespaceSDict *usingNamespaces,
    SDict<Definition> *usingClasses)
{
  QCString className=cl;
  QCString namespaceName=ns;

  // strip template specialization from class name if present
  //int til=className.find('<'),tir=className.find('>');
  //if (til!=-1 && tir!=-1 && tir>til) 
  //{
  //  className=className.left(til)+className.right(className.length()-tir-1);
  //}

  //printf("matchArguments(%s,%s) className=%s namespaceName=%s checkCV=%d usingNamespaces=%d usingClasses=%d\n",
  //    srcAl ? argListToString(srcAl).data() : "",
  //    dstAl ? argListToString(dstAl).data() : "",
  //    cl,ns,checkCV,
  //    usingNamespaces?usingNamespaces->count():0,
  //    usingClasses?usingClasses->count():0
  //    );

  if (srcAl==0 || dstAl==0)
  {
    bool match = srcAl==dstAl; // at least one of the members is not a function
    if (match)
    {
      MATCH
      return TRUE;
    }
    else
    {
      NOMATCH
      return FALSE;
    }
  }

  // handle special case with void argument
  if ( srcAl->count()==0 && dstAl->count()==1 && 
      dstAl->getFirst()->type=="void" )
  { // special case for finding match between func() and func(void)
    Argument *a=new Argument;
    a->type = "void";
    srcAl->append(a);
    MATCH
    return TRUE;
  }
  if ( dstAl->count()==0 && srcAl->count()==1 &&
      srcAl->getFirst()->type=="void" )
  { // special case for finding match between func(void) and func()
    Argument *a=new Argument;
    a->type = "void";
    dstAl->append(a);
    MATCH
    return TRUE;
  }

  if (srcAl->count() != dstAl->count())
  {
    NOMATCH
    return FALSE; // different number of arguments -> no match
  }

  if (checkCV)
  {
    if (srcAl->constSpecifier != dstAl->constSpecifier) 
    {
      NOMATCH
      return FALSE; // one member is const, the other not -> no match
    }
    if (srcAl->volatileSpecifier != dstAl->volatileSpecifier)
    {
      NOMATCH
      return FALSE; // one member is volatile, the other not -> no match
    }
  }

  // so far the argument list could match, so we need to compare the types of
  // all arguments.
  ArgumentListIterator srcAli(*srcAl),dstAli(*dstAl);
  Argument *srcA,*dstA;
  for (;(srcA=srcAli.current(),dstA=dstAli.current());++srcAli,++dstAli)
  { 
    if (!matchArgument(srcA,dstA,className,namespaceName,
          usingNamespaces,usingClasses))
    {
      NOMATCH
      return FALSE;
    }
  }
  MATCH
  return TRUE; // all arguments match 
}

#endif

#if 0
static QCString resolveSymbolName(FileDef *fs,Definition *symbol,QCString &templSpec)
{
  ASSERT(symbol!=0);
  if (symbol->definitionType()==Definition::TypeMember && 
      ((MemberDef*)symbol)->isTypedef()) // if symbol is a typedef then try
    // to resolve it
  {
    MemberDef *md = 0;
    ClassDef *cd = newResolveTypedef(fs,(MemberDef*)symbol,&md,&templSpec);
    if (cd)
    {
      return cd->qualifiedName()+templSpec;
    }
    else if (md)
    {
      return md->qualifiedName();
    }
  }
  return symbol->qualifiedName();
}
#endif

static QCString stripDeclKeywords(const QCString &s)
{
  int i=s.find(" class ");
  if (i!=-1) return s.left(i)+s.mid(i+6);
  i=s.find(" typename ");
  if (i!=-1) return s.left(i)+s.mid(i+9);
  i=s.find(" union ");
  if (i!=-1) return s.left(i)+s.mid(i+6);
  i=s.find(" struct ");
  if (i!=-1) return s.left(i)+s.mid(i+7);
  return s;
}

// forward decl for circular dependencies
static QCString extractCanonicalType(Definition *d,FileDef *fs,QCString type);

QCString getCanonicalTemplateSpec(Definition *d,FileDef *fs,const QCString& spec)
{
  
  QCString templSpec = spec.stripWhiteSpace();
  // this part had been commented out before... but it is needed to match for instance
  // std::list<std::string> against list<string> so it is now back again!
  if (!templSpec.isEmpty() && templSpec.at(0) == '<') 
  {
    templSpec = "< " + extractCanonicalType(d,fs,templSpec.right(templSpec.length()-1).stripWhiteSpace());
  }
  QCString resolvedType = resolveTypeDef(d,templSpec);
  if (!resolvedType.isEmpty()) // not known as a typedef either
  {
    templSpec = resolvedType;
  }
  //printf("getCanonicalTemplateSpec(%s)=%s\n",spec.data(),templSpec.data());
  return templSpec;
}


static QCString getCanonicalTypeForIdentifier(
    Definition *d,FileDef *fs,const QCString &word,
    QCString *tSpec,int count=0)
{
  if (count>10) return word; // oops recursion

  QCString symName,scope,result,templSpec,tmpName;
  //DefinitionList *defList=0;
  if (tSpec && !tSpec->isEmpty()) 
    templSpec = stripDeclKeywords(getCanonicalTemplateSpec(d,fs,*tSpec));

  if (word.findRev("::")!=-1 && !(tmpName=stripScope(word)).isEmpty())
  {
    symName=tmpName; // name without scope
  }
  else
  {
    symName=word;
  }
  //printf("getCanonicalTypeForIdentifier(%s,[%s->%s]) start\n",
  //    word.data(),tSpec?tSpec->data():"<none>",templSpec.data());

  ClassDef *cd = 0;
  MemberDef *mType = 0;
  QCString ts;
  QCString resolvedType;

  // lookup class / class template instance
  cd = getResolvedClass(d,fs,word+templSpec,&mType,&ts,TRUE,TRUE,&resolvedType);
  bool isTemplInst = cd && !templSpec.isEmpty();
  if (!cd && !templSpec.isEmpty())
  {
    // class template specialization not known, look up class template
    cd = getResolvedClass(d,fs,word,&mType,&ts,TRUE,TRUE,&resolvedType);
  }
  if (cd && cd->isUsedOnly()) cd=0; // ignore types introduced by usage relations

  //printf("cd=%p mtype=%p\n",cd,mType);
  //printf("  getCanonicalTypeForIdentifer: symbol=%s word=%s cd=%s d=%s fs=%s cd->isTemplate=%d\n",
  //    symName.data(),
  //    word.data(),
  //    cd?cd->name().data():"<none>",
  //    d?d->name().data():"<none>",
  //    fs?fs->name().data():"<none>",
  //    cd?cd->isTemplate():-1
  //   );

  //printf("  >>>> word '%s' => '%s' templSpec=%s ts=%s tSpec=%s isTemplate=%d resolvedType=%s\n",
  //    (word+templSpec).data(),
  //    cd?cd->qualifiedName().data():"<none>",
  //    templSpec.data(),ts.data(),
  //    tSpec?tSpec->data():"<null>",
  //    cd?cd->isTemplate():FALSE,
  //    resolvedType.data());

  //printf("  mtype=%s\n",mType?mType->name().data():"<none>");

  if (cd) // resolves to a known class type
  {
    if (cd==d && tSpec) *tSpec="";

    if (mType && mType->isTypedef()) // but via a typedef
    {
      result = resolvedType;
    }
    else
    {
      if (isTemplInst)
      {
        // spec is already part of class type
        templSpec="";
        if (tSpec) *tSpec="";
      }
      else if (!ts.isEmpty() && templSpec.isEmpty())
      {
        // use formal template args for spec
        templSpec = stripDeclKeywords(getCanonicalTemplateSpec(d,fs,ts));
      }

      result = removeRedundantWhiteSpace(cd->qualifiedName() + templSpec);

      if (cd->isTemplate() && tSpec) //
      {
        if (!templSpec.isEmpty()) // specific instance
        {
          result=cd->name()+templSpec;
        }
        else // use template type
        {
          result=cd->qualifiedNameWithTemplateParameters();
        }
        // template class, so remove the template part (it is part of the class name)
        *tSpec="";
      }
      else if (ts.isEmpty() && !templSpec.isEmpty() && cd && !cd->isTemplate() && tSpec)
      {
        // obscure case, where a class is used as a template, but doxygen think it is
        // not (could happen when loading the class from a tag file).
        *tSpec="";
      }
    }
  }
  else if (mType && mType->isEnumerate()) // an enum
  {
    result = mType->qualifiedName();
  }
  else if (mType && mType->isTypedef()) // a typedef
  {
    //result = mType->qualifiedName(); // changed after 1.7.2
    //result = mType->typeString();
    //printf("word=%s typeString=%s\n",word.data(),mType->typeString());
    if (word!=mType->typeString())
    {
      result = getCanonicalTypeForIdentifier(d,fs,mType->typeString(),tSpec,count+1);
    }
    else
    {
      result = mType->typeString();
    }
  }
  else // fallback
  {
    resolvedType = resolveTypeDef(d,word);
    //printf("typedef [%s]->[%s]\n",word.data(),resolvedType.data());
    if (resolvedType.isEmpty()) // not known as a typedef either
    {
      result = word;
    }
    else
    {
      result = resolvedType;
    }
  }
  //printf("getCanonicalTypeForIdentifier [%s]->[%s]\n",word.data(),result.data());
  return result;
}

static QCString extractCanonicalType(Definition *d,FileDef *fs,QCString type)
{
  type = type.stripWhiteSpace();

  // strip const and volatile keywords that are not relevant for the type
  stripIrrelevantConstVolatile(type);

  // strip leading keywords
  type.stripPrefix("class ");
  type.stripPrefix("struct ");
  type.stripPrefix("union ");
  type.stripPrefix("enum ");
  type.stripPrefix("typename ");

  type = removeRedundantWhiteSpace(type);
  //printf("extractCanonicalType(type=%s) start: def=%s file=%s\n",type.data(),
  //    d ? d->name().data() : "<null>",fs ? fs->name().data() : "<null>");

  //static QRegExp id("[a-z_A-Z\\x80-\\xFF][:a-z_A-Z0-9\\x80-\\xFF]*");

  QCString canType;
  QCString templSpec,word;
  int i,p=0,pp=0;
  while ((i=extractClassNameFromType(type,p,word,templSpec))!=-1)
    // foreach identifier in the type
  {
    //printf("     i=%d p=%d\n",i,p);
    if (i>pp) canType += type.mid(pp,i-pp);


    QCString ct = getCanonicalTypeForIdentifier(d,fs,word,&templSpec);

    // in case the ct is empty it means that "word" represents scope "d"
    // and this does not need to be added to the canonical 
    // type (it is redundant), so/ we skip it. This solves problem 589616.
    if (ct.isEmpty() && type.mid(p,2)=="::")
    {
      p+=2;
    }
    else
    {
      canType += ct;
    }
    //printf(" word=%s templSpec=%s canType=%s ct=%s\n",
    //    word.data(),templSpec.data(),canType.data(),ct.data());
    if (!templSpec.isEmpty()) // if we didn't use up the templSpec already
                              // (i.e. type is not a template specialization)
                              // then resolve any identifiers inside. 
    {
      static QRegExp re("[a-z_A-Z\\x80-\\xFF][a-z_A-Z0-9\\x80-\\xFF]*");
      int tp=0,tl,ti;
      // for each identifier template specifier
      //printf("adding resolved %s to %s\n",templSpec.data(),canType.data());
      while ((ti=re.match(templSpec,tp,&tl))!=-1)
      {
        canType += templSpec.mid(tp,ti-tp);
        canType += getCanonicalTypeForIdentifier(d,fs,templSpec.mid(ti,tl),0);
        tp=ti+tl;
      }
      canType+=templSpec.right(templSpec.length()-tp);
    }

    pp=p;
  }
  canType += type.right(type.length()-pp);
  //printf("extractCanonicalType = '%s'->'%s'\n",type.data(),canType.data());

  return removeRedundantWhiteSpace(canType);
}

static QCString extractCanonicalArgType(Definition *d,FileDef *fs,const Argument *arg)
{
  QCString type = arg->type.stripWhiteSpace();
  QCString name = arg->name;
  //printf("----- extractCanonicalArgType(type=%s,name=%s)\n",type.data(),name.data());
  if ((type=="const" || type=="volatile") && !name.isEmpty()) 
  { // name is part of type => correct
    type+=" ";
    type+=name;
  } 
  if (name=="const" || name=="volatile")
  { // name is part of type => correct
    if (!type.isEmpty()) type+=" ";
    type+=name;
  }
  if (!arg->array.isEmpty())
  {
    type+=arg->array;
  }

  return extractCanonicalType(d,fs,type);
}

static bool matchArgument2(
    Definition *srcScope,FileDef *srcFileScope,Argument *srcA,
    Definition *dstScope,FileDef *dstFileScope,Argument *dstA
    )
{
  //printf(">> match argument: %s::`%s|%s' (%s) <-> %s::`%s|%s' (%s)\n",
  //    srcScope ? srcScope->name().data() : "",
  //    srcA->type.data(),srcA->name.data(),srcA->canType.data(),
  //    dstScope ? dstScope->name().data() : "",
  //    dstA->type.data(),dstA->name.data(),dstA->canType.data());

  //if (srcA->array!=dstA->array) // nomatch for char[] against char
  //{
  //  NOMATCH
  //  return FALSE;
  //}
  QCString sSrcName = " "+srcA->name;
  QCString sDstName = " "+dstA->name;
  QCString srcType  = srcA->type;
  QCString dstType  = dstA->type;
  stripIrrelevantConstVolatile(srcType);
  stripIrrelevantConstVolatile(dstType);
  //printf("'%s'<->'%s'\n",sSrcName.data(),dstType.right(sSrcName.length()).data());
  //printf("'%s'<->'%s'\n",sDstName.data(),srcType.right(sDstName.length()).data());
  if (sSrcName==dstType.right(sSrcName.length()))
  { // case "unsigned int" <-> "unsigned int i"
    srcA->type+=sSrcName;
    srcA->name="";
    srcA->canType=""; // invalidate cached type value
  }
  else if (sDstName==srcType.right(sDstName.length()))
  { // case "unsigned int i" <-> "unsigned int"
    dstA->type+=sDstName;
    dstA->name="";
    dstA->canType=""; // invalidate cached type value
  }

  if (srcA->canType.isEmpty())
  {
    srcA->canType = extractCanonicalArgType(srcScope,srcFileScope,srcA);
  }
  if (dstA->canType.isEmpty())
  {
    dstA->canType = extractCanonicalArgType(dstScope,dstFileScope,dstA);
  }

  if (srcA->canType==dstA->canType)
  {
    MATCH
    return TRUE;
  }
  else
  {
    //printf("   Canonical types do not match [%s]<->[%s]\n",
    //    srcA->canType.data(),dstA->canType.data());
    NOMATCH
    return FALSE;
  }
}


// new algorithm for argument matching
bool matchArguments2(Definition *srcScope,FileDef *srcFileScope,ArgumentList *srcAl,
    Definition *dstScope,FileDef *dstFileScope,ArgumentList *dstAl,
    bool checkCV
    )
{
  //printf("*** matchArguments2\n");
  ASSERT(srcScope!=0 && dstScope!=0);

  if (srcAl==0 || dstAl==0)
  {
    bool match = srcAl==dstAl; // at least one of the members is not a function
    if (match)
    {
      MATCH
      return TRUE;
    }
    else
    {
      NOMATCH
      return FALSE;
    }
  }

  // handle special case with void argument
  if ( srcAl->count()==0 && dstAl->count()==1 && 
      dstAl->getFirst()->type=="void" )
  { // special case for finding match between func() and func(void)
    Argument *a=new Argument;
    a->type = "void";
    srcAl->append(a);
    MATCH
    return TRUE;
  }
  if ( dstAl->count()==0 && srcAl->count()==1 &&
      srcAl->getFirst()->type=="void" )
  { // special case for finding match between func(void) and func()
    Argument *a=new Argument;
    a->type = "void";
    dstAl->append(a);
    MATCH
    return TRUE;
  }

  if (srcAl->count() != dstAl->count())
  {
    NOMATCH
    return FALSE; // different number of arguments -> no match
  }

  if (checkCV)
  {
    if (srcAl->constSpecifier != dstAl->constSpecifier) 
    {
      NOMATCH
      return FALSE; // one member is const, the other not -> no match
    }
    if (srcAl->volatileSpecifier != dstAl->volatileSpecifier)
    {
      NOMATCH
      return FALSE; // one member is volatile, the other not -> no match
    }
  }

  // so far the argument list could match, so we need to compare the types of
  // all arguments.
  ArgumentListIterator srcAli(*srcAl),dstAli(*dstAl);
  Argument *srcA,*dstA;
  for (;(srcA=srcAli.current(),dstA=dstAli.current());++srcAli,++dstAli)
  { 
    if (!matchArgument2(srcScope,srcFileScope,srcA,
          dstScope,dstFileScope,dstA)
       )
    {
      NOMATCH
      return FALSE;
    }
  }
  MATCH
  return TRUE; // all arguments match 
}



// merges the initializer of two argument lists
// pre:  the types of the arguments in the list should match.
void mergeArguments(ArgumentList *srcAl,ArgumentList *dstAl,bool forceNameOverwrite)
{
  //printf("mergeArguments `%s', `%s'\n",
  //    argListToString(srcAl).data(),argListToString(dstAl).data());

  if (srcAl==0 || dstAl==0 || srcAl->count()!=dstAl->count())
  {
    return; // invalid argument lists -> do not merge
  }

  ArgumentListIterator srcAli(*srcAl),dstAli(*dstAl);
  Argument *srcA,*dstA;
  for (;(srcA=srcAli.current(),dstA=dstAli.current());++srcAli,++dstAli)
  {
    if (srcA->defval.isEmpty() && !dstA->defval.isEmpty())
    {
      //printf("Defval changing `%s'->`%s'\n",srcA->defval.data(),dstA->defval.data());
      srcA->defval=dstA->defval.copy();
    }
    else if (!srcA->defval.isEmpty() && dstA->defval.isEmpty())
    {
      //printf("Defval changing `%s'->`%s'\n",dstA->defval.data(),srcA->defval.data());
      dstA->defval=srcA->defval.copy();
    }

    // fix wrongly detected const or volatile specifiers before merging.
    // example: "const A *const" is detected as type="const A *" name="const"
    if (srcA->name=="const" || srcA->name=="volatile")
    {
      srcA->type+=" "+srcA->name;
      srcA->name.resize(0);
    }
    if (dstA->name=="const" || dstA->name=="volatile")
    {
      dstA->type+=" "+dstA->name;
      dstA->name.resize(0);
    }

    if (srcA->type==dstA->type)
    {
      //printf("1. merging %s:%s <-> %s:%s\n",srcA->type.data(),srcA->name.data(),dstA->type.data(),dstA->name.data());
      if (srcA->name.isEmpty() && !dstA->name.isEmpty())
      {
        //printf("type: `%s':=`%s'\n",srcA->type.data(),dstA->type.data());
        //printf("name: `%s':=`%s'\n",srcA->name.data(),dstA->name.data());
        srcA->type = dstA->type.copy();
        srcA->name = dstA->name.copy();
      }
      else if (!srcA->name.isEmpty() && dstA->name.isEmpty())
      {
        //printf("type: `%s':=`%s'\n",dstA->type.data(),srcA->type.data());
        //printf("name: `%s':=`%s'\n",dstA->name.data(),srcA->name.data());
        dstA->type = srcA->type.copy();
        dstA->name = dstA->name.copy();
      }
      else if (!srcA->name.isEmpty() && !dstA->name.isEmpty())
      {
        //printf("srcA->name=%s dstA->name=%s\n",srcA->name.data(),dstA->name.data());
        if (forceNameOverwrite)
        {
          srcA->name = dstA->name;
        }
        else
        {
          if (srcA->docs.isEmpty() && !dstA->docs.isEmpty())
          {
            srcA->name = dstA->name;
          }
          else if (!srcA->docs.isEmpty() && dstA->docs.isEmpty())
          {
            dstA->name = srcA->name;
          }
        }
      }
    }
    else
    {
      //printf("2. merging '%s':'%s' <-> '%s':'%s'\n",srcA->type.data(),srcA->name.data(),dstA->type.data(),dstA->name.data());
      srcA->type=srcA->type.stripWhiteSpace();
      dstA->type=dstA->type.stripWhiteSpace();
      if (srcA->type+" "+srcA->name==dstA->type) // "unsigned long:int" <-> "unsigned long int:bla"
      {
        srcA->type+=" "+srcA->name;
        srcA->name=dstA->name;
      }
      else if (dstA->type+" "+dstA->name==srcA->type) // "unsigned long int bla" <-> "unsigned long int"
      {
        dstA->type+=" "+dstA->name;
        dstA->name=srcA->name;
      }
      else if (srcA->name.isEmpty() && !dstA->name.isEmpty())
      {
        srcA->name = dstA->name;
      }
      else if (dstA->name.isEmpty() && !srcA->name.isEmpty())
      {
        dstA->name = srcA->name;
      }
    }
    int i1=srcA->type.find("::"),
        i2=dstA->type.find("::"),
        j1=srcA->type.length()-i1-2,
        j2=dstA->type.length()-i2-2;
    if (i1!=-1 && i2==-1 && srcA->type.right(j1)==dstA->type)
    {
      //printf("type: `%s':=`%s'\n",dstA->type.data(),srcA->type.data());
      //printf("name: `%s':=`%s'\n",dstA->name.data(),srcA->name.data());
      dstA->type = srcA->type.left(i1+2)+dstA->type;
      dstA->name = dstA->name.copy();
    }
    else if (i1==-1 && i2!=-1 && dstA->type.right(j2)==srcA->type)
    {
      //printf("type: `%s':=`%s'\n",srcA->type.data(),dstA->type.data());
      //printf("name: `%s':=`%s'\n",dstA->name.data(),srcA->name.data());
      srcA->type = dstA->type.left(i2+2)+srcA->type;
      srcA->name = dstA->name.copy();
    }
    if (srcA->docs.isEmpty() && !dstA->docs.isEmpty())
    {
      srcA->docs = dstA->docs.copy();
    }
    else if (dstA->docs.isEmpty() && !srcA->docs.isEmpty())
    {
      dstA->docs = srcA->docs.copy();
    }
    //printf("Merge argument `%s|%s' `%s|%s'\n",
    //  srcA->type.data(),srcA->name.data(),
    //  dstA->type.data(),dstA->name.data());
  }
}

static void findMembersWithSpecificName(MemberName *mn,
                                        const char *args,
                                        bool checkStatics,
                                        FileDef *currentFile,
                                        bool checkCV,
                                        const char *forceTagFile,
                                        QList<MemberDef> &members)
{
  //printf("  Function with global scope name `%s' args=`%s'\n",
  //       mn->memberName(),args);
  MemberListIterator mli(*mn);
  MemberDef *md;
  for (mli.toFirst();(md=mli.current());++mli)
  {
    FileDef  *fd=md->getFileDef();
    GroupDef *gd=md->getGroupDef();
    //printf("  md->name()=`%s' md->args=`%s' fd=%p gd=%p current=%p ref=%s\n",
    //    md->name().data(),args,fd,gd,currentFile,md->getReference().data());
    if (
        ((gd && gd->isLinkable()) || (fd && fd->isLinkable()) || md->isReference()) && 
        md->getNamespaceDef()==0 && md->isLinkable() &&
        (!checkStatics || (!md->isStatic() && !md->isDefine()) || 
         currentFile==0 || fd==currentFile) // statics must appear in the same file
       ) 
    {
      bool match=TRUE;
      ArgumentList *argList=0;
      if (args && !md->isDefine() && strcmp(args,"()")!=0)
      {
        argList=new ArgumentList;
        LockingPtr<ArgumentList> mdAl = md->argumentList();
        stringToArgumentList(args,argList);
        match=matchArguments2(
            md->getOuterScope(),fd,mdAl.pointer(),
            Doxygen::globalScope,fd,argList,
            checkCV); 
        delete argList; argList=0;
      }
      if (match && (forceTagFile==0 || md->getReference()==forceTagFile)) 
      {
        //printf("Found match!\n");
        members.append(md);
      }
    }
  }
}

/*!
 * Searches for a member definition given its name `memberName' as a string.
 * memberName may also include a (partial) scope to indicate the scope
 * in which the member is located.
 *
 * The parameter `scName' is a string representing the name of the scope in 
 * which the link was found.
 *
 * In case of a function args contains a string representation of the 
 * argument list. Passing 0 means the member has no arguments. 
 * Passing "()" means any argument list will do, but "()" is preferred.
 *
 * The function returns TRUE if the member is known and documented or
 * FALSE if it is not.
 * If TRUE is returned parameter `md' contains a pointer to the member 
 * definition. Furthermore exactly one of the parameter `cd', `nd', or `fd' 
 * will be non-zero:
 *   - if `cd' is non zero, the member was found in a class pointed to by cd.
 *   - if `nd' is non zero, the member was found in a namespace pointed to by nd.
 *   - if `fd' is non zero, the member was found in the global namespace of
 *     file fd.
 */
bool getDefs(const QCString &scName,const QCString &memberName, 
    const char *args,
    MemberDef *&md, 
    ClassDef *&cd, FileDef *&fd, NamespaceDef *&nd, GroupDef *&gd,
    bool forceEmptyScope,
    FileDef *currentFile,
    bool checkCV,
    const char *forceTagFile
    )
{
  fd=0, md=0, cd=0, nd=0, gd=0;
  if (memberName.isEmpty()) return FALSE; /* empty name => nothing to link */

  QCString scopeName=scName;
  scopeName = substitute(scopeName,"\\","::"); // for PHP
  //printf("Search for name=%s args=%s in scope=%s forceEmpty=%d\n",
  //          memberName.data(),args,scopeName.data(),forceEmptyScope);

  int is,im=0,pm=0;
  // strip common part of the scope from the scopeName
  while ((is=scopeName.findRev("::"))!=-1 && 
         (im=memberName.find("::",pm))!=-1 &&
          (scopeName.right(scopeName.length()-is-2)==memberName.mid(pm,im-pm))
        )
  {
    scopeName=scopeName.left(is); 
    pm=im+2;
  }
  //printf("result after scope corrections scope=%s name=%s\n",
  //          scopeName.data(),memberName.data());

  QCString mName=memberName;
  QCString mScope;
  if (memberName.left(9)!="operator " && // treat operator conversion methods
      // as a special case
      (im=memberName.findRev("::"))!=-1 && 
      im<(int)memberName.length()-2 // not A::
     )
  {
    mScope=memberName.left(im); 
    mName=memberName.right(memberName.length()-im-2);
  }

  // handle special the case where both scope name and member scope are equal
  if (mScope==scopeName) scopeName.resize(0);

  //printf("mScope=`%s' mName=`%s'\n",mScope.data(),mName.data());

  MemberName *mn = Doxygen::memberNameSDict->find(mName);
  //printf("mName=%s mn=%p\n",mName.data(),mn);

  if ((!forceEmptyScope || scopeName.isEmpty()) && // this was changed for bug638856, forceEmptyScope => empty scopeName
      mn && !(scopeName.isEmpty() && mScope.isEmpty()))
  {
    //printf("  >member name '%s' found\n",mName.data());
    int scopeOffset=scopeName.length();
    do
    {
      QCString className = scopeName.left(scopeOffset);
      if (!className.isEmpty() && !mScope.isEmpty())
      {
        className+="::"+mScope;
      }
      else if (!mScope.isEmpty())
      {
        className=mScope;
      }

      ClassDef *fcd=getResolvedClass(Doxygen::globalScope,0,className);
      //printf("Trying class scope %s: %p\n",className.data(),fcd);
      // todo: fill in correct fileScope!
      if (fcd &&  // is it a documented class
          fcd->isLinkable() 
         )
      {
        //printf("  Found fcd=%p\n",fcd);
        MemberListIterator mmli(*mn);
        MemberDef *mmd;
        int mdist=maxInheritanceDepth; 
        ArgumentList *argList=0;
        if (args)
        {
          argList=new ArgumentList;
          stringToArgumentList(args,argList);
        }
        for (mmli.toFirst();(mmd=mmli.current());++mmli)
        {
          //if (mmd->isLinkable())
          //{
          LockingPtr<ArgumentList> mmdAl = mmd->argumentList();
          bool match=args==0 || 
            matchArguments2(mmd->getOuterScope(),mmd->getFileDef(),mmdAl.pointer(),
                fcd,fcd->getFileDef(),argList,
                checkCV
                );  
          //printf("match=%d\n",match);
          if (match)
          {
            ClassDef *mcd=mmd->getClassDef();
            if (mcd)
            {
              int m=minClassDistance(fcd,mcd);
              if (m<mdist && mcd->isLinkable())
              {
                mdist=m;
                cd=mcd;
                md=mmd;
              }
            }
          }
          //}
        }
        if (argList)
        {
          delete argList; argList=0;
        }
        if (mdist==maxInheritanceDepth && args && strcmp(args,"()")==0)
          // no exact match found, but if args="()" an arbitrary member will do
        {
          //printf("  >Searching for arbitrary member\n");
          for (mmli.toFirst();(mmd=mmli.current());++mmli)
          {
            //if (mmd->isLinkable())
            //{
            ClassDef *mcd=mmd->getClassDef();
            //printf("  >Class %s found\n",mcd->name().data());
            if (mcd)
            {
              int m=minClassDistance(fcd,mcd);
              if (m<mdist /* && mcd->isLinkable()*/ )
              {
                //printf("Class distance %d\n",m);
                mdist=m;
                cd=mcd;
                md=mmd;
              }
            }
            //}
          }
        }
        //printf("  >Succes=%d\n",mdist<maxInheritanceDepth);
        if (mdist<maxInheritanceDepth) 
        {
          if (!md->isLinkable()) 
          {
            md=0; // avoid returning things we cannot link to
            cd=0;
            return FALSE; // match found, but was not linkable
          }
          else
          {
            gd=md->getGroupDef();
            if (gd) cd=0;
            return TRUE; /* found match */
          }
        }
      } 
      /* go to the parent scope */
      if (scopeOffset==0)
      {
        scopeOffset=-1;
      }
      else if ((scopeOffset=scopeName.findRev("::",scopeOffset-1))==-1)
      {
        scopeOffset=0;
      }
    } while (scopeOffset>=0);

  }
  if (mn && scopeName.isEmpty() && mScope.isEmpty()) // Maybe a related function?
  {
    MemberListIterator mmli(*mn);
    MemberDef *mmd, *fuzzy_mmd = 0;
    ArgumentList *argList = 0;
    bool hasEmptyArgs = args && strcmp(args, "()") == 0;

    if (args)
      stringToArgumentList(args, argList = new ArgumentList);

    for (mmli.toFirst(); (mmd = mmli.current()); ++mmli)
    {
      if (!mmd->isLinkable() || (!mmd->isRelated() && !mmd->isForeign()) ||
           !mmd->getClassDef())
        continue;

      if (!args) break;

      QCString className = mmd->getClassDef()->name();

      LockingPtr<ArgumentList> mmdAl = mmd->argumentList();
      if (matchArguments2(mmd->getOuterScope(),mmd->getFileDef(),mmdAl.pointer(),
            Doxygen::globalScope,mmd->getFileDef(),argList,
            checkCV
            )
         ) break;

      if (!fuzzy_mmd && hasEmptyArgs)
        fuzzy_mmd = mmd;
    }

    if (argList) delete argList, argList = 0;

    mmd = mmd ? mmd : fuzzy_mmd;

    if (mmd)
    {
      md = mmd;
      cd = mmd->getClassDef();
      return TRUE;
    }
  }


  // maybe an namespace, file or group member ?
  //printf("Testing for global symbol scopeName=`%s' mScope=`%s' :: mName=`%s'\n",
  //              scopeName.data(),mScope.data(),mName.data());
  if ((mn=Doxygen::functionNameSDict->find(mName))) // name is known
  {
    //printf("  >symbol name found\n");
    NamespaceDef *fnd=0;
    int scopeOffset=scopeName.length();
    do
    {
      QCString namespaceName = scopeName.left(scopeOffset);
      if (!namespaceName.isEmpty() && !mScope.isEmpty())
      {
        namespaceName+="::"+mScope;
      }
      else if (!mScope.isEmpty())
      {
        namespaceName=mScope.copy();
      }
      //printf("Trying namespace %s\n",namespaceName.data());
      if (!namespaceName.isEmpty() && 
          (fnd=Doxygen::namespaceSDict->find(namespaceName)) &&
          fnd->isLinkable()
         )
      {
        //printf("Function inside existing namespace `%s'\n",namespaceName.data());
        bool found=FALSE;
        MemberListIterator mmli(*mn);
        MemberDef *mmd;
        for (mmli.toFirst();((mmd=mmli.current()) && !found);++mmli)
        {
          //printf("mmd->getNamespaceDef()=%p fnd=%p\n",
          //    mmd->getNamespaceDef(),fnd);
          if (mmd->getNamespaceDef()==fnd /* && mmd->isLinkable() */ )
          { // namespace is found
            bool match=TRUE;
            ArgumentList *argList=0;
            if (args && strcmp(args,"()")!=0)
            {
              argList=new ArgumentList;
              LockingPtr<ArgumentList> mmdAl = mmd->argumentList();
              stringToArgumentList(args,argList);
              match=matchArguments2(
                  mmd->getOuterScope(),mmd->getFileDef(),mmdAl.pointer(),
                  fnd,mmd->getFileDef(),argList,
                  checkCV); 
            }
            if (match)
            {
              nd=fnd;
              md=mmd;
              found=TRUE;
            }
            if (args)
            {
              delete argList; argList=0;
            }
          }
        }
        if (!found && args && !strcmp(args,"()")) 
          // no exact match found, but if args="()" an arbitrary 
          // member will do
        {
          for (mmli.toFirst();((mmd=mmli.current()) && !found);++mmli)
          {
            if (mmd->getNamespaceDef()==fnd /*&& mmd->isLinkable() */ )
            {
              nd=fnd;
              md=mmd;
              found=TRUE;
            }
          }
        }
        if (found) 
        {
          if (!md->isLinkable()) 
          {
            md=0; // avoid returning things we cannot link to
            nd=0;
            return FALSE; // match found but not linkable
          }
          else
          {
            gd=md->getGroupDef();
            if (gd && gd->isLinkable()) nd=0; else gd=0;
            return TRUE;
          }
        }
      }
      if (scopeOffset==0)
      {
        scopeOffset=-1;
      }
      else if ((scopeOffset=scopeName.findRev("::",scopeOffset-1))==-1)
      {
        scopeOffset=0;
      }
    } while (scopeOffset>=0);

    //else // no scope => global function
    {
      QList<MemberDef> members;
      // search for matches with strict static checking
      findMembersWithSpecificName(mn,args,TRUE,currentFile,checkCV,forceTagFile,members);
      if (members.count()==0) // nothing found
      {
        // search again without strict static checking
        findMembersWithSpecificName(mn,args,FALSE,currentFile,checkCV,forceTagFile,members);
      }
      //printf("found %d members\n",members.count());
      if (members.count()!=1 && args && !strcmp(args,"()"))
      {
        // no exact match found, but if args="()" an arbitrary 
        // member will do
        md=mn->last();
        while (md /* && md->isLinkable()*/)
        {
          //printf("Found member `%s'\n",md->name().data());
          //printf("member is linkable md->name()=`%s'\n",md->name().data());
          fd=md->getFileDef();
          gd=md->getGroupDef();
          if (
              (gd && gd->isLinkable()) || (fd && fd->isLinkable()) 
             )
          {
            members.append(md);
          }
          md=mn->prev();
        }
      }
      //printf("found %d candidate members\n",members.count());
      if (members.count()>0) // at least one match
      {
        md=members.last();
      }
      if (md) // found a matching global member
      {
        fd=md->getFileDef();
        gd=md->getGroupDef();
        //printf("fd=%p gd=%p gd->isLinkable()=%d\n",fd,gd,gd->isLinkable());
        if (gd && gd->isLinkable()) fd=0; else gd=0;
        return TRUE;
      }
    }
  }

  // no nothing found
  return FALSE;
}

/*!
 * Searches for a scope definition given its name as a string via parameter
 * `scope`. 
 *
 * The parameter `docScope` is a string representing the name of the scope in 
 * which the `scope` string was found.
 *
 * The function returns TRUE if the scope is known and documented or
 * FALSE if it is not.
 * If TRUE is returned exactly one of the parameter `cd`, `nd` 
 * will be non-zero:
 *   - if `cd` is non zero, the scope was a class pointed to by cd.
 *   - if `nd` is non zero, the scope was a namespace pointed to by nd.
 */
static bool getScopeDefs(const char *docScope,const char *scope,
    ClassDef *&cd, NamespaceDef *&nd)
{
  cd=0;nd=0;

  QCString scopeName=scope;
  //printf("getScopeDefs: docScope=`%s' scope=`%s'\n",docScope,scope);
  if (scopeName.isEmpty()) return FALSE;

  bool explicitGlobalScope=FALSE;
  if (scopeName.at(0)==':' && scopeName.at(1)==':')
  {
    scopeName=scopeName.right(scopeName.length()-2);  
    explicitGlobalScope=TRUE;
  }

  QCString docScopeName=docScope;
  int scopeOffset=explicitGlobalScope ? 0 : docScopeName.length();

  do // for each possible docScope (from largest to and including empty)
  {
    QCString fullName=scopeName.copy();
    if (scopeOffset>0) fullName.prepend(docScopeName.left(scopeOffset)+"::");

    if (((cd=getClass(fullName)) ||         // normal class
         (cd=getClass(fullName+"-p")) ||    // ObjC protocol
         (cd=getClass(fullName+"-g"))       // C# generic
        ) && cd->isLinkable())
    {
      return TRUE; // class link written => quit 
    }
    else if ((nd=Doxygen::namespaceSDict->find(fullName)) && nd->isLinkable())
    {
      return TRUE; // namespace link written => quit 
    }
    if (scopeOffset==0)
    {
      scopeOffset=-1;
    }
    else if ((scopeOffset=docScopeName.findRev("::",scopeOffset-1))==-1)
    {
      scopeOffset=0;
    }
  } while (scopeOffset>=0);

  return FALSE;
}

static bool isLowerCase(QCString &s)
{
  uchar *p=(uchar*)s.data();
  if (p==0) return TRUE;
  int c;
  while ((c=*p++)) if (!islower(c)) return FALSE;
  return TRUE; 
}

/*! Returns an object to reference to given its name and context 
 *  @post return value TRUE implies *resContext!=0 or *resMember!=0
 */
bool resolveRef(/* in */  const char *scName,
    /* in */  const char *name,
    /* in */  bool inSeeBlock,
    /* out */ Definition **resContext,
    /* out */ MemberDef  **resMember,
    bool lookForSpecialization,
    FileDef *currentFile,
    bool checkScope
    )
{
  QCString tsName = name;
  //bool memberScopeFirst = tsName.find('#')!=-1;
  QCString fullName = substitute(tsName,"#","::");
  fullName = removeRedundantWhiteSpace(substitute(fullName,".","::"));

  int bracePos=fullName.findRev('('); // reverse is needed for operator()(...)
  int endNamePos=bracePos!=-1 ? bracePos : fullName.length();
  int scopePos=fullName.findRev("::",endNamePos);
  bool explicitScope = fullName.left(2)=="::" &&   // ::scope or #scope
                       (scopePos>2 ||              // ::N::A
                        tsName.left(2)=="::" ||    // ::foo in local scope
                        scName==0                  // #foo  in global scope
                       );

  // default result values
  *resContext=0;
  *resMember=0;

  if (bracePos==-1) // simple name
  {
    ClassDef *cd=0;
    NamespaceDef *nd=0;

    // the following if() was commented out for releases in the range 
    // 1.5.2 to 1.6.1, but has been restored as a result of bug report 594787.
    if (!inSeeBlock && scopePos==-1 && isLowerCase(tsName))
    { // link to lower case only name => do not try to autolink 
      return FALSE;
    }

    //printf("scName=%s fullName=%s\n",scName,fullName.data());

    // check if this is a class or namespace reference
    if (scName!=fullName && getScopeDefs(scName,fullName,cd,nd))
    {
      if (cd) // scope matches that of a class
      {
        *resContext = cd;
      }
      else // scope matches that of a namespace
      {
        ASSERT(nd!=0);
        *resContext = nd;
      }
      return TRUE;
    }
    else if (scName==fullName || (!inSeeBlock && scopePos==-1)) 
      // nothing to link => output plain text
    {
      //printf("found scName=%s fullName=%s scName==fullName=%d "
      //    "inSeeBlock=%d scopePos=%d!\n",
      //    scName,fullName.data(),scName==fullName,inSeeBlock,scopePos);
      return FALSE;
    }
    // continue search...
  }

  // extract userscope+name
  QCString nameStr=fullName.left(endNamePos);
  if (explicitScope) nameStr=nameStr.mid(2);

  // extract arguments
  QCString argsStr;
  if (bracePos!=-1) argsStr=fullName.right(fullName.length()-bracePos);

  // strip template specifier
  // TODO: match against the correct partial template instantiation 
  int templPos=nameStr.find('<');
  bool tryUnspecializedVersion = FALSE;
  if (templPos!=-1 && nameStr.find("operator")==-1)
  {
    int endTemplPos=nameStr.findRev('>');
    if (endTemplPos!=-1)
    {
      if (!lookForSpecialization)
      {
        nameStr=nameStr.left(templPos)+nameStr.right(nameStr.length()-endTemplPos-1);
      }
      else
      {
        tryUnspecializedVersion = TRUE;
      }
    }
  }

  QCString scopeStr=scName;

  MemberDef    *md = 0;
  ClassDef     *cd = 0;
  FileDef      *fd = 0;
  NamespaceDef *nd = 0;
  GroupDef     *gd = 0;

  // check if nameStr is a member or global.
  //printf("getDefs(scope=%s,name=%s,args=%s checkScope=%d)\n",
  //    scopeStr.data(),nameStr.data(),argsStr.data(),checkScope);
  if (getDefs(scopeStr,nameStr,argsStr,
        md,cd,fd,nd,gd,
        //scopePos==0 && !memberScopeFirst, // forceEmptyScope
        explicitScope, // replaces prev line due to bug 600829
        currentFile,
        TRUE                              // checkCV
        )
     )
  {
    //printf("after getDefs checkScope=%d nameStr=%s cd=%p nd=%p\n",checkScope,nameStr.data(),cd,nd);
    if (checkScope && md && md->getOuterScope()==Doxygen::globalScope && 
        (!scopeStr.isEmpty() || nameStr.find("::")>0))
    {
      // we did find a member, but it is a global one while we were explicitly 
      // looking for a scoped variable. See bug 616387 for an example why this check is needed.
      // note we do need to support autolinking to "::symbol" hence the >0
      //printf("not global member!\n");
      *resContext=0;
      *resMember=0;
      return FALSE;
    }
    //printf("after getDefs md=%p cd=%p fd=%p nd=%p gd=%p\n",md,cd,fd,nd,gd);
    if      (md) { *resMember=md; *resContext=md; }
    else if (cd) *resContext=cd;
    else if (nd) *resContext=nd;
    else if (fd) *resContext=fd;
    else if (gd) *resContext=gd;
    else         { *resContext=0; *resMember=0; return FALSE; }
    //printf("member=%s (md=%p) anchor=%s linkable()=%d context=%s\n",
    //    md->name().data(),md,md->anchor().data(),md->isLinkable(),(*resContext)->name().data());
    return TRUE;
  }
  else if (inSeeBlock && !nameStr.isEmpty() && (gd=Doxygen::groupSDict->find(nameStr)))
  { // group link
    *resContext=gd;
    return TRUE;
  }
  else if (tsName.find('.')!=-1) // maybe a link to a file
  {
    bool ambig;
    fd=findFileDef(Doxygen::inputNameDict,tsName,ambig);
    if (fd && !ambig)
    {
      *resContext=fd;
      return TRUE;
    }
  }

  if (tryUnspecializedVersion)
  {
    return resolveRef(scName,name,inSeeBlock,resContext,resMember,FALSE,0,checkScope);
  }
  //printf("resolveRef: %s not found!\n",name);

  return FALSE;
}

QCString linkToText(SrcLangExt lang,const char *link,bool isFileName)
{
  //static bool optimizeOutputJava = Config_getBool("OPTIMIZE_OUTPUT_JAVA");
  QCString result=link;
  if (!result.isEmpty())
  {
    // replace # by ::
    result=substitute(result,"#","::");
    // replace . by ::
    if (!isFileName) result=substitute(result,".","::");
    // strip leading :: prefix if present
    if (result.at(0)==':' && result.at(1)==':')
    {
      result=result.right(result.length()-2);
    }
    QCString sep = getLanguageSpecificSeparator(lang);
    if (sep!="::")
    {
      result=substitute(result,"::",sep);
    }
  }
  return result;
}

#if 0
/*
 * generate a reference to a class, namespace or member.
 * `scName' is the name of the scope that contains the documentation 
 * string that is returned.
 * `name' is the name that we want to link to.
 * `name' may have five formats:
 *    1) "ScopeName"
 *    2) "memberName()"    one of the (overloaded) function or define 
 *                         with name memberName.
 *    3) "memberName(...)" a specific (overloaded) function or define 
 *                         with name memberName
 *    4) "::name           a global variable or define
 *    4) "\#memberName     member variable, global variable or define
 *    5) ("ScopeName::")+"memberName()" 
 *    6) ("ScopeName::")+"memberName(...)" 
 *    7) ("ScopeName::")+"memberName" 
 * instead of :: the \# symbol may also be used.
 */

bool generateRef(OutputDocInterface &od,const char *scName,
    const char *name,bool inSeeBlock,const char *rt)
{
  //printf("generateRef(scName=%s,name=%s,inSee=%d,rt=%s)\n",scName,name,inSeeBlock,rt);

  Definition *compound;
  MemberDef *md;

  // create default link text
  QCString linkText = linkToText(rt,FALSE);

  if (resolveRef(scName,name,inSeeBlock,&compound,&md))
  {
    if (md && md->isLinkable()) // link to member
    {
      od.writeObjectLink(md->getReference(),
          md->getOutputFileBase(),
          md->anchor(),linkText);
      // generate the page reference (for LaTeX)
      if (!md->isReference())
      {
        writePageRef(od,md->getOutputFileBase(),md->anchor());
      }
      return TRUE;
    }
    else if (compound && compound->isLinkable()) // link to compound
    {
      if (rt==0 && compound->definitionType()==Definition::TypeGroup)
      {
        linkText=((GroupDef *)compound)->groupTitle();
      }
      if (compound && compound->definitionType()==Definition::TypeFile)
      {
        linkText=linkToText(rt,TRUE);
      }
      od.writeObjectLink(compound->getReference(),
          compound->getOutputFileBase(),
          0,linkText);
      if (!compound->isReference())
      {
        writePageRef(od,compound->getOutputFileBase(),0);
      }
      return TRUE;
    }
  }
  od.docify(linkText);
  return FALSE;
}
#endif

bool resolveLink(/* in */ const char *scName,
    /* in */ const char *lr,
    /* in */ bool /*inSeeBlock*/,
    /* out */ Definition **resContext,
    /* out */ QCString &resAnchor
    )
{
  *resContext=0;

  QCString linkRef=lr;
  //printf("ResolveLink linkRef=%s inSee=%d\n",lr,inSeeBlock);
  FileDef  *fd;
  GroupDef *gd;
  PageDef  *pd;
  ClassDef *cd;
  DirDef   *dir;
  NamespaceDef *nd;
  bool ambig;
  if (linkRef.isEmpty()) // no reference name!
  {
    return FALSE;
  }
  else if ((pd=Doxygen::pageSDict->find(linkRef))) // link to a page
  {
    GroupDef *gd = pd->getGroupDef();
    if (gd)
    {
      SectionInfo *si=0;
      if (!pd->name().isEmpty()) si=Doxygen::sectionDict[pd->name()];
      *resContext=gd;
      if (si) resAnchor = si->label;
    }
    else
    {
      *resContext=pd;
    }
    return TRUE;
  }
  else if ((pd=Doxygen::exampleSDict->find(linkRef))) // link to an example
  {
    *resContext=pd;
    return TRUE;
  }
  else if ((gd=Doxygen::groupSDict->find(linkRef))) // link to a group
  {
    *resContext=gd;
    return TRUE;
  }
  else if ((fd=findFileDef(Doxygen::inputNameDict,linkRef,ambig)) // file link
      && fd->isLinkable())
  {
    *resContext=fd;
    return TRUE;
  }
  else if ((cd=getClass(linkRef))) // class link
  {
    *resContext=cd;
    resAnchor=cd->anchor();
    return TRUE;
  }
  else if ((cd=getClass(linkRef+"-p"))) // Obj-C protocol link
  {
    *resContext=cd;
    resAnchor=cd->anchor();
    return TRUE;
  }
  else if ((cd=getClass(linkRef+"-g"))) // C# generic link
  {
    *resContext=cd;
    resAnchor=cd->anchor();
    return TRUE;
  }
  else if ((nd=Doxygen::namespaceSDict->find(linkRef)))
  {
    *resContext=nd;
    return TRUE;
  }
  else if ((dir=Doxygen::directories->find(QFileInfo(linkRef).absFilePath()+"/"))
      && dir->isLinkable()) // TODO: make this location independent like filedefs
  {
    *resContext=dir;
    return TRUE;
  }
  else // probably a member reference
  {
    MemberDef *md;
    bool res = resolveRef(scName,lr,TRUE,resContext,&md);
    if (md) resAnchor=md->anchor();
    return res;
  }
}


//----------------------------------------------------------------------
// General function that generates the HTML code for a reference to some
// file, class or member from text `lr' within the context of class `clName'. 
// This link has the text 'lt' (if not 0), otherwise `lr' is used as a
// basis for the link's text.
// returns TRUE if a link could be generated.

bool generateLink(OutputDocInterface &od,const char *clName,
    const char *lr,bool inSeeBlock,const char *lt)
{
  //printf("generateLink(clName=%s,lr=%s,lr=%s)\n",clName,lr,lt);
  Definition *compound;
  //PageDef *pageDef=0;
  QCString anchor,linkText=linkToText(SrcLangExt_Unknown,lt,FALSE);
  //printf("generateLink linkText=%s\n",linkText.data());
  if (resolveLink(clName,lr,inSeeBlock,&compound,anchor))
  {
    if (compound) // link to compound
    {
      if (lt==0 && anchor.isEmpty() &&                      /* compound link */
          compound->definitionType()==Definition::TypeGroup /* is group */ 
         )
      {
        linkText=((GroupDef *)compound)->groupTitle(); // use group's title as link
      }
      else if (compound->definitionType()==Definition::TypeFile)
      {
        linkText=linkToText(compound->getLanguage(),lt,TRUE); 
      }
      od.writeObjectLink(compound->getReference(),
          compound->getOutputFileBase(),anchor,linkText);
      if (!compound->isReference())
      {
        writePageRef(od,compound->getOutputFileBase(),anchor);
      }
    }
    else
    {
      err("%s:%d: Internal error: resolveLink successful but no compound found!",__FILE__,__LINE__);
    }
    return TRUE;
  }
  else // link could not be found
  {
    od.docify(linkText);
    return FALSE;
  }
}

void generateFileRef(OutputDocInterface &od,const char *name,const char *text)
{
  //printf("generateFileRef(%s,%s)\n",name,text);
  QCString linkText = text ? text : name;
  //FileInfo *fi;
  FileDef *fd;
  bool ambig;
  if ((fd=findFileDef(Doxygen::inputNameDict,name,ambig)) && 
      fd->isLinkable()) 
    // link to documented input file
    od.writeObjectLink(fd->getReference(),fd->getOutputFileBase(),0,linkText);
  else
    od.docify(linkText); 
}

//----------------------------------------------------------------------

#if 0
QCString substituteClassNames(const QCString &s)
{
  int i=0,l,p;
  QCString result;
  if (s.isEmpty()) return result;
  QRegExp r("[a-z_A-Z][a-z_A-Z0-9]*");
  while ((p=r.match(s,i,&l))!=-1)
  {
    QCString *subst;
    if (p>i) result+=s.mid(i,p-i);
    if ((subst=substituteDict[s.mid(p,l)]))
    {
      result+=*subst;
    }
    else
    {
      result+=s.mid(p,l);
    }
    i=p+l;
  }
  result+=s.mid(i,s.length()-i);
  return result;
}
#endif

//----------------------------------------------------------------------

struct FindFileCacheElem
{
  FindFileCacheElem(FileDef *fd,bool ambig) : fileDef(fd), isAmbig(ambig) {}
  FileDef *fileDef;
  bool isAmbig;
};

static QCache<FindFileCacheElem> g_findFileDefCache(5000);

FileDef *findFileDef(const FileNameDict *fnDict,const char *n,bool &ambig)
{
  ambig=FALSE;
  if (n==0) return 0;

  QCString key;
  key.sprintf("%p:",fnDict);
  key+=n;

  g_findFileDefCache.setAutoDelete(TRUE);
  FindFileCacheElem *cachedResult = g_findFileDefCache.find(key);
  //printf("key=%s cachedResult=%p\n",key.data(),cachedResult);
  if (cachedResult)
  {
    ambig = cachedResult->isAmbig;
    //printf("cached: fileDef=%p\n",cachedResult->fileDef);
    return cachedResult->fileDef;
  }
  else
  {
    cachedResult = new FindFileCacheElem(0,FALSE);
  }

  QCString name=convertToQCString(QDir::cleanDirPath(n));
  QCString path;
  int slashPos;
  FileName *fn;
  if (name.isEmpty()) goto exit;
  slashPos=QMAX(name.findRev('/'),name.findRev('\\'));
  if (slashPos!=-1)
  {
    path=name.left(slashPos+1);
    name=name.right(name.length()-slashPos-1); 
    //printf("path=%s name=%s\n",path.data(),name.data());
  }
  if (name.isEmpty()) goto exit;
  if ((fn=(*fnDict)[name]))
  {
    //printf("fn->count()=%d\n",fn->count());
    if (fn->count()==1)
    {
      FileDef *fd = fn->getFirst();
#if defined(_WIN32) || defined(__MACOSX__) // Windows or MacOSX
      bool isSamePath = fd->getPath().right(path.length()).lower()==path.lower();
#else // Unix
      bool isSamePath = fd->getPath().right(path.length())==path;
#endif
      if (path.isEmpty() || isSamePath)
      {
        cachedResult->fileDef = fd;
        g_findFileDefCache.insert(key,cachedResult);
        //printf("=1 ===> add to cache %p\n",fd);
        return fd;
      }
    }
    else // file name alone is ambiguous
    {
      int count=0;
      FileNameIterator fni(*fn);
      FileDef *fd;
      FileDef *lastMatch=0;
      QCString pathStripped = stripFromIncludePath(path);
      for (fni.toFirst();(fd=fni.current());++fni)
      {
        QCString fdStripPath = stripFromIncludePath(fd->getPath());
        if (path.isEmpty() || fdStripPath.right(pathStripped.length())==pathStripped) 
        { 
          count++; 
          lastMatch=fd; 
        }
      }
      //printf(">1 ===> add to cache %p\n",fd);

      ambig=(count>1);
      cachedResult->isAmbig = ambig;
      cachedResult->fileDef = lastMatch;
      g_findFileDefCache.insert(key,cachedResult);
      return lastMatch;
    }
  }
  else
  {
    //printf("not found!\n");
  }
exit:
  //printf("0  ===> add to cache %p: %s\n",cachedResult,n);
  g_findFileDefCache.insert(key,cachedResult);
  //delete cachedResult;
  return 0;
}

//----------------------------------------------------------------------

QCString showFileDefMatches(const FileNameDict *fnDict,const char *n)
{
  QCString result;
  QCString name=n;
  QCString path;
  int slashPos=QMAX(name.findRev('/'),name.findRev('\\'));
  if (slashPos!=-1)
  {
    path=name.left(slashPos+1);
    name=name.right(name.length()-slashPos-1); 
  }
  FileName *fn;
  if ((fn=(*fnDict)[name]))
  {
    FileNameIterator fni(*fn);
    FileDef *fd;
    for (fni.toFirst();(fd=fni.current());++fni)
    {
      if (path.isEmpty() || fd->getPath().right(path.length())==path)
      {
        result+="   "+fd->absFilePath()+"\n";
      }
    }
  }
  return result;
}

//----------------------------------------------------------------------

QCString substituteKeywords(const QCString &s,const char *title,
         const char *projName,const char *projNum,const char *projBrief)
{
  QCString result = s;
  if (title) result = substitute(result,"$title",title);
  result = substitute(result,"$datetime",dateToString(TRUE));
  result = substitute(result,"$date",dateToString(FALSE));
  result = substitute(result,"$year",yearToString());
  result = substitute(result,"$doxygenversion",versionString);
  result = substitute(result,"$projectname",projName);
  result = substitute(result,"$projectnumber",projNum);
  result = substitute(result,"$projectbrief",projBrief);
  result = substitute(result,"$projectlogo",stripPath(Config_getString("PROJECT_LOGO")));
  return result;
}

//----------------------------------------------------------------------

/*! Returns the character index within \a name of the first prefix
 *  in Config_getList("IGNORE_PREFIX") that matches \a name at the left hand side,
 *  or zero if no match was found
 */ 
int getPrefixIndex(const QCString &name)
{
  if (name.isEmpty()) return 0;
  static QStrList &sl = Config_getList("IGNORE_PREFIX");
  char *s = sl.first();
  while (s)
  {
    const char *ps=s;
    const char *pd=name.data();
    int i=0;
    while (*ps!=0 && *pd!=0 && *ps==*pd) ps++,pd++,i++;
    if (*ps==0 && *pd!=0)
    {
      return i;
    }
    s = sl.next();
  }
  return 0;
}

//----------------------------------------------------------------------------

static void initBaseClassHierarchy(BaseClassList *bcl)
{
  if (bcl==0) return;
  BaseClassListIterator bcli(*bcl);
  for ( ; bcli.current(); ++bcli)
  {
    ClassDef *cd=bcli.current()->classDef;
    if (cd->baseClasses()==0) // no base classes => new root
    {
      initBaseClassHierarchy(cd->baseClasses());
    }
    cd->visited=FALSE;
  }
}

//----------------------------------------------------------------------------

void initClassHierarchy(ClassSDict *cl)
{
  ClassSDict::Iterator cli(*cl);
  ClassDef *cd;
  for ( ; (cd=cli.current()); ++cli)
  {
    cd->visited=FALSE;
    initBaseClassHierarchy(cd->baseClasses());
  }
}

//----------------------------------------------------------------------------

bool hasVisibleRoot(BaseClassList *bcl)
{
  if (bcl)
  {
    BaseClassListIterator bcli(*bcl);
    for ( ; bcli.current(); ++bcli)
    {
      ClassDef *cd=bcli.current()->classDef;
      if (cd->isVisibleInHierarchy()) return TRUE;
      hasVisibleRoot(cd->baseClasses());
    }
  }
  return FALSE;
}

//----------------------------------------------------------------------

// note that this function is not reentrant due to the use of static growBuf!
QCString escapeCharsInString(const char *name,bool allowDots,bool allowUnderscore)
{
  static bool caseSenseNames = Config_getBool("CASE_SENSE_NAMES");
  static GrowBuf growBuf;
  growBuf.clear();
  char c;
  const char *p=name;
  while ((c=*p++)!=0)
  {
    switch(c)
    {
      case '_': if (allowUnderscore) growBuf.addChar('_'); else growBuf.addStr("__"); break;
      case '-': growBuf.addChar('-');  break;
      case ':': growBuf.addStr("_1"); break;
      case '/': growBuf.addStr("_2"); break;
      case '<': growBuf.addStr("_3"); break;
      case '>': growBuf.addStr("_4"); break;
      case '*': growBuf.addStr("_5"); break;
      case '&': growBuf.addStr("_6"); break;
      case '|': growBuf.addStr("_7"); break;
      case '.': if (allowDots) growBuf.addChar('.'); else growBuf.addStr("_8"); break;
      case '!': growBuf.addStr("_9"); break;
      case ',': growBuf.addStr("_00"); break;
      case ' ': growBuf.addStr("_01"); break;
      case '{': growBuf.addStr("_02"); break;
      case '}': growBuf.addStr("_03"); break;
      case '?': growBuf.addStr("_04"); break;
      case '^': growBuf.addStr("_05"); break;
      case '%': growBuf.addStr("_06"); break;
      case '(': growBuf.addStr("_07"); break;
      case ')': growBuf.addStr("_08"); break;
      case '+': growBuf.addStr("_09"); break;
      case '=': growBuf.addStr("_0A"); break;
      case '$': growBuf.addStr("_0B"); break;
      default: 
                if (c<0)
                {
                  static char map[] = "0123456789ABCDEF";
                  char ids[5];
                  unsigned char id = (unsigned char)c;
                  ids[0]='_';
                  ids[1]='x';
                  ids[2]=map[id>>4];
                  ids[3]=map[id&0xF];
                  ids[4]=0;
                  growBuf.addStr(ids);
                }
                else if (caseSenseNames || !isupper(c))
                {
                  growBuf.addChar(c);
                }
                else
                {
                  growBuf.addChar('_');
                  growBuf.addChar(tolower(c)); 
                }
                break;
    }
  }
  growBuf.addChar(0);
  return growBuf.get();
}

/*! This function determines the file name on disk of an item
 *  given its name, which could be a class name with template 
 *  arguments, so special characters need to be escaped.
 */
QCString convertNameToFile(const char *name,bool allowDots,bool allowUnderscore)
{
  static bool shortNames = Config_getBool("SHORT_NAMES");
  static bool createSubdirs = Config_getBool("CREATE_SUBDIRS");
  QCString result;
  if (shortNames) // use short names only
  {
    static QDict<int> usedNames(10007);
    usedNames.setAutoDelete(TRUE);
    static int count=1;

    int *value=usedNames.find(name);
    int num;
    if (value==0)
    {
      usedNames.insert(name,new int(count));
      num = count++;
    }
    else
    {
      num = *value;
    }
    result.sprintf("a%05d",num); 
  }
  else // long names
  {
    result=escapeCharsInString(name,allowDots,allowUnderscore);
    int resultLen = result.length();
    if (resultLen>=128) // prevent names that cannot be created!
    {
      // third algorithm based on MD5 hash
      uchar md5_sig[16];
      QCString sigStr(33);
      MD5Buffer((const unsigned char *)result.data(),resultLen,md5_sig);
      MD5SigToString(md5_sig,sigStr.data(),33);
      result=result.left(128-32)+sigStr; 
    }
  }
  if (createSubdirs)
  {
    int l1Dir=0,l2Dir=0;

#if MAP_ALGO==ALGO_COUNT 
    // old algorithm, has the problem that after regeneration the
    // output can be located in a different dir.
    if (Doxygen::htmlDirMap==0) 
    {
      Doxygen::htmlDirMap=new QDict<int>(100003);
      Doxygen::htmlDirMap->setAutoDelete(TRUE);
    }
    static int curDirNum=0;
    int *dirNum = Doxygen::htmlDirMap->find(result);
    if (dirNum==0) // new name
    {
      Doxygen::htmlDirMap->insert(result,new int(curDirNum)); 
      l1Dir = (curDirNum)&0xf;    // bits 0-3
      l2Dir = (curDirNum>>4)&0xff; // bits 4-11
      curDirNum++;
    }
    else // existing name
    {
      l1Dir = (*dirNum)&0xf;       // bits 0-3
      l2Dir = ((*dirNum)>>4)&0xff; // bits 4-11
    }
#elif MAP_ALGO==ALGO_CRC16
    // second algorithm based on CRC-16 checksum
    int dirNum = qChecksum(result,result.length());
    l1Dir = dirNum&0xf;
    l2Dir = (dirNum>>4)&0xff;
#elif MAP_ALGO==ALGO_MD5
    // third algorithm based on MD5 hash
    uchar md5_sig[16];
    MD5Buffer((const unsigned char *)result.data(),result.length(),md5_sig);
    l1Dir = md5_sig[14]&0xf;
    l2Dir = md5_sig[15];
#endif
    result.prepend(QCString().sprintf("d%x/d%02x/",l1Dir,l2Dir));
  }
  //printf("*** convertNameToFile(%s)->%s\n",name,result.data());
  return result;
}

QCString relativePathToRoot(const char *name)
{
  QCString result;
  if (Config_getBool("CREATE_SUBDIRS"))
  {
    if (name==0)
    {
      return REL_PATH_TO_ROOT;
    }
    else
    {
      QCString n = name;
      int i = n.findRev('/');
      if (i!=-1)
      {
        result=REL_PATH_TO_ROOT;
      }
    }
  }
  return result;
}

void createSubDirs(QDir &d)
{
  if (Config_getBool("CREATE_SUBDIRS"))
  {
    // create 4096 subdirectories
    int l1,l2;
    for (l1=0;l1<16;l1++)
    {
      d.mkdir(QString().sprintf("d%x",l1));
      for (l2=0;l2<256;l2++)
      {
        d.mkdir(QString().sprintf("d%x/d%02x",l1,l2));
      }
    }
  }
}

/*! Input is a scopeName, output is the scopename split into a
 *  namespace part (as large as possible) and a classname part.
 */
void extractNamespaceName(const QCString &scopeName,
    QCString &className,QCString &namespaceName,
    bool allowEmptyClass)
{
  int i,p;
  QCString clName=scopeName;
  NamespaceDef *nd = 0;
  if (!clName.isEmpty() && (nd=getResolvedNamespace(clName)) && getClass(clName)==0)
  { // the whole name is a namespace (and not a class)
    namespaceName=nd->name().copy();
    className.resize(0);
    goto done;
  }
  p=clName.length()-2;
  while (p>=0 && (i=clName.findRev("::",p))!=-1) 
    // see if the first part is a namespace (and not a class)
  {
    //printf("Trying %s\n",clName.left(i).data());
    if (i>0 && (nd=getResolvedNamespace(clName.left(i))) && getClass(clName.left(i))==0)
    {
      //printf("found!\n");
      namespaceName=nd->name().copy();
      className=clName.right(clName.length()-i-2);
      goto done;
    } 
    p=i-2; // try a smaller piece of the scope
  }
  //printf("not found!\n");

  // not found, so we just have to guess.
  className=scopeName.copy();
  namespaceName.resize(0);

done:
  if (className.isEmpty() && !namespaceName.isEmpty() && !allowEmptyClass)
  {
    // class and namespace with the same name, correct to return the class.
    className=namespaceName.copy();
    namespaceName.resize(0);
  }
  //printf("extractNamespace `%s' => `%s|%s'\n",scopeName.data(),
  //       className.data(),namespaceName.data());
  return;
}

QCString insertTemplateSpecifierInScope(const QCString &scope,const QCString &templ)
{
  QCString result=scope.copy();
  if (!templ.isEmpty() && scope.find('<')==-1)
  {
    int si,pi=0;
    ClassDef *cd=0;
    while (
        (si=scope.find("::",pi))!=-1 && !getClass(scope.left(si)+templ) && 
        ((cd=getClass(scope.left(si)))==0 || cd->templateArguments()==0) 
        ) 
    { 
      //printf("Tried `%s'\n",(scope.left(si)+templ).data()); 
      pi=si+2; 
    }
    if (si==-1) // not nested => append template specifier
    {
      result+=templ; 
    }
    else // nested => insert template specifier before after first class name
    {
      result=scope.left(si) + templ + scope.right(scope.length()-si);
    }
  }
  //printf("insertTemplateSpecifierInScope(`%s',`%s')=%s\n",
  //    scope.data(),templ.data(),result.data());
  return result;
}

#if 0 // original version
/*! Strips the scope from a name. Examples: A::B will return A
 *  and A<T>::B<N::C<D> > will return A<T>.
 */
QCString stripScope(const char *name)
{
  QCString result = name;
  int l=result.length();
  int p=l-1;
  bool done;
  int count;

  while (p>=0)
  {
    char c=result.at(p);
    switch (c)
    {
      case ':': 
        //printf("stripScope(%s)=%s\n",name,result.right(l-p-1).data());
        return result.right(l-p-1);
      case '>':
        count=1;
        done=FALSE;
        //printf("pos < = %d\n",p);
        p--;
        while (p>=0 && !done)
        {
          c=result.at(p--);
          switch (c)
          {
            case '>': count++; break;
            case '<': count--; if (count<=0) done=TRUE; break;
            default: 
                      //printf("c=%c count=%d\n",c,count);
                      break;
          }
        }
        //printf("pos > = %d\n",p+1);
        break;
      default:
        p--;
    }
  }
  //printf("stripScope(%s)=%s\n",name,name);
  return name;
}
#endif

// new version by Davide Cesari which also works for Fortran
QCString stripScope(const char *name)
{
  QCString result = name;
  int l=result.length();
  int p;
  bool done = FALSE;
  bool skipBracket=FALSE; // if brackets do not match properly, ignore them altogether
  int count=0;

  do
  {
    p=l-1; // start at the end of the string
    while (p>=0 && count>=0)
    {
      char c=result.at(p);
      switch (c)
      {
        case ':': 
          // only exit in the case of ::
          //printf("stripScope(%s)=%s\n",name,result.right(l-p-1).data());
          if (p>0 && result.at(p-1)==':') return result.right(l-p-1);
          p--;
          break;
        case '>':
          if (skipBracket) // we don't care about brackets
          {
            p--;
          }
          else // count open/close brackets
          {
            if (p>0 && result.at(p-1)=='>') // skip >> operator
            {
              p-=2;
              break;
            }
            count=1;
            //printf("pos < = %d\n",p);
            p--;
            bool foundMatch=false;
            while (p>=0 && !foundMatch)
            {
              c=result.at(p--);
              switch (c)
              {
                case '>': 
                  count++; 
                  break;
                case '<': 
                  if (p>0)
                  {
                    if (result.at(p-1) == '<') // skip << operator
                    {
                      p--;
                      break;
                    }
                  }
                  count--; 
                  foundMatch = count==0;
                  break;
                default: 
                  //printf("c=%c count=%d\n",c,count);
                  break;
              }
            }
          }
          //printf("pos > = %d\n",p+1);
          break;
        default:
          p--;
      }
    }
    done = count==0 || skipBracket; // reparse if brackets do not match
    skipBracket=TRUE;
  }
  while (!done); // if < > unbalanced repeat ignoring them
  //printf("stripScope(%s)=%s\n",name,name);
  return name;
}


/*! Converts a string to an XML-encoded string */
QCString convertToXML(const char *s)
{
  static GrowBuf growBuf;
  growBuf.clear();
  if (s==0) return "";
  const char *p=s;
  char c;
  while ((c=*p++))
  {
    switch (c)
    {
      case '<':  growBuf.addStr("&lt;");   break;
      case '>':  growBuf.addStr("&gt;");   break;
      case '&':  growBuf.addStr("&amp;");  break;
      case '\'': growBuf.addStr("&apos;"); break; 
      case '"':  growBuf.addStr("&quot;"); break;
      default:   growBuf.addChar(c);       break;
    }
  }
  growBuf.addChar(0);
  return growBuf.get();
}

/*! Converts a string to a HTML-encoded string */
QCString convertToHtml(const char *s,bool keepEntities)
{
  static GrowBuf growBuf;
  growBuf.clear();
  if (s==0) return "";
  const char *p=s;
  char c;
  while ((c=*p++))
  {
    switch (c)
    {
      case '<':  growBuf.addStr("&lt;");   break;
      case '>':  growBuf.addStr("&gt;");   break;
      case '&':  if (keepEntities)
                 {
                   const char *e=p;
                   char ce;
                   while ((ce=*e++))
                   {
                     if (ce==';' || (!(isId(ce) || ce=='#'))) break;
                   }
                   if (ce==';') // found end of an entity
                   {
                     // copy entry verbatim
                     growBuf.addChar(c);
                     while (p<e) growBuf.addChar(*p++);
                   }
                   else
                   {
                     growBuf.addStr("&amp;");
                   }
                 }
                 else
                 {
                   growBuf.addStr("&amp;");  
                 }
                 break;
      case '\'': growBuf.addStr("&#39;");  break; 
      case '"':  growBuf.addStr("&quot;"); break;
      default:   growBuf.addChar(c);       break;
    }
  }
  growBuf.addChar(0);
  return growBuf.get();
}

QCString convertToJSString(const char *s)
{
  static GrowBuf growBuf;
  growBuf.clear();
  if (s==0) return "";
  const char *p=s;
  char c;
  while ((c=*p++))
  {
    switch (c)
    {
      case '"':  growBuf.addStr("\\\""); break;
      case '\\': growBuf.addStr("\\\\"); break;
      default:   growBuf.addChar(c);   break;
    }
  }
  growBuf.addChar(0);
  return growBuf.get();
}


QCString convertCharEntitiesToUTF8(const QCString &s)
{
  static QDict<char> entityMap(67);
  static bool init=TRUE;
  QCString result;
  static QRegExp entityPat("&[a-zA-Z]+;");

  if (init)
  {
    entityMap.insert("copy",  "\xC2\xA9");
    entityMap.insert("tm",    "\xE2\x84\xA2");
    entityMap.insert("trade", "\xE2\x84\xA2");
    entityMap.insert("reg",   "\xC2\xAE");
    entityMap.insert("lsquo", "\xE2\x80\x98");
    entityMap.insert("rsquo", "\xE2\x80\x99");
    entityMap.insert("ldquo", "\xE2\x80\x9C");
    entityMap.insert("rdquo", "\xE2\x80\x9D");
    entityMap.insert("ndash", "\xE2\x80\x93");
    entityMap.insert("mdash", "\xE2\x80\x94");
    entityMap.insert("Auml",  "\xC3\x84");
    entityMap.insert("Euml",  "\xC3\x8B");
    entityMap.insert("Iuml",  "\xC3\x8F");
    entityMap.insert("Ouml",  "\xC3\x96");
    entityMap.insert("Uuml",  "\xC3\x9C");
    entityMap.insert("Yuml",  "\xC5\xB8");
    entityMap.insert("auml",  "\xC3\xA4");
    entityMap.insert("euml",  "\xC3\xAB");
    entityMap.insert("iuml",  "\xC3\xAF");
    entityMap.insert("ouml",  "\xC3\xB6");
    entityMap.insert("uuml",  "\xC3\xBC");
    entityMap.insert("yuml",  "\xC3\xBF");
    entityMap.insert("Aacute","\xC3\x81");
    entityMap.insert("Eacute","\xC3\x89");
    entityMap.insert("Iacute","\xC3\x8D");
    entityMap.insert("Oacute","\xC3\x93");
    entityMap.insert("Uacute","\xC3\x9A");
    entityMap.insert("aacute","\xC3\xA1");
    entityMap.insert("eacute","\xC3\xA9");
    entityMap.insert("iacute","\xC3\xAD");
    entityMap.insert("oacute","\xC3\xB3");
    entityMap.insert("uacute","\xC3\xBA");
    entityMap.insert("Agrave","\xC3\x80");
    entityMap.insert("Egrave","\xC3\x88");
    entityMap.insert("Igrave","\xC3\x8C");
    entityMap.insert("Ograve","\xC3\x92");
    entityMap.insert("Ugrave","\xC3\x99");
    entityMap.insert("agrave","\xC3\xA0");
    entityMap.insert("egrave","\xC3\xA8");
    entityMap.insert("igrave","\xC3\xAC");
    entityMap.insert("ograve","\xC3\xB2");
    entityMap.insert("ugrave","\xC3\xB9");
    entityMap.insert("Acirc", "\xC3\x82");
    entityMap.insert("Ecirc", "\xC3\x8A");
    entityMap.insert("Icirc", "\xC3\x8E");
    entityMap.insert("Ocirc", "\xC3\x94");
    entityMap.insert("Ucirc", "\xC3\x9B");
    entityMap.insert("acirc", "\xC3\xA2");
    entityMap.insert("ecirc", "\xC3\xAA");
    entityMap.insert("icirc", "\xC3\xAE");
    entityMap.insert("ocirc", "\xC3\xB4");
    entityMap.insert("ucirc", "\xC3\xBB");
    entityMap.insert("Atilde","\xC3\x83");
    entityMap.insert("Ntilde","\xC3\x91");
    entityMap.insert("Otilde","\xC3\x95");
    entityMap.insert("atilde","\xC3\xA3");
    entityMap.insert("ntilde","\xC3\xB1");
    entityMap.insert("otilde","\xC3\xB5");
    entityMap.insert("szlig", "\xC3\x9F");
    entityMap.insert("Ccedil","\xC3\x87");
    entityMap.insert("ccedil","\xC3\xA7");
    entityMap.insert("Aring", "\xC3\x85");
    entityMap.insert("aring", "\xC3\xA5");
    entityMap.insert("nbsp",  "\xC2\xA0");
    init=FALSE;
  }

  if (s==0) return result;
  int p,i=0,l;
  while ((p=entityPat.match(s,i,&l))!=-1)
  {
    if (p>i) result+=s.mid(i,p-i);
    QCString entity = s.mid(p+1,l-2);
    char *code = entityMap.find(entity);
    if (code)
    {
      result+=code;
    }
    else
    {
      result+=s.mid(p,l);
    }
    i=p+l;
  }
  result+=s.mid(i,s.length()-i);
  return result;
}

/*! Returns the standard string that is generated when the \\overload
 * command is used.
 */
QCString getOverloadDocs()
{
  return theTranslator->trOverloadText();
  //"This is an overloaded member function, "
  //       "provided for convenience. It differs from the above "
  //       "function only in what argument(s) it accepts.";
}

void addMembersToMemberGroup(MemberList *ml,
    MemberGroupSDict **ppMemberGroupSDict,
    Definition *context)
{
  ASSERT(context!=0);
  //printf("addMemberToMemberGroup()\n");
  if (ml==0) return;
  MemberListIterator mli(*ml);
  MemberDef *md;
  uint index;
  for (index=0;(md=mli.current());)
  {
    if (md->isEnumerate()) // insert enum value of this enum into groups
    {
      LockingPtr<MemberList> fmdl=md->enumFieldList();
      if (fmdl!=0)
      {
        MemberDef *fmd=fmdl->first();
        while (fmd)
        {
          int groupId=fmd->getMemberGroupId();
          if (groupId!=-1)
          {
            MemberGroupInfo *info = Doxygen::memGrpInfoDict[groupId];
            //QCString *pGrpHeader = Doxygen::memberHeaderDict[groupId];
            //QCString *pDocs      = Doxygen::memberDocDict[groupId];
            if (info)
            {
              if (*ppMemberGroupSDict==0)
              {
                *ppMemberGroupSDict = new MemberGroupSDict;
                (*ppMemberGroupSDict)->setAutoDelete(TRUE);
              }
              MemberGroup *mg = (*ppMemberGroupSDict)->find(groupId);
              if (mg==0)
              {
                mg = new MemberGroup(
                    context,
                    groupId,
                    info->header,
                    info->doc,
                    info->docFile
                    );
                (*ppMemberGroupSDict)->append(groupId,mg);
              }
              mg->insertMember(fmd); // insert in member group
              fmd->setMemberGroup(mg);
            }
          }
          fmd=fmdl->next();
        }
      }
    }
    int groupId=md->getMemberGroupId();
    if (groupId!=-1)
    {
      MemberGroupInfo *info = Doxygen::memGrpInfoDict[groupId];
      //QCString *pGrpHeader = Doxygen::memberHeaderDict[groupId];
      //QCString *pDocs      = Doxygen::memberDocDict[groupId];
      if (info)
      {
        if (*ppMemberGroupSDict==0)
        {
          *ppMemberGroupSDict = new MemberGroupSDict;
          (*ppMemberGroupSDict)->setAutoDelete(TRUE);
        }
        MemberGroup *mg = (*ppMemberGroupSDict)->find(groupId);
        if (mg==0)
        {
          mg = new MemberGroup(
              context,
              groupId,
              info->header,
              info->doc,
              info->docFile
              );
          (*ppMemberGroupSDict)->append(groupId,mg);
        }
        md = ml->take(index); // remove from member list
        mg->insertMember(md); // insert in member group
        mg->setRefItems(info->m_sli);
        md->setMemberGroup(mg);
        continue;
      }
    }
    ++mli;++index;
  }
}

/*! Extracts a (sub-)string from \a type starting at \a pos that
 *  could form a class. The index of the match is returned and the found
 *  class \a name and a template argument list \a templSpec. If -1 is returned
 *  there are no more matches.
 */
int extractClassNameFromType(const QCString &type,int &pos,QCString &name,QCString &templSpec,SrcLangExt lang)
{
  static const QRegExp re_norm("[a-z_A-Z\\x80-\\xFF][a-z_A-Z0-9:\\x80-\\xFF]*");
  static const QRegExp re_ftn("[a-z_A-Z\\x80-\\xFF][()=_a-z_A-Z0-9:\\x80-\\xFF]*");
  QRegExp re;

  if (lang == SrcLangExt_Fortran)
  {
    if (type.at(pos)==',') return -1;
    if (type.left(4).lower()=="type")
    {
      re = re_norm;
    }
    else
    {
      re = re_ftn;
    }
  }
  else
  {
    re = re_norm;
  }

  name.resize(0);
  templSpec.resize(0);
  int i,l;
  int typeLen=type.length();
  if (typeLen>0)
  {
    if ((i=re.match(type,pos,&l))!=-1) // for each class name in the type
    {
      int ts=i+l;
      int te=ts;
      int tl=0;
      while (type.at(ts)==' ' && ts<typeLen) ts++,tl++; // skip any whitespace
      if (type.at(ts)=='<') // assume template instance
      {
        // locate end of template
        te=ts+1;
        int brCount=1;
        while (te<typeLen && brCount!=0)
        {
          if (type.at(te)=='<') 
          {
            if (te<typeLen-1 && type.at(te+1)=='<') te++; else brCount++;
          }
          if (type.at(te)=='>') 
          {
            if (te<typeLen-1 && type.at(te+1)=='>') te++; else brCount--;
          }
          te++;
        }
      }
      name = type.mid(i,l);
      if (te>ts) 
      {
        templSpec = type.mid(ts,te-ts),tl+=te-ts;
        pos=i+l+tl;
      }
      else // no template part
      {
        pos=i+l;
      }
      //printf("extractClassNameFromType([in] type=%s,[out] pos=%d,[out] name=%s,[out] templ=%s)=TRUE\n",
      //    type.data(),pos,name.data(),templSpec.data());
      return i;
    }
  }
  pos = typeLen;
  //printf("extractClassNameFromType([in] type=%s,[out] pos=%d,[out] name=%s,[out] templ=%s)=FALSE\n",
  //       type.data(),pos,name.data(),templSpec.data());
  return -1;
}

/*! Substitutes any occurrence of a formal argument from argument list
 *  \a formalArgs in \a name by the corresponding actual argument in
 *  argument list \a actualArgs. The result after substitution
 *  is returned as a string. The argument \a name is used to
 *  prevent recursive substitution.
 */
QCString substituteTemplateArgumentsInString(
    const QCString &name,
    ArgumentList *formalArgs,
    ArgumentList *actualArgs)
{
  //printf("substituteTemplateArgumentsInString(name=%s formal=%s actualArg=%s)\n",
  //    name.data(),argListToString(formalArgs).data(),argListToString(actualArgs).data());
  if (formalArgs==0) return name;
  QCString result;
  static QRegExp re("[a-z_A-Z\\x80-\\xFF][a-z_A-Z0-9\\x80-\\xFF]*");
  int p=0,l,i;
  // for each identifier in the base class name (e.g. B<T> -> B and T)
  while ((i=re.match(name,p,&l))!=-1)
  {
    result += name.mid(p,i-p);
    QCString n = name.mid(i,l);
    ArgumentListIterator formAli(*formalArgs);
    Argument *formArg;
    Argument *actArg=actualArgs->first();

    // if n is a template argument, then we substitute it
    // for its template instance argument.
    bool found=FALSE;
    for (formAli.toFirst();
        (formArg=formAli.current()) && !found;
        ++formAli,actArg=actualArgs->next()
        )
    {
      if (formArg->type.left(6)=="class " && formArg->name.isEmpty())
      {
        formArg->name = formArg->type.mid(6);
        formArg->type = "class";
      }
      if (formArg->type.left(9)=="typename " && formArg->name.isEmpty())
      {
        formArg->name = formArg->type.mid(9);
        formArg->type = "typename";
      }
      if (formArg->type=="class" || formArg->type=="typename" || formArg->type.left(8)=="template")
      {
        //printf("n=%s formArg->type='%s' formArg->name='%s' formArg->defval='%s'\n",
        //  n.data(),formArg->type.data(),formArg->name.data(),formArg->defval.data());
        //printf(">> formArg->name='%s' actArg->type='%s' actArg->name='%s'\n",
        //    formArg->name.data(),actArg->type.data(),actArg->name.data()
        //    );
        if (formArg->name==n && actArg && !actArg->type.isEmpty()) // base class is a template argument
        {
          // replace formal argument with the actual argument of the instance
          if (!leftScopeMatch(actArg->type,n)) 
            // the scope guard is to prevent recursive lockup for 
            // template<class A> class C : public<A::T>, 
            // where A::T would become A::T::T here, 
            // since n==A and actArg->type==A::T
            // see bug595833 for an example
          {
            if (actArg->name.isEmpty())
            {
              result += actArg->type+" "; 
              found=TRUE;
            }
            else 
              // for case where the actual arg is something like "unsigned int"
              // the "int" part is in actArg->name.
            {
              result += actArg->type+" "+actArg->name+" "; 
              found=TRUE;
            }
          }
        }
        else if (formArg->name==n && 
                 actArg==0 && 
                 !formArg->defval.isEmpty() &&
                 formArg->defval!=name /* to prevent recursion */
            )
        {
          result += substituteTemplateArgumentsInString(formArg->defval,formalArgs,actualArgs)+" ";
          found=TRUE;
        }
      }
      else if (formArg->name==n && 
               actArg==0 && 
               !formArg->defval.isEmpty() &&
               formArg->defval!=name /* to prevent recursion */
              )
      {
        result += substituteTemplateArgumentsInString(formArg->defval,formalArgs,actualArgs)+" ";
        found=TRUE;
      }
    }
    if (!found) result += n;
    p=i+l;
  }
  result+=name.right(name.length()-p);
  //printf("      Inheritance relation %s -> %s\n",
  //    name.data(),result.data());
  return result.stripWhiteSpace();
}


/*! Makes a deep copy of argument list \a src. Will allocate memory, that
 *  is owned by the caller. 
 */
ArgumentList *copyArgumentList(const ArgumentList *src)
{
  ASSERT(src!=0);
  ArgumentList *dst = new ArgumentList;
  dst->setAutoDelete(TRUE);
  ArgumentListIterator tali(*src);
  Argument *a;
  for (;(a=tali.current());++tali)
  {
    dst->append(new Argument(*a));
  }
  dst->constSpecifier    = src->constSpecifier;
  dst->volatileSpecifier = src->volatileSpecifier;
  dst->pureSpecifier     = src->pureSpecifier;
  return dst;
}

/*! Makes a deep copy of the list of argument lists \a srcLists. 
 *  Will allocate memory, that is owned by the caller.
 */
QList<ArgumentList> *copyArgumentLists(const QList<ArgumentList> *srcLists)
{
  ASSERT(srcLists!=0);
  QList<ArgumentList> *dstLists = new QList<ArgumentList>;
  dstLists->setAutoDelete(TRUE);
  QListIterator<ArgumentList> sli(*srcLists);
  ArgumentList *sl;
  for (;(sl=sli.current());++sli)
  {
    dstLists->append(copyArgumentList(sl));
  }
  return dstLists;
}

/*! Strips template specifiers from scope \a fullName, except those 
 *  that make up specialized classes. The switch \a parentOnly 
 *  determines whether or not a template "at the end" of a scope 
 *  should be considered, e.g. with \a parentOnly is \c TRUE, A<T>::B<S> will 
 *  try to strip \<T\> and not \<S\>, while \a parentOnly is \c FALSE will 
 *  strip both unless A<T> or B<S> are specialized template classes. 
 */
QCString stripTemplateSpecifiersFromScope(const QCString &fullName,
    bool parentOnly,
    QCString *pLastScopeStripped)
{
  QCString result;
  int p=0;
  int l=fullName.length();
  int i=fullName.find('<');
  while (i!=-1)
  {
    //printf("1:result+=%s\n",fullName.mid(p,i-p).data());
    int e=i+1;
    bool done=FALSE;
    int count=1;
    while (e<l && !done)
    {
      char c=fullName.at(e++);
      if (c=='<') 
      {
        count++;
      }
      else if (c=='>') 
      {
        count--;
        done = count==0;
      }
    }
    int si= fullName.find("::",e);

    if (parentOnly && si==-1) break; 
    // we only do the parent scope, so we stop here if needed

    result+=fullName.mid(p,i-p);
    //printf("  trying %s\n",(result+fullName.mid(i,e-i)).data());
    if (getClass(result+fullName.mid(i,e-i))!=0)
    {
      result+=fullName.mid(i,e-i);
      //printf("  2:result+=%s\n",fullName.mid(i,e-i-1).data());
    }
    else if (pLastScopeStripped)
    {
      //printf("  last stripped scope '%s'\n",fullName.mid(i,e-i).data());
      *pLastScopeStripped=fullName.mid(i,e-i);
    }
    p=e;
    i=fullName.find('<',p);
  }
  result+=fullName.right(l-p);
  //printf("3:result+=%s\n",fullName.right(l-p).data());
  return result;
}

/*! Merges two scope parts together. The parts may (partially) overlap.
 *  Example1: \c A::B and \c B::C will result in \c A::B::C <br>
 *  Example2: \c A and \c B will be \c A::B <br>
 *  Example3: \c A::B and B will be \c A::B
 *  
 *  @param leftScope the left hand part of the scope.
 *  @param rightScope the right hand part of the scope.
 *  @returns the merged scope. 
 */
QCString mergeScopes(const QCString &leftScope,const QCString &rightScope)
{
  // case leftScope=="A" rightScope=="A::B" => result = "A::B"
  if (leftScopeMatch(rightScope,leftScope)) return rightScope;
  QCString result;
  int i=0,p=leftScope.length();

  // case leftScope=="A::B" rightScope=="B::C" => result = "A::B::C"
  // case leftScope=="A::B" rightScope=="B" => result = "A::B"
  bool found=FALSE;
  while ((i=leftScope.findRev("::",p))!=-1)
  {
    if (leftScopeMatch(rightScope,leftScope.right(leftScope.length()-i-2)))
    {
      result = leftScope.left(i+2)+rightScope;
      found=TRUE;
    }
    p=i-1;
  }
  if (found) return result;

  // case leftScope=="A" rightScope=="B" => result = "A::B"
  result=leftScope.copy();
  if (!result.isEmpty() && !rightScope.isEmpty()) result+="::";
  result+=rightScope;
  return result;
}

/*! Returns a fragment from scope \a s, starting at position \a p.
 *
 *  @param s the scope name as a string.
 *  @param p the start position (0 is the first).
 *  @param l the resulting length of the fragment.
 *  @returns the location of the fragment, or -1 if non is found.
 */
int getScopeFragment(const QCString &s,int p,int *l)
{
  int sl=s.length();
  int sp=p;
  int count=0;
  bool done;
  if (sp>=sl) return -1;
  while (sp<sl)
  {
    char c=s.at(sp);
    if (c==':') sp++,p++; else break;
  }
  while (sp<sl)
  {
    char c=s.at(sp);
    switch (c)
    {
      case ':': // found next part
        goto found;
      case '<': // skip template specifier
        count=1;sp++;
        done=FALSE;
        while (sp<sl && !done)
        {
          // TODO: deal with << and >> operators!
          char c=s.at(sp++);
          switch(c)
          {
            case '<': count++; break;
            case '>': count--; if (count==0) done=TRUE; break;
            default: break;
          }
        }
        break;
      default:
        sp++;
        break;
    }
  }
found:
  *l=sp-p;
  //printf("getScopeFragment(%s,%d)=%s\n",s.data(),p,s.mid(p,*l).data());
  return p;
}

//----------------------------------------------------------------------------

PageDef *addRelatedPage(const char *name,const QCString &ptitle,
    const QCString &doc,
    QList<SectionInfo> * /*anchors*/,
    const char *fileName,int startLine,
    const QList<ListItemInfo> *sli,
    GroupDef *gd,
    TagInfo *tagInfo,
    SrcLangExt lang
    )
{
  PageDef *pd=0;
  //printf("addRelatedPage(name=%s gd=%p)\n",name,gd);
  if ((pd=Doxygen::pageSDict->find(name)) && !tagInfo)
  {
    // append documentation block to the page.
    pd->setDocumentation(doc,fileName,startLine);
    //printf("Adding page docs `%s' pi=%p name=%s\n",doc.data(),pi,name);
  }
  else // new page
  {
    QCString baseName=name;
    if (baseName.right(4)==".tex") 
      baseName=baseName.left(baseName.length()-4);
    else if (baseName.right(Doxygen::htmlFileExtension.length())==Doxygen::htmlFileExtension)
      baseName=baseName.left(baseName.length()-Doxygen::htmlFileExtension.length());

    QCString title=ptitle.stripWhiteSpace();
    pd=new PageDef(fileName,startLine,baseName,doc,title);

    pd->setRefItems(sli);
    pd->setLanguage(lang);

    if (tagInfo)
    {
      pd->setReference(tagInfo->tagName);
    }

    pd->setFileName(convertNameToFile(pd->name(),FALSE,TRUE));

    //printf("Appending page `%s'\n",baseName.data());
    Doxygen::pageSDict->append(baseName,pd);

    if (gd) gd->addPage(pd);

    if (!pd->title().isEmpty())
    {
      //outputList->writeTitle(pi->name,pi->title);

      // a page name is a label as well!
      QCString file;
      if (gd)
      {
        file=gd->getOutputFileBase();
      }
      else 
      {
        file=pd->getOutputFileBase();
      }
      SectionInfo *si=new SectionInfo(
          file,pd->name(),pd->title(),SectionInfo::Page,0,pd->getReference());
      //printf("si->label=`%s' si->definition=%s si->fileName=`%s'\n",
      //      si->label.data(),si->definition?si->definition->name().data():"<none>",
      //      si->fileName.data());
      //printf("  SectionInfo: sec=%p sec->fileName=%s\n",si,si->fileName.data());
      //printf("Adding section key=%s si->fileName=%s\n",pageName.data(),si->fileName.data());
      Doxygen::sectionDict.append(pd->name(),si);
    }
  }
  return pd;
}

//----------------------------------------------------------------------------

void addRefItem(const QList<ListItemInfo> *sli,
    const char *key, 
    const char *prefix, const char *name,const char *title,const char *args)
{
  //printf("addRefItem(sli=%p,key=%s,prefix=%s,name=%s,title=%s,args=%s)\n",sli,key,prefix,name,title,args);
  if (sli)
  {
    QListIterator<ListItemInfo> slii(*sli);
    ListItemInfo *lii;
    for (slii.toFirst();(lii=slii.current());++slii)
    {
      RefList *refList = Doxygen::xrefLists->find(lii->type);
      if (refList
          &&
          (
           // either not a built-in list or the list is enabled
           (lii->type!="todo"       || Config_getBool("GENERATE_TODOLIST")) &&
           (lii->type!="test"       || Config_getBool("GENERATE_TESTLIST")) &&
           (lii->type!="bug"        || Config_getBool("GENERATE_BUGLIST"))  &&
           (lii->type!="deprecated" || Config_getBool("GENERATE_DEPRECATEDLIST"))
          )
         )
      {
        RefItem *item = refList->getRefItem(lii->itemId);
        ASSERT(item!=0);

        item->prefix = prefix;
        item->name   = name;
        item->title  = title;
        item->args   = args;

        refList->insertIntoList(key,item);

      }
    }
  }
}

void addGroupListToTitle(OutputList &ol,Definition *d)
{
  LockingPtr<GroupList> groups = d->partOfGroups();
  if (groups!=0) // write list of group to which this definition belongs
  {
    ol.pushGeneratorState();
    ol.disableAllBut(OutputGenerator::Html);
    ol.writeString("<div class=\"ingroups\">");
    GroupListIterator gli(*groups);
    GroupDef *gd;
    bool first=TRUE;
    for (gli.toFirst();(gd=gli.current());++gli)
    {
      if (!first) { ol.writeString(" &#124; "); } else first=FALSE; 
      ol.writeObjectLink(gd->getReference(),
          gd->getOutputFileBase(),0,gd->groupTitle());
    }
    ol.writeString("</div>");
    ol.popGeneratorState();
  }
}

void filterLatexString(FTextStream &t,const char *str,
    bool insideTabbing,bool insidePre,bool insideItem)
{
  if (str==0) return;
  //printf("filterLatexString(%s)\n",str);
  //if (strlen(str)<2) stackTrace();
  const unsigned char *p=(const unsigned char *)str;
  unsigned char c;
  unsigned char pc='\0';
  while (*p)
  {
    c=*p++;

    if (insidePre)
    {
      switch(c)
      {
        case '\\': t << "\\(\\backslash\\)"; break;
        case '{':  t << "\\{"; break;
        case '}':  t << "\\}"; break;
        case '_':  t << "\\_"; break;
        default: 
                   t << (char)c;
      }
    }
    else
    {
      switch(c)
      {
        case '#':  t << "\\#";           break;
        case '$':  t << "\\$";           break;
        case '%':  t << "\\%";           break;
        case '^':  t << "$^\\wedge$";    break;
        case '&':  t << "\\&";           break;
        case '*':  t << "$\\ast$";       break;
        case '_':  if (!insideTabbing) t << "\\-";  
                   t << "\\_"; 
                   if (!insideTabbing) t << "\\-";  
                   break;
        case '{':  t << "\\{";           break;
        case '}':  t << "\\}";           break;
        case '<':  t << "$<$";           break;
        case '>':  t << "$>$";           break;
        case '|':  t << "$|$";           break;
        case '~':  t << "$\\sim$";       break;
        case '[':  if (Config_getBool("PDF_HYPERLINKS") || insideItem) 
                     t << "\\mbox{[}"; 
                   else
                     t << "[";
                   break;
        case ']':  if (pc=='[') t << "$\\,$";
                     if (Config_getBool("PDF_HYPERLINKS") || insideItem)
                       t << "\\mbox{]}";
                     else
                       t << "]";             
                   break;
        case '-':  t << "-\\/";
                   break;
        case '\\': if (*p=='<') 
                   { t << "$<$"; p++; }
                   else if (*p=='>')
                   { t << "$>$"; p++; } 
                   else  
                   { t << "$\\backslash$"; }
                   break;           
        case '"':  { t << "\\char`\\\"{}"; }
                   break;

        default:   
                   //if (!insideTabbing && forceBreaks && c!=' ' && *p!=' ')
                   if (!insideTabbing && 
                       ((c>='A' && c<='Z' && pc!=' ' && pc!='\0') || (c==':' && pc!=':') || (pc=='.' && isId(c)))
                      )
                   {
                     t << "\\-";
                   }
                   t << (char)c;
      }
    }
    pc = c;
  }
}


QCString rtfFormatBmkStr(const char *name)
{
  static QCString g_nextTag( "AAAAAAAAAA" );
  static QDict<QCString> g_tagDict( 5003 );

  g_tagDict.setAutoDelete(TRUE);

  // To overcome the 40-character tag limitation, we
  // substitute a short arbitrary string for the name
  // supplied, and keep track of the correspondence
  // between names and strings.
  QCString key( name );
  QCString* tag = g_tagDict.find( key );
  if ( !tag )
  {
    // This particular name has not yet been added
    // to the list. Add it, associating it with the
    // next tag value, and increment the next tag.
    tag = new QCString( g_nextTag.copy() ); // Make sure to use a deep copy!
    g_tagDict.insert( key, tag );

    // This is the increment part
    char* nxtTag = g_nextTag.data() + g_nextTag.length() - 1;
    for ( unsigned int i = 0; i < g_nextTag.length(); ++i, --nxtTag )
    {
      if ( ( ++(*nxtTag) ) > 'Z' )
      {
        *nxtTag = 'A';
      }
      else
      {
        // Since there was no carry, we can stop now
        break;
      }
    }
  }

  return *tag;
}

QCString stripExtension(const char *fName)
{
  QCString result=fName;
  if (result.right(Doxygen::htmlFileExtension.length())==Doxygen::htmlFileExtension)
  {
    result=result.left(result.length()-Doxygen::htmlFileExtension.length());
  }
  return result;
}


void replaceNamespaceAliases(QCString &scope,int i)
{
  while (i>0)
  {
    QCString ns = scope.left(i);
    QCString *s = Doxygen::namespaceAliasDict[ns];
    if (s)
    {
      scope=*s+scope.right(scope.length()-i);
      i=s->length();
    }
    if (i>0 && ns==scope.left(i)) break;
  }
}

QCString stripPath(const char *s)
{
  QCString result=s;
  int i=result.findRev('/');
  if (i!=-1)
  {
    result=result.mid(i+1);
  }
  return result;
}

/** returns \c TRUE iff string \a s contains word \a w */
bool containsWord(const QCString &s,const QCString &word)
{
  static QRegExp wordExp("[a-z_A-Z\\x80-\\xFF]+");
  int p=0,i,l;
  while ((i=wordExp.match(s,p,&l))!=-1)
  {
    if (s.mid(i,l)==word) return TRUE;
    p=i+l;
  }
  return FALSE;
}

bool findAndRemoveWord(QCString &s,const QCString &word)
{
  static QRegExp wordExp("[a-z_A-Z\\x80-\\xFF]+");
  int p=0,i,l;
  while ((i=wordExp.match(s,p,&l))!=-1)
  {
    if (s.mid(i,l)==word) 
    {
      if (i>0 && isspace((uchar)s.at(i-1))) 
        i--,l++;
      else if (i+l<(int)s.length() && isspace(s.at(i+l))) 
        l++;
      s = s.left(i)+s.mid(i+l); // remove word + spacing
      return TRUE;
    }
    p=i+l;
  }
  return FALSE;
}

/** Special version of QCString::stripWhiteSpace() that only strips
 *  empty lines.
 */
QCString stripLeadingAndTrailingEmptyLines(const QCString &s)
{
  const char *p = s.data();
  if (p==0) return 0;

  // search for leading empty lines
  int i=0,li=-1,l=s.length();
  char c;
  while ((c=*p++))
  {
    if (c==' ' || c=='\t' || c=='\r') i++;
    else if (c=='\n') i++,li=i;
    else break;
  }

  // search for trailing empty lines
  int b=l-1,bi=-1;
  p=s.data()+b;
  while (b>=0)
  {
    c=*p; p--;
    if (c==' ' || c=='\t' || c=='\r') b--;
    else if (c=='\n') bi=b,b--;
    else break;
  }

  // return whole string if no leading or trailing lines where found
  if (li==-1 && bi==-1) return s;

  // return substring
  if (bi==-1) bi=l;
  if (li==-1) li=0;
  if (bi<=li) return 0; // only empty lines
  return s.mid(li,bi-li);
}

#if 0
void stringToSearchIndex(const QCString &docBaseUrl,const QCString &title,
    const QCString &str,bool priority,const QCString &anchor)
{
  static bool searchEngine = Config_getBool("SEARCHENGINE");
  if (searchEngine)
  {
    Doxygen::searchIndex->setCurrentDoc(title,docBaseUrl,anchor);
    static QRegExp wordPattern("[a-z_A-Z\\x80-\\xFF][a-z_A-Z0-9\\x80-\\xFF]*");
    int i,p=0,l;
    while ((i=wordPattern.match(str,p,&l))!=-1)
    {
      Doxygen::searchIndex->addWord(str.mid(i,l),priority);
      p=i+l;
    }
  }
}
#endif

//--------------------------------------------------------------------------

static QDict<int> g_extLookup;

static struct Lang2ExtMap
{
  const char *langName;
  const char *parserName;
  SrcLangExt parserId;
} 
g_lang2extMap[] =
{
//  language       parser     parser option
  { "idl",         "c",       SrcLangExt_IDL      },
  { "java",        "c",       SrcLangExt_Java     },
  { "javascript",  "c",       SrcLangExt_JS       },
  { "csharp",      "c",       SrcLangExt_CSharp   },
  { "d",           "c",       SrcLangExt_D        },
  { "php",         "c",       SrcLangExt_PHP      },
  { "objective-c", "c",       SrcLangExt_ObjC     },
  { "c",           "c",       SrcLangExt_Cpp      },
  { "c++",         "c",       SrcLangExt_Cpp      },
  { "python",      "python",  SrcLangExt_Python   },
  { "fortran",     "fortran", SrcLangExt_Fortran  },
  { "vhdl",        "vhdl",    SrcLangExt_VHDL     },
  { "dbusxml",     "dbusxml", SrcLangExt_XML      },
  { "tcl",         "tcl",     SrcLangExt_Tcl      },
  { "md",          "md",      SrcLangExt_Markdown },
  { 0,             0,        (SrcLangExt)0        }
};

bool updateLanguageMapping(const QCString &extension,const QCString &language)
{
  const Lang2ExtMap *p = g_lang2extMap;
  QCString langName = language.lower();
  while (p->langName)
  {
    if (langName==p->langName) break;
    p++;
  }
  if (!p->langName) return FALSE;

  // found the language
  SrcLangExt parserId = p->parserId;
  QCString extName = extension.lower();
  if (extName.isEmpty()) return FALSE;
  if (extName.at(0)!='.') extName.prepend(".");
  if (g_extLookup.find(extension)!=0) // language was already register for this ext
  {
    g_extLookup.remove(extension);
  }
  //printf("registering extension %s\n",extName.data());
  g_extLookup.insert(extName,new int(parserId));
  if (!Doxygen::parserManager->registerExtension(extName,p->parserName))
  {
    err("Failed to assign extension %s to parser %s for language %s\n",
        extName.data(),p->parserName,language.data());
  }
  else
  {
    //msg("Registered extension %s to language parser %s...\n",
    //    extName.data(),language.data());
  }
  return TRUE;
}

void initDefaultExtensionMapping()
{
  g_extLookup.setAutoDelete(TRUE);
  //                  extension      parser id
  updateLanguageMapping(".idl",      "idl"); 
  updateLanguageMapping(".ddl",      "idl"); 
  updateLanguageMapping(".odl",      "idl"); 
  updateLanguageMapping(".java",     "java");
  updateLanguageMapping(".as",       "javascript"); 
  updateLanguageMapping(".js",       "javascript");
  updateLanguageMapping(".cs",       "csharp");
  updateLanguageMapping(".d",        "d");
  updateLanguageMapping(".php",      "php"); 
  updateLanguageMapping(".php4",     "php");
  updateLanguageMapping(".php5",     "php");
  updateLanguageMapping(".inc",      "php");
  updateLanguageMapping(".phtml",    "php");
  updateLanguageMapping(".m",        "objective-c");
  updateLanguageMapping(".M",        "objective-c");
  updateLanguageMapping(".mm",       "objective-c");
  updateLanguageMapping(".py",       "python");
  updateLanguageMapping(".f",        "fortran");
  updateLanguageMapping(".for",      "fortran");
  updateLanguageMapping(".f90",      "fortran");
  updateLanguageMapping(".vhd",      "vhdl");
  updateLanguageMapping(".vhdl",     "vhdl");
  updateLanguageMapping(".tcl",      "tcl");
  updateLanguageMapping(".ucf",      "vhdl");
  updateLanguageMapping(".qsf",      "vhdl");
  updateLanguageMapping(".md",       "md");
  updateLanguageMapping(".markdown", "md");

  //updateLanguageMapping(".xml",   "dbusxml");
}

SrcLangExt getLanguageFromFileName(const QCString fileName)
{
  int i = fileName.findRev('.');
  if (i!=-1) // name has an extension
  {
    QCString extStr=fileName.right(fileName.length()-i).lower();
    if (!extStr.isEmpty()) // non-empty extension
    {
      int *pVal=g_extLookup.find(extStr);
      if (pVal) // listed extension
      {
        //printf("getLanguageFromFileName(%s)=%x\n",extStr.data(),*pVal);
        return (SrcLangExt)*pVal; 
      }
    }
  }
  //printf("getLanguageFromFileName(%s) not found!\n",fileName.data());
  return SrcLangExt_Cpp; // not listed => assume C-ish language.
}

//--------------------------------------------------------------------------

MemberDef *getMemberFromSymbol(Definition *scope,FileDef *fileScope, 
                                const char *n)
{
  if (scope==0 ||
      (scope->definitionType()!=Definition::TypeClass &&
       scope->definitionType()!=Definition::TypeNamespace
      )
     )
  {
    scope=Doxygen::globalScope;
  }

  QCString name = n;
  if (name.isEmpty())
    return 0; // no name was given

  DefinitionIntf *di = Doxygen::symbolMap->find(name);
  if (di==0)
    return 0; // could not find any matching symbols

  // mostly copied from getResolvedClassRec()
  QCString explicitScopePart;
  int qualifierIndex = computeQualifiedIndex(name);
  if (qualifierIndex!=-1)
  {
    explicitScopePart = name.left(qualifierIndex);
    replaceNamespaceAliases(explicitScopePart,explicitScopePart.length());
    name = name.mid(qualifierIndex+2);
  }
  //printf("explicitScopePart=%s\n",explicitScopePart.data());

  int minDistance = 10000;
  MemberDef *bestMatch = 0;

  if (di->definitionType()==DefinitionIntf::TypeSymbolList)
  {
    //printf("multiple matches!\n");
    // find the closest closest matching definition
    DefinitionListIterator dli(*(DefinitionList*)di);
    Definition *d;
    for (dli.toFirst();(d=dli.current());++dli)
    {
      if (d->definitionType()==Definition::TypeMember)
      {
        g_visitedNamespaces.clear();
        int distance = isAccessibleFromWithExpScope(scope,fileScope,d,explicitScopePart);
        if (distance!=-1 && distance<minDistance)
        {
          minDistance = distance;
          bestMatch = (MemberDef *)d;
          //printf("new best match %s distance=%d\n",bestMatch->qualifiedName().data(),distance);
        }
      }
    }
  }
  else if (di->definitionType()==Definition::TypeMember)
  {
    //printf("unique match!\n");
    Definition *d = (Definition *)di;
    g_visitedNamespaces.clear();
    int distance = isAccessibleFromWithExpScope(scope,fileScope,d,explicitScopePart);
    if (distance!=-1 && distance<minDistance)
    {
      minDistance = distance;
      bestMatch = (MemberDef *)d;
      //printf("new best match %s distance=%d\n",bestMatch->qualifiedName().data(),distance);
    }
  }
  return bestMatch;
}

/*! Returns true iff the given name string appears to be a typedef in scope. */
bool checkIfTypedef(Definition *scope,FileDef *fileScope,const char *n)
{
  MemberDef *bestMatch = getMemberFromSymbol(scope,fileScope,n);

  if (bestMatch && bestMatch->isTypedef())
    return TRUE; // closest matching symbol is a typedef
  else
    return FALSE;
}


int nextUtf8CharPosition(const QCString &utf8Str,int len,int startPos)
{
  int bytes=1;
  if (startPos>=len) return len;
  char c = utf8Str[startPos];
  if (c<0) // multibyte utf-8 character
  {
    bytes++;   // 1xxx.xxxx: >=2 byte character
    if (((uchar)c&0xE0)==0xE0)
    {
      bytes++; // 111x.xxxx: >=3 byte character
    }
    if (((uchar)c&0xF0)==0xF0)
    {
      bytes++; // 1111.xxxx: 4 byte character
    }
  }
  else if (c=='&') // skip over character entities
  {
    static QRegExp re1("&#[0-9]+;");     // numerical entity
    static QRegExp re2("&[A-Z_a-z]+;");  // named entity
    int l1,l2;
    int i1 = re1.match(utf8Str,startPos,&l1);
    int i2 = re2.match(utf8Str,startPos,&l2);
    if (i1!=-1)
    {
      bytes=l1;
    }
    else if (i2!=-1)
    {
      bytes=l2;
    }
  }
  return startPos+bytes;
}

QCString parseCommentAsText(const Definition *scope,const MemberDef *md,
    const QCString &doc,const QCString &fileName,int lineNr)
{
  QGString s;
  if (doc.isEmpty()) return s.data();
  FTextStream t(&s);
  DocNode *root = validatingParseDoc(fileName,lineNr,
      (Definition*)scope,(MemberDef*)md,doc,FALSE,FALSE);
  TextDocVisitor *visitor = new TextDocVisitor(t);
  root->accept(visitor);
  delete visitor;
  delete root;
  QCString result = s.data();
  int i=0;
  int charCnt=0;
  int l=result.length();
  bool addEllipsis=FALSE;
  while ((i=nextUtf8CharPosition(result,l,i))<l)
  {
    charCnt++;
    if (charCnt>=80) break;
  }
  if (charCnt>=80) // try to truncate the string
  {
    while ((i=nextUtf8CharPosition(result,l,i))<l && charCnt<100)
    {
      charCnt++;
      if (result.at(i)>=0 && isspace(result.at(i)))
      {
        addEllipsis=TRUE;
      }
      else if (result.at(i)==',' || 
               result.at(i)=='.' || 
               result.at(i)=='?')
      {
        break;
      }
    }
  }
  if (addEllipsis || charCnt==100) result=result.left(i)+"...";
  return result.data();
}

//--------------------------------------------------------------------------------------

static QDict<void> aliasesProcessed;

static QCString expandAliasRec(const QCString s);

struct Marker
{
  Marker(int p, int n,int s) : pos(p),number(n),size(s) {}
  int pos; // position in the string
  int number; // argument number
  int size; // size of the marker
};

/** Replaces the markers in an alias definition \a aliasValue 
 *  with the corresponding values found in the comma separated argument 
 *  list \a argList and the returns the result after recursive alias expansion.
 */
static QCString replaceAliasArguments(const QCString &aliasValue,const QCString &argList)
{
  //printf("----- replaceAliasArguments(val=[%s],args=[%s])\n",aliasValue.data(),argList.data());

  // first make a list of arguments from the comma separated argument list
  QList<QCString> args;
  args.setAutoDelete(TRUE);
  int i,l=(int)argList.length();
  int s=0;
  for (i=0;i<l;i++)
  {
    if (argList.at(i)==',' && (i==0 || argList.at(i-1)!='\\')) 
    {
      args.append(new QCString(argList.mid(s,i-s)));
      s=i+1; // start of next argument
    }
  }
  if (l>s) args.append(new QCString(argList.right(l-s)));
  //printf("found %d arguments\n",args.count());

  // next we look for the positions of the markers and add them to a list
  QList<Marker> markerList;
  markerList.setAutoDelete(TRUE);
  l = aliasValue.length();
  int markerStart=0;
  int markerEnd=0;
  for (i=0;i<l;i++)
  {
    if (markerStart==0 && aliasValue.at(i)=='\\') // start of a \xx marker
    {
      markerStart=i+1;
    }
    else if (markerStart>0 && aliasValue.at(i)>='0' && aliasValue.at(i)<='9')
    {
      // read digit that make up the marker number
      markerEnd=i+1;
    }
    else
    {
      if (markerStart>0 && markerEnd>markerStart) // end of marker
      {
        int markerLen = markerEnd-markerStart;
        markerList.append(new Marker(markerStart-1, // include backslash
                    atoi(aliasValue.mid(markerStart,markerLen)),markerLen+1));
        //printf("found marker at %d with len %d and number %d\n",
        //    markerStart-1,markerLen+1,atoi(aliasValue.mid(markerStart,markerLen)));
      }
      markerStart=0; // outside marker
      markerEnd=0;
    }
  }

  // then we replace the markers with the corresponding arguments in one pass
  QCString result;
  int p=0;
  for (i=0;i<(int)markerList.count();i++)
  {
    Marker *m = markerList.at(i);
    result+=aliasValue.mid(p,m->pos-p);
    //printf("part before marker %d: '%s'\n",i,aliasValue.mid(p,m->pos-p).data());
    if (m->number>0 && m->number<=(int)args.count()) // valid number
    {
      result+=*args.at(m->number-1);
      //printf("marker index=%d pos=%d number=%d size=%d replacement %s\n",i,m->pos,m->number,m->size,
      //    args.at(m->number-1)->data());
    }
    p=m->pos+m->size; // continue after the marker
  }
  result+=aliasValue.right(l-p); // append remainder
  //printf("string after replacement of markers: '%s'\n",result.data());
  
  // expand the result again
  result = substitute(result,"\\{","{");
  result = substitute(result,"\\}","}");
  result = expandAliasRec(substitute(result,"\\,",","));

  return result;
}

static QCString escapeCommas(const QCString &s)
{
  QGString result;
  const char *p = s.data();
  char c,pc=0;
  while ((c=*p++))
  {
    if (c==',' && pc!='\\')
    {
      result+="\\,";
    }
    else
    {
      result+=c;
    }
    pc=c;
  }
  result+='\0';
  //printf("escapeCommas: '%s'->'%s'\n",s.data(),result.data());
  return result.data();
}

static QCString expandAliasRec(const QCString s)
{
  QCString result;
  static QRegExp cmdPat("[\\\\@][a-z_A-Z][a-z_A-Z0-9]*");
  QCString value=s;
  int i,p=0,l;
  while ((i=cmdPat.match(value,p,&l))!=-1)
  {
    result+=value.mid(p,i-p);
    QCString args = extractAliasArgs(value,i+l);
    bool hasArgs = !args.isEmpty();            // found directly after command
    int argsLen = args.length();
    QCString cmd = value.mid(i+1,l-1);
    QCString cmdNoArgs = cmd;
    int numArgs=0;
    if (hasArgs)
    {
      numArgs = countAliasArguments(args);
      cmd += QCString().sprintf("{%d}",numArgs);  // alias name + {n}
    }
    QCString *aliasText=Doxygen::aliasDict.find(cmd);
    if (numArgs>1 && aliasText==0) 
    { // in case there is no command with numArgs parameters, but there is a command with 1 parameter, 
      // we also accept all text as the argument of that command (so you don't have to escape commas)
      aliasText=Doxygen::aliasDict.find(cmdNoArgs+"{1}");
      if (aliasText)
      {
        cmd = cmdNoArgs+"{1}";
        args = escapeCommas(args); // escape , so that everything is seen as one argument
      }
    }
    //printf("Found command s='%s' cmd='%s' numArgs=%d args='%s' aliasText=%s\n",
    //    s.data(),cmd.data(),numArgs,args.data(),aliasText?aliasText->data():"<none>");
    if (aliasesProcessed.find(cmd)==0 && aliasText) // expand the alias
    {
      //printf("is an alias!\n");
      aliasesProcessed.insert(cmd,(void *)0x8);
      QCString val = *aliasText;
      if (hasArgs)
      {
        val = replaceAliasArguments(val,args);
        //printf("replace '%s'->'%s' args='%s'\n",
        //       aliasText->data(),val.data(),args.data());
      }
      result+=expandAliasRec(val);
      aliasesProcessed.remove(cmd);
      p=i+l;
      if (hasArgs) p+=argsLen+2;
    }
    else // command is not an alias
    {
      //printf("not an alias!\n");
      result+=value.mid(i,l);
      p=i+l;
    }
  }
  result+=value.right(value.length()-p);

  //printf("expandAliases '%s'->'%s'\n",s.data(),result.data());
  return result;
}

int countAliasArguments(const QCString argList)
{
  int count=1;
  int l = argList.length();
  int i;
  for (i=0;i<l;i++) 
  {
    if (argList.at(i)==',' && (i==0 || argList.at(i-1)!='\\')) count++;
  }
  return count;
}

QCString extractAliasArgs(const QCString &args,int pos)
{
  int i;
  int bc=0;
  char prevChar=0;
  if (args.at(pos)=='{') // alias has argument
  {
    for (i=pos;i<(int)args.length();i++)
    {
      if (prevChar!='\\')
      {
        if (args.at(i)=='{') bc++;
        if (args.at(i)=='}') bc--;
        prevChar=args.at(i);
      }
      else
      {
        prevChar=0;
      }

      if (bc==0) 
      {
        //printf("extractAliasArgs('%s')->'%s'\n",args.data(),args.mid(pos+1,i-pos-1).data());
        return args.mid(pos+1,i-pos-1);
      }
    }
  }
  return "";
}

QCString resolveAliasCmd(const QCString aliasCmd)
{
  QCString result;
  aliasesProcessed.clear();
  //printf("Expanding: '%s'\n",aliasCmd.data());
  result = expandAliasRec(aliasCmd);
  //printf("Expanding result: '%s'->'%s'\n",aliasCmd.data(),result.data());
  return result;
}

QCString expandAlias(const QCString &aliasName,const QCString &aliasValue)
{
  QCString result;
  aliasesProcessed.clear();
  // avoid expanding this command recursively
  aliasesProcessed.insert(aliasName,(void *)0x8);
  // expand embedded commands
  //printf("Expanding: '%s'->'%s'\n",aliasName.data(),aliasValue.data());
  result = expandAliasRec(aliasValue);
  //printf("Expanding result: '%s'->'%s'\n",aliasName.data(),result.data());
  return result;
}

void writeTypeConstraints(OutputList &ol,Definition *d,ArgumentList *al)
{
  if (al==0) return;
  ol.startConstraintList(theTranslator->trTypeConstraints()); 
  ArgumentListIterator ali(*al);
  Argument *a;
  for (;(a=ali.current());++ali)
  {
    ol.startConstraintParam();
    ol.parseText(a->name);
    ol.endConstraintParam();
    ol.startConstraintType();
    linkifyText(TextGeneratorOLImpl(ol),d,0,0,a->type);
    ol.endConstraintType();
    ol.startConstraintDocs();
    ol.parseDoc(d->docFile(),d->docLine(),d,0,a->docs,TRUE,FALSE);
    ol.endConstraintDocs();
  }
  ol.endConstraintList();
}

bool usingTreeIndex()
{
  static bool treeView = Config_getBool("USE_INLINE_TREES");
  return treeView;
}

void stackTrace()
{
#ifdef TRACINGSUPPORT
  void *backtraceFrames[128];
  int frameCount = backtrace(backtraceFrames, 128);
  static char cmd[40960];
  char *p = cmd;
  p += sprintf(p,"/usr/bin/atos -p %d ", (int)getpid());
  for (int x = 0; x < frameCount; x++) 
  {
    p += sprintf(p,"%p ", backtraceFrames[x]);
  }
  fprintf(stderr,"========== STACKTRACE START ==============\n");
  if (FILE *fp = popen(cmd, "r"))
  {
    char resBuf[512];
    while (size_t len = fread(resBuf, 1, sizeof(resBuf), fp))
    {
      fwrite(resBuf, 1, len, stderr);
    }
    pclose(fp);
  }
  fprintf(stderr,"============ STACKTRACE END ==============\n");
  //fprintf(stderr,"%s\n", frameStrings[x]);
#endif
}

static int transcodeCharacterBuffer(const char *fileName,BufStr &srcBuf,int size,
           const char *inputEncoding,const char *outputEncoding)
{
  if (inputEncoding==0 || outputEncoding==0) return size;
  if (qstricmp(inputEncoding,outputEncoding)==0) return size;
  void *cd = portable_iconv_open(outputEncoding,inputEncoding);
  if (cd==(void *)(-1)) 
  {
    err("error: unsupported character conversion: '%s'->'%s': %s\n"
        "Check the INPUT_ENCODING setting in the config file!\n",
        inputEncoding,outputEncoding,strerror(errno));
    exit(1);
  }
  int tmpBufSize=size*4+1;
  BufStr tmpBuf(tmpBufSize);
  size_t iLeft=size;
  size_t oLeft=tmpBufSize;
  const char *srcPtr = srcBuf.data();
  char *dstPtr = tmpBuf.data();
  uint newSize=0;
  if (!portable_iconv(cd, &srcPtr, &iLeft, &dstPtr, &oLeft))
  {
    newSize = tmpBufSize-oLeft;
    srcBuf.shrink(newSize);
    strncpy(srcBuf.data(),tmpBuf.data(),newSize);
    //printf("iconv: input size=%d output size=%d\n[%s]\n",size,newSize,srcBuf.data());
  }
  else
  {
    err("%s: error: failed to translate characters from %s to %s: check INPUT_ENCODING\n",
        fileName,inputEncoding,outputEncoding);
    exit(1);
  }
  portable_iconv_close(cd);
  return newSize;
}

//! read a file name \a fileName and optionally filter and transcode it
bool readInputFile(const char *fileName,BufStr &inBuf)
{
  // try to open file
  int size=0;
  //uint oldPos = dest.curPos();
  //printf(".......oldPos=%d\n",oldPos);

  QFileInfo fi(fileName);
  if (!fi.exists()) return FALSE;
  QCString filterName = getFileFilter(fileName,FALSE);
  if (filterName.isEmpty())
  {
    QFile f(fileName);
    if (!f.open(IO_ReadOnly))
    {
      err("error: could not open file %s\n",fileName);
      return FALSE;
    }
    size=fi.size();
    // read the file
    inBuf.skip(size);
    if (f.readBlock(inBuf.data()/*+oldPos*/,size)!=size)
    {
      err("error: problems while reading file %s\n",fileName);
      return FALSE;
    }
  }
  else
  {
    QCString cmd=filterName+" \""+fileName+"\"";
    Debug::print(Debug::ExtCmd,0,"Executing popen(`%s`)\n",cmd.data());
    FILE *f=portable_popen(cmd,"r");
    if (!f)
    {
      err("error: could not execute filter %s\n",filterName.data());
      return FALSE;
    }
    const int bufSize=1024;
    char buf[bufSize];
    int numRead;
    while ((numRead=fread(buf,1,bufSize,f))>0) 
    {
      //printf(">>>>>>>>Reading %d bytes\n",numRead);
      inBuf.addArray(buf,numRead),size+=numRead;
    }
    portable_pclose(f);
    inBuf.at(inBuf.curPos()) ='\0';
    Debug::print(Debug::FilterOutput, 0, "Filter output\n");
    Debug::print(Debug::FilterOutput,0,"-------------\n%s\n-------------\n",inBuf.data());
  }

  int start=0;
  if (size>=2 &&
      ((inBuf.at(0)==-1 && inBuf.at(1)==-2) || // Litte endian BOM
       (inBuf.at(0)==-2 && inBuf.at(1)==-1)    // big endian BOM
      )
     ) // UCS-2 encoded file
  {
    transcodeCharacterBuffer(fileName,inBuf,inBuf.curPos(),
        "UCS-2","UTF-8");
  }
  else if (size>=3 &&
           (uchar)inBuf.at(0)==0xEF &&
           (uchar)inBuf.at(1)==0xBB &&
           (uchar)inBuf.at(2)==0xBF
     ) // UTF-8 encoded file
  {
    inBuf.dropFromStart(3); // remove UTF-8 BOM: no translation needed
  }
  else // transcode according to the INPUT_ENCODING setting
  {
    // do character transcoding if needed.
    transcodeCharacterBuffer(fileName,inBuf,inBuf.curPos(),
        Config_getString("INPUT_ENCODING"),"UTF-8");
  }

  inBuf.addChar('\n'); /* to prevent problems under Windows ? */

  // and translate CR's
  size=inBuf.curPos()-start;
  int newSize=filterCRLF(inBuf.data()+start,size);
  //printf("filter char at %p size=%d newSize=%d\n",dest.data()+oldPos,size,newSize);
  if (newSize!=size) // we removed chars
  {
    inBuf.shrink(newSize); // resize the array
    //printf(".......resizing from %d to %d result=[%s]\n",oldPos+size,oldPos+newSize,dest.data());
  }
  inBuf.at(inBuf.curPos())='\0';
  return TRUE;
}

// Replace %word by word in title
QCString filterTitle(const QCString &title)
{
  QCString tf;
  static QRegExp re("%[A-Z_a-z]");
  int p=0,i,l;
  while ((i=re.match(title,p,&l))!=-1)
  {
    tf+=title.mid(p,i-p);
    tf+=title.mid(i+1,l-1); // skip %
    p=i+l;
  }
  tf+=title.right(title.length()-p);
  return tf;
}

//----------------------------------------------------------------------------
// returns TRUE if the name of the file represented by `fi' matches
// one of the file patterns in the `patList' list.

bool patternMatch(const QFileInfo &fi,const QStrList *patList)
{
  bool found=FALSE;
  if (patList)
  { 
    QStrListIterator it(*patList);
    QCString pattern;
    for (it.toFirst();(pattern=it.current());++it)
    {
      if (!pattern.isEmpty() && !found)
      {
        int i=pattern.find('=');
        if (i!=-1) pattern=pattern.left(i); // strip of the extension specific filter name

#if defined(_WIN32) || defined(__MACOSX__) // Windows or MacOSX
        QRegExp re(pattern,FALSE,TRUE); // case insensitive match 
#else                // unix
        QRegExp re(pattern,TRUE,TRUE);  // case sensitive match
#endif
        found = found || re.match(fi.fileName().data())!=-1 || 
                         re.match(fi.filePath().data())!=-1 ||
                         re.match(fi.absFilePath().data())!=-1;
        //printf("Matching `%s' against pattern `%s' found=%d\n",
        //    fi->fileName().data(),pattern.data(),found);
      }
    }
  }
  return found;
}

void writeSummaryLink(OutputList &ol,const char *label,const char *title,
                      bool &first)
{
  if (first)
  {
    ol.writeString("  <div class=\"summary\">\n");
    first=FALSE;
  }
  else
  {
    ol.writeString(" &#124;\n");
  }
  ol.writeString("<a href=\"#");
  ol.writeString(label);
  ol.writeString("\">");
  ol.writeString(title);
  ol.writeString("</a>");
}

QCString externalLinkTarget()
{
  static bool extLinksInWindow = Config_getBool("EXT_LINKS_IN_WINDOW");
  if (extLinksInWindow) return "target=\"_blank\" "; else return "";
}

QCString externalRef(const QCString &relPath,const QCString &ref,bool href)
{
  QCString result;
  if (!ref.isEmpty())
  {
    QCString *dest = Doxygen::tagDestinationDict[ref];
    if (dest)
    {
      result = *dest;
      int l = result.length();
      if (!relPath.isEmpty() && l>0 && result.at(0)=='.')
      { // relative path -> prepend relPath.
        result.prepend(relPath);
      }
      if (!href) result.prepend("doxygen=\""+ref+":");
      if (l>0 && result.at(l-1)!='/') result+='/';
      if (!href) result.append("\" ");
    }
  }
  else
  {
    result = relPath;
  }
  return result;
}

/** Writes the intensity only bitmap representated by \a data as an image to 
 *  directory \a dir using the colors defined by HTML_COLORSTYLE_*.
 */
void writeColoredImgData(const char *dir,ColoredImgDataItem data[])
{
  static int hue   = Config_getInt("HTML_COLORSTYLE_HUE");
  static int sat   = Config_getInt("HTML_COLORSTYLE_SAT");
  static int gamma = Config_getInt("HTML_COLORSTYLE_GAMMA");
  while (data->name)
  {
    QCString fileName;
    fileName=(QCString)dir+"/"+data->name;
    QFile f(fileName);
    if (f.open(IO_WriteOnly))
    {
      ColoredImage img(data->width,data->height,data->content,data->alpha,
                       sat,hue,gamma);
      img.save(fileName);
    }
    else
    {
      fprintf(stderr,"Warning: Cannot open file %s for writing\n",data->name);
    }
    Doxygen::indexList.addImageFile(data->name);
    data++;
  }
}

/** Replaces any markers of the form \#\#AA in input string \a str
 *  by new markers of the form \#AABBCC, where \#AABBCC represents a 
 *  valid color, based on the intensity represented by hex number AA 
 *  and the current HTML_COLORSTYLE_* settings.
 */
QCString replaceColorMarkers(const char *str)
{
  QCString result;
  QCString s=str;
  if (s.isEmpty()) return result;
  static QRegExp re("##[0-9A-Fa-f][0-9A-Fa-f]");
  static const char hex[] = "0123456789ABCDEF";
  static int hue   = Config_getInt("HTML_COLORSTYLE_HUE");
  static int sat   = Config_getInt("HTML_COLORSTYLE_SAT");
  static int gamma = Config_getInt("HTML_COLORSTYLE_GAMMA");
  int i,l,sl=s.length(),p=0;
  while ((i=re.match(s,p,&l))!=-1)
  {
    result+=s.mid(p,i-p);
    QCString lumStr = s.mid(i+2,l-2);
#define HEXTONUM(x) (((x)>='0' && (x)<='9') ? ((x)-'0') :       \
                     ((x)>='a' && (x)<='f') ? ((x)-'a'+10) :    \
                     ((x)>='A' && (x)<='F') ? ((x)-'A'+10) : 0)
    
    double r,g,b;
    int red,green,blue;
    int level = HEXTONUM(lumStr[0])*16+HEXTONUM(lumStr[1]);
    ColoredImage::hsl2rgb(hue/360.0,sat/255.0,
                          pow(level/255.0,gamma/100.0),&r,&g,&b);
    red   = (int)(r*255.0);
    green = (int)(g*255.0);
    blue  = (int)(b*255.0);
    char colStr[8];
    colStr[0]='#';
    colStr[1]=hex[red>>4];
    colStr[2]=hex[red&0xf];
    colStr[3]=hex[green>>4];
    colStr[4]=hex[green&0xf];
    colStr[5]=hex[blue>>4];
    colStr[6]=hex[blue&0xf];
    colStr[7]=0;
    //printf("replacing %s->%s (level=%d)\n",lumStr.data(),colStr,level);
    result+=colStr;
    p=i+l;
  }
  result+=s.right(sl-p);
  return result;
}

/** Copies the contents of file with name \a src to the newly created 
 *  file with name \a dest. Returns TRUE if successful.
 */
bool copyFile(const QCString &src,const QCString &dest)
{
  QFile sf(src);
  if (sf.open(IO_ReadOnly))
  {
    QFileInfo fi(src);
    QFile df(dest);
    if (df.open(IO_WriteOnly))
    {
      char *buffer = new char[fi.size()];
      sf.readBlock(buffer,fi.size());
      df.writeBlock(buffer,fi.size());
      df.flush();
      delete[] buffer;
    }
    else
    {
      err("error: could not write to file %s\n",dest.data());
      return FALSE;
    }
  }
  else
  {
    err("error: could not open user specified file %s\n",src.data());
    return FALSE;
  }
  return TRUE;
}

/** Returns the section of text, in between a pair of markers. 
 *  Full lines are returned, excluding the lines on which the markers appear.
 */
QCString extractBlock(const QCString text,const QCString marker)
{
  QCString result;
  int p=0,i;
  bool found=FALSE;

  // find the character positions of the markers
  int m1 = text.find(marker);
  if (m1==-1) return result;
  int m2 = text.find(marker,m1+marker.length());
  if (m2==-1) return result;

  // find start and end line positions for the markers
  int l1=-1,l2=-1;
  while (!found && (i=text.find('\n',p))!=-1)
  {
    found = (p<=m1 && m1<i); // found the line with the start marker
    p=i+1;
  }
  l1=p;
  if (found)
  {
    while ((i=text.find('\n',p))!=-1)
    {
      if (p<=m2 && m2<i) // found the line with the end marker
      {
        l2=p;
        break;
      }
      p=i+1;
    }
  }
  //printf("text=[%s]\n",text.mid(l1,l2-l1).data());
  return text.mid(l1,l2-l1);
}

/** Returns a string representation of \a lang. */
QCString langToString(SrcLangExt lang)
{
  switch(lang)
  {
    case SrcLangExt_Unknown:  return "Unknown";
    case SrcLangExt_IDL:      return "IDL";
    case SrcLangExt_Java:     return "Java";
    case SrcLangExt_CSharp:   return "C#";
    case SrcLangExt_D:        return "D";
    case SrcLangExt_PHP:      return "PHP";
    case SrcLangExt_ObjC:     return "Objective-C";
    case SrcLangExt_Cpp:      return "C++";
    case SrcLangExt_JS:       return "Javascript";
    case SrcLangExt_Python:   return "Python";
    case SrcLangExt_Fortran:  return "Fortran";
    case SrcLangExt_VHDL:     return "VHDL";
    case SrcLangExt_XML:      return "XML";
    case SrcLangExt_Tcl:      return "Tcl";
    case SrcLangExt_Markdown: return "Markdown";
  }
  return "Unknown";
}

/** Returns the scope separator to use given the programming language \a lang */
QCString getLanguageSpecificSeparator(SrcLangExt lang,bool classScope)
{
  if (lang==SrcLangExt_Java || lang==SrcLangExt_CSharp || lang==SrcLangExt_VHDL || lang==SrcLangExt_Python)
  {
    return ".";
  }
  else if (lang==SrcLangExt_PHP && !classScope)
  {
    return "\\";
  }
  else
  {
    return "::";
  }
}

/** Corrects URL \a url according to the relative path \a relPath.
 *  Returns the corrected URL. For absolute URLs no correction will be done.
 */
QCString correctURL(const QCString &url,const QCString &relPath)
{
  QCString result = url;
  if (!relPath.isEmpty() && 
      url.left(5)!="http:" && url.left(6)!="https:" && 
      url.left(4)!="ftp:" && url.left(5)!="file:")
  {
    result.prepend(relPath);
  }
  return result;
}

//---------------------------------------------------------------------------

bool protectionLevelVisible(Protection prot)
{
  static bool extractPrivate = Config_getBool("EXTRACT_PRIVATE");
  static bool extractPackage = Config_getBool("EXTRACT_PACKAGE");

  return (prot!=Private && prot!=Package)  || 
         (prot==Private && extractPrivate) || 
         (prot==Package && extractPackage);
}

