/*
  tidy2.c - HTML TidyLib command line driver

  Copyright (c) 1998-2007 World Wide Web Consortium
  (Massachusetts Institute of Technology, European Research 
  Consortium for Informatics and Mathematics, Keio University).
  All Rights Reserved.

  This version is wish an 'enhanced' command processor, where
  (a) All command line checks are from tables
  (b) Only support ONE command line set at a time
  (c) Supports @input.fil, with line delimited options, comments start with ';'
  (d) Will ABORT (exit) on any command error
  (e) Help puts out the table entry
  (f) Allow every argument to be single '-', or '--' ...
  (f) Exit with -
      0 = no errors or warnings in parse, or any HELP command
      1 = some warnings
      2 = some errors, and maybe warnings
      3 = command parse error

  CVS Info :

    $Author: geoffmc $ 
    $Date: 2007/05/11 12:00:00 $ 
    $Revision: 1.47 $ 

*/

#include "tidy.h"

/* forward references */
void Clear_CMDTABLE( void );
void Clear_ENCODINGS( void );
void  tidy_show_unmarked( void );

typedef Bool (*CMDSERVB)( TidyDoc, TidyOptionId, Bool  );
typedef Bool (*CMDSERV2)( TidyDoc, tmbstr );

/* tidySetCharEncoding( tdoc, arg ); */
typedef struct tagENCODINGS {
   tmbstr  name; /* encoding name */
   uint    flag; /* various */
}ENCODINGS, * PENCODINGS;

typedef struct tagCMDTABLE {
   tmbstr cmd;          /* command */
   tmbstr altcmd;          /* alternate command */
   CMDSERVB  servb;        /* server */
   TidyOptionId optid;  /* option ID */
   CMDSERV2  serv2;        /* server with 2nd argument */
   uint     flag;       /* for output checking */
}CMDTABLE, * PCMDTABLE;

/* static data variables */
static tmbstr prog = "dummy";
static FILE* errout = NULL;  /* set to stderr */
static ctmbstr cfgfil = NULL;
static ctmbstr errfil = NULL;
static ctmbstr htmlfil = NULL;
/* static FILE* txtout = NULL; */  /* set to stdout */

static void pgm_exit( TidyDoc tdoc, int ex )
{
   if ( tdoc )
      tidyRelease( tdoc );
   exit( ex );
}

static Bool samefile( ctmbstr filename1, ctmbstr filename2 )
{
#if FILENAMES_CASE_SENSITIVE
    return ( strcmp( filename1, filename2 ) == 0 );
#else
    return ( strcasecmp( filename1, filename2 ) == 0 );
#endif
}

static const char *cutToWhiteSpace(const char *s, uint offset, char *sbuf)
{
    if (!s)
    {
        sbuf[0] = '\0';
        return NULL;
    }
    else if (strlen(s) <= offset)
    {
        strcpy(sbuf,s);
        sbuf[offset] = '\0';
        return NULL;
    }
    else
    {
        uint j, l, n;
        j = offset;
        while(j && s[j] != ' ')
            --j;
        l = j;
        n = j+1;
        /* no white space */
        if (j==0)
        {
            l = offset;
            n = offset;
        }
        strncpy(sbuf,s,l);
        sbuf[l] = '\0';
        return s+n;
    }
}

static void print2Columns( const char* fmt, uint l1, uint l2,
                           const char *c1, const char *c2 )
{
    const char *pc1=c1, *pc2=c2;
    char *c1buf = (char *)malloc(l1+1);
    char *c2buf = (char *)malloc(l2+1);

    do
    {
        pc1 = cutToWhiteSpace(pc1, l1, c1buf);
        pc2 = cutToWhiteSpace(pc2, l2, c2buf);
        printf(fmt,
               c1buf[0]!='\0'?c1buf:"",
               c2buf[0]!='\0'?c2buf:"");
    } while (pc1 || pc2);
    free(c1buf);
    free(c2buf);
}

static void print3Columns( const char* fmt, uint l1, uint l2, uint l3,
                           const char *c1, const char *c2, const char *c3 )
{
    const char *pc1=c1, *pc2=c2, *pc3=c3;
    char *c1buf = (char *)malloc(l1+1);
    char *c2buf = (char *)malloc(l2+1);
    char *c3buf = (char *)malloc(l3+1);

    do
    {
        pc1 = cutToWhiteSpace(pc1, l1, c1buf);
        pc2 = cutToWhiteSpace(pc2, l2, c2buf);
        pc3 = cutToWhiteSpace(pc3, l3, c3buf);
        printf(fmt,
               c1buf[0]!='\0'?c1buf:"",
               c2buf[0]!='\0'?c2buf:"",
               c3buf[0]!='\0'?c3buf:"");
    } while (pc1 || pc2 || pc3);
    free(c1buf);
    free(c2buf);
    free(c3buf);
}

static const char helpfmt[] = " %-19.19s %-58.58s\n";
static const char helpul[]
        = "-----------------------------------------------------------------";
static const char fmt[] = "%-27.27s %-9.9s  %-40.40s\n";
static const char valfmt[] = "%-27.27s %-9.9s %-1.1s%-39.39s\n";
static const char ul[]
        = "=================================================================";

typedef enum
{
  CmdOptFileManip,
  CmdOptCatFIRST = CmdOptFileManip,
  CmdOptProcDir,
  CmdOptCharEnc,
  CmdOptMisc,
  CmdOptCatLAST
} CmdOptCategory;

static const struct {
    ctmbstr mnemonic;
    ctmbstr name;
} cmdopt_catname[] = {
    { "file-manip", "File manipulation" },
    { "process-directives", "Processing directives" },
    { "char-encoding", "Character encodings" },
    { "misc", "Miscellaneous" }
};

typedef struct {
    ctmbstr name1;      /**< Name */
    ctmbstr desc;       /**< Description */
    ctmbstr eqconfig;   /**< Equivalent configuration option */
    CmdOptCategory cat; /**< Category */
    ctmbstr name2;      /**< Name */
    ctmbstr name3;      /**< Name */
} CmdOptDesc;

Bool tidy_Set_Out( TidyDoc tdoc, tmbstr arg2 );


static const CmdOptDesc cmdopt_defs[] =  {
    { "-output <file>",
      "write output to the specified <file>",
      "output-file: <file>", CmdOptFileManip, "-o <file>", 0 }, /* add -output-file??? */
    { "-config <file>",
      "set configuration options from the specified <file>",
      NULL, CmdOptFileManip },
    { "-file <file>",
      "write errors and warnings to the specified <file>",
      "error-file: <file>", CmdOptFileManip, "-f <file>" },
    { "-modify",
      "modify the original input files",
      "write-back: yes", CmdOptFileManip, "-m", "-change" },   /* added -change */
    { "-indent",
      "indent element content",
      "indent: auto", CmdOptProcDir, "-i" },
    { "-wrap <column>",
      "wrap text at the specified <column>"
      ". 0 is assumed if <column> is missing. "
      "When this option is omitted, the default of the configuration option "
      "\"wrap\" applies.",
      "wrap: <column>", CmdOptProcDir, "-w <column>" },
    { "-upper",
      "force tags to upper case",
      "uppercase-tags: yes", CmdOptProcDir, "-u" },
    { "-clean",
      "replace FONT, NOBR and CENTER tags by CSS",
      "clean: yes", CmdOptProcDir, "-c" },
    { "-bare",
      "strip out smart quotes and em dashes, etc.",
      "bare: yes", CmdOptProcDir, "-b" },
    { "-numeric",
      "output numeric rather than named entities",
      "numeric-entities: yes", CmdOptProcDir, "-n" },
    { "-errors",
      "show only errors and warnings",
      "markup: no", CmdOptProcDir, "-e" },
    { "-quiet",
      "suppress nonessential output",
      "quiet: yes", CmdOptProcDir, "-q" },
    { "-omit",
      "omit optional end tags",
      "hide-endtags: yes", CmdOptProcDir },
    { "-xml",
      "specify the input is well formed XML",
      "input-xml: yes", CmdOptProcDir },
    { "-asxml",
      "convert HTML to well formed XHTML",
      "output-xhtml: yes", CmdOptProcDir, "-asxhtml" },
    { "-ashtml",
      "force XHTML to well formed HTML",
      "output-html: yes", CmdOptProcDir },
#if SUPPORT_ACCESSIBILITY_CHECKS
    { "-access <level>",
      "do additional accessibility checks (<level> = 0, 1, 2, 3)"
      ". 0 is assumed if <level> is missing.",
      "accessibility-check: <level>", CmdOptProcDir },
#endif
    { "-raw",
      "output values above 127 without conversion to entities",
      NULL, CmdOptCharEnc },
    { "-ascii",
      "use ISO-8859-1 for input, US-ASCII for output",
      NULL, CmdOptCharEnc },
    { "-latin0",
      "use ISO-8859-15 for input, US-ASCII for output",
      NULL, CmdOptCharEnc },
    { "-latin1",
      "use ISO-8859-1 for both input and output",
      NULL, CmdOptCharEnc },
#ifndef NO_NATIVE_ISO2022_SUPPORT
    { "-iso2022",
      "use ISO-2022 for both input and output",
      NULL, CmdOptCharEnc },
#endif
    { "-utf8",
      "use UTF-8 for both input and output",
      NULL, CmdOptCharEnc },
    { "-mac",
      "use MacRoman for input, US-ASCII for output",
      NULL, CmdOptCharEnc },
    { "-win1252",
      "use Windows-1252 for input, US-ASCII for output",
      NULL, CmdOptCharEnc },
    { "-ibm858",
      "use IBM-858 (CP850+Euro) for input, US-ASCII for output",
      NULL, CmdOptCharEnc },
#if SUPPORT_UTF16_ENCODINGS
    { "-utf16le",
      "use UTF-16LE for both input and output",
      NULL, CmdOptCharEnc },
    { "-utf16be",
      "use UTF-16BE for both input and output",
      NULL, CmdOptCharEnc },
    { "-utf16",
      "use UTF-16 for both input and output",
      NULL, CmdOptCharEnc },
#endif
#if SUPPORT_ASIAN_ENCODINGS /* #431953 - RJ */
    { "-big5",
      "use Big5 for both input and output",
      NULL, CmdOptCharEnc },
    { "-shiftjis",
      "use Shift_JIS for both input and output",
      NULL, CmdOptCharEnc },
    { "-language <lang>",
      "set the two-letter language code <lang> (for future use)",
      "language: <lang>", CmdOptCharEnc },
#endif
    { "-version",
      "show the version of Tidy",
      NULL, CmdOptMisc, "-v" },
    { "-help",
      "list the command line options",
      NULL, CmdOptMisc, "-h", "-?" },
    { "-xml-help",
      "list the command line options in XML format",
      NULL, CmdOptMisc },
    { "-help-config",
      "list all configuration options",
      NULL, CmdOptMisc },
    { "-xml-config",
      "list all configuration options in XML format",
      NULL, CmdOptMisc },
    { "-show-config",
      "list the current configuration settings",
      NULL, CmdOptMisc },
    { NULL, NULL, NULL, CmdOptMisc }
};

int mark_CMDTABLE_name( tmbstr name );
int mark_ENCODINGS( tmbstr nm );
static void mark_one_name( tmbstr name )
{
   tmbstr nm, p;
   nm = name;
   if ( *nm == '-' ) nm++;
   p = strchr(nm, ' ');
   if ( p )
      *p = 0;
   if ( !mark_CMDTABLE_name(nm) )
   {
      if ( !mark_ENCODINGS( nm ) )
      {
         printf( "\nCHECK ME! [%s]\n", nm );
      }
   }
}

#define  CHKMEM(a)   if( !a ) { fprintf( errout, "ERROR: MEMORY FAILED!\n" ); pgm_exit( 0, 3 ); }

static void tidy_mark_item( const CmdOptDesc* pos, tmbstr name )
{
   strcpy(name, pos->name1 );
   mark_one_name(name);
   if (pos->name2)
   {
      strcpy(name, pos->name2 );
      mark_one_name(name);
   }
   if (pos->name3)
   {
      strcpy(name, pos->name3 );
      mark_one_name(name);
   }
}


static tmbstr get_option_names( const CmdOptDesc* pos )
{
    tmbstr name;
    uint len = strlen(pos->name1);
    if (pos->name2)
        len += 2+strlen(pos->name2);
    if (pos->name3)
        len += 2+strlen(pos->name3);

    name = (tmbstr)malloc(len+1);
    strcpy(name, pos->name1);
    if (pos->name2)
    {
        strcat(name, ", ");
        strcat(name, pos->name2);
    }
    if (pos->name3)
    {
        strcat(name, ", ");
        strcat(name, pos->name3);
    }
    return name;
}

static tmbstr get_escaped_name( ctmbstr name )
{
    tmbstr escpName;
    char aux[2];
    uint len = 0;
    ctmbstr c;
    for(c=name; *c!='\0'; ++c)
        switch(*c)
        {
        case '<':
        case '>':
            len += 4;
            break;
        case '"':
            len += 6;
            break;
        default:
            len += 1;
            break;
        }

    escpName = (tmbstr)malloc(len+1);
    escpName[0] = '\0';

    aux[1] = '\0';
    for(c=name; *c!='\0'; ++c)
        switch(*c)
        {
        case '<':
            strcat(escpName, "&lt;");
            break;
        case '>':
            strcat(escpName, "&gt;");
            break;
        case '"':
            strcat(escpName, "&quot;");
            break;
        default:
            aux[0] = *c;
            strcat(escpName, aux);
            break;
        }

    return escpName;
}

static void print_help_option( void )
{
    CmdOptCategory cat = CmdOptCatFIRST;
    const CmdOptDesc* pos = cmdopt_defs;
    Clear_ENCODINGS();
    Clear_CMDTABLE();
    for( cat=CmdOptCatFIRST; cat!=CmdOptCatLAST; ++cat)
    {
        size_t len =  strlen(cmdopt_catname[cat].name);
        printf("%s\n", cmdopt_catname[cat].name );
        printf("%*.*s\n", (int)len, (int)len, helpul );
        for( pos=cmdopt_defs; pos->name1; ++pos)
        {
            tmbstr name;
            if (pos->cat != cat)
                continue;
            name = get_option_names( pos );
            print2Columns( helpfmt, 19, 58, name, pos->desc );
            tidy_mark_item( pos, name );
            free(name);
        }
        printf("\n");
    }
    tidy_show_unmarked();
}

static void print_xml_help_option_element( ctmbstr element, ctmbstr name )
{
    tmbstr escpName;
    if (!name)
        return;
    printf("  <%s>%s</%s>\n", element, escpName = get_escaped_name(name),
           element);
    free(escpName);
}

static void print_xml_help_option( void )
{
    const CmdOptDesc* pos = cmdopt_defs;

    for( pos=cmdopt_defs; pos->name1; ++pos)
    {
        printf(" <option class=\"%s\">\n", cmdopt_catname[pos->cat].mnemonic );
        print_xml_help_option_element("name", pos->name1);
        print_xml_help_option_element("name", pos->name2);
        print_xml_help_option_element("name", pos->name3);
        print_xml_help_option_element("description", pos->desc);
        if (pos->eqconfig)
            print_xml_help_option_element("eqconfig", pos->eqconfig);
        else
            printf("  <eqconfig />\n");
        printf(" </option>\n");
    }
}

static void xml_help( void )
{
    printf( "<?xml version=\"1.0\"?>\n"
            "<cmdline version=\"%s\">\n", tidyReleaseDate());
    print_xml_help_option();
    printf( "</cmdline>\n" );
}

static tmbstr single_letters = "bceimnqu";

static void help( ctmbstr prog )
{
    printf( "%s [OPTIONS] input-file\n", prog );
    printf( "Utility to clean up and pretty print HTML/XHTML/XML\n");
    printf( "See http://tidy.sourceforge.net/\n");
    printf( "\n");

#ifdef PLATFORM_NAME
    printf( "Options for HTML Tidy for %s released on %s:\n",
             PLATFORM_NAME, tidyReleaseDate() );
#else
    printf( "Options for HTML Tidy released on %s:\n", tidyReleaseDate() );
#endif
    printf( "\n");

    print_help_option();

    printf( "Use \"-help-config\", or refer to the man page, for additonal configuration options.\n"
       "These options can be placed in a configuration file, in the form \"optionX: valueX\", or\n"
       "to use these configuration options on the command line, use \"--optionX valueX\"\n" );
    printf( "File output defaults to stdout, unless \"-o out-file-name\" is given.\n");
    printf( "Similarly, warnings, errors and other message default to stdout unless\n"
       "\"-f err-file\" is given.\n" );
    printf( "Single letter options, %s, may be combined, like \"-imu foo.html\".\n", single_letters);
    printf( "For further info on HTML see http://www.w3.org/MarkUp\n");
    printf( "\n");
}

static Bool isAutoBool( TidyOption topt )
{
    TidyIterator pos;
    ctmbstr def;

    if ( tidyOptGetType( topt ) != TidyInteger)
        return no;

    pos = tidyOptGetPickList( topt );
    while ( pos )
    {
        def = tidyOptGetNextPick( topt, &pos );
        if (0==strcmp(def,"yes"))
           return yes;
    }
    return no;
}

static
ctmbstr ConfigCategoryName( TidyConfigCategory id )
{
    switch( id )
    {
    case TidyMarkup:
        return "markup";
    case TidyDiagnostics:
        return "diagnostics";
    case TidyPrettyPrint:
        return "print";
    case TidyEncoding:
        return "encoding";
    case TidyMiscellaneous:
        return "misc";
    }
    fprintf(stderr, "Fatal error: impossible value for id='%d'.\n", (int)id);
    assert(0);
    abort();
    return "unknown";
}

/* Description of an option */
typedef struct {
    ctmbstr name;  /**< Name */
    ctmbstr cat;   /**< Category */
    ctmbstr type;  /**< "String, ... */
    ctmbstr vals;  /**< Potential values. If NULL, use an external function */
    ctmbstr def;   /**< default */
    tmbchar tempdefs[80]; /**< storage for default such as integer */
    Bool haveVals; /**< if yes, vals is valid */
} OptionDesc;

typedef void (*OptionFunc)( TidyDoc, TidyOption, OptionDesc * );


/* Create description "d" related to "opt" */
static
void GetOption( TidyDoc tdoc, TidyOption topt, OptionDesc *d )
{
    TidyOptionId optId = tidyOptGetId( topt );
    TidyOptionType optTyp = tidyOptGetType( topt );

    d->name = tidyOptGetName( topt );
    d->cat = ConfigCategoryName( tidyOptGetCategory( topt ) );
    d->vals = NULL;
    d->def = NULL;
    d->haveVals = yes;

    /* Handle special cases first.
     */
    switch ( optId )
    {
    case TidyDuplicateAttrs:
    case TidyNewline:
    case TidyAccessibilityCheckLevel:
        d->type = "enum";
        d->vals = NULL;
        d->def =
            optId==TidyNewline ?
            "<em>Platform dependent</em>"
            :tidyOptGetCurrPick( tdoc, optId );
        break;

    case TidyDoctype:
        d->type = "DocType";
        d->vals = NULL;
        {
            ctmbstr sdef = NULL;
            sdef = tidyOptGetCurrPick( tdoc, TidyDoctypeMode );
            if ( !sdef || *sdef == '*' )
                sdef = tidyOptGetValue( tdoc, TidyDoctype );
            d->def = sdef;
        }
        break;

    case TidyInlineTags:
    case TidyBlockTags:
    case TidyEmptyTags:
    case TidyPreTags:
        d->type = "Tag names";
        d->vals = "tagX, tagY, ...";
        d->def = NULL;
        break;

    case TidyCharEncoding:
    case TidyInCharEncoding:
    case TidyOutCharEncoding:
        d->type = "Encoding";
        d->def = tidyOptGetEncName( tdoc, optId );
        if (!d->def)
            d->def = "?";
        d->vals = NULL;
        break;

        /* General case will handle remaining */
    default:
        switch ( optTyp )
        {
        case TidyBoolean:
            d->type = "Boolean";
            d->vals = "y/n, yes/no, t/f, true/false, 1/0";
            d->def = tidyOptGetCurrPick( tdoc, optId );
            break;

        case TidyInteger:
            if (isAutoBool(topt))
            {
                d->type = "AutoBool";
                d->vals = "auto, y/n, yes/no, t/f, true/false, 1/0";
                d->def = tidyOptGetCurrPick( tdoc, optId );
            }
            else
            {
                uint idef;
                d->type = "Integer";
                if ( optId == TidyWrapLen )
                    d->vals = "0 (no wrapping), 1, 2, ...";
                else
                    d->vals = "0, 1, 2, ...";

                idef = tidyOptGetInt( tdoc, optId );
                sprintf(d->tempdefs, "%u", idef);
                d->def = d->tempdefs;
            }
            break;

        case TidyString:
            d->type = "String";
            d->vals = NULL;
            d->haveVals = no;
            d->def = tidyOptGetValue( tdoc, optId );
            break;
        }
    }
}

/* Array holding all options. Contains a trailing sentinel. */
typedef struct {
    TidyOption topt[N_TIDY_OPTIONS];
} AllOption_t;

static
int cmpOpt(const void* e1_, const void *e2_)
{
    const TidyOption* e1 = (const TidyOption*)e1_;
    const TidyOption* e2 = (const TidyOption*)e2_;
    return strcmp(tidyOptGetName(*e1), tidyOptGetName(*e2));
}

static
void getSortedOption( TidyDoc tdoc, AllOption_t *tOption )
{
    TidyIterator pos = tidyGetOptionList( tdoc );
    uint i = 0;

    while ( pos )
    {
        TidyOption topt = tidyGetNextOption( tdoc, &pos );
        tOption->topt[i] = topt;
        ++i;
    }
    tOption->topt[i] = NULL; /* sentinel */

    qsort(tOption->topt,
          /* Do not sort the sentinel: hence `-1' */
          sizeof(tOption->topt)/sizeof(tOption->topt[0])-1,
          sizeof(tOption->topt[0]),
          cmpOpt);
}

static void ForEachSortedOption( TidyDoc tdoc, OptionFunc OptionPrint )
{
    AllOption_t tOption;
    const TidyOption *topt;

    getSortedOption( tdoc, &tOption );
    for( topt = tOption.topt; *topt; ++topt)
    {
        OptionDesc d;

        GetOption( tdoc, *topt, &d );
        (*OptionPrint)( tdoc, *topt, &d );
    }
}

static void ForEachOption( TidyDoc tdoc, OptionFunc OptionPrint )
{
    TidyIterator pos = tidyGetOptionList( tdoc );

    while ( pos )
    {
        TidyOption topt = tidyGetNextOption( tdoc, &pos );
        OptionDesc d;

        GetOption( tdoc, topt, &d );
        (*OptionPrint)( tdoc, topt, &d );
    }
}

static
void PrintAllowedValuesFromPick( TidyOption topt )
{
    TidyIterator pos = tidyOptGetPickList( topt );
    Bool first = yes;
    ctmbstr def;
    while ( pos )
    {
        if (first)
            first = no;
        else
            printf(", ");
        def = tidyOptGetNextPick( topt, &pos );
        printf("%s", def);
    }
}

static
void PrintAllowedValues( TidyOption topt, const OptionDesc *d )
{
    if (d->vals)
        printf( "%s", d->vals );
    else
        PrintAllowedValuesFromPick( topt );
}

static
void printXMLDescription( TidyDoc tdoc, TidyOption topt )
{
    ctmbstr doc = tidyOptGetDoc( tdoc, topt );

    if (doc)
        printf("  <description>%s</description>\n", doc);
    else
    {
        printf("  <description />\n");
        fprintf(stderr, "Warning: option `%s' is not documented.\n",
                tidyOptGetName( topt ));
    }
}

static
void printXMLCrossRef( TidyDoc tdoc, TidyOption topt )
{
    TidyOption optLinked;
    TidyIterator pos = tidyOptGetDocLinksList(tdoc, topt);
    while( pos )
    {
        optLinked = tidyOptGetNextDocLinks(tdoc, &pos );
        printf("  <seealso>%s</seealso>\n",tidyOptGetName(optLinked));
    }
}

static
void printXMLOption( TidyDoc tdoc, TidyOption topt, OptionDesc *d )
{
    if ( tidyOptIsReadOnly(topt) )
        return;

    printf( " <option class=\"%s\">\n", d->cat );
    printf  ("  <name>%s</name>\n",d->name);
    printf  ("  <type>%s</type>\n",d->type);
    if (d->def)
        printf("  <default>%s</default>\n",d->def);
    else
        printf("  <default />\n");
    if (d->haveVals)
    {
        printf("  <example>");
        PrintAllowedValues( topt, d );
        printf("</example>\n");
    }
    else
    {
        printf("  <example />\n");
    }
    printXMLDescription( tdoc, topt );
    printXMLCrossRef( tdoc, topt );
    printf( " </option>\n" );
}

static void XMLoptionhelp( TidyDoc tdoc )
{
    printf( "<?xml version=\"1.0\"?>\n"
            "<config version=\"%s\">\n", tidyReleaseDate());
    ForEachOption( tdoc, printXMLOption );
    printf( "</config>\n" );
}

static
tmbstr GetAllowedValuesFromPick( TidyOption topt )
{
    TidyIterator pos;
    Bool first;
    ctmbstr def;
    uint len = 0;
    tmbstr val;

    pos = tidyOptGetPickList( topt );
    first = yes;
    while ( pos )
    {
        if (first)
            first = no;
        else
            len += 2;
        def = tidyOptGetNextPick( topt, &pos );
        len += strlen(def);
    }
    val = (tmbstr)malloc(len+1);
    val[0] = '\0';
    pos = tidyOptGetPickList( topt );
    first = yes;
    while ( pos )
    {
        if (first)
            first = no;
        else
            strcat(val, ", ");
        def = tidyOptGetNextPick( topt, &pos );
        strcat(val, def);
    }
    return val;
}

static
tmbstr GetAllowedValues( TidyOption topt, const OptionDesc *d )
{
    if (d->vals)
    {
        tmbstr val = (tmbstr)malloc(1+strlen(d->vals));
        strcpy(val, d->vals);
        return val;
    }
    else
        return GetAllowedValuesFromPick( topt );
}

static
void printOption( TidyDoc ARG_UNUSED(tdoc), TidyOption topt,
                  OptionDesc *d )
{
    if ( tidyOptIsReadOnly(topt) )
        return;

    if ( *d->name || *d->type )
    {
        ctmbstr pval = d->vals;
        tmbstr val = NULL;
        if (!d->haveVals)
        {
            pval = "-";
        }
        else if (pval == NULL)
        {
            val = GetAllowedValues( topt, d);
            pval = val;
        }
        print3Columns( fmt, 27, 9, 40, d->name, d->type, pval );
        if (val)
            free(val);
    }
}

static void optionhelp( TidyDoc tdoc )
{
    printf( "\nHTML Tidy Configuration Settings\n\n" );
    printf( "Within a file, use the form:\n\n" );
    printf( "wrap: 72\n" );
    printf( "indent: no\n\n" );
    printf( "When specified on the command line, use the form:\n\n" );
    printf( "--wrap 72 --indent no\n\n");

    printf( fmt, "Name", "Type", "Allowable values" );
    printf( fmt, ul, ul, ul );

    ForEachSortedOption( tdoc, printOption );
}

static
void printOptionValues( TidyDoc ARG_UNUSED(tdoc), TidyOption topt,
                        OptionDesc *d )
{
    TidyOptionId optId = tidyOptGetId( topt );
    ctmbstr ro = tidyOptIsReadOnly( topt ) ? "*" : "" ;

    switch ( optId )
    {
    case TidyInlineTags:
    case TidyBlockTags:
    case TidyEmptyTags:
    case TidyPreTags:
        {
            TidyIterator pos = tidyOptGetDeclTagList( tdoc );
            while ( pos )
            {
                d->def = tidyOptGetNextDeclTag(tdoc, optId, &pos);
                if ( pos )
                {
                    if ( *d->name )
                        printf( valfmt, d->name, d->type, ro, d->def );
                    else
                        printf( fmt, d->name, d->type, d->def );
                    d->name = "";
                    d->type = "";
                }
            }
        }
        break;
    case TidyNewline:
        d->def = tidyOptGetCurrPick( tdoc, optId );
        break;
    }

    /* fix for http://tidy.sf.net/bug/873921 */
    if ( *d->name || *d->type || (d->def && *d->def) )
    {
        if ( ! d->def )
            d->def = "";
        if ( *d->name )
            printf( valfmt, d->name, d->type, ro, d->def );
        else
            printf( fmt, d->name, d->type, d->def );
    }
}

static void optionvalues( TidyDoc tdoc )
{
    printf( "\nConfiguration File Settings:\n\n" );
    printf( fmt, "Name", "Type", "Current Value" );
    printf( fmt, ul, ul, ul );

    ForEachSortedOption( tdoc, printOptionValues );

    printf( "\n\nValues marked with an *asterisk are calculated \n"
            "internally by HTML Tidy\n\n" );
}

static void version( void )
{
#ifdef PLATFORM_NAME
    printf( "HTML Tidy for %s released on %s\n",
             PLATFORM_NAME, tidyReleaseDate() );
#else
    printf( "HTML Tidy released on %s\n", tidyReleaseDate() );
#endif
}

static void unknownOption( uint c, tmbstr arg )
{
    fprintf( errout, "ERROR: unknown option: %c - Part of %s\n", (char)c, arg );
}


ENCODINGS _Encodings[] = {
   { "raw", 0 },
   { "ascii", 0 },
   { "latin0", 0 },
   { "latin1", 0 },
   { "utf8", 0 },
#ifndef NO_NATIVE_ISO2022_SUPPORT
   { "iso2022", 0 },
#endif
#if SUPPORT_UTF16_ENCODINGS
   { "utf16le", 0 },
   { "utf16be", 0 },
   { "utf16", 0 },
#endif
#if SUPPORT_ASIAN_ENCODINGS
   { "shiftjis", 0 },
   { "big5", 0 },
#endif
   { "mac", 0 },
   { "win1252", 0 },
   { "ibm858", 0 },
   { 0,        0 }
};

Bool tidy_SetIndent( TidyDoc tdoc, TidyOptionId id, Bool b )
{
   Bool ret = tidyOptSetInt( tdoc, id, TidyAutoState );
   if ( tidyOptGetInt(tdoc, TidyIndentSpaces) == 0 )
      tidyOptResetToDefault( tdoc, TidyIndentSpaces );
   return ret;
}

Bool tidy_SetBool( TidyDoc tdoc, TidyOptionId id, Bool b )
{
   return tidyOptSetBool( tdoc, id, yes );
}
Bool tidy_UnsetBool( TidyDoc tdoc, TidyOptionId id, Bool b )
{
   return tidyOptSetBool( tdoc, id, no );
}

Bool tidy_Help( TidyDoc tdoc, TidyOptionId id, Bool b )
{
   help( prog );
   pgm_exit( tdoc, 0 );
   return 0; /* success */
}

Bool tidy_Xml_Help( TidyDoc tdoc, TidyOptionId id, Bool b )
{
   xml_help( );
   pgm_exit( tdoc, 0 );
   return 0; /* success */
}

Bool tidy_Conf_Help( TidyDoc tdoc, TidyOptionId id, Bool b )
{
   optionhelp( tdoc );
   pgm_exit( tdoc, 0 );
   return 0; /* success */
}

Bool tidy_Xml_Conf( TidyDoc tdoc, TidyOptionId id, Bool b )
{
   XMLoptionhelp( tdoc );
   pgm_exit( tdoc, 0 );
   return 0; /* success */
}

Bool tidy_Shw_Conf( TidyDoc tdoc, TidyOptionId id, Bool b )
{
   optionvalues( tdoc );
   pgm_exit( tdoc, 0 );
   return 0; /* success */
}

void tidy_Check_Arg2( TidyDoc tdoc, tmbstr arg2 )
{
   if ( !arg2 || ( *arg2 == 0 ) )
   {
      fprintf(errout, "ERROR: No second argment located!\n");
      pgm_exit(tdoc, 3);
   }
}

/* Set new error output stream if setting changed */
void  tidy_Fix_Error_File( TidyDoc tdoc )
{
   ctmbstr post = tidyOptGetValue( tdoc, TidyErrFile );
   if ( post && ( !errfil || !samefile(errfil, post)) )
   {
      errfil = post;
      errout = tidySetErrorFile( tdoc, post );
   }
}

Bool tidy_Conf_File( TidyDoc tdoc, tmbstr arg2 )
{
   tidy_Check_Arg2( tdoc, arg2 );
   if ( tidyLoadConfig( tdoc, arg2 ) )
   {
      fprintf(errout, "ERROR: Loading config file \"%s\"!\n", arg2);
      pgm_exit(tdoc, 3);
      return no;
   }

   /* Set new error output stream if setting changed */
   tidy_Fix_Error_File( tdoc );
   return yes;
}

Bool tidy_Set_Lang( TidyDoc tdoc, tmbstr arg2 )
{
   tidy_Check_Arg2( tdoc, arg2 );
   if ( !tidyOptSetValue( tdoc, TidyLanguage, arg2 ) )
   {
      fprintf(errout, "ERROR: Setting language \"%s\"!\n", arg2);
      pgm_exit(tdoc, 3);
      return no;
   }
   return yes;
}

Bool tidy_Set_Out( TidyDoc tdoc, tmbstr arg2 )
{
   tidy_Check_Arg2( tdoc, arg2 );
   if ( !tidyOptSetValue( tdoc, TidyOutFile, arg2 ) )
   {
      fprintf(errout, "ERROR: Setting output file \"%s\"!\n", arg2);
      pgm_exit(tdoc, 3);
      return no;
   }
   return yes;
}

Bool tidy_Set_Err( TidyDoc tdoc, tmbstr arg2 )
{
   tidy_Check_Arg2( tdoc, arg2 );
   errfil = arg2;
   errout = tidySetErrorFile( tdoc, errfil );
   return yes;
}

Bool tidy_Set_Wrap( TidyDoc tdoc, tmbstr arg2 )
{
   uint wraplen = 0;
   int nfields = 0;
   tidy_Check_Arg2( tdoc, arg2 );
   nfields = sscanf( arg2, "%u", &wraplen );
   tidyOptSetInt( tdoc, TidyWrapLen, wraplen );
   return yes;
}

Bool tidy_Show_Vers( TidyDoc tdoc, tmbstr arg2 )
{
   version();
   pgm_exit( tdoc, 0 );
   return 0;  /* success */
}

Bool tidy_Set_Access( TidyDoc tdoc, tmbstr arg2 )
{
   uint acclvl = 0;
   int nfields;
   tidy_Check_Arg2( tdoc, arg2 );
   nfields = sscanf( arg2, "%u", &acclvl );
   tidyOptSetInt( tdoc, TidyAccessibilityCheckLevel, acclvl );
   return yes;
}

#define  TIDY_MAX_ARGS  128

CMDTABLE cmd_table[] = {
   { "xml",   0,         tidy_SetBool,   TidyXmlTags,       0, 0  },
   { "asxml", "asxhtml", tidy_SetBool,   TidyXhtmlOut,      0, 0  },
   { "ashtml", 0,        tidy_SetBool,   TidyHtmlOut,       0, 0  }, 
   { "indent", "i",      tidy_SetIndent, TidyIndentContent, 0, 0  }, /* added i */
   { "omit",   0,        tidy_SetBool,   TidyHideEndTags,   0, 0  }, 
   { "upper",  "u",      tidy_SetBool,   TidyUpperCaseTags, 0, 0  }, /* added u */
   { "clean",  "c",      tidy_SetBool,   TidyMakeClean,     0, 0  }, /* added c */
   { "bare",   "b",      tidy_SetBool,   TidyMakeBare,      0, 0  }, /* added b */
   { "numeric","n",      tidy_SetBool,   TidyNumEntities,   0, 0  }, /* added n */
   { "modify", "m",      tidy_SetBool,   TidyWriteBack,     0, 0  }, /* added m */
   { "change", "update", tidy_SetBool,   TidyWriteBack,     0, 0  }, /* obsolete */
   { "errors", "e",      tidy_UnsetBool, TidyShowMarkup,    0, 0  }, /* added e */
   { "quiet",  "q",      tidy_SetBool,   TidyQuiet,         0, 0  }, /* added q */
   { "help",   "h",      tidy_Help,      0,                 0, 0  },
   { "?",      0,        tidy_Help,      0,                 0, 0  },
   { "xml-help", 0,      tidy_Xml_Help,  0,                 0, 0  },
   { "help-config", 0,   tidy_Conf_Help, 0,                 0, 0  },
   { "xml-config",  0,   tidy_Xml_Conf,  0,                 0, 0  },
   { "show-config", 0,   tidy_Shw_Conf,  0,                 0, 0  },
   { "config",      0,   0,              0,    tidy_Conf_File, 0  },
#if SUPPORT_ASIAN_ENCODINGS
   { "language", "lang", 0,              0,    tidy_Set_Lang,  0  },
#endif
   { "output",   "o",    0,              0,    tidy_Set_Out,   0  },
   { "output-file", 0,   0,              0,    tidy_Set_Out,   0  },
   { "file",     "f",    0,              0,    tidy_Set_Err,   0  },
   { "wrap",     "w",    0,              0,    tidy_Set_Wrap,  0  },
   { "version",  "v",    0,              0,    tidy_Show_Vers, 0  },
#if SUPPORT_ACCESSIBILITY_CHECKS
   { "access",   0,      0,              0,    tidy_Set_Access,0  },
#endif
   { 0,       0,         0,              0,                 0, 0  }
};

void  Clear_CMDTABLE( void )
{
   PCMDTABLE pct = &cmd_table[0];
   while ( pct->cmd )
   {
      pct->flag = 0;
      pct++;
   }
}

int mark_CMDTABLE_name( tmbstr arg )
{
   PCMDTABLE pct = &cmd_table[0];
   while ( pct->cmd )
   {
      if (( strcasecmp(arg, pct->cmd) == 0 ) ||
          ( pct->altcmd && ( strcasecmp(arg, pct->altcmd) == 0 )))
      {
         pct->flag++;
         return 1;
         break;
      }
      pct++;
   }
   return 0;
}

void Clear_ENCODINGS( void )
{
   PENCODINGS pce = &_Encodings[0];
   while ( pce->name )
   {
      pce->flag = 0;
      pce++;
   }
}


int mark_ENCODINGS( tmbstr arg )
{
   PENCODINGS pce = &_Encodings[0];
   while ( pce->name )
   {
      if ( strcasecmp(arg, pce->name) == 0 )
      {
         pce->flag++;
         return 1;
         break;
      }
      pce++;
   }
   return 0;
}

int tidy_unmarked_cmds( void )
{
   int   i = 0;
   PCMDTABLE pct = &cmd_table[0];
   while ( pct->cmd )
   {
      if( pct->flag == 0 )
         i++;
      pct++;
   }
   return i;
}

int tidy_unmarked_encs( void )
{
   int i = 0;
   PENCODINGS pce = &_Encodings[0];
   while ( pce->name )
   {
      if( pce->flag == 0 )
         i++;
      pce++;
   }
   return i;
}

void  tidy_show_unmarked( void )
{
   PCMDTABLE pct;
   PENCODINGS pce;
   int   i = tidy_unmarked_cmds();
   int   j = tidy_unmarked_encs();
   tmbstr other = "Other Commands";
   size_t len =  strlen(other);
   if( i )
   {
      printf("%s\n", other );
      printf("%*.*s\n", (int)len, (int)len, helpul );
      pct = &cmd_table[0];
      while ( pct->cmd )
      {
         if( pct->flag == 0 )
            print2Columns( helpfmt, 19, 58, pct->cmd, "???" );
         pct++;
      }
      printf("\n");
   }
   if( j )
   {
      other = "Other Encodings";
      len = strlen(other);
      printf("%s\n", other );
      printf("%*.*s\n", (int)len, (int)len, helpul );
      pce = &_Encodings[0];
      while ( pce->name )
      {
         if( pce->flag == 0 )
            print2Columns( helpfmt, 19, 58, pce->name, "???" );
         pce++;
      }
      printf("\n");
   }
}

int   Process_Args( TidyDoc tdoc, int argc, char** argv );
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 = 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;
}

static tmbstr tidy_strdupe( TidyDoc tdoc, tmbstr stg )
{
   ulong len = strlen(stg);
   tmbstr dupe = (tmbstr)malloc( len ) + 1;
   if ( !dupe )
   {
      fprintf(errout, "ERROR: MEMORY FAILED!\n" );
      pgm_exit( tdoc, 3 );
   }
   strcpy(dupe, stg);
   return dupe;
}

int   Process_Args( TidyDoc tdoc, int argc, char** argv )
{
   PCMDTABLE pct;
   PENCODINGS pce;
   int   i, dncmd, is2;
   tmbstr arg, arg2;
   dncmd = 0;
   for ( i = 1; i < argc; i++ ) {
      arg = argv[i];
      arg2 = 0;
      is2  = 0;
      if( (i + 1) < argc )
         arg2 = argv[i+1];
      if ( *arg == '@' )
      {
         arg++;
         Process_Input( tdoc, arg );
      }
      else if( *arg == '-' )
      {
         arg++;
         if ( *arg == '-' )
         {
            is2 = 1;
            arg++;
         }
         pct = &cmd_table[0];
         while( pct->cmd )
         {
            if (( strcasecmp(arg, pct->cmd) == 0 ) ||
                ( pct->altcmd && ( strcasecmp(arg, pct->altcmd) == 0 )))
            {
               if( pct->servb )
               {
                  pct->servb( tdoc, pct->optid, yes );
               }
               else if ( pct->serv2 )
               {
                  pct->serv2( tdoc, arg2 );
                  i++;  /* used up argument 2 */
               }
               dncmd = 1;
               break;
            }
            pct++;
         }
         if( !dncmd )
         {
            pce = &_Encodings[0];
            while( pce->name )
            {
               if ( strcasecmp(arg, pce->name) == 0 )
               {
                  tidySetCharEncoding( tdoc, arg );
                  dncmd = 1;
                  break;
               }
               pce++;
            }
         }

         if ( !dncmd )
         {
            if ( is2 )
            {
               /* else if ( strncmp(argv[1], "--", 2 ) == 0) */
               if ( tidyOptParseValue(tdoc, arg, arg2) )
               {
                  /* Set new error output stream if setting changed */
                  tidy_Fix_Error_File( tdoc );
                  dncmd = 1;
                  i++; /* used up argument */
               }
            }
            else
            {
               /* can only try for list of single options */
               uint c;
               ctmbstr s = arg;
               while ( c = *s++ )
               {
                  switch ( c )
                  {
                  case 'i':
                     tidyOptSetInt( tdoc, TidyIndentContent, TidyAutoState );
                     if ( tidyOptGetInt(tdoc, TidyIndentSpaces) == 0 )
                         tidyOptResetToDefault( tdoc, TidyIndentSpaces );
                     break;

                  case 'u':
                     tidyOptSetBool( tdoc, TidyUpperCaseTags, yes );
                     break;

                  case 'c':
                     tidyOptSetBool( tdoc, TidyMakeClean, yes );
                     break;

                  case 'b':
                     tidyOptSetBool( tdoc, TidyMakeBare, yes );
                     break;

                  case 'n':
                     tidyOptSetBool( tdoc, TidyNumEntities, yes );
                     break;

                  case 'm':
                     tidyOptSetBool( tdoc, TidyWriteBack, yes );
                     break;

                  case 'e':
                     tidyOptSetBool( tdoc, TidyShowMarkup, no );
                     break;

                  case 'q':
                     tidyOptSetBool( tdoc, TidyQuiet, yes );
                     break;

                  default:
                     unknownOption( c, argv[i] );
                     pgm_exit( tdoc, 3 );
                     break;
                  }
               }
            }
         }

         if ( !dncmd )
         {
            fprintf( errout, "ERROR: Unknown command: %s\n", argv[i] );
            pgm_exit( tdoc, 3 );
         }
      }
      else
      {
         /* not '@' or '-', then assumed is the HTML input file */
         if ( htmlfil )
         {
            fprintf( errout, "ERROR: Found second input file: %s\n"
               "Already have %s!\n", arg, htmlfil );
            pgm_exit( tdoc, 3 );
         }

         htmlfil = tidy_strdupe(tdoc, arg); /* duplicate string */
         if ( tidyOptGetBool(tdoc, TidyEmacs) )
            tidyOptSetValue( tdoc, TidyEmacsFile, htmlfil );
      }
   }
   return 0;
}

int main( int argc, char** argv )
{
    /* ctmbstr cfgfil = NULL, errfil = NULL, htmlfil = NULL; */
    TidyDoc tdoc = tidyCreate();
    int status = 0;

    uint contentErrors = 0;
    uint contentWarnings = 0;
    uint accessWarnings = 0;

    errout = stderr;  /* initialize to stderr */
    status = 0;
    prog = argv[0];
    
#ifdef TIDY_CONFIG_FILE
    if ( tidyFileExists( tdoc, TIDY_CONFIG_FILE) )
    {
        status = tidyLoadConfig( tdoc, TIDY_CONFIG_FILE );
        if ( status != 0 ) {
           fprintf(errout, "ERROR: Loading config file \"%s\" failed, err = %d\n", TIDY_CONFIG_FILE, status);
           pgm_exit( tdoc, 3 );
           return 3;
        }
    }
#endif /* TIDY_CONFIG_FILE */

    /* look for env var "HTML_TIDY" */
    /* then for ~/.tidyrc (on platforms defining $HOME) */

    if ( cfgfil = getenv("HTML_TIDY") )
    {
        status = tidyLoadConfig( tdoc, cfgfil );
        if ( status != 0 ) {
           fprintf(errout, "ERROR: Loading config file \"%s\" failed, err = %d\n", cfgfil, status);
           pgm_exit( tdoc, 3 );
           return 3;
        }
    }
#ifdef TIDY_USER_CONFIG_FILE
    else if ( tidyFileExists( tdoc, TIDY_USER_CONFIG_FILE) )
    {
        status = tidyLoadConfig( tdoc, TIDY_USER_CONFIG_FILE );
        if ( status != 0 ) {
           fprintf(errout, "ERROR: Loading config file \"%s\" failed, err = %d\n", TIDY_USER_CONFIG_FILE, status);
           pgm_exit( tdoc, 3 );
           return 3;
        }
    }
#endif /* TIDY_USER_CONFIG_FILE */

    /* read command line */
    Process_Args( tdoc, argc, argv );

    if ( !htmlfil )
    {
       fprintf(errout, "ERROR: No input file located in command!\n" );
       pgm_exit( tdoc, 3 );
       return 3;
    }

    status = tidyParseFile( tdoc, htmlfil );

    if ( status >= 0 )
       status = tidyCleanAndRepair( tdoc );

    if ( status >= 0 )
       status = tidyRunDiagnostics( tdoc );

    if ( status > 1 ) /* If errors, do we want to force output? */
       status = ( tidyOptGetBool(tdoc, TidyForceOutput) ? status : -1 );

    if ( status >= 0 && tidyOptGetBool(tdoc, TidyShowMarkup) )
    {
       if ( tidyOptGetBool(tdoc, TidyWriteBack) && argc > 1 )
          status = tidySaveFile( tdoc, htmlfil );
       else
       {
          ctmbstr outfil = tidyOptGetValue( tdoc, TidyOutFile );
          if ( outfil )
             status = tidySaveFile( tdoc, outfil );
          else
             status = tidySaveStdout( tdoc );   
       }
    }

    contentErrors   += tidyErrorCount( tdoc );
    contentWarnings += tidyWarningCount( tdoc );
    accessWarnings  += tidyAccessWarningCount( tdoc );

    if (!tidyOptGetBool(tdoc, TidyQuiet) &&
        errout == stderr && !contentErrors)
        fprintf(errout, "\n");

    if (contentErrors + contentWarnings > 0 && 
         !tidyOptGetBool(tdoc, TidyQuiet))
        tidyErrorSummary(tdoc);

    if (!tidyOptGetBool(tdoc, TidyQuiet))
        tidyGeneralInfo(tdoc);

    /* called to free hash tables etc. */
    tidyRelease( tdoc );

    /* return status can be used by scripts */
    if ( contentErrors > 0 )
        return 2;

    if ( contentWarnings > 0 )
        return 1;

    /* 0 signifies all is ok */
    return 0;
}

/* OLD MAIN FOR REFERENCE ONLY */
int OLD_main( int argc, char** argv )
{
    ctmbstr prog = argv[0];
    ctmbstr cfgfil = NULL, errfil = NULL, htmlfil = NULL;
    TidyDoc tdoc = tidyCreate();
    int status = 0;

    uint contentErrors = 0;
    uint contentWarnings = 0;
    uint accessWarnings = 0;

    errout = stderr;  /* initialize to stderr */
    status = 0;
    
#ifdef TIDY_CONFIG_FILE
    if ( tidyFileExists( tdoc, TIDY_CONFIG_FILE) )
    {
        status = tidyLoadConfig( tdoc, TIDY_CONFIG_FILE );
        if ( status != 0 )
            fprintf(errout, "Loading config file \"%s\" failed, err = %d\n", TIDY_CONFIG_FILE, status);
    }
#endif /* TIDY_CONFIG_FILE */

    /* look for env var "HTML_TIDY" */
    /* then for ~/.tidyrc (on platforms defining $HOME) */

    if ( cfgfil = getenv("HTML_TIDY") )
    {
        status = tidyLoadConfig( tdoc, cfgfil );
        if ( status != 0 )
            fprintf(errout, "Loading config file \"%s\" failed, err = %d\n", cfgfil, status);
    }
#ifdef TIDY_USER_CONFIG_FILE
    else if ( tidyFileExists( tdoc, TIDY_USER_CONFIG_FILE) )
    {
        status = tidyLoadConfig( tdoc, TIDY_USER_CONFIG_FILE );
        if ( status != 0 )
            fprintf(errout, "Loading config file \"%s\" failed, err = %d\n", TIDY_USER_CONFIG_FILE, status);
    }
#endif /* TIDY_USER_CONFIG_FILE */

    /* read command line */
    while ( argc > 0 )
    {
        if (argc > 1 && argv[1][0] == '-')
        {
            /* support -foo and --foo */
            ctmbstr arg = argv[1] + 1;

            if ( strcasecmp(arg, "xml") == 0)
                tidyOptSetBool( tdoc, TidyXmlTags, yes );

            else if ( strcasecmp(arg,   "asxml") == 0 ||
                      strcasecmp(arg, "asxhtml") == 0 )
            {
                tidyOptSetBool( tdoc, TidyXhtmlOut, yes );
            }
            else if ( strcasecmp(arg,   "ashtml") == 0 )
                tidyOptSetBool( tdoc, TidyHtmlOut, yes );

            else if ( strcasecmp(arg, "indent") == 0 )
            {
                tidyOptSetInt( tdoc, TidyIndentContent, TidyAutoState );
                if ( tidyOptGetInt(tdoc, TidyIndentSpaces) == 0 )
                    tidyOptResetToDefault( tdoc, TidyIndentSpaces );
            }
            else if ( strcasecmp(arg, "omit") == 0 )
                tidyOptSetBool( tdoc, TidyHideEndTags, yes );

            else if ( strcasecmp(arg, "upper") == 0 )
                tidyOptSetBool( tdoc, TidyUpperCaseTags, yes );

            else if ( strcasecmp(arg, "clean") == 0 )
                tidyOptSetBool( tdoc, TidyMakeClean, yes );

            else if ( strcasecmp(arg, "bare") == 0 )
                tidyOptSetBool( tdoc, TidyMakeBare, yes );

            else if ( strcasecmp(arg, "raw") == 0      ||
                      strcasecmp(arg, "ascii") == 0    ||
                      strcasecmp(arg, "latin0") == 0   ||
                      strcasecmp(arg, "latin1") == 0   ||
                      strcasecmp(arg, "utf8") == 0     ||
#ifndef NO_NATIVE_ISO2022_SUPPORT
                      strcasecmp(arg, "iso2022") == 0  ||
#endif
#if SUPPORT_UTF16_ENCODINGS
                      strcasecmp(arg, "utf16le") == 0  ||
                      strcasecmp(arg, "utf16be") == 0  ||
                      strcasecmp(arg, "utf16") == 0    ||
#endif
#if SUPPORT_ASIAN_ENCODINGS
                      strcasecmp(arg, "shiftjis") == 0 ||
                      strcasecmp(arg, "big5") == 0     ||
#endif
                      strcasecmp(arg, "mac") == 0      ||
                      strcasecmp(arg, "win1252") == 0  ||
                      strcasecmp(arg, "ibm858") == 0 )
            {
                tidySetCharEncoding( tdoc, arg );
            }
            else if ( strcasecmp(arg, "numeric") == 0 )
                tidyOptSetBool( tdoc, TidyNumEntities, yes );

            else if ( strcasecmp(arg, "modify") == 0 ||
                      strcasecmp(arg, "change") == 0 ||  /* obsolete */
                      strcasecmp(arg, "update") == 0 )   /* obsolete */
            {
                tidyOptSetBool( tdoc, TidyWriteBack, yes );
            }
            else if ( strcasecmp(arg, "errors") == 0 )
                tidyOptSetBool( tdoc, TidyShowMarkup, no );

            else if ( strcasecmp(arg, "quiet") == 0 )
                tidyOptSetBool( tdoc, TidyQuiet, yes );

            else if ( strcasecmp(arg, "help") == 0 ||
                      strcasecmp(arg,    "h") == 0 || *arg == '?' )
            {
                help( prog );
                tidyRelease( tdoc );
                return 0; /* success */
            }
            else if ( strcasecmp(arg, "xml-help") == 0)
            {
                xml_help( );
                tidyRelease( tdoc );
                return 0; /* success */
            }
            else if ( strcasecmp(arg, "help-config") == 0 )
            {
                optionhelp( tdoc );
                tidyRelease( tdoc );
                return 0; /* success */
            }
            else if ( strcasecmp(arg, "xml-config") == 0 )
            {
                XMLoptionhelp( tdoc );
                tidyRelease( tdoc );
                return 0; /* success */
            }
            else if ( strcasecmp(arg, "show-config") == 0 )
            {
                optionvalues( tdoc );
                tidyRelease( tdoc );
                return 0; /* success */
            }
            else if ( strcasecmp(arg, "config") == 0 )
            {
                if ( argc >= 3 )
                {
                    ctmbstr post;

                    tidyLoadConfig( tdoc, argv[2] );

                    /* Set new error output stream if setting changed */
                    post = tidyOptGetValue( tdoc, TidyErrFile );
                    if ( post && (!errfil || !samefile(errfil, post)) )
                    {
                        errfil = post;
                        errout = tidySetErrorFile( tdoc, post );
                    }

                    --argc;
                    ++argv;
                }
            }

#if SUPPORT_ASIAN_ENCODINGS
            else if ( strcasecmp(arg, "language") == 0 ||
                      strcasecmp(arg,     "lang") == 0 )
            {
                if ( argc >= 3 )
                {
                    tidyOptSetValue( tdoc, TidyLanguage, argv[2] );
                    --argc;
                    ++argv;
                }
            }
#endif

            else if ( strcasecmp(arg, "output") == 0 ||
                      strcasecmp(arg, "-output-file") == 0 ||
                      strcasecmp(arg, "o") == 0 )
            {
                if ( argc >= 3 )
                {
                    tidyOptSetValue( tdoc, TidyOutFile, argv[2] );
                    --argc;
                    ++argv;
                }
            }
            else if ( strcasecmp(arg,  "file") == 0 ||
                      strcasecmp(arg, "-file") == 0 ||
                      strcasecmp(arg,     "f") == 0 )
            {
                if ( argc >= 3 )
                {
                    errfil = argv[2];
                    errout = tidySetErrorFile( tdoc, errfil );
                    --argc;
                    ++argv;
                }
            }
            else if ( strcasecmp(arg,  "wrap") == 0 ||
                      strcasecmp(arg, "-wrap") == 0 ||
                      strcasecmp(arg,     "w") == 0 )
            {
                if ( argc >= 3 )
                {
                    uint wraplen = 0;
                    int nfields = sscanf( argv[2], "%u", &wraplen );
                    tidyOptSetInt( tdoc, TidyWrapLen, wraplen );
                    if (nfields > 0)
                    {
                        --argc;
                        ++argv;
                    }
                }
            }
            else if ( strcasecmp(arg,  "version") == 0 ||
                      strcasecmp(arg, "-version") == 0 ||
                      strcasecmp(arg,        "v") == 0 )
            {
                version();
                tidyRelease( tdoc );
                return 0;  /* success */

            }
            else if ( strncmp(argv[1], "--", 2 ) == 0)
            {
                if ( tidyOptParseValue(tdoc, argv[1]+2, argv[2]) )
                {
                    /* Set new error output stream if setting changed */
                    ctmbstr post = tidyOptGetValue( tdoc, TidyErrFile );
                    if ( post && (!errfil || !samefile(errfil, post)) )
                    {
                        errfil = post;
                        errout = tidySetErrorFile( tdoc, post );
                    }

                    ++argv;
                    --argc;
                }
            }

#if SUPPORT_ACCESSIBILITY_CHECKS
            else if ( strcasecmp(arg, "access") == 0 )
            {
                if ( argc >= 3 )
                {
                    uint acclvl = 0;
                    int nfields = sscanf( argv[2], "%u", &acclvl );
                    tidyOptSetInt( tdoc, TidyAccessibilityCheckLevel, acclvl );
                    if (nfields > 0)
                    {
                        --argc;
                        ++argv;
                    }
                }
            }
#endif

            else
            {
                uint c;
                ctmbstr s = argv[1];

                while ( c = *++s )
                {
                    switch ( c )
                    {
                    case 'i':
                        tidyOptSetInt( tdoc, TidyIndentContent, TidyAutoState );
                        if ( tidyOptGetInt(tdoc, TidyIndentSpaces) == 0 )
                            tidyOptResetToDefault( tdoc, TidyIndentSpaces );
                        break;

                    /* Usurp -o for output file.  Anyone hiding end tags?
                    case 'o':
                        tidyOptSetBool( tdoc, TidyHideEndTags, yes );
                        break;
                    */

                    case 'u':
                        tidyOptSetBool( tdoc, TidyUpperCaseTags, yes );
                        break;

                    case 'c':
                        tidyOptSetBool( tdoc, TidyMakeClean, yes );
                        break;

                    case 'b':
                        tidyOptSetBool( tdoc, TidyMakeBare, yes );
                        break;

                    case 'n':
                        tidyOptSetBool( tdoc, TidyNumEntities, yes );
                        break;

                    case 'm':
                        tidyOptSetBool( tdoc, TidyWriteBack, yes );
                        break;

                    case 'e':
                        tidyOptSetBool( tdoc, TidyShowMarkup, no );
                        break;

                    case 'q':
                        tidyOptSetBool( tdoc, TidyQuiet, yes );
                        break;

                    default:
                        unknownOption( c, argv[1] );
                        break;
                    }
                }
            }

            --argc;
            ++argv;
            continue;
        }

        if ( argc > 1 )
        {
            htmlfil = argv[1];
            if ( tidyOptGetBool(tdoc, TidyEmacs) )
                tidyOptSetValue( tdoc, TidyEmacsFile, htmlfil );
            status = tidyParseFile( tdoc, htmlfil );
        }
        else
        {
            htmlfil = "stdin";
            status = tidyParseStdin( tdoc );
        }

        if ( status >= 0 )
            status = tidyCleanAndRepair( tdoc );

        if ( status >= 0 )
            status = tidyRunDiagnostics( tdoc );

        if ( status > 1 ) /* If errors, do we want to force output? */
            status = ( tidyOptGetBool(tdoc, TidyForceOutput) ? status : -1 );

        if ( status >= 0 && tidyOptGetBool(tdoc, TidyShowMarkup) )
        {
            if ( tidyOptGetBool(tdoc, TidyWriteBack) && argc > 1 )
                status = tidySaveFile( tdoc, htmlfil );
            else
            {
                ctmbstr outfil = tidyOptGetValue( tdoc, TidyOutFile );
                if ( outfil )
                    status = tidySaveFile( tdoc, outfil );
                else
                    status = tidySaveStdout( tdoc );
            }
        }

        contentErrors   += tidyErrorCount( tdoc );
        contentWarnings += tidyWarningCount( tdoc );
        accessWarnings  += tidyAccessWarningCount( tdoc );

        --argc;
        ++argv;

        if ( argc <= 1 )
            break;
    }

    if (!tidyOptGetBool(tdoc, TidyQuiet) &&
        errout == stderr && !contentErrors)
        fprintf(errout, "\n");

    if (contentErrors + contentWarnings > 0 && 
         !tidyOptGetBool(tdoc, TidyQuiet))
        tidyErrorSummary(tdoc);

    if (!tidyOptGetBool(tdoc, TidyQuiet))
        tidyGeneralInfo(tdoc);

    /* called to free hash tables etc. */
    tidyRelease( tdoc );

    /* return status can be used by scripts */
    if ( contentErrors > 0 )
        return 2;

    if ( contentWarnings > 0 )
        return 1;

    /* 0 signifies all is ok */
    return 0;
}



// eof - tidy2.c
