
/* tidytags.c
   show the current tags supported by tidy
 */

#include "tidy.h"
#include "forward.h"
#include "lexer.h"
#include "tags.h"
#include "tidy-int.h"
#include "message.h"
#include "tmbstr.h"

//#define  InStr    TY_(tmbsubstr)
#define  InStr(a,b)    TY_(tmbstrcasecmp)(a,b) ? 0 : 1

#define  TIDY_MAX_ARGS  128
#define  EndBuf(a)   ( a + strlen(a) )

#define CM_ALL (CM_EMPTY|CM_HTML|CM_HEAD|CM_BLOCK|CM_INLINE|   \
   CM_LIST|CM_DEFLIST|CM_TABLE|CM_ROWGRP|CM_ROW|CM_FIELD|      \
   CM_OBJECT|CM_PARAM|CM_FRAMES|CM_HEADING|CM_OPT|CM_IMG|      \
   CM_MIXED|CM_NO_INDENT|CM_OBSOLETE|CM_NEW|CM_OMITST)

/* ********************************************************
   ONLY FOR DEBUG
struct _Dict {
    TidyTagId       id;
    tmbstr          name;
    uint            versions;
    AttrVersion const *    attrvers;
    uint            model;
    Parser*         parser;
    CheckAttribs*   chkattrs;
    Dict*           next;
};
 */

#define  MALLOC   malloc
#define  MFREE    free
static void pgm_exit( TidyDoc tdoc, int ex );
#define  CHKMEM(a)   if( !a ) { fprintf( errout, "ERROR: MEMORY FAILED!\n" ); pgm_exit( 0, 3 ); }

typedef struct {
   void * next;
   char string[1];
}STGLIST, * PSTGLIST;

uint  def_type = 0;
uint  not_type = 0;

static tmbstr prog = "dummy";

char * file_date = __DATE__;
char * file_time = __TIME__;

extern const Dict * get_tag_block( void );

// local - forward references
uint string_2_model( tmbstr pm );
void show_tags( TidyDoc tdoc, uint type, uint tnot );
tmbstr model_2_string( uint model );
const Dict * get_tag_pointer( tmbstr s );
int   Process_Input( TidyDoc tdoc, char * file );

PSTGLIST ptaglist = NULL;

static FILE* errout = NULL;  /* set to stderr */

void  append_tag_list( PSTGLIST ptl ) {
   PSTGLIST pt = ptaglist;
   ptl->next = NULL;
   if(pt) {
      PSTGLIST last = pt;
      PSTGLIST pn = pt->next;
      while(pn) {
         last = pn;
         pn = pn->next;
      }
      last->next = ptl;
   } else {
      ptaglist = ptl;
   }
}

PSTGLIST get_end_tag_ptr( void )
{
   PSTGLIST pcl;
   PSTGLIST pcllast;
   pcllast = NULL;
   for ( pcl = ptaglist; pcl; pcl = pcl->next ) {
      pcllast = pcl;
   }
   return pcllast;
}

int get_tag_count( void )
{
   PSTGLIST pcl = ptaglist;
   int   cnt = 0;
   for ( ; pcl; pcl = pcl->next ) {
      cnt++;
   }
   return cnt;
}

PSTGLIST get_tag_item( int i )
{
   PSTGLIST pcl = ptaglist;
   int   cnt = 0;
   for ( ; pcl; pcl = pcl->next ) {
      if ( i == cnt )
         return pcl;
      cnt++;
   }
   return pcl;
}

PSTGLIST in_tag_list( char * tag ) {
   int   cnt = get_tag_count();
   while( cnt-- ) {
      PSTGLIST pcl = get_tag_item(cnt);
      if(pcl) {
         if( strcmpi( pcl->string, tag ) == 0 ) {
            return pcl;
         }
      }
   }
   return NULL;
}

PSTGLIST add_2_tag_list( char * tag )
{
   size_t len = strlen(tag);
   PSTGLIST ptl = MALLOC( sizeof(STGLIST) + len );
   CHKMEM(ptl);
   ptl->next = NULL;
   strcpy(ptl->string, tag);
   append_tag_list( ptl );
   return ptl;
}

static void pgm_exit( TidyDoc tdoc, int ex )
{
/* #ifndef  NDEBUG
   if ( ex == 3 )
      show_commands( tdoc );
/* #endif /* #ifndef  NDEBUG */
   if ( tdoc )
      tidyRelease( tdoc );
   if ( ex == 3 )
      fprintf( errout, "ERROR: ABORTING APPLICATION! Error Level 3!\n" );
   exit( ex );
}

tmbstr As_Bits( uint model )
{
   static tmbchar _s_bb[128];
   tmbstr pb = _s_bb;
   uint  test = 0x80000000;
   int   had1 = 0;

   *pb = 0;
   while(test && model)
   {
      if(model & test) {
         if( had1 ) {
            strcat(pb,"1");
         } else {
            strcat(pb,"0");
            strcat(pb,"1");
            had1 = 1;
         }
         model &= ~(test);
      } else if(had1) {
         strcat(pb,"0");
      }
      test = test >> 1;
   }
   if( had1 && test ) {
      while(test) {
         strcat(pb,"0");
         test = test >> 1;
      }
   }
   return pb;
}

void show_tags( TidyDoc tdoc, uint type, uint tnot )
{
   const Dict *np = get_tag_block();
   static tmbchar tagbuf[1024];
   tmbstr pt = tagbuf;
   uint  test = (uint)-1;
   tmbstr cp = model_2_string( test );
   size_t sz = strlen(cp);
   size_t mx = 0;
   size_t len;
   size_t excl = 0;
   int   tag_count = get_tag_count();
   if( tag_count ) {
      int i;
      *pt = 0;
      for( i = 0; i < tag_count; i++ ) {
         PSTGLIST ptl = get_tag_item(i);
         sprintf(EndBuf(pt),"%s ", ptl->string);
      }
      printf( "Show Tags: %s\n", pt );
   } else if( type == 0 ) {
      if( tnot ) {
         test &= ~(tnot);
         cp = model_2_string( test );
         printf( "These Models: %s (bits %#X)...\n", cp, test );
         cp = model_2_string( tnot );
         printf( "NOT Models: %s (bits %#X)...\n", cp, tnot );
      } else {
         printf( "All Models: %s (%u)...\n", cp, sz );
      }
   } else {
      cp = model_2_string( type );
      if(strlen(cp)) {
         // printf( "Show Models: %s (%s)...\n", cp, As_Bits(type) );
         printf( "Show Models: %s (bits %#X)...\n", cp, type );
         if( tnot ) {
            cp = model_2_string( tnot );
            printf( "But NOT Models: %s (bits %#X)...\n", cp, tnot );
         }
      } else {
         printf( "NO MODELS WITH %#X bits...\n", type );
         return;
      }
   }
   sz = 0;
   np++; // skip first
   for ( ; np->name != NULL; np++)
   {
      len = strlen(np->name);
      if( len > mx )
         mx = len;
   }
   mx += 10;
   np = get_tag_block();
   np++; // skip first
   for ( ; np->name != NULL; np++)
   {
      cp = model_2_string( np->model );
      sprintf(pt, "Tag: <%s>,", np->name );
      while(strlen(pt) < mx)
         strcat(pt," ");
      if( tag_count ) {
         if( in_tag_list( np->name ) ) {
            printf( "%s Model: %s\n", pt, cp );
            sz++;
         }
      } else if( (type == 0) || (type & np->model) ) {
         if( tnot && (tnot & np->model) ) {
            // exclude this
            excl++;
         } else {
            // show this
            printf( "%s Model: %s\n", pt, cp );
            sz++;
         }
      }
   }

   if(sz) {
      if(excl) {
         printf( "Printed %u tags and models, excluded %u ...\n", sz, excl );
      } else {
         printf( "Printed %u tags and models ...\n", sz );
      }
   } else {
      if(excl) {
         printf( "NO tags with this models, but excluded %u ...\n", excl );
      } else {
         printf( "NO tags with this models ...\n" );
      }
   }

}

const Dict * get_tag_pointer( tmbstr s )
{
   const Dict *np = get_tag_block();
   np++; /* skip first */
   for ( ; np->name != NULL; np++)
   {
        if (TY_(tmbstrcmp)(s, np->name) == 0)
            return np;
   }
   return NULL;
}


void give_help( void )
{
   printf( "tidytags [OPTIONS] [MODELS]\n" );
   printf( "A blank input will show all tags and models in Tidy library. Or a model list like -\n" );
   printf( "CM_OBSOLETE        - Will show all tags with this bit set. Can be repeated.\n" );
   printf( "OPTIONS: Preceeded with -- for full type, or optional - for single letter.\n" );
   printf( " --help (-? or -h) - Show this brief help.\n" );
   printf( " --tag (-t) model  - Show model(s) for the tag. Can be repeated.\n" );
   printf( " @filename.txt     - Will be treated as an input file, with line\n" );
   printf( "                     separated items. ';' begins a file comment.\n" );
   printf( "Build date and time: on %s at %s\n", file_date, file_time );

}

void Process_Args( TidyDoc tdoc, int argc, char** argv )
{
   int   i, c;
   char * arg;
   uint model;
   for( i = 1; i < argc; i++ ) {
      arg = argv[i];
      c = arg[0];
      if(c == '-') {
         if(( strcmp(arg, "-?") == 0 ) ||
            ( strcmpi(arg, "-h") == 0 ) ||
            ( strcmpi(arg, "--help") == 0 ) )
         {
            give_help();
            pgm_exit( tdoc, 0 );
         } else if( (strcmpi(arg, "-t") == 0) ||
            (strcmpi(arg, "--tag") == 0) )
         {
            i++;
            if( i < argc ) {
               arg = argv[i];
               if( get_tag_pointer( arg ) ) {
                  add_2_tag_list( arg );
               } else {
                  printf( "ERROR: No such TAG name [%s]!\n", arg );
                  pgm_exit( tdoc, 3 );
               }
            } else {
               printf( "ERROR: Option %s MUST be followed by argument!\n", arg );
               pgm_exit(tdoc, 3);
            }
         } else {
            printf("ERROR: Unknown command [%s]!\n", arg );
            pgm_exit(tdoc, 3);
         }
      } else if( c == '@' ) {
         arg++;
         Process_Input(tdoc, arg);
      } else if( c == '~' ) {
         arg++;
         model = string_2_model( arg );
         if(model) {
            not_type |= model;
         } else {
            tmbstr cp = model_2_string( (uint)-1 );
            arg--;
            printf( "ERROR: [%s] does NOT appear to be a MODEL!\n", arg );
            printf( "Must be one or more of ...\n" );
            printf( "%s\n", cp );
            pgm_exit(tdoc, 1);
         }
      } else {
         model = string_2_model( arg );
         if(model) {
            def_type |= model;
         } else {
            tmbstr cp = model_2_string( (uint)-1 );
            printf( "ERROR: [%s] does NOT appear to be a MODEL!\n", arg );
            printf( "Must be one or more of ...\n" );
            printf( "%s\n", cp );
            pgm_exit(tdoc, 1);
         }
      }
   }
}

tmbstr model_2_string( uint model )
{
   static tmbchar modbuf[256];
   tmbstr pm = modbuf;
   *pm = 0;
   /* #define CM_UNKNOWN      0 */
   /* Elements with no content. Map to HTML specification. */
   if( model & CM_EMPTY )
      strcat(pm,"CM_EMPTY ");

   /* Elements that appear outside of "BODY". */
   if( model & CM_HTML )
      strcat(pm,"CM_HTML ");

   /* Elements that can appear within HEAD. */
   if( model & CM_HEAD )
      strcat(pm,"CM_HEAD ");

   /* HTML "block" elements. */
   if( model & CM_BLOCK )
      strcat(pm,"CM_BLOCK ");

   /* HTML "inline" elements. */
   if( model & CM_INLINE )
      strcat(pm, "CM_INLINE ");

   /* Elements that mark list item ("LI"). */
   if( model & CM_LIST )
      strcat(pm, "CM_LIST ");

   /* Elements that mark definition list item ("DL", "DT"). */
   if( model & CM_DEFLIST )
      strcat(pm, "CM_DEFLIST ");

   /* Elements that can appear inside TABLE. */
   if( model & CM_TABLE )
      strcat(pm, "CM_TABLE ");

   /* Used for "THEAD", "TFOOT" or "TBODY". */
   if( model & CM_ROWGRP )
      strcat(pm, "CM_ROWGP ");

   /* Used for "TD", "TH" */
   if( model & CM_ROW )
      strcat(pm, "CM_ROW ");

   /* Elements whose content must be protected against white space movement.
      Includes some elements that can found in forms. */
   if( model & CM_FIELD )
      strcat(pm, "CM_FIELD ");

   /* Used to avoid propagating inline emphasis inside some elements
      such as OBJECT or APPLET. */
   if( model & CM_OBJECT )
      strcat(pm, "CM_OBJECT ");

   /* Elements that allows "PARAM". */
   if( model & CM_PARAM )
      strcat(pm, "CM_PARAM ");

   /* "FRAME", "FRAMESET", "NOFRAMES". Used in ParseFrameSet. */
   if( model & CM_FRAMES )
      strcat(pm, "CM_FRAMES ");

   /* Heading elements (h1, h2, ...). */
   if( model & CM_HEADING )
      strcat(pm, "CM_HEADING ");

   /* Elements with an optional end tag. */
   if( model & CM_OPT )
      strcat(pm, "CM_OPT ");

   /* Elements that use "align" attribute for vertical position. */
   if( model & CM_IMG )
      strcat(pm, "CM_IMG ");

   /* Elements with inline and block model. Used to avoid calling InlineDup. */
   if( model & CM_MIXED )
      strcat(pm, "CM_MIXED ");

   /* Elements whose content needs to be indented only if containing one 
      CM_BLOCK element. */
   if( model & CM_NO_INDENT )
      strcat(pm, "CM_NO_INDENT ");

   /* Elements that are obsolete (such as "dir", "menu"). */
   if( model & CM_OBSOLETE )
      strcat(pm, "CM_OBSOLETE ");

   /* User defined elements. Used to determine how attributes wihout value
      should be printed. */
   if( model & CM_NEW )
      strcat(pm, "CM_NEW ");

   /* Elements that cannot be omitted. */
   if( model & CM_OMITST )
      strcat(pm, "CM_OMITST ");

   return pm;
}

uint string_2_model( tmbstr pm )
{
   uint model = 0;
   /* #define CM_UNKNOWN      0 */
   /* Elements with no content. Map to HTML specification. */
   if(InStr("CM_EMPTY",pm))
      model |= CM_EMPTY;

   /* Elements that appear outside of "BODY". */
   if(InStr("CM_HTML",pm))
      model |= CM_HTML;

   /* Elements that can appear within HEAD. */
   if(InStr("CM_HEAD",pm))
      model |= CM_HEAD;

   /* HTML "block" elements. */
   if(InStr("CM_BLOCK",pm))
      model |= CM_BLOCK;

   /* HTML "inline" elements. */
   if(InStr("CM_INLINE",pm))
      model |= CM_INLINE;

   /* Elements that mark list item ("LI"). */
   if(InStr("CM_LIST",pm))
      model |= CM_LIST;

   /* Elements that mark definition list item ("DL", "DT"). */
   if(InStr("CM_DEFLIST",pm))
      model |= CM_DEFLIST;

   /* Elements that can appear inside TABLE. */
   if(InStr("CM_TABLE",pm))
      model |= CM_TABLE;

   /* Used for "THEAD", "TFOOT" or "TBODY". */
   if(InStr("CM_ROWGP",pm))
      model |= CM_ROWGRP;

   /* Used for "TD", "TH" */
   if(InStr("CM_ROW",pm))
      model |= CM_ROW;

   /* Elements whose content must be protected against white space movement.
      Includes some elements that can found in forms. */
   if(InStr("CM_FIELD",pm))
      model |= CM_FIELD;

   /* Used to avoid propagating inline emphasis inside some elements
      such as OBJECT or APPLET. */
   if(InStr("CM_OBJECT",pm))
      model |= CM_OBJECT;

   /* Elements that allows "PARAM". */
   if(InStr("CM_PARAM",pm))
      model |= CM_PARAM;

   /* "FRAME", "FRAMESET", "NOFRAMES". Used in ParseFrameSet. */
   if(InStr("CM_FRAMES",pm))
      model |= CM_FRAMES;

   /* Heading elements (h1, h2, ...). */
   if(InStr("CM_HEADING",pm))
      model |= CM_HEADING;

   /* Elements with an optional end tag. */
   if(InStr("CM_OPT",pm))
      model |= CM_OPT;

   /* Elements that use "align" attribute for vertical position. */
   if(InStr("CM_IMG",pm))
      model |= CM_IMG;

   /* Elements with inline and block model. Used to avoid calling InlineDup. */
   if(InStr("CM_MIXED",pm))
      model |= CM_MIXED;

   /* Elements whose content needs to be indented only if containing one 
      CM_BLOCK element. */
   if(InStr("CM_NO_INDENT",pm))
      model |= CM_NO_INDENT;

   /* Elements that are obsolete (such as "dir", "menu"). */
   if(InStr("CM_OBSOLETE",pm))
      model |= CM_OBSOLETE;

   /* User defined elements. Used to determine how attributes wihout value
      should be printed. */
   if(InStr("CM_NEW",pm))
      model |= CM_NEW;

   /* Elements that cannot be omitted. */
   if(InStr("CM_OMITST",pm))
      model |= CM_OMITST;

   return model;
}

int   Process_Input( TidyDoc tdoc, char * file )
{
   FILE * fp;
   struct stat sbuf;
   tmbstr fbuf;
   ulong size, ui, len, bgnui;
   tmbstr * argv;
   int   argc;
   tmbstr arg, p;
   char c;
   if ( stat( file, &sbuf ) )
   {
      fprintf(errout, "ERROR: stat of input file \"%s\" failed!\n", file);
      pgm_exit(tdoc, 3);
   }
   if ( sbuf.st_size == 0 )
      return 0;   /* quietly ignore zero length files */
   if ( sbuf.st_size > 4294967295 )
   {
      fprintf(errout, "ERROR: input file \"%s\" TOO LARGE!\n", file);
      pgm_exit(tdoc, 3);
   }
   fbuf = malloc( sbuf.st_size + (sizeof(char *) * (TIDY_MAX_ARGS + 1)) );
   if ( !fbuf )
   {
      fprintf(errout, "ERROR: memory allocation for input file \"%s\" failed!\n", file);
      pgm_exit(tdoc, 3);
   }
   fp = fopen( file, "rb" );
   if ( !fp )
   {
      free( fbuf );
      fprintf(errout, "ERROR: Opening input file \"%s\" failed!\n", file);
      pgm_exit(tdoc, 3);
   }
   size = (ulong)fread( fbuf, 1, sbuf.st_size, fp );
   fclose( fp );
   if ( size != sbuf.st_size )
   {
      free( fbuf );
      fprintf(errout, "ERROR: Full read of input file \"%s\" failed!\n", file);
      pgm_exit(tdoc, 3);
   }
   argv = (char **)&fbuf[size];
   argc = 0;
   argv[argc++] = prog;
   for ( ui = 0; ui < size; ui++ )
   {
      c = fbuf[ui];
      if ( c > ' ' )
      {
         if ( c == ';' )
         {
            ui++;
            for ( ; ui < size; ui++ )
            {
               c = fbuf[ui];
               if (( c < ' ' ) && ( c != '\t' ))
                  break;
            }
         }
         else
         {
            arg = &fbuf[ui];
            bgnui = ui;
            ui++;
            len = 1;
            for ( ; ui < size; ui++ )
            {
               c = fbuf[ui];
               if ((( c < ' ' ) && ( c != '\t' )) || ( c == ';' )) {
                  fbuf[ui] = 0;  /* zero termination */
                  if ( c == ';' )
                  {
                     ui++;
                     for ( ; ui < size; ui++ )
                     {
                        c = fbuf[ui];
                        if (( c < ' ' ) && ( c != '\t' ))
                           break;
                     }
                  }
                  break;
               }
               len++;
            }
            while(len--)
            {
               p = &fbuf[bgnui+len];
               if ( *p > ' ' )
                  break;
               *p = 0;
            }
            if ( *arg == '"' )
            {
               /* unpeel the quotes */
               arg++;
               p = strrchr(arg, '"');
               if(p)
                  *p = 0;
            }
            else if ( *arg == '-' )
            {
               /* may be two! */
               p = strchr(arg, ' ');
               if ( p )
               {
                  *p = 0;
                  argv[argc++] = arg;
                  p++;  /* step up to next char */
                  while ( *p <= ' ' )
                     p++;  /* walk of spacey stuff */
                  arg = p;
               }
            }
            argv[argc++] = arg;
            if ( argc >= (TIDY_MAX_ARGS - 1) )
            {
               free( fbuf );
               fprintf(errout, "ERROR: TOO MANY ARGUMENTS IN file \"%s\" failed!\n"
                  "RECOMPILE increasing TIDY_MAX_ARGS!\n", file);
               pgm_exit(tdoc, 3);
            }
         }
      }
   }
   argv[argc] = 0;   /* NULL terminate array of pointers */
   Process_Args( tdoc, argc, argv );
   free( fbuf );
   return 0;
}

int main( int argc, char** argv )
{
   TidyDoc tdoc = tidyCreate();
   errout = stderr;  /* initialize to stderr */
   prog = argv[0];   /* init program name */
   Process_Args( tdoc, argc, argv );
   show_tags( tdoc, def_type, not_type );
   pgm_exit( tdoc, 0 );
   return 0;
}

/* eof - tidytags.c */
