/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : TokenUtils.c                           */
/*                                                 */
/* Purpose: Utility functions for finding out      */
/*          information about tokens.              */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 09-Apr-97: Created.                    */
/***************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

#include "wimp.h"
#include "wimplib.h"
#include "event.h"

#include "swis.h"
#include "kernel.h"

#include "toolbox.h"
#include "event.h"

#include "svcprint.h"
#include "Global.h"
#include "MiscDefs.h"
#include "Utils.h"

#include "Redraw.h"

#include "TokenUtils.h"

/* Locals */

static tokenutils_npaths = 0;

/* Static function prototypes */

static int tokenutils_line_range_r(int toplevel, browser_data * b, reformat_cell * d, HStream * token, int * fline, int * fchunk, int * lline, int * lchunk, token_path ** path);

/*************************************************/
/* tokenutils_anchor_range()                     */
/*                                               */
/* Will return the first (inclusive) and last    */
/* (inclusive) tokens that consecutively point   */
/* to the same thing as anchors. This is useful  */
/* as some links may be made of several tokens,  */
/* as style changes to mark the result of a web  */
/* search (for example) or whatever may be used  */
/* in what is meant to be the same link. If this */
/* link is to be highlighted or selected, need   */
/* to know which tokens are involved.            */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the tokens;           */
/*                                               */
/*             Pointer to one of the tokens in   */
/*             the collection for this anchor;   */
/*                                               */
/*             Pointer to an HStream pointer,    */
/*             into which the address of the     */
/*             first token involved in the link  */
/*             is placed;                        */
/*                                               */
/*             Pointer to an HStream pointer,    */
/*             into which the address of the     */
/*             last token involved in the link   */
/*             is placed.                        */
/*                                               */
/* Assumes:    Either of the last two pointers   */
/*             may be NULL. The token pointer    */
/*             given at the start should point   */
/*             to a link. If not, the returned   */
/*             addresses will be undefined...    */
/*************************************************/

void tokenutils_anchor_range(browser_data * b, HStream * token, HStream ** first, HStream ** last)
{
  HStream * top   = NULL;
  HStream * end   = NULL;
  HStream * store = NULL;

  if (first) *first = NULL;
  if (last)  *last  = NULL;

  if (!b || !token) return;

  /* Can only proceed if the given token represents a link */

  if (token && (token->style & A) && token->anchor)
  {
    store = top = token;

    /* Move up the token list for as long as the token given matches */
    /* the details of the token being looked at.                     */

    while (
            top                                  &&
            (top->style & A)                     &&
            top->anchor                          &&
            !strcmp(top->anchor, token->anchor)
          )
          store = top, top = top->prev;

    /* Make sure we haven't overshot the first item */

    if (!top) top = store;

    if (
         top && !(
                   (top->style & A)                    &&
                   top->anchor                         &&
                   !strcmp(top->anchor, token->anchor)
                 )
       )
       top = top->next;

    store = end = token;

    /* Similary, move down the token list to find the first token after */
    /* all those involved in the link.                                  */

    while (
            end                                  &&
            (end->flags & HFlags_DealtWithToken) &&
            (end->style & A)                     &&
            end->anchor                          &&
            !strcmp(end->anchor, token->anchor)
          )
          store = end, end = end->next;

    /* Ensure we haven't overshot */

    if (!end) end = store;

    if (
         end && !(
                   (end->flags & HFlags_DealtWithToken) &&
                   (end->style & A)                     &&
                   end->anchor                          &&
                   !strcmp(end->anchor, token->anchor)
                 )
       )
       end = end->prev;
  }
  else top = end = token;

  /* Return the found values */

  if (first) *first = top;
  if (last)  *last  = end;

  return;
}

/*************************************************/
/* tokenutils_line_range()                       */
/*                                               */
/* Returns the first chunk and line number to    */
/* use a given token, and the last line and      */
/* chunk number to use that token.               */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             Pointer to the token;             */
/*                                               */
/*             Pointer to an int, into which the */
/*             first line number is placed;      */
/*                                               */
/*             Pointer to an int, into which the */
/*             first chunk number is placed;     */
/*                                               */
/*             Pointer to an int, into which the */
/*             last line number is placed;       */
/*                                               */
/*             Pointer to an int, into which the */
/*             last chunk number is placed;      */
/*                                               */
/*             Pointer to a token_path pointer,  */
/*             which will be filled in with the  */
/*             address of a malloced array of    */
/*             reformat_cell structures for      */
/*             tokens within tables, or NULL if  */
/*             this shouldn't be built.          */
/*                                               */
/* Returns:    See parameters list, and note     */
/*             that '-1' will be returned in all */
/*             four fields should be token not   */
/*             be found in a line to start with; */
/*                                               */
/*             The number of entries in the      */
/*             associated token_path structure   */
/*             for table-based tokens.           */
/*                                               */
/* Assumes:    Any of the pointers may be NULL.  */
/*************************************************/

int tokenutils_line_range(browser_data * b, HStream * token,
                          int * fline, int * fchunk, int * lline, int * lchunk, token_path ** path)
{
  tokenutils_npaths = 0;
  if (path) *path = NULL;

  return tokenutils_line_range_r(1, b, b->cell, token, fline, fchunk, lline, lchunk, path);
}

/*************************************************/
/* tokenutils_line_range_r()                     */
/*                                               */
/* Recursive back-end to tokenutils_line_range.  */
/*                                               */
/* Parameters: 1 for a top level call, else 0 if */
/*             being called recursively;         */
/*                                               */
/*             Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             containing the token or a table   */
/*             which holds the token;            */
/*                                               */
/*             Pointer to the token;             */
/*                                               */
/*             Pointer to an int, into which the */
/*             first line number is placed;      */
/*                                               */
/*             Pointer to an int, into which the */
/*             first chunk number is placed;     */
/*                                               */
/*             Pointer to an int, into which the */
/*             last line number is placed;       */
/*                                               */
/*             Pointer to an int, into which the */
/*             last chunk number is placed;      */
/*                                               */
/*             Pointer to a token_path pointer,  */
/*             which will be filled in with the  */
/*             address of a malloced array of    */
/*             token_path structures for tokens  */
/*             within tables, or NULL if this    */
/*             shouldn't be built.               */
/*                                               */
/* Returns:    As for tokenutils_line_range.     */
/*                                               */
/* Assumes:    As for tokenutils_line_range.     */
/*************************************************/

static int tokenutils_line_range_r(int toplevel, browser_data * b, reformat_cell * d, HStream * token,
                                   int * fline, int * fchunk, int * lline, int * lchunk, token_path ** path)
{
  int fl = -1, fc = -1, ll = -1, lc = -1; /* First Line, First Chunk, Last Line, Last Chunk */

  /* Start with all values at -1; saves having to fill them */
  /* in at any given failure point.                         */

  if (fline)  *fline  = fl;
  if (fchunk) *fchunk = fc;
  if (lline)  *lline  = ll;
  if (lchunk) *lchunk = lc;

  /* Can only try to find the token if the browser_data */
  /* and token pointers were not NULL.                  */

  if (b && d && token && d->nlines)
  {
    int cl, cc  = -1, mc; /* Current Line, Current Chunk, Maximum Chunk */
    int found   = 0;

    /* Must find the first line / chunk even if not asked for it */

    cl = 0;

    /* Loop through all lines */

    while (cl < d->nlines && !found)
    {
      cc = d->ldata[cl].chunks;
      mc = cc + d->ldata[cl].n;

      /* Go through this line's chunks looking for the token */

      while (cc < mc && !found)
      {
        if (toplevel) tokenutils_npaths = 0;

        if (d->cdata[cc].t == token) found = 1;
        else
        {
          /* Must recursively scan token lists for tables */

          if (d->cdata[cc].t->tagno == TAG_TABLE)
          {
            table_stream   * table     = (table_stream *) d->cdata[cc].t;
            table_row      * row       = NULL;
            table_headdata * head      = NULL;
            reformat_cell  * cellarray = table->cells;
            reformat_cell  * cell;
            int              found     = 0;
            int              cellindex;
            int              cellcount = 0;
            int              cellmax   = table->ColSpan * table->RowSpan;

            /* Naming convention: [Pointer] Table First/Last Line/Chunk */

            int              tfl = -1, tfc = -1, tll = -1, tlc = -1;
            int            * ptfl, * ptfc, * ptll, * ptlc;

            if (cellarray)
            {
              /* For recursive scanning of line lists, keep efficiency */
              /* as high as possible by only asking for the same       */
              /* values as originally passed to the function (e.g.     */
              /* don't want to look at last lines or chunks during     */
              /* the recursive scan unless they are needed at the top  */
              /* level too).                                           */

              if (fline)  ptfl = &tfl;
              else        ptfl = NULL;

              if (fchunk) ptfc = &tfc;
              else        ptfc = NULL;

              if (lline)  ptll = &tll;
              else        ptll = NULL;

              if (lchunk) ptlc = &tlc;
              else        ptlc = NULL;

              /* Scan the table */

              row = table->List;

              while (row && !found && cellcount < cellmax)
              {
                head = row->List;

                while (head && !found && cellcount < cellmax)
                {
                  switch (head->Tag)
                  {
                    case TagTableData:
                    case TagTableHead:
                    {
                      cellindex = table->ColSpan * head->RowOffs + head->ColOffs;

                      if (cellindex < cellmax)
                      {
                        cell = &cellarray[cellindex];

                        /* If a cell corresponding to this headdata item can be */
                        /* found and it has lines to scan, call this function   */
                        /* recursively on its contents.                         */

                        if (cell && cell->nlines)
                        {
                          /* Move flex anchors into the temporary browser_data struct */

                          tokenutils_line_range_r(0,
                                                  b,
                                                  cell,
                                                  token,
                                                  ptfl,
                                                  ptfc,
                                                  ptll,
                                                  ptlc,
                                                  path);

                          /* Check if the token was found */

                          if (tfl != -1 || tfc != -1 || tll != -1 || tlc != -1) found = 1;
                        }
                      }
                    }
                    break;
                  }

                  if (!found)
                  {
                    cellcount++;

                    head = head->Next;
                  }
                }

                if (!found) row = row->Next;
              }

              if (found)
              {
                if (path)
                {
                  token_path * temp;

                  /* Increment the tokenutils_npaths counter and allocate memory for a */
                  /* new token_path structure.                                         */

                  tokenutils_npaths++;

                  /* Note 'tokenutils_npaths + 1' - an extra structure for marking the */
                  /* end of the array.                                                 */

                  if (*path) temp = realloc(*path, (tokenutils_npaths + 1) * sizeof(token_path));
                  else       temp =         malloc((tokenutils_npaths + 1) * sizeof(token_path));

                  /* Complain and bail out if the allocation failed */

                  if (!temp)
                  {
                    if (*path)
                    {
                      free(*path);
                      *path = NULL;
                    }

                    show_error_ret(make_no_memory_error(1));

                    return 0;
                  }
                  else *path = temp;

                  /* Fill in the new token_path structure */

                  (*path)[tokenutils_npaths - 1].line  = cl;
                  (*path)[tokenutils_npaths - 1].chunk = cc;
                  (*path)[tokenutils_npaths - 1].head  = head;

                  /* Fill in the terminating structure */

                  (*path)[tokenutils_npaths].line  = -1;
                  (*path)[tokenutils_npaths].chunk = -1;
                  (*path)[tokenutils_npaths].head  = NULL;
                }

                /* Since we've found the token within a table inside this line, */
                /* and the token_path is filled in, want to exit now.           */

                if (fline)  *fline  = tfl;
                if (fchunk) *fchunk = tfc;
                if (lline)  *lline  = tll;
                if (lchunk) *lchunk = tlc;

                return tokenutils_npaths;
              }

            /* Closure of 'if (cellarray)' */
            }

          /* Closure of 'if (d->cdata[cc].t->tagno == TAG_TABLE)' */
          }

          cc++;

        /* Closure of 'else' section to 'if (d->cdata[cc].t == token)' */
        }

      /* Closure of 'while' loop going through all the chunks */
      }

      /* If we haven't found a chunk yet, go to the next line */

      if (!found) cl ++;
    }

    if (found)
    {
      if (cl >= d->nlines) cl = d->nlines - 1;
      fl = cl, fc = cc;

      /* If a line and chunk have been found, record them in fl and fc */

      if (cl >= 0 && cc >= 0)
      {
        /* Only try to find the last line / chunk if asked for them */

        if (lline || lchunk)
        {
          /* Start at the next line and if this starts with the given */
          /* token, keep moving on until a line which doesn't start   */
          /* with the token is found.                                 */

          cl++;
          while (cl < d->nlines && d->cdata[d->ldata[cl].chunks].t == token) cl++;

          /* Coming out of the above loop either we'll be at cl = d->nlines, */
          /* or cl will be the first line that doesn't start with the given  */
          /* token. In either case, must decrement cl by one to get back to  */
          /* a line which does include the token.                            */

          cl--;

          if (lchunk)
          {
            /* Now search the chunks for the token, starting at the last chunk */

            cc = d->ldata[cl].chunks + d->ldata[cl].n - 1;

            while (cc >= d->ldata[cl].chunks && d->cdata[cc].t != token) cc--;

            /* If cc is still in range, a token was found on this line (it */
            /* should always be, but something might go wrong above and    */
            /* this check is worth doing for robustness). So copy the last */
            /* line and chunk details to ll and lc.                        */

            if (cc >= d->ldata[cl].chunks) ll = cl, lc = cc;
          }
          else ll = cl;
        }
      }
    }
  }

  /* Fill in any requested values */

  if (fline)  *fline  = fl;
  if (fchunk) *fchunk = fc;
  if (lline)  *lline  = ll;
  if (lchunk) *lchunk = lc;

  return tokenutils_npaths;
}

/*************************************************/
/* tokenutils_find_ancestor_line()               */
/*                                               */
/* Returns the line in a given browser's main    */
/* line list in which a given token lies, or -1  */
/* if the token can't be found.                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token and lines;  */
/*                                               */
/*             Pointer to the HStream struct to  */
/*             find.                             */
/*                                               */
/* Returns:    Line number that the token lies   */
/*             in, in the main line list - so    */
/*             the token could well lie inside   */
/*             a table within that line. -1 is   */
/*             returned if the token is not      */
/*             found in the line list at all.    */
/*************************************************/

int tokenutils_find_ancestor_line(browser_data * b, HStream * t)
{
  int          l;
  int          chunk, depth, noline = 0;
  token_path * path = NULL;

  depth = tokenutils_line_range(b, t, &l, &chunk, NULL, NULL, &path);

  if (l < 0) noline = 1;
  else
  {
    /* If a line was found and depth is non-zero, the line was inside */
    /* a table - want to find the parent line of the table            */

    if (depth) l = path[depth - 1].line;
    if (l < 0) noline = 1;
  }

  if (path) free(path);

  if (noline) return -1;

  return l;
}

/*************************************************/
/* tokenutils_find_token                         */
/*                                               */
/* For a given token in a given reformat_cell,   */
/* returns the line and chunk number that the    */
/* token spans.                                  */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             Pointer to a reformat_cell struct */
/*             containing the token or a table   */
/*             which holds the token;            */
/*                                               */
/*             Pointer to the token;             */
/*                                               */
/*             Pointer to an int, into which the */
/*             first line number is placed;      */
/*                                               */
/*             Pointer to an int, into which the */
/*             first chunk number is placed;     */
/*                                               */
/*             Pointer to an int, into which the */
/*             last line number is placed;       */
/*                                               */
/*             Pointer to an int, into which the */
/*             last chunk number is placed;      */
/*                                               */
/* Returns:    See parameters list, and note     */
/*             that '-1' will be returned in all */
/*             four fields should be token not   */
/*             be found in a line to start with; */
/*                                               */
/* Assumes:    Any of the pointers may be NULL.  */
/*************************************************/

void tokenutils_find_token(browser_data * b, reformat_cell * d, HStream * token, int * fline, int * fchunk, int * lline, int * lchunk)
{
  int fl = -1, fc = -1, ll = -1, lc = -1; /* First Line, First Chunk, Last Line, Last Chunk */

  /* Start with all values at -1; saves having to fill them */
  /* in at any given failure point.                         */

  if (fline)  *fline  = fl;
  if (fchunk) *fchunk = fc;
  if (lline)  *lline  = ll;
  if (lchunk) *lchunk = lc;

  /* Can only try to find the token if the browser_data */
  /* and token pointers were not NULL.                  */

  if (b && d && token && d->nlines)
  {
    int cl, cc  = -1, mc; /* Current Line, Current Chunk, Maximum Chunk */
    int found   = 0;

    /* Must find the first line / chunk even if not asked for it */

    cl = 0;

    /* Loop through all lines */

    while (cl < d->nlines && !found)
    {
      cc = d->ldata[cl].chunks;
      mc = cc + d->ldata[cl].n;

      /* Go through this line's chunks looking for the token */

      while (cc < mc && !found)
      {
        if (d->cdata[cc].t == token) found = 1;

        /* If not found, go to the next chunk */

        else cc++;
      }

      /* If we haven't found a chunk yet, go to the next line */

      if (!found) cl ++;
    }

    if (found)
    {
      if (cl >= d->nlines) cl = d->nlines - 1;
      fl = cl, fc = cc;

      /* If a line and chunk have been found, record them in fl and fc */

      if (cl >= 0 && cc >= 0)
      {
        /* Only try to find the last line / chunk if asked for them */

        if (lline || lchunk)
        {
          /* Start at the next line and if this starts with the given */
          /* token, keep moving on until a line which doesn't start   */
          /* with the token is found.                                 */

          cl++;
          while (cl < d->nlines && d->cdata[d->ldata[cl].chunks].t == token) cl++;

          /* Coming out of the above loop either we'll be at cl = d->nlines, */
          /* or cl will be the first line that doesn't start with the given  */
          /* token. In either case, must decrement cl by one to get back to  */
          /* a line which does include the token.                            */

          cl--;

          if (lchunk)
          {
            /* Now search the chunks for the token, starting at the last chunk */

            cc = d->ldata[cl].chunks + d->ldata[cl].n - 1;

            while (cc >= d->ldata[cl].chunks && d->cdata[cc].t != token) cc--;

            /* If cc is still in range, a token was found on this line (it */
            /* should always be, but something might go wrong above and    */
            /* this check is worth doing for robustness). So copy the last */
            /* line and chunk details to ll and lc.                        */

            if (cc >= d->ldata[cl].chunks) ll = cl, lc = cc;
          }
          else ll = cl;
        }
      }
    }
  }

  /* Fill in any requested values */

  if (fline)  *fline  = fl;
  if (fchunk) *fchunk = fc;
  if (lline)  *lline  = ll;
  if (lchunk) *lchunk = lc;

  /* Finished */

  return;
}

/*************************************************/
/* tokenutils_token_cell_offset()                */
/*                                               */
/* When a token has been found to lie in a table */
/* (tokenutils_line_range has filled in an       */
/* array of token_path structures) this call     */
/* can be used to quickly find the x and y       */
/* offset from the top left of the main page     */
/* that the line list the token was in is        */
/* positioned at (i.e. for tables, the offset of */
/* the table cell the token lies in). This can   */
/* be used as an origin shift for any subsequent */
/* redraws (say).                                */
/*                                               */
/* External callers trying to then find the      */
/* token x and y position should remember to     */
/* work this out for the correct line array,     */
/* which will be indicated by the first entry    */
/* in the token_path array.                      */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token_path array; */
/*                                               */
/*             Pointer to the token_path array;  */
/*                                               */
/*             Pointer to an int, in which the   */
/*             x offset will be written;         */
/*                                               */
/*             Pointer to an int, in which the   */
/*             y offset will be written.         */
/*                                               */
/* Assumes:    Any pointer may be NULL. If the   */
/*             token_path array is NULL, then    */
/*             an offset of 0,0 is given.        */
/*************************************************/

void tokenutils_token_offset(browser_data * b, token_path * path, int * offset_x, int * offset_y)
{
  int             actx = 0, acty = 0;
  reformat_cell * cell = b->cell;

  /* Only start working things out if there's a token_path array */

  if (path && tokenutils_npaths)
  {
    int              index = tokenutils_npaths - 1;
    int              line, chunk;
    table_stream   * table;
    table_headdata * head;
    reformat_cell  * cellarray = NULL;
    reformat_cell  * c         = NULL;

    /* Find the line, chunk, headdata structure, table structure and */
    /* cell array for the last item in the token_path array.         */

    line      = path[index].line;
    chunk     = path[index].chunk;
    head      = path[index].head;
    table     = (table_stream *) cell->cdata[chunk].t;
    cellarray = table->cells;

    /* Increment by the offset of the top left of the table. */
    /* Since the last item in the array corresponds to the   */
    /* browser_data structure's cell array itself, here we   */
    /* use b->cell (stored in 'cell' above) rather than a    */
    /* table-derived cell.                                   */

    actx += redraw_start_x(b, cell, cell->cdata[chunk].t, line);
    acty += cell->ldata[line].y + cell->ldata[line].h;

    /* If there's a cell array, find the cell the current headdata */
    /* item is represented by.                                     */

    if (cellarray) c = &cellarray[table->ColSpan * head->RowOffs + head->ColOffs];

    /* If there's a cell, offset actx and acty by the top left */
    /* hand offset of that cell.                               */

    if (c)
    {
      int incx, incy;

      convert_pair_to_os(c->x, c->y, &incx, &incy);

      actx += incx, acty += incy;
    }

    /* Now do much the same for the remaining items in the token_path array */

    index--;

    while (index >= 0 && c && c->ldata && c->cdata)
    {
      line      = path[index].line;
      chunk     = path[index].chunk;
      head      = path[index].head;
      table     = (table_stream *) c->cdata[chunk].t;
      cellarray = table->cells;

      actx += redraw_start_x(b, c, c->cdata[chunk].t, line);
      acty += c->ldata[line].y + c->ldata[line].h;

      if (cellarray) c = &cellarray[table->ColSpan * head->RowOffs + head->ColOffs];

      if (c)
      {
        int incx, incy;

        convert_pair_to_os(c->x, c->y, &incx, &incy);

        actx += incx, acty += incy;
      }

      index--;
    }
  }

  /* actx and acty will either have been calculated from the token_path */
  /* array or will still be at 0 if no array was given.                 */

  if (offset_x) *offset_x = actx;
  if (offset_y) *offset_y = acty;

  return;
}

/*************************************************/
/* tokenutils_find_cell()                        */
/*                                               */
/* Examines a token_path array and returns the   */
/* address of the reformat_cell structure that   */
/* corresponds to the last entry.                */
/*                                               */
/* Parameters: Pointer to the reformat_cell that */
/*             the token_path array refers to in */
/*             the first entry (e.g. the 'cell'  */
/*             field of the parent browser_data  */
/*             structure);                       */
/*                                               */
/*             Number of entries in the array;   */
/*                                               */
/*             Pointer to the first entry.       */
/*                                               */
/* Returns:    Pointer to the reformat_cell      */
/*             structure corresponding to the    */
/*             last entry, or NULL if none may   */
/*             be found.                         */
/*************************************************/

reformat_cell * tokenutils_find_cell(reformat_cell * cell, int depth, token_path * path)
{
  if (depth && path && path[0].line >= 0 && path[depth - 1].line >= 0)
  {
    table_stream   * table;
    table_headdata * head;
    reformat_cell  * cellarray    = NULL;
    reformat_cell  * c            = NULL;
    int              index        = depth - 1;
    int              line, chunk;

    /* Start looking at the token_path array. The last */
    /* entry refers to the main line array, so get to  */
    /* the appropriate reformat_cell structure through */
    /* the main line list.                             */

    line  = path[index].line;
    chunk = path[index].chunk;
    head  = path[index].head;

    table     = (table_stream *) cell->cdata[chunk].t;
    cellarray = table->cells;

    if (cellarray) c = &cellarray[table->ColSpan * head->RowOffs + head->ColOffs];

    index--;

    /* Need to look at subsequent cell line arrays for any */
    /* nested tables.                                      */

    while (index >= 0 && c)
    {
      line  = path[index].line;
      chunk = path[index].chunk;
      head  = path[index].head;

      table     = (table_stream *) c->cdata[chunk].t;
      cellarray = table->cells;

      if (cellarray) c = &cellarray[table->ColSpan * head->RowOffs + head->ColOffs];

      index--;
    }

    return c;
  }

  return NULL;
}

/*************************************************/
/* tokenutils_token_cell()                       */
/*                                               */
/* Returns the reformat_cell that a given token  */
/* lies in. This may be NULL.                    */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the token;            */
/*                                               */
/*             Pointer to the token.             */
/*************************************************/

reformat_cell * tokenutils_token_cell(browser_data * b, HStream * token)
{
  table_headdata * head;
  table_row      * row;
  table_stream   * table;

  reformat_cell  * cellarray;
  int              cellnumber;

  if (!b || !token) return NULL;

  /* Find the table the token lies in */

  head = token->parent;
  if (!head) return NULL;

  row = head->parent;
  if (!row) return NULL;

  table = row->parent;
  if (!table) return NULL;

  cellarray = table->cells;
  if (!cellarray) return NULL;

  /*�Work out what number this cell is in the cell array */

  cellnumber = head->ColOffs + table->ColSpan * head->RowOffs;

  if (cellnumber > table->ncells) return NULL;
  else                            return &cellarray[cellnumber];
}

/*************************************************/
/* tokenutils_within_distance()                  */
/*                                               */
/* Returns 1 if a given token is within a given  */
/* distance of another, vertically.              */
/*                                               */
/* Parameters: Pointer to a browser_data struct  */
/*             relevant to the tokens;           */
/*                                               */
/*             Pointer to the first token;       */
/*                                               */
/*             Pointer to the second token;      */
/*                                               */
/*             Distance that the tokens must be  */
/*             within.                           */
/*                                               */
/* Returns:    1 if the tokens are within the    */
/*             given distance of each other,     */
/*             else 0.                           */
/*                                               */
/* Assumes:    If either HStream pointer is NULL */
/*             the function return 1.            */
/*************************************************/

int tokenutils_within_distance(browser_data * b, HStream * t1, HStream * t2, int distance)
{
  int             lfir1, llas1, lfir2, llas2;
  int             check;
  reformat_cell * cell = b->cell;

  if (!t1 || !t2) return 1;

  /* Ensure distance is a positive number and find the */
  /* lines that the tokens span.                       */

  if (distance < 0) distance = -distance;

  tokenutils_line_range(b, t1, &lfir1, NULL, &llas1, NULL, NULL);
  tokenutils_line_range(b, t2, &lfir2, NULL, &llas2, NULL, NULL);

  /* Special case, tokens must be at zero distance effectively */

  if (lfir1 == lfir2 || llas1 == llas2) return 1;

  /* Want to ensure that the maximum distance between the tokens */
  /* is taken for the check. E.g. if t1 is above t2 (i.e. lfir1  */
  /* is less than lfir2) then want to check the distance between */
  /* the top line of t1 to the bottom line of t2.                */

  if (lfir1 < lfir2) check = cell->ldata[lfir1].y + cell->ldata[lfir1].h - cell->ldata[lfir2].y;
  else               check = cell->ldata[lfir2].y + cell->ldata[lfir2].h - cell->ldata[lfir1].y;

  return (check <= distance);
}