/*
 * Miscellaneous utilities for manipulating LaTeX files and constructs
 *
 */
 
#include <wx.h>
#include <iostream.h>
#include <ctype.h>
#include "tex2any.h"

wxList TexReferences(wxKEY_STRING);
wxList BibList(wxKEY_STRING);
wxStringList CitationList;
wxList CustomMacroList(wxKEY_STRING);
 
// Look for \label macro, use this ref name if found or
// make up a topic name otherwise.
static long topicCounter = 0;

void ResetTopicCounter(void)
{
  topicCounter = 0;
}

char *FindTopicName(TexChunk *chunk)
{
  char *topicName = NULL;
  static char topicBuf[100];

  if (chunk && (chunk->type == CHUNK_TYPE_MACRO) &&
      (strcmp(chunk->name, "label") == 0))
  {
    wxNode *node = chunk->children.First();
    if (node)
    {
      TexChunk *child = (TexChunk *)node->Data();
      if (child->type == CHUNK_TYPE_ARG)
      {
        wxNode *snode = child->children.First();
        if (snode)
        {
          TexChunk *schunk = (TexChunk *)snode->Data();
          if (schunk->type == CHUNK_TYPE_STRING)
            topicName = schunk->value;
        }
      }
    }
  }
  if (topicName)
    return topicName;
  else
  {
    sprintf(topicBuf, "topic%ld", topicCounter);
    topicCounter ++;
    return topicBuf;
  }
}

/*
 * Simulate argument data, so we can 'drive' clients which implement
 * certain basic formatting behaviour.
 * Snag is that some save a TexChunk, so don't use yet...
 *
 */
 
void StartSimulateArgument(char *data)
{
  strcpy(currentArgData, data);
  haveArgData = TRUE;
}

void EndSimulateArgument(void)
{
  haveArgData = FALSE;
}

/*
 * Parse and convert unit arguments to points
 *
 */

int ParseUnitArgument(char *unitArg)
{
  float conversionFactor = 1.0;
  float unitValue = 0.0;
  int len = strlen(unitArg);
  if (unitArg && (len > 0) && (isdigit(unitArg[0]) || unitArg[0] == '-'))
  {
    sscanf(unitArg, "%f", &unitValue);
    if (len > 1)
    {
      char units[3]; 
      units[0] = unitArg[len-1];
      units[1] = unitArg[len-2];
      units[2] = 0;
      if (strcmp(units, "in") == 0)
        conversionFactor = 72.0;
      else if (strcmp(units, "cm") == 0)
        conversionFactor = 72.0/2.51;
      else if (strcmp(units, "mm") == 0)
        conversionFactor = 72.0/25.1;
      else if (strcmp(units, "pt") == 0)
        conversionFactor = 1;
    }
    return (int)(unitValue*conversionFactor);
  }
  else return 0;
}

/*
 * Strip off any extension (dot something) from end of file,
 * IF one exists. Inserts zero into buffer.
 *
 */
 
void StripExtension(char *buffer)
{
  int len = strlen(buffer);
  int i = len-1;
  while (i > 0)
  {
    if (buffer[i] == '.')
    {
      buffer[i] = 0;
      break;
    }
    i --;
  }
}

/*
 * Latex font setting
 *
 */

void SetFontSizes(int pointSize)
{
  switch (pointSize)
  {
    case 12:
    {
      normalFont = 12;
      smallFont = 10;
      tinyFont = 8;
      largeFont1 = 14;
      LargeFont2 = 16;
      LARGEFont3 = 20;
      hugeFont1 = 24;
      HugeFont2 = 28;
      HUGEFont3 = 32;
      break;
    }
    case 11:
    {
      normalFont = 11;
      smallFont = 9;
      tinyFont = 7;
      largeFont1 = 13;
      LargeFont2 = 16;
      LARGEFont3 = 19;
      hugeFont1 = 22;
      HugeFont2 = 26;
      HUGEFont3 = 30;
      break;
    }
    case 10:
    {
      normalFont = 10;
      smallFont = 8;
      tinyFont = 6;
      largeFont1 = 12;
      LargeFont2 = 14;
      LARGEFont3 = 18;
      hugeFont1 = 20;
      HugeFont2 = 24;
      HUGEFont3 = 28;
      break;
    }
  }
}

 
/*
 * Latex references
 *
 */
 
void AddTexRef(char *name, char *file, char *sectionName,
               int chapter, int section, int subsection, int subsubsection)
{
  wxNode *node = TexReferences.Find(name);
  if (node) delete node;
  char buf[100];
  buf[0] = 0;
  if (sectionName)
  {
    strcat(buf, sectionName);
    strcat(buf, " ");
  }

  if (chapter)
  {
    char buf2[10];
    sprintf(buf2, "%d", chapter);
    strcat(buf, buf2);
  }
  if (section)
  {
    char buf2[10];
    if (chapter)
      strcat(buf, ".");

    sprintf(buf2, "%d", section);
    strcat(buf, buf2);
  }
  if (subsection)
  {
    char buf2[10];
    strcat(buf, ".");
    sprintf(buf2, "%d", subsection);
    strcat(buf, buf2);
  }
  if (subsubsection)
  {
    char buf2[10];
    sprintf(buf2, "%d", subsubsection);
    strcat(buf, buf2);
  }
  TexReferences.Append(name, new TexRef(name, file, (strlen(buf) > 0) ? buf : NULL));
}

void WriteTexReferences(char *filename)
{
  ofstream ostr(filename);
  if (ostr.bad()) return;
  char buf[200];
  
  wxNode *node = TexReferences.First();
  while (node)
  {
    TexRef *ref = (TexRef *)node->Data();
    ostr << ref->refLabel << " " << (ref->refFile ? ref->refFile : "??") << " ";
    ostr << (ref->sectionNumber ? ref->sectionNumber : "??") << "\n";
    if (!ref->sectionNumber || (strcmp(ref->sectionNumber, "??") == 0))
    {
      sprintf(buf, "Warning: reference %s not resolved.", ref->refLabel);
      OnInform(buf);
    }
    node = node->Next();
  }
}

void ReadTexReferences(char *filename)
{
  ifstream istr(filename);
  if (istr.bad()) return;

  char label[100];
  char file[400];
  char section[100];

  while (!istr.eof())
  {
    istr >> label;
    if (!istr.eof())
    {
      istr >> file;
      char ch;
      istr.get(ch); // Read past space
      istr.get(ch);
      int i = 0;
      while (ch != '\n' && !istr.eof())
      {
        section[i] = ch;
        i ++;
        istr.get(ch);
      }
      section[i] = 0;
      TexReferences.Append(label, new TexRef(label, file, section));
    }
  }
}


/*
 * Bibliography-handling code
 *
 */

void BibEatWhiteSpace(istream& str)
{
  char ch = str.peek();
  
  while (!str.eof() && (ch == ' ' || ch == '\t' || ch == 13 || ch == 10 || ch == EOF))
  {
    str.get(ch);
    ch = str.peek();
  }
}

// Read word up to { or , or space
void BibReadWord(istream& istr, char *buffer)
{
  int i = 0;
  buffer[i] = 0;
  char ch = istr.peek();
  while (!istr.eof() && ch != ' ' && ch != '{' && ch != 13 && ch != 10 && ch != '\t' &&
         ch != ',' && ch != '=')
  {
    istr.get(ch);
    buffer[i] = ch;
    i ++;
    ch = istr.peek();
  }
  buffer[i] = 0;
}

void BibReadToEOL(istream& istr, char *buffer)
{
  int i = 0;
  buffer[i] = 0;
  char ch = istr.peek();
  while (!istr.eof() && ch != 13 && ch != 10)
  {
    istr.get(ch);
    buffer[i] = ch;
    i ++;
    ch = istr.peek();
  }
  buffer[i] = 0;
}

// Read }-terminated value, taking nested braces into account.
void BibReadValue(istream& istr, char *buffer, Bool ignoreBraces = TRUE,
                  Bool quotesMayTerminate = TRUE)
{
  int braceCount = 1;
  int i = 0;
  buffer[i] = 0;
  char ch = istr.peek();
  Bool stopping = FALSE;
  while (!istr.eof() && !stopping)
  {
    istr.get(ch);
    
    if (ch == '{')
      braceCount ++;

    if (ch == '}')
    {
      braceCount --;
      if (braceCount == 0)
      {
        stopping = TRUE;
        break;
      }
    }
    else if (quotesMayTerminate && ch == '"')
    {
      stopping = TRUE;
      break;
    }
    if (!stopping)
    {
      if (!ignoreBraces || (ch != '{' && ch != '}'))
      {
        buffer[i] = ch;
        i ++;
      }
    }
  }
  buffer[i] = 0;
}
 
Bool ReadBib(char *filename)
{
  ifstream istr(filename);
  if (istr.bad()) return FALSE;

  OnInform("Reading .bib file...");

  char ch;
  char fieldValue[2000];
  char recordType[100];
  char recordKey[100];
  char recordField[100];
  while (!istr.eof())
  {
    BibEatWhiteSpace(istr);
    istr.get(ch);
    if (ch != '@')
    {
      OnError("Expected @: malformed .bib file.");
      return FALSE;
    }
    BibReadWord(istr, recordType);
    BibEatWhiteSpace(istr);
    istr.get(ch);
    if (ch != '{' && ch != '(')
    {
      OnError("Expected { or ( after record type: malformed .bib file.");
      return FALSE;
    }
    BibEatWhiteSpace(istr);
    if (StringMatch(recordType, "string", FALSE, TRUE))
    {
      BibReadWord(istr, fieldValue);
      BibEatWhiteSpace(istr);
      istr.get(ch);
      if (ch != '=')
      {
        OnError("Expected = after string key: malformed .bib file.");
        return FALSE;
      }
      BibEatWhiteSpace(istr);
      istr.get(ch);
      if (ch != '"' && ch != '{')
      {
        OnError("Expected = after string key: malformed .bib file.");
        return FALSE;
      }
      BibReadValue(istr, fieldValue);
      BibEatWhiteSpace(istr);
    }
    else
    {
      BibReadWord(istr, recordKey);

      BibEntry *bibEntry = new BibEntry;
      bibEntry->key = copystring(recordKey);
      bibEntry->type = copystring(recordType);

      Bool moreRecords = TRUE;
      while (moreRecords && !istr.eof())
      {
        BibEatWhiteSpace(istr);
        istr.get(ch);
        if (ch == '}' || ch == ')')
        {
          moreRecords = FALSE;
        }
        else if (ch == ',')
        {
          BibEatWhiteSpace(istr);
          BibReadWord(istr, recordField);
          BibEatWhiteSpace(istr);
          istr.get(ch);
          if (ch != '=')
          {
            OnError("Expected = after field type: malformed .bib file.");
            return FALSE;
          }
          BibEatWhiteSpace(istr);
          istr.get(ch);
          if (ch != '{' && ch != '"')
          {
            fieldValue[0] = ch;
            BibReadWord(istr, fieldValue+1);
          }
          else
            BibReadValue(istr, fieldValue);

          // Now we can add a field
          if (StringMatch(recordField, "author", FALSE, TRUE))
            bibEntry->author = copystring(fieldValue);
          else if (StringMatch(recordField, "key", FALSE, TRUE))
            {}
          else if (StringMatch(recordField, "annotate", FALSE, TRUE))
            {}
          else if (StringMatch(recordField, "edition", FALSE, TRUE))
            {}
          else if (StringMatch(recordField, "howpublished", FALSE, TRUE))
            {}
          else if (StringMatch(recordField, "note", FALSE, TRUE))
            {}
          else if (StringMatch(recordField, "series", FALSE, TRUE))
            {}
          else if (StringMatch(recordField, "type", FALSE, TRUE))
            {}
          else if (StringMatch(recordField, "editor", FALSE, TRUE))
            bibEntry->editor= copystring(fieldValue);
          else if (StringMatch(recordField, "title", FALSE, TRUE))
            bibEntry->title= copystring(fieldValue);
          else if (StringMatch(recordField, "booktitle", FALSE, TRUE))
            bibEntry->booktitle= copystring(fieldValue);
          else if (StringMatch(recordField, "journal", FALSE, TRUE))
            bibEntry->journal= copystring(fieldValue);
          else if (StringMatch(recordField, "volume", FALSE, TRUE))
            bibEntry->volume= copystring(fieldValue);
          else if (StringMatch(recordField, "number", FALSE, TRUE))
            bibEntry->number= copystring(fieldValue);
          else if (StringMatch(recordField, "year", FALSE, TRUE))
            bibEntry->year= copystring(fieldValue);
          else if (StringMatch(recordField, "month", FALSE, TRUE))
            bibEntry->month= copystring(fieldValue);
          else if (StringMatch(recordField, "pages", FALSE, TRUE))
            bibEntry->pages= copystring(fieldValue);
          else if (StringMatch(recordField, "publisher", FALSE, TRUE))
            bibEntry->publisher= copystring(fieldValue);
          else if (StringMatch(recordField, "address", FALSE, TRUE))
            bibEntry->address= copystring(fieldValue);
          else if (StringMatch(recordField, "institution", FALSE, TRUE) || StringMatch(recordField, "school", FALSE, TRUE))
            bibEntry->institution= copystring(fieldValue);
          else if (StringMatch(recordField, "organization", FALSE, TRUE))
            bibEntry->organization= copystring(fieldValue);
          else if (StringMatch(recordField, "comment", FALSE, TRUE))
            bibEntry->comment= copystring(fieldValue);
          else if (StringMatch(recordField, "chapter", FALSE, TRUE))
            bibEntry->chapter= copystring(fieldValue);
          else
          {
            char buf[200];
            sprintf(buf, "Unrecognised field type %s", recordField);
            OnError(buf);
          }
        }
        BibList.Append(recordKey, bibEntry);
        BibEatWhiteSpace(istr);
      }
    }
  }
  return TRUE;
}

void OutputBibItem(TexRef *ref, BibEntry *bib)
{
  OnMacro("bibitem", 2, TRUE);
  OnArgument("bibitem", 1, TRUE);
  TexOutput(ref->sectionNumber);
  OnArgument("bibitem", 1, FALSE);
  OnArgument("bibitem", 2, TRUE);

  TexOutput(" ");
  OnMacro("bf", 1, TRUE);
  OnArgument("bf", 1, TRUE);
  if (bib->author)
    TexOutput(bib->author);
  OnArgument("bf", 1, FALSE);
  OnMacro("bf", 1, FALSE);
  if (bib->author && (strlen(bib->author) > 0) && (bib->author[strlen(bib->author) - 1] != '.'))
    TexOutput(". ");
  else
    TexOutput(" ");

  if (bib->year)
  {
    TexOutput(bib->year);
  }
  if (bib->month)
  {
    TexOutput(" (");
    TexOutput(bib->month);
    TexOutput(")");
  }
  if (bib->year || bib->month)
    TexOutput(". ");

  if (StringMatch(bib->type, "article", FALSE, TRUE))
  {
    if (bib->title)
    {
      TexOutput(bib->title);
      TexOutput(". ");
    }
    if (bib->journal)
    {
      OnMacro("it", 1, TRUE);
      OnArgument("it", 1, TRUE);
      TexOutput(bib->journal);
      OnArgument("it", 1, FALSE);
      OnMacro("it", 1, FALSE);
    }
    if (bib->volume)
    {
      TexOutput(", ");
      OnMacro("bf", 1, TRUE);
      OnArgument("bf", 1, TRUE);
      TexOutput(bib->volume);
      OnArgument("bf", 1, FALSE);
      OnMacro("bf", 1, FALSE);
    }
    if (bib->number)
    {
      TexOutput("(");
      TexOutput(bib->number);
      TexOutput(")");
    }
    if (bib->pages)
    {
      TexOutput(", pages ");
      TexOutput(bib->pages);
    }
    TexOutput(".");
  }
  else if (StringMatch(bib->type, "book", FALSE, TRUE) ||
           StringMatch(bib->type, "unpublished", FALSE, TRUE) ||
           StringMatch(bib->type, "manual", FALSE, TRUE) ||
           StringMatch(bib->type, "phdthesis", FALSE, TRUE) ||
           StringMatch(bib->type, "mastersthesis", FALSE, TRUE) ||
           StringMatch(bib->type, "misc", FALSE, TRUE) ||
           StringMatch(bib->type, "techreport", FALSE, TRUE) ||
           StringMatch(bib->type, "booklet", FALSE, TRUE))
  {
    if (bib->title || bib->booktitle)
    {
      OnMacro("it", 1, TRUE);
      OnArgument("it", 1, TRUE);
      TexOutput(bib->title ? bib->title : bib->booktitle);
      TexOutput(". ");
      OnArgument("it", 1, FALSE);
      OnMacro("it", 1, FALSE);
    }
    if (StringMatch(bib->type, "phdthesis", FALSE, TRUE))
      TexOutput("PhD thesis. ");
    if (StringMatch(bib->type, "techreport", FALSE, TRUE))
      TexOutput("Technical report. ");
    if (bib->editor)
    {
      TexOutput("Ed. ");
      TexOutput(bib->editor);
      TexOutput(". ");
    }
    if (bib->institution)
    {
      TexOutput(bib->institution);
      TexOutput(". ");
    }
    if (bib->organization)
    {
      TexOutput(bib->organization);
      TexOutput(". ");
    }
    if (bib->publisher)
    {
      TexOutput(bib->publisher);
      TexOutput(". ");
    }
    if (bib->address)
    {
      TexOutput(bib->address);
      TexOutput(". ");
    }
  }
  else if (StringMatch(bib->type, "inbook", FALSE, TRUE) ||
           StringMatch(bib->type, "inproceedings", FALSE, TRUE) ||
           StringMatch(bib->type, "incollection", FALSE, TRUE) ||
           StringMatch(bib->type, "conference", FALSE, TRUE))
  {
    if (bib->title)
    {
      TexOutput(bib->title);
    }
    if (bib->booktitle)
    {
      TexOutput(", from ");
      OnMacro("it", 1, TRUE);
      OnArgument("it", 1, TRUE);
      TexOutput(bib->booktitle);
      TexOutput(".");
      OnArgument("it", 1, FALSE);
      OnMacro("it", 1, FALSE);
    }
    if (bib->editor)
    {
      TexOutput(", ed. ");
      TexOutput(bib->editor);
    }
    if (bib->publisher)
    {
      TexOutput(" ");
      TexOutput(bib->publisher);
    }
    if (bib->address)
    {
      if (bib->publisher) TexOutput(", ");
      else TexOutput(" ");
      TexOutput(bib->address);
    }
    if (bib->publisher || bib->address)
      TexOutput(".");

    if (bib->volume)
    {
      TexOutput(" ");
      OnMacro("bf", 1, TRUE);
      OnArgument("bf", 1, TRUE);
      TexOutput(bib->volume);
      OnArgument("bf", 1, FALSE);
      OnMacro("bf", 1, FALSE);
    }
    if (bib->number)
    {
      if (bib->volume)
      {
        TexOutput("(");
        TexOutput(bib->number);
        TexOutput(").");
      }
      else
      {
        TexOutput(" Number ");
        TexOutput(bib->number);
        TexOutput(".");
      }
    }
    if (bib->chapter)
    {
      TexOutput(" Chap. "); TexOutput(bib->chapter);
    }
    if (bib->pages)
    {
      if (bib->chapter) TexOutput(", pages ");
      else TexOutput(" Pages ");
      TexOutput(bib->pages);
      TexOutput(".");
    }
  }
  OnArgument("bibitem", 2, FALSE);
  OnMacro("bibitem", 2, FALSE);
}

void OutputBib(void)
{
  // Write the title
  OnMacro("references", 0, TRUE);
  OnMacro("references", 0, FALSE);

  wxNode *node = CitationList.First();
  while (node)
  {
    char *citeKey = (char *)node->Data();
    wxNode *texNode = TexReferences.Find(citeKey);
    wxNode *bibNode = BibList.Find(citeKey);
    if (bibNode && texNode)
    {
      BibEntry *entry = (BibEntry *)bibNode->Data();
      TexRef *ref = (TexRef *)texNode->Data();
      OutputBibItem(ref, entry);
    }
    node = node->Next();
  }
}

static int citeCount = 1;

void ResolveBibReferences(void)
{
  if (CitationList.Number() > 0)
    OnInform("Resolving bibliographic references...");

  citeCount = 1;
  char buf[200];
  wxNode *node = CitationList.First();
  while (node)
  {
    char *citeKey = (char *)node->Data();
    wxNode *texNode = TexReferences.Find(citeKey);
    wxNode *bibNode = BibList.Find(citeKey);
    if (bibNode && texNode)
    {
      TexRef *ref = (TexRef *)texNode->Data();
      BibEntry *entry = (BibEntry *)bibNode->Data();
      if (ref->sectionNumber) delete[] ref->sectionNumber;
      sprintf(buf, "[%d]", citeCount);
      ref->sectionNumber = copystring(buf);
      citeCount ++;
    }
    else
    {
      sprintf(buf, "Warning: bib ref %s not resolved.", citeKey);
      OnInform(buf);
    }
    node = node->Next();
  }
}

// Remember we need to resolve this citation
void AddCitation(char *citeKey)
{
  if (!CitationList.Member(citeKey))
    CitationList.Add(citeKey);

  if (!TexReferences.Find(citeKey))
  {
    TexReferences.Append(citeKey, new TexRef(citeKey, "??", NULL));
  }
}

/*
 * Custom macro stuff
 *
 */

// Define a variable value from the .ini file
void RegisterSetting(char *settingName, char *settingValue)
{
  if (StringMatch(settingName, "chapterFontSize", FALSE, TRUE))
    StringToInt(settingValue, &chapterFont);
  else if (StringMatch(settingName, "sectionFontSize", FALSE, TRUE))
    StringToInt(settingValue, &sectionFont);
  else if (StringMatch(settingName, "subsectionFontSize", FALSE, TRUE))
    StringToInt(settingValue, &subsectionFont);
  else if (StringMatch(settingName, "documentFontSize", FALSE, TRUE))
  {
    int n;
    StringToInt(settingValue, &n);
    if (n == 10 || n == 11 || n == 12)
      SetFontSizes(n);
    else
    {
      char buf[200];
      sprintf(buf, "Initialisation file error: nonstandard document font size %d.", n);
      OnInform(buf);
    }
  }
  else
  {
    char buf[200];
    sprintf(buf, "Initialisation file error: unrecognised setting %s.", settingName);
    OnInform(buf);
  }
}

Bool ReadCustomMacros(char *filename)
{
  ifstream istr(filename);
  if (istr.bad()) return FALSE;

  OnInform("Reading custom macros...");
  CustomMacroList.Clear();
  char ch;
  char macroName[100];
  char macroBody[1000];
  int noArgs;

  while (!istr.eof())
  {
    BibEatWhiteSpace(istr);
    istr.get(ch);
    if (istr.eof())
      break;
      
    if (ch != '\\') // Not a macro definition, so must be NAME=VALUE
    {
      char settingName[100];
      settingName[0] = ch;
      BibReadWord(istr, (settingName+1));
      BibEatWhiteSpace(istr);
      istr.get(ch);
      if (ch != '=')
      {
        OnError("Expected = following name: malformed tex2rtf.ini file.");
        return FALSE;
      }
      else
      {
        char settingValue[200];
        BibEatWhiteSpace(istr);
        BibReadToEOL(istr, settingValue);
        RegisterSetting(settingName, settingValue);
      }
    }
    else
    {
      BibReadWord(istr, macroName);
      BibEatWhiteSpace(istr);
      istr.get(ch);
      if (ch != '[')
      {
        OnError("Expected [ followed by number of arguments: malformed tex2rtf.ini file.");
        return FALSE;
      }
      istr >> noArgs;
      istr.get(ch);
      if (ch != ']')
      {
        OnError("Expected ] following number of arguments: malformed tex2rtf.ini file.");
        return FALSE;
      }
      BibEatWhiteSpace(istr);
      istr.get(ch);
      if (ch != '{')
      {
        OnError("Expected { followed by macro body: malformed tex2rtf.ini file.");
        return FALSE;
      }
      CustomMacro *macro = new CustomMacro(macroName, noArgs, NULL);
      BibReadValue(istr, macroBody, FALSE, FALSE); // Don't ignore extra braces
      if (strlen(macroBody) > 0)
        macro->macroBody = copystring(macroBody);
    
      BibEatWhiteSpace(istr);
      CustomMacroList.Append(macroName, macro);
      AddMacroDef(macroName, noArgs);
    }
  }
  return TRUE;
}
 
CustomMacro *FindCustomMacro(char *name)
{
  wxNode *node = CustomMacroList.Find(name);
  if (node)
  {
    CustomMacro *macro = (CustomMacro *)node->Data();
    return macro;
  }
  return NULL;
}

// Display custom macros
void ShowCustomMacros(void)
{
  wxNode *node = CustomMacroList.First();
  if (!node)
  {
    OnInform("No custom macros loaded.\n");
    return;
  }
  
  char buf[400];
  while (node)
  {
    CustomMacro *macro = (CustomMacro *)node->Data();
    sprintf(buf, "\\%s[%d]\n    {%s}", macro->macroName, macro->noArgs,
     macro->macroBody ? macro->macroBody : "");
    OnInform(buf);
    node = node->Next();
  }
}

