Logo Search packages:      
Sourcecode: manedit version File versions  Download package

mpfio.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#ifdef HAVE_GZIP
#include <zlib.h>
#endif
#ifdef HAVE_BZIP2
#include <bzlib.h>
#endif

#include "../include/fio.h"
#include "../include/disk.h"
#include "../include/string.h"
#include "../include/strexp.h"

#include "mpfio.h"


#define MAX(a,b)        (((a) > (b)) ? (a) : (b))
#define MIN(a,b)        (((a) < (b)) ? (a) : (b))
#define CLIP(a,l,h)     (MIN(MAX((a),(l)),(h)))

#define ISCR(c)         (((c) == '\n') || ((c) == '\r'))


/* Low level file IO (for loading and saving as regular or compressed). */
static char *MPLoadFileBZip2(const char *path, int *rtn_buf_len);
static char *MPLoadFileGZip(const char *path, int *rtn_buf_len);
static char *MPLoadFile(const char *path, int *rtn_buf_len);
static void MPSaveFileBZip2(const char *path, const char *buf, int buf_len);
static void MPSaveFileGZip(const char *path, const char *buf, int buf_len);
static void MPSaveFile(const char *path, const char *buf, int buf_len);

/* Groff format string parsers. */
static const char *MPLoadStringNextCR(const char *buf);
static const char *MPLoadStringNextDoubleQuote(const char *buf);
static void MPLoadExplodeNewLines(
        char ***line, int *total_lines,
        const char *buf, const char *buf_end
);
static void MPLoadExplodeParseMPFmt(
      char ***line, int *total_lines,
        const char *buf, const char *buf_end
);
static char *MPSaveFormatMP(char *line_ptr);

static char *MPSubStringIterate(
        char *buf, char **sub_start,
        const char *token, const char *replace,
      int strip_tail
);
static char *MPSubString(
      char *buf, const char *token, const char *replace,
      int strip_tail
);

/* Load and save front ends. */
int MPLoad(
        const char *filename, const char *data,
        mp_header_struct **mp_header_rtn,
        mp_section_struct ***mp_section_rtn, int *mp_total_sections_rtn,
      void *client_data, int (*progress_cb)(long, long, void *)
);
int MPSave(
        const char *filename, char **data_rtn,
        mp_header_struct *mp_header,
        mp_section_struct **mp_section, int mp_total_sections,
        void *client_data, int (*progress_cb)(long, long, void *)
);

/* Deallocaters. */
void MPDeleteHeader(mp_header_struct *mp_header);
void MPDeleteAllSections(
        mp_section_struct **mp_section,
        int mp_total_sections
);



/*
 *    Load from bzip2 compressed file, called by MPLoadFile().
 */
static char *MPLoadFileBZip2(const char *path, int *rtn_buf_len)
{
#ifdef HAVE_BZIP2
        int status, bzerror;
        FILE *fp;
        BZFILE *zfp;
#define bzip2_chunk_size        32767
        char *buf = NULL;
        int buf_cur_pos, buf_len = 0;


        if(path == NULL)
            return(NULL);

        fp = FOpen(path, "rb");
        if(fp == NULL)
            return(NULL);
        zfp = BZ2_bzReadOpen(
            &bzerror, fp,
            0,          /* Do not conserve memory, do this fast. */
            0,          /* No need to be verbose. */
            NULL, 0     /* Nonsense buffer, always NULL and 0. */
        );
        if(bzerror != BZ_OK)
        {
            BZ2_bzReadClose(&bzerror, zfp);
            FClose(fp);
            return(NULL);
        }

        while(1)
        {
            /* Record current buffer index and increase buffer
             * allocation.
             */
            buf_cur_pos = buf_len;
            buf_len += bzip2_chunk_size;
            buf = (char *)realloc(
                buf,
                (buf_len + 1) * sizeof(char)
            );
            if(buf == NULL)
            {
                buf_len = 0;
                break;
            }


            status = BZ2_bzRead(&bzerror, zfp, &buf[buf_cur_pos], bzip2_chunk_size);
            if((status <= 0) ||
               ((bzerror != BZ_OK) && (bzerror != BZ_STREAM_END))
            )
            {
                buf[buf_cur_pos] = '\0';
                break;
            }

            if((buf_cur_pos + status) <= buf_len)
                buf[buf_cur_pos + status] = '\0';
            else
                buf[buf_len - 1] = '\0';
        }

        BZ2_bzReadClose(&bzerror, zfp);
        zfp = NULL;
        FClose(fp);
        fp = NULL;

        if(rtn_buf_len != NULL)
            *rtn_buf_len = buf_len;

        return(buf);
#undef bzip2_chunk_size
#else
      return(NULL);
#endif
}

/*
 *    Load from gzip compressed file, called by MPLoadFile().
 */
static char *MPLoadFileGZip(const char *path, int *rtn_buf_len)
{
#ifdef HAVE_GZIP
        int status;
        gzFile zfp;
#define zlib_chunk_size         32767
        char *buf = NULL;
        int buf_cur_pos, buf_len = 0;



        if(path == NULL)
            return(NULL);

        zfp = gzopen(path, "rb");
        if(zfp == NULL)
            return(NULL);

        while(1)
        {
            /* Record current buffer index and increase buffer
             * allocation.
             */
            buf_cur_pos = buf_len;
            buf_len += zlib_chunk_size;
            buf = (char *)realloc(
                buf,
                (buf_len + 1) * sizeof(char)
            );
            if(buf == NULL)
            {
                buf_len = 0;
                break;
            }

            status = gzread(zfp, &buf[buf_cur_pos], zlib_chunk_size);
            if(status <= 0)
            {
                buf[buf_cur_pos] = '\0';
                break;
            }

            if((buf_cur_pos + status) <= buf_len)
                buf[buf_cur_pos + status] = '\0';
            else
                buf[buf_len - 1] = '\0';
        }

        gzclose(zfp);
        zfp = NULL;

      if(rtn_buf_len != NULL)
          *rtn_buf_len = buf_len;

        return(buf);
#undef zlib_chunk_size
#else
        return(NULL);
#endif
}

/*
 *    Front end to load file, the extension of the given path will
 *    be checked and loaded accordingly (if it is compressed or not).
 */
static char *MPLoadFile(const char *path, int *rtn_buf_len)
{
      const char *cstrptr, *ext_ptr;
      int need_load_regular = 0;


      if(rtn_buf_len != NULL)
          *rtn_buf_len = 0;

      if(path == NULL)
          return(NULL);

      /* Get pointer to extension (if any). */
      cstrptr = strrchr(path, DIR_DELIMINATOR);
      if(cstrptr != NULL)
          cstrptr++;
      else
          cstrptr = path;
      ext_ptr = strrchr(cstrptr, '.');

      if(ext_ptr != NULL)
      {
            if(!strcasecmp(ext_ptr, ".bz2"))
                return(MPLoadFileBZip2(path, rtn_buf_len));
          else if(!strcasecmp(ext_ptr, ".gz"))
            return(MPLoadFileGZip(path, rtn_buf_len));
          else
            need_load_regular = 1;
      }
      else
      {
          need_load_regular = 1;
      }

      /* Load as regular non-compressed file? */
      if(need_load_regular)
      {
          FILE *fp = FOpen(path, "rb");
          char *buf;
          int buf_len;
          struct stat stat_buf;

          if(fp == NULL)
            return(NULL);

          if(fstat(fileno(fp), &stat_buf))
            stat_buf.st_size = 0;

          buf_len = stat_buf.st_size;
          if(buf_len > 0)
            buf = (char *)malloc((buf_len + 1) * sizeof(char));
          else
            buf = NULL;
          if(buf == NULL)
          {
            FClose(fp);
            return(NULL);
          }

          fread(buf, sizeof(char), buf_len, fp);
          buf[buf_len] = '\0';

          FClose(fp);

          if(rtn_buf_len != NULL)
            *rtn_buf_len = buf_len;

          return(buf);
      }
      else
      {
          return(NULL);
      }
}


/*
 *    Saves the data using bzip2 compression, called from MPSaveFile().
 */
static void MPSaveFileBZip2(const char *path, const char *buf, int buf_len)
{
#ifdef HAVE_BZIP2
        int bzerror;
        FILE *fp;
        BZFILE *zfp;
#define bzip2_chunk_size        32767
        int buf_cur_pos, buf_cur_len;


        fp = FOpen(path, "wb");
        if(fp == NULL)
            return;
        zfp = BZ2_bzWriteOpen(
            &bzerror, fp,
            9,          /* Compression strength (0 to 9). */
            0,          /* No need to be verbose. */
            30          /* Work factor (default is 30). */
        );
        if(bzerror != BZ_OK)
        {
            BZ2_bzWriteClose(&bzerror, zfp, 0, NULL, NULL);
            FClose(fp);
            return;
        }

        /* Begin writing compressed file. */
        buf_cur_pos = 0;
        while(buf_cur_pos < buf_len)
        {
            /* Calculate current segment length, making it no longer
             * than (buf_len - buf_cur_pos) or bzip2_chunk_size,
             * whichever is shorter.
             */
            buf_cur_len = buf_len - buf_cur_pos;
            if(buf_cur_len > bzip2_chunk_size)
                buf_cur_len = bzip2_chunk_size;
            if(buf_cur_len <= 0)
                break;

            /* Write the current calculated buffer segment. */
            BZ2_bzWrite(&bzerror, zfp, (void *)&buf[buf_cur_pos], buf_cur_len);
            if(bzerror != Z_OK)
                break;

            /* Increment current position past the number of bytes that
             * were written.
             */
            buf_cur_pos += buf_cur_len;
        }

        BZ2_bzWriteClose(&bzerror, zfp, 0, NULL, NULL);
        zfp = NULL;
        FClose(fp);
        fp = NULL;

#undef bzip2_chunk_size
#endif
}

/*
 *      Saves the data using gzip compression, called from MPSaveFile().
 */
static void MPSaveFileGZip(const char *path, const char *buf, int buf_len)
{
#ifdef HAVE_GZIP
        int status;
        gzFile zfp;
#define zlib_chunk_size         32767
      int buf_cur_pos, buf_cur_len;


        zfp = gzopen(path, "wb");
        if(zfp == NULL)
            return;

        /* Begin writing compressed file. */
        buf_cur_pos = 0;
        while(buf_cur_pos < buf_len)
        {
            /* Calculate current segment length, making it no longer
             * than (buf_len - buf_cur_pos) or bzip2_chunk_size,
             * whichever is shorter.
             */
            buf_cur_len = buf_len - buf_cur_pos;
            if(buf_cur_len > zlib_chunk_size)
                buf_cur_len = zlib_chunk_size;
            if(buf_cur_len <= 0)
                break;

            /* Write the current calculated buffer segment. */
          status = gzwrite(zfp, (const voidp)&buf[buf_cur_pos], buf_cur_len);
            if(status <= 0)
                break;

            /* Increment current position past the number of bytes that
             * were written.
             */
            buf_cur_pos += buf_cur_len;
        }

        gzclose(zfp);
        zfp = NULL;

#undef zlib_chunk_size
#endif
}

/*
 *      Front end to save to file, the extension of the given path will
 *      be checked and saved accordingly (needing compression or not).
 */
static void MPSaveFile(const char *path, const char *buf, int buf_len)
{
        const char *cstrptr, *ext_ptr;
        int need_save_regular = 0;


        if((path == NULL) || (buf == NULL) || (buf_len <= 0))
            return;

        /* Get pointer to extension (if any). */
        cstrptr = strrchr(path, DIR_DELIMINATOR);
        if(cstrptr != NULL)
            cstrptr++;
        else
            cstrptr = path;
        ext_ptr = strrchr(cstrptr, '.');

        if(ext_ptr != NULL)
        {
            if(!strcasecmp(ext_ptr, ".bz2"))
                MPSaveFileBZip2(path, buf, buf_len);
            else if(!strcasecmp(ext_ptr, ".gz"))
                MPSaveFileGZip(path, buf, buf_len);
            else
                need_save_regular = 1;

          /* Saved as one of the compressed formats? If so then just
           * return now and skip further processing.
           */
          if(!need_save_regular)
            return;
        }
        else
        {
            need_save_regular = 1;
        }

        /* Save as regular non-compressed file? */
        if(need_save_regular)
        {
            FILE *fp = FOpen(path, "wb");
            if(fp == NULL)
                return;

          fwrite(buf, sizeof(char), buf_len, fp);

            FClose(fp);
        }
}


/*
 *    Returns pointer in buf to next occurance of non-escaped
 *    newline character or NULL if there isn't any.
 */
static const char *MPLoadStringNextCR(const char *buf)
{
      while((*buf) != '\0')
      {
          if((*buf) == '\\')
          {
            buf++;            /* Increment past current '\\' char. */

            /* Next char is a null? */
            if((*buf) == '\0')
                return(NULL);

            /* Skip whatever next char was. */
            buf++;
            continue;
          }

          if(ISCR(*buf))
               return(buf);

          buf++;
      }

      return(NULL);
}

/*
 *      Returns pointer in buf to next occurance of non-escaped
 *      double quote character or NULL if there isn't any.
 */
static const char *MPLoadStringNextDoubleQuote(const char *buf)
{
        while((*buf) != '\0')
        {
            if((*buf) == '\\')
            {
                buf++;          /* Increment past current '\\' char. */

                /* Next char is a null? */
                if((*buf) == '\0')
                    return(NULL);

                /* Skip whatever next char was. */
                buf++;
                continue;
            }

            if((*buf) == '"')
               return(buf);

            buf++;
        }

        return(NULL);
}

/*
 *    Explodes the buf into lines from buf to buf_end (but not
 *    including buf_end) at all non-escaped deliminated newline
 *    characters..
 *
 *    Inputs are assumed valid.
 */
static void MPLoadExplodeNewLines(
      char ***line, int *total_lines,
      const char *buf, const char *buf_end
)
{
      const char *buf_limit_ptr;
      const char *buf_cur_ptr;
      int n, cur_seg_len;
      char *line_ptr;


      if((*total_lines) < 0)
          (*total_lines) = 0;

      buf_cur_ptr = buf;

      do
      {
          buf_limit_ptr = MPLoadStringNextCR(buf_cur_ptr);
          if((buf_limit_ptr == NULL) || (buf_limit_ptr > buf_end))
              buf_limit_ptr = buf_end;

          cur_seg_len = MAX(
            (int)buf_limit_ptr - (int)buf_cur_ptr, 0
          );

          /* Allocate a new line. */
          n = (*total_lines);
          (*total_lines) = n + 1;
          (*line) = (char **)realloc(*line, (*total_lines) * sizeof(char *));
          if((*line) == NULL)
          {
            (*total_lines) = 0;
            break;
          }
          else
          {
            line_ptr = (char *)malloc((cur_seg_len + 1) * sizeof(char));
            (*line)[n] = line_ptr;
            if(line_ptr != NULL)
            {
                if(cur_seg_len > 0)
                  strncpy(line_ptr, buf_cur_ptr, cur_seg_len);
                line_ptr[cur_seg_len] = '\0';
            }
          }

          /* Seek past current buffer limit pointer. */
          buf_cur_ptr = buf_limit_ptr + 1;

      } while(buf_cur_ptr < buf_end);

}


/*
 *      Coppies the given buffer into a local tempory buffer then
 *    parses it with all manual page format substitutions into
 *    XML format.
 *
 *    Then explodes each line deliminated by non-escaped newline
 *    characters.
 *
 *      Inputs are assumed valid.
 */
static void MPLoadExplodeParseMPFmt(
        char ***line, int *total_lines,
        const char *buf, const char *buf_end
)
{
      char *tmp_buf;
        int cur_seg_len;


      /* Copy given buffer to a tempory buffer. */
      cur_seg_len = MAX((int)buf_end - (int)buf, 0);
      tmp_buf = (char *)malloc((cur_seg_len + 1) * sizeof(char));
      if(tmp_buf == NULL)
          return;

      if(cur_seg_len > 0)
          memcpy(tmp_buf, buf, cur_seg_len * sizeof(char));
      tmp_buf[cur_seg_len] = '\0';

      /* Make substitutions to tmp_buf, note that the order is
       * important.
       *
       * When adding new substitutions, make sure the inverse operation
       * is included in function MPSaveFormatMP() so that saving does
       * the inverse substitution. Remember to make each inverse
       * substitution in reverse order from substitutions here.
       *
       * Don't forget to add an universe operation below in
       * MPSaveFormatMP()!
       */
      tmp_buf = MPSubString(tmp_buf, "\\-",     "-",        0);
      tmp_buf = MPSubString(tmp_buf, "&",     "&amp;",      0);
      tmp_buf = MPSubString(tmp_buf, "<", "&lt;",     0);
      tmp_buf = MPSubString(tmp_buf, ">", "&gt;",     0);
      /* Now we can replace to replace strings with < and >. */
      tmp_buf = MPSubString(tmp_buf, ".BI",     "<BI>",           1);
      tmp_buf = MPSubString(tmp_buf, ".BR",   "<BR>",       1);
      tmp_buf = MPSubString(tmp_buf, ".br",     "<br>",     1);
      tmp_buf = MPSubString(tmp_buf, ".fi",     "<fi>",     1);
      tmp_buf = MPSubString(tmp_buf, ".If",     "<If>",           1);
      tmp_buf = MPSubString(tmp_buf, ".Im",     "<Im>",           1);
      tmp_buf = MPSubString(tmp_buf, ".IP",   "<IP>",       1);
      tmp_buf = MPSubString(tmp_buf, ".LP",   "<LP>",       1);
      tmp_buf = MPSubString(tmp_buf, ".RP",   "<RP>",       1);
      tmp_buf = MPSubString(tmp_buf, ".TP",   "<TP>",         1);
      tmp_buf = MPSubString(tmp_buf, ".nf",   "<nf>",       1);
      tmp_buf = MPSubString(tmp_buf, ".PP",   "<PP>",       1);
      tmp_buf = MPSubString(tmp_buf, ".RB",   "<RB>",       1);
      tmp_buf = MPSubString(tmp_buf, ".RI",   "<RI>",       1);
      tmp_buf = MPSubString(tmp_buf, ".Sp",     "<Sp>",           1);
      tmp_buf = MPSubString(tmp_buf, ".ta",     "<ta>",           1);
      /* Single character id substitutions after two character ids. */
      tmp_buf = MPSubString(tmp_buf, ".B",    "<B>",        1);
      tmp_buf = MPSubString(tmp_buf, ".I",    "<I>",        1);
/*
 * Need to handle:

.ad b
.hy 1
.if t

 */
      tmp_buf = MPSubString(tmp_buf, "\\1i",    "<li>",           1);
      tmp_buf = MPSubString(tmp_buf, "\\fB",  "<fB>",       0);
      tmp_buf = MPSubString(tmp_buf, "\\fI",    "<fI>",           0);
      tmp_buf = MPSubString(tmp_buf, "\\fR",  "<fR>",         0);
      tmp_buf = MPSubString(tmp_buf, "\\fP",    "<fP>",           0);


      /* Get new length, since it may have changed after the above
       * substitutions.
       */
      cur_seg_len = strlen(tmp_buf);

      /* Explode at all new line occurances. */
      MPLoadExplodeNewLines(
          line, total_lines,
          tmp_buf, (const char *)(tmp_buf + cur_seg_len)
      );

      /* Free the tempory buffer. */
      free(tmp_buf);
}


/*
 *      Takes the given buffer and makes XML to manual page substitutions
 *    then returns a reallocated buffer. Basically doing the opposite
 *    of MPLoadExplodeParseMPFmt() but does not concatinate any lines.
 *
 *    The given buffer should no longer be referenced after this call.
 *
 *      Inputs are assumed valid.
 */
static char *MPSaveFormatMP(char *line_ptr)
{
      char *tmp_buf = line_ptr;


        /* Make substitutions to tmp_buf, note that the order is
         * important and should be in reverse inverse order from function
       * MPLoadExplodeParseMPFmt().
       */
        tmp_buf = MPSubString(tmp_buf, "<fP>",  "\\fP",           0);
      tmp_buf = MPSubString(tmp_buf, "<fR>",  "\\fR",         0);
        tmp_buf = MPSubString(tmp_buf, "<fI>",  "\\fI",           0);
      tmp_buf = MPSubString(tmp_buf, "<fB>",  "\\fB",       0);
        tmp_buf = MPSubString(tmp_buf, "<li>",  "\\1i ",    0);

      /* Single character tags. */
        tmp_buf = MPSubString(tmp_buf, "<I>",   ".I ",            0);
        tmp_buf = MPSubString(tmp_buf, "<B>",   ".B ",            0);

        /* Two character tags. */
        tmp_buf = MPSubString(tmp_buf, "<ta>",  ".ta ",           0);
        tmp_buf = MPSubString(tmp_buf, "<Sp>",  ".Sp ",           0);
        tmp_buf = MPSubString(tmp_buf, "<RI>",  ".RI ",           0);
        tmp_buf = MPSubString(tmp_buf, "<RB>",  ".RB ",           0);
        tmp_buf = MPSubString(tmp_buf, "<PP>",  ".PP ",           0);
        tmp_buf = MPSubString(tmp_buf, "<nf>",  ".nf ",           0);
      tmp_buf = MPSubString(tmp_buf, "<TP>",  ".TP ",         0);
        tmp_buf = MPSubString(tmp_buf, "<RP>",  ".RP ",           0);
      tmp_buf = MPSubString(tmp_buf, "<LP>",  ".LP ",       0);
        tmp_buf = MPSubString(tmp_buf, "<IP>",  ".IP ",           0);
      tmp_buf = MPSubString(tmp_buf, "<Im>",    ".Im ",           0);
      tmp_buf = MPSubString(tmp_buf, "<If>",    ".If ",           0);
        tmp_buf = MPSubString(tmp_buf, "<fi>",  ".fi ",           0);
        tmp_buf = MPSubString(tmp_buf, "<br>",  ".br ",           0);
        tmp_buf = MPSubString(tmp_buf, "<BR>",  ".BR ",           0);
      tmp_buf = MPSubString(tmp_buf, "<BI>",    ".BI ",           0);

      /* Literal deliminator characters. */
        tmp_buf = MPSubString(tmp_buf, "&gt;",  ">",        0);
        tmp_buf = MPSubString(tmp_buf, "&lt;",  "<",        0);
        tmp_buf = MPSubString(tmp_buf, "&amp;",  "&",       0);
        tmp_buf = MPSubString(tmp_buf, "-",     "\\-",            0);

      return(tmp_buf);
}


/*
 *    Used by MPSubString(), no other functions should call this.
 *    Inputs assumed valid.
 *
 *    Returns a reallocated string from buf, the given buf should no
 *    longer be referenced. The sub_start will be a pointer to a pointer
 *    in buf in which the substitution should start at. If a
 *    substitution is made then sub_start will be set to the pointer
 *    in the new buffer after the substituted segment, otherwise if no
 *    substitution is made then sub_start will be set to point to NULL.
 *
 *    Inputs assumed valid.
 */
static char *MPSubStringIterate(
      char *buf, char **sub_start,
      const char *token, const char *replace, int strip_tail
)
{
      char *bp;
      int i, start_offset;
      int buf_len, token_len, replace_len;


      token_len = strlen(token);
      replace_len = strlen(replace);

      /* Search for token in buf at the given starting position
       * assumed to be in buf.
       */
      (*sub_start) = strstr(*sub_start, token);
      if((*sub_start) == NULL)
          return(buf);

      /* Number of bytes into buf that matched the token. */
      start_offset = (int)(*sub_start) - (int)buf;

      /* Now (*sub_start) is positioned at the start of the
       * substitution. Begin removing token string from buf.
       */
      bp = (*sub_start);
      while(1)
      {
          (*bp) = (*(bp + token_len));
          if((*bp) == '\0')
            break;
          else
            bp++;
      }

      /* Reallocate buffer to accomidate the current length plus
       * the added replace string.
       */
      buf_len = strlen(buf) + replace_len;
      buf = (char *)realloc(buf, (buf_len + 1) * sizeof(char));
      if(buf == NULL)
          return(NULL);

      /* Since reallocated buffer, need to calculate new start of
       * substitution pointer.
       */
      (*sub_start) = buf + start_offset;

      /* Replace string has length? */
      if(replace_len > 0)
      {
          /* Shift to make room for incoming string. */
          bp = buf + buf_len;
          while((bp - replace_len) >= (*sub_start))
          {
            (*bp) = (char)(*(bp - replace_len));
            bp--;
          }

          /* Put in replace buffer. */
          memcpy(*sub_start, replace, replace_len * sizeof(char));
      }

      /* Remove tailing spaces after replaced string. */
      if(strip_tail > 0)
      {
          for(i = 0; i < strip_tail; i++)
          {
            bp = (char *)((*sub_start) + replace_len);
            if(!ISBLANK(*bp))
                break;

            while((*bp) != '\0')
            {
                (*bp) = (char)(*(bp + 1));
                bp++;
            }
          }
      }


      /* Move (*sub_start) pointer past the replaced substitution. */
      (*sub_start) = (char *)((*sub_start) + replace_len);

      return(buf);
}

/*
 *    Replaces all occurances of token in the given string buf with the
 *    value of replace. If replace is NULL then an empty string "" will
 *    be used as the replace string.
 *
 *    If strip_tail is positive then the number of spaces (if any or
 *    less) occuring after the replaced string will be stripped.
 *    If strip_tail is 2 then "hi there  baby" "there" "ya" becomes
 *    "hi yababy".
 *
 *    The returned buffer will be either buf, NULL or a reallocated
 *    buf, in any case you should always use the return value as
 *    the new buf (works like realloc()).
 */
static char *MPSubString(
      char *buf, const char *token, const char *replace,
      int strip_tail
)
{
      char *last_sub_ptr;


      if(buf == NULL)
          return(NULL);

      if(token == NULL)
          return(buf);
      else if((*token) == '\0')
          return(buf);

      if(replace == NULL)
          replace = "";

      /* Start last substitution pointer at buf. */
      last_sub_ptr = buf;
      do
      {
          buf = MPSubStringIterate(
            buf, &last_sub_ptr,
            token, replace, strip_tail
          );
      }
      while(last_sub_ptr != NULL);

      return(buf);
}


/*
 *    Loads the contents of the manual page file specified by filename
 *    or data into the given pointers to substructures which this
 *    function will allocate.
 *
 *    If the progress_cb function is not NULL and it returns non-zero
 *    then the loading will be aborted. In which case return will be
 *    success but returned data will be short.
 *
 *    The loaded data needs to be freed with a call to MPDeleteHeader()
 *    and MPDeleteAllSections().
 *
 *      Returns non-zero on error, where -1 is a general error and -2 is
 *      file not found/not allowed to read.
 */
int MPLoad(
        const char *filename, const char *data,
        mp_header_struct **mp_header_rtn,
        mp_section_struct ***mp_section_rtn, int *mp_total_sections_rtn,
        void *client_data, int (*progress_cb)(long, long, void *)
)
{
      int n, status;
      long data_len;
      mp_header_struct *mp_header;
      mp_section_struct **mp_section, *mp_section_ptr;
      int mp_total_sections;
      char *buf, *buf_end;    /* Entire loaded buffer and length. */
      char *cur_buf_ptr, *limit_buf_ptr;
      char *cur_section_name;
      int buf_len, cur_seg_len;


      /* Reset returns. */
      if(mp_header_rtn != NULL)
          (*mp_header_rtn) = NULL;
      if(mp_section_rtn != NULL)
          (*mp_section_rtn) = NULL;
      if(mp_total_sections_rtn != NULL)
          (*mp_total_sections_rtn) = 0;


      /* Load from file? */
      if(filename != NULL)
      {
          /* Call progress callback? */
          if(progress_cb != NULL)
          {
            /* Start progress at 0. */
            status = progress_cb(0, 1, client_data);
            if(status)
            {
                return(0);
            }
          }

          /* Load data from file, this will return a dynamically
           * allocated buffer containing the literal data loaded from
           * file.  The buffer length buf_len will not include the null
           * terminating byte but buf will contain it if it is not NULL.
           */
          buf = MPLoadFile(filename, &buf_len);
          if((buf == NULL) || (buf_len <= 0))
          {
            free(buf);
            return(-1);
          }

          data_len = buf_len;
      }
      /* Load from data? */
      else if(data != NULL)
      {
          data_len = strlen(data);

            /* Call progress callback. */
            if(progress_cb != NULL)
            {
            /* Start progress at 0. */
                status = progress_cb(0, data_len, client_data);
                if(status)
                {
                    return(0);
                }
            }

            /* How much buffer do we need? */
            buf_len = data_len;
            if(buf_len < 0)
                buf_len = 0;

            /* Allocate buffer to load file into. */
            buf = (char *)malloc((buf_len + 1) * sizeof(char));
            if(buf == NULL)
                return(-1);

          /* Copy data to new buf. */
          memcpy(
            (void *)buf, (const void *)data,
            buf_len * sizeof(char)
          );
          buf[buf_len] = '\0';      /* Yes its allocated. */
      }
      else
      {
          /* No given filename or data to load from. */
          return(-1);
      }


      /* At this point the buffer buf is not NULL and should contain the
       * given data or the data loaded from file.
       */

      /* Begin parsing buffer, get buffer boundary pointers. */
      buf_end = buf + buf_len;
      cur_buf_ptr = buf;

      /* Parse header. */
      mp_header = (mp_header_struct *)calloc(1, sizeof(mp_header_struct));
      if(mp_header != NULL)
      {
          /* Get pointer to start of first section heading (if any). */
          limit_buf_ptr = strstr(cur_buf_ptr, ".SH");
          if(limit_buf_ptr == NULL)
            limit_buf_ptr = buf_end;

          cur_seg_len = MAX((int)limit_buf_ptr - (int)cur_buf_ptr, 0);

/* Simply load header as exploded newlines for now. */
          MPLoadExplodeNewLines(
            &mp_header->line, &mp_header->total_lines,
            cur_buf_ptr, limit_buf_ptr
          );

          /* Set cur_buf_ptr to start of first section for section
           * parsing which happens next.
           */
            cur_buf_ptr = limit_buf_ptr;
      }
      if(mp_header_rtn == NULL)
          MPDeleteHeader(mp_header);
      else
          (*mp_header_rtn) = mp_header;
      mp_header = NULL;

      /* Call progress callback. */
      if(progress_cb != NULL)
      {
            status = progress_cb(
                CLIP((long)cur_buf_ptr - (long)buf, 0, data_len),
                data_len,
                client_data
            );
            if(status)
            {
            free(buf);
                return(0);
            }
        }


      /* Begin loading sections. */
      mp_section = NULL;
      mp_total_sections = 0;
      while(cur_buf_ptr < buf_end)
      {
          /* Call progress callback? */
            if(progress_cb != NULL)
            {
                status = progress_cb(
                CLIP((long)cur_buf_ptr - (long)buf, 0, data_len),
                data_len, client_data
            );
                if(status)
                {
                if((mp_section_rtn == NULL) || (mp_total_sections_rtn == NULL))
                {
                  MPDeleteAllSections(mp_section, mp_total_sections);
                }
                else 
                {
                  (*mp_section_rtn) = mp_section;
                  (*mp_total_sections_rtn) = mp_total_sections;
                }
                free(buf);
                    return(0);
                }
            }


            /* Increment current buffer pointer past the ".SH" marking
           * plus any spaces.
           */
          cur_buf_ptr += strlen(".SH");
            while(ISBLANK(*cur_buf_ptr))
                cur_buf_ptr++;

          /* Now positioned at start of section name, check if
           * section name is encapsulated withing double quotes.
           */
          cur_section_name = NULL;
          if((*cur_buf_ptr) == '"')
          {
            /* Need to seek to other end of '"'. */
            cur_buf_ptr++;
            limit_buf_ptr = (char *)MPLoadStringNextDoubleQuote(cur_buf_ptr);
            if(limit_buf_ptr == NULL)
                limit_buf_ptr = buf_end;

                cur_seg_len = MAX((int)limit_buf_ptr - (int)cur_buf_ptr, 0);
            cur_section_name = (char *)malloc((cur_seg_len + 1) * sizeof(char));
            if(cur_section_name != NULL)
            {
                strncpy(cur_section_name, cur_buf_ptr, cur_seg_len);
                cur_section_name[cur_seg_len] = '\0';
            }

            /* Seek current buffer to start of section body. */
            cur_buf_ptr = limit_buf_ptr;
            while((cur_buf_ptr < buf_end) &&
                      (((*cur_buf_ptr) == '"') ||
                       ISBLANK(*cur_buf_ptr) ||
                       ISCR(*cur_buf_ptr)
                      )
                )
                    cur_buf_ptr++;
          }
          else
          {
            /* Seek to next newline character. */
            limit_buf_ptr = cur_buf_ptr;
            while((limit_buf_ptr < buf_end) &&
/* Some older man pages need to have entire .SH line loaded as section name
                  !ISBLANK(*limit_buf_ptr) && */
                      !ISCR(*limit_buf_ptr)
            )
                limit_buf_ptr++;

                cur_seg_len = MAX((int)limit_buf_ptr - (int)cur_buf_ptr, 0);
                cur_section_name = (char *)malloc((cur_seg_len + 1) * sizeof(char));
                if(cur_section_name != NULL)
                {
                    strncpy(cur_section_name, cur_buf_ptr, cur_seg_len);
                    cur_section_name[cur_seg_len] = '\0';
                }

                /* Seek current buffer to start of section body. */
                cur_buf_ptr = limit_buf_ptr;
                while((cur_buf_ptr < buf_end) &&
                      (ISBLANK(*cur_buf_ptr) || 
                       ISCR(*cur_buf_ptr)
                      )
                )
                    cur_buf_ptr++;
          }

          /* Current buffer position is now positioned at start of
           * section body.
           */

          /* Allocate new section structure. */
          n = mp_total_sections;
          mp_total_sections = n + 1;
          mp_section = (mp_section_struct **)realloc(
            mp_section, mp_total_sections * sizeof(mp_section_struct *)
          );
          if(mp_section == NULL)
          {
            mp_total_sections = 0;
            cur_buf_ptr = buf_end;  /* Set current buffer to end. */
            break;
          }
          else
          {
            mp_section_ptr = (mp_section_struct *)calloc(
                1, sizeof(mp_section_struct)
            );
            mp_section[n] = mp_section_ptr;
            if(mp_section_ptr == NULL)
            {
                    cur_buf_ptr = buf_end;      /* Set current buffer to end. */
                    break;
                }
          }

          /* Set section name. */
          mp_section_ptr->section = cur_section_name;
          cur_section_name = NULL;

          /* Fetch section's body. */
          limit_buf_ptr = strstr(cur_buf_ptr, ".SH");
            if(limit_buf_ptr == NULL)
                limit_buf_ptr = buf_end;

          /* Explode and parse the manual page format, storing the
           * lines into the given lines array after parsing cur_buf_ptr
           * to limit_buf_ptr.
           */
          MPLoadExplodeParseMPFmt(
            &mp_section_ptr->line, &mp_section_ptr->total_lines,
            cur_buf_ptr, limit_buf_ptr
          );

          /* Move current position to start of next section's
           * deliminator.
           */
          cur_buf_ptr = limit_buf_ptr;
      }
        if((mp_section_rtn == NULL) || (mp_total_sections_rtn == NULL))
      {
            MPDeleteAllSections(mp_section, mp_total_sections);
      }
        else
      {
            (*mp_section_rtn) = mp_section;
          (*mp_total_sections_rtn) = mp_total_sections;
      }
      mp_section = NULL;
      mp_total_sections = 0;


        /* Call progress callback. */
        if(progress_cb != NULL)
        {
            status = progress_cb(
                CLIP((long)cur_buf_ptr - (long)buf, 0, data_len),
                data_len,
                client_data
            );
            if(status)
            {  
                free(buf);
                return(0);
            }
        }   


      /* Deallocate local data buffer. */
      free(buf);


      return(0);
}

/*
 *    Saves the manual page data into either the specified filename
 *    or data_rtn (whichever is not NULL).
 *
 *      If the progress_cb function is not NULL and it returns non-zero
 *      then the saving will be aborted. In which case return will be
 *      success but saved data will be short.
 *
 *      Returns non-zero on error, where -1 is a general error and -2 is
 *      cannot write to file.
 */
int MPSave(
        const char *filename, char **data_rtn,
        mp_header_struct *mp_header,
        mp_section_struct **mp_section, int mp_total_sections,
        void *client_data, int (*progress_cb)(long, long, void *)
)
{
      char *buf = NULL;
      int buf_len = 0;
      const char *out_ptr = NULL;

      int i, section_num;
      mp_section_struct *section_ptr;
      const char *line_ptr;
      int tmp_line_len;
      char *tmp_line_ptr;


      /* Both filename and data return may not be both NULL. */
      if((filename == NULL) && (data_rtn == NULL))
          return(-1);

/* Appends the string pointed to by out_ptr (which is assumed not
 * NULL) and appends it to the buf, reallocating buf as needed and
 * increasing the buf_len.
 */
#define DO_WRITE_OUT    \
{ \
 int cur_out_len = strlen(out_ptr); \
 int cur_out_pos = buf_len; \
\
 if(cur_out_len > 0) \
 { \
  buf_len += cur_out_len; \
  buf = (char *)realloc(buf, (buf_len + 1) * sizeof(char)); \
  if(buf == NULL) \
  { \
   buf_len = 0; \
  } \
  else \
  { \
   memcpy(&buf[cur_out_pos], out_ptr, cur_out_len); \
   buf[buf_len] = '\0'; \
  } \
 } \
}

      /* Write header. */
      if(mp_header != NULL)
      {
          for(i = 0; i < mp_header->total_lines; i++)
          {
            line_ptr = mp_header->line[i];
            if(line_ptr == NULL)
                continue;

            /* Write out line. */
            out_ptr = line_ptr;
            DO_WRITE_OUT

                /* Add new line at the end. */
                out_ptr = "\n";
                DO_WRITE_OUT
          }
      }

      /* Write each section. */
      if(mp_section != NULL)
      {
          /* Go through each section. */
            for(section_num = 0; section_num < mp_total_sections; section_num++)
            {
            section_ptr = mp_section[section_num];
            if(section_ptr == NULL)
                    continue;

            /* Write section deliminator. */
            out_ptr = ".SH ";
            DO_WRITE_OUT

            /* Section name not available? */
            if(section_ptr->section == NULL)  
            {
                out_ptr = "\"UNTITLED\"";
                DO_WRITE_OUT
            }
            else
            {
                /* Allocate tempory line for section name. */
                tmp_line_len = strlen(section_ptr->section) + 4;
                tmp_line_ptr = (char *)malloc(
                  (tmp_line_len + 1) * sizeof(char)
                );
                if(tmp_line_ptr != NULL)
                {
                  /* Format then write section name. */
                  sprintf(tmp_line_ptr, "\"%s\"",
                      section_ptr->section
                        );
                        out_ptr = tmp_line_ptr;
                        DO_WRITE_OUT

                        /* Free tempory line for section name. */
                        free(tmp_line_ptr);
                    }
                    tmp_line_ptr = NULL;
            }

            /* Add new line at the end. */
            out_ptr = "\n";
            DO_WRITE_OUT


            /* Go through each line on section. */
                for(i = 0; i < section_ptr->total_lines; i++)
                {
                    line_ptr = section_ptr->line[i];
                    if(line_ptr == NULL)
                        continue;

                /* Make a copy of the line which we will make
                 * substitutions to and then deallocate after
                 * writing it out.
                 */
                tmp_line_ptr = strdup(line_ptr);
                if(tmp_line_ptr == NULL)
                  continue;

                /* Make substitutions and other formatting to the
                 * tmp_line_ptr before writing it out.
                 */
                    tmp_line_ptr = MPSaveFormatMP(tmp_line_ptr);

                /* Write out tmp_line_ptr. */
                    out_ptr = tmp_line_ptr;
                    DO_WRITE_OUT

                /* Add new line at the end. */
                out_ptr = "\n";
                    DO_WRITE_OUT

                /* Free coppied line. */
                free(tmp_line_ptr);
                tmp_line_ptr = NULL;
            }
            }
      }

#undef DO_WRITE_OUT


      /* At this point buf and buf_len specify the (null terminated)
       * data to be written out.
       */

      /* Write out to file? */
      if(filename != NULL)
      {
          MPSaveFile(filename, buf, buf_len);
      }
      else if(data_rtn != NULL)
        {
/* Need to work on this. */
        }

      /* Deallocate the local buffer, it is no longer needed. */
      free(buf);
      buf = NULL;
      buf_len = 0;

      return(0);
}


/*
 *    Deallocates the given header structure and all its substructures.
 */
void MPDeleteHeader(mp_header_struct *mp_header)
{
      if(mp_header == NULL)
          return;

      free(mp_header->title);

      strlistfree(mp_header->line, mp_header->total_lines);

      free(mp_header);
}

/*
 *      Deallocates the given section structures and the pointer
 *    array.
 */
void MPDeleteAllSections(
      mp_section_struct **mp_section,
      int mp_total_sections
)
{
      int i;
      mp_section_struct *mp_section_ptr;


      /* Go through each manual page section structure. */
      for(i = 0; i < mp_total_sections; i++)
      {
          mp_section_ptr = mp_section[i];
          if(mp_section_ptr == NULL)
            continue;

          free(mp_section_ptr->section);

          strlistfree(
            mp_section_ptr->line,
            mp_section_ptr->total_lines
          );

          free(mp_section_ptr);
      }

      /* Free pointer array. */
      free(mp_section);
}

Generated by  Doxygen 1.6.0   Back to index