
/**
 *  @file xml2ag.c
 *
 *  This is the main routine for xml2ag.
 *
 *  @group xml2ag
 *  @{
 */
/*
 *  xml2ag Copyright (C) 2002-2015 by Bruce Korb - all rights reserved
 *  This file is part of AutoGen.
 *  AutoGen Copyright (C) 1992-2014 by Bruce Korb - all rights reserved
 *
 *  AutoGen is free software: you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  AutoGen is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

static char const zConflict[] =
    "the file name operand conflicts with the definitions option.\n";

static char const zTextFmt[] =
    "text = '%s';\n";


static char const * typeName[] = {
    "0 - inval",
    "ELEMENT_NODE",
    "ATTRIBUTE_NODE",
    "TEXT_NODE",
    "CDATA_SECTION_NODE",
    "ENTITY_REF_NODE",
    "ENTITY_NODE",
    "PI_NODE",
    "COMMENT_NODE",
    "DOCUMENT_NODE",
    "DOCUMENT_TYPE_NODE",
    "DOCUMENT_FRAG_NODE",
    "NOTATION_NODE",
    "HTML_DOCUMENT_NODE",
    "DTD_NODE",
    "ELEMENT_DECL",
    "ATTRIBUTE_DECL",
    "ENTITY_DECL",
    "NAMESPACE_DECL",
    "XINCLUDE_START",
    "XINCLUDE_END",
    "DOCB_DOCUMENT_NODE" };

int   level = 0;
FILE * outFp;

#define CHUNK_SZ  4096

/* = = = START-STATIC-FORWARD = = = */
/* static forward declarations maintained by :mkfwd */
static char *
loadFile(FILE * fp, size_t * pzSize);

static void
emitIndentation(void);

static char *
trim(char const * pzSrc, size_t * pSz);

static xmlNodePtr
printHeader(xmlDocPtr pDoc);

static void
printAttrs(xmlAttrPtr pAttr);

static void
printNode(xmlNodePtr pNode);

static void
printChildren(xmlNodePtr pNode);
/* = = = END-STATIC-FORWARD = = = */
#define TRIM(s,psz) trim( (char const *)(s), (size_t *)(psz) )

extern void forkAutogen( char const * pzInput );


int
main(int argc, char ** argv)
{
    xmlDocPtr   pDoc;
    char const * pzFile = NULL;

    {
        int ct = optionProcess( &xml2agOptions, argc, argv );
        argc -= ct;
        argv += ct;

        switch (argc) {
        case 1:
            if (strcmp( *argv, "-" ) != 0) {
                if (HAVE_OPT( DEFINITIONS )) {
                    fprintf( stderr, zConflict );
                    USAGE( EXIT_FAILURE );
                }
                pzFile = *argv;
                break;
            }
        case 0:
            if (   HAVE_OPT( DEFINITIONS )
               && (strcmp( OPT_ARG( DEFINITIONS ), "-" ) != 0) )

                pzFile = OPT_ARG( DEFINITIONS );
            break;

        default:
            fprintf( stderr, "only one argument allowed\n" );
            return EXIT_FAILURE;
        }
    }

    if (! HAVE_OPT( OUTPUT ))
        forkAutogen( pzFile );
    else
        outFp = stdout;

    if (pzFile != NULL) {
        fprintf( outFp, "/* Parsing file %s */\n", pzFile );
        pDoc = xmlParseFile( pzFile );
    }
    else {
        size_t sz;
        char * pz = loadFile( stdin, &sz );
        pDoc = xmlParseMemory( pz, (int)sz );
        fprintf( outFp, "/* Parsed from stdin */\n" );
    }

    {
        static char const z_not_doc[] =
            "/* type %d doc is not DOCUMENT or HTML_DOCUMENT */\n";

        xmlNodePtr pRoot = printHeader( pDoc );
        printAttrs( pRoot->properties );
        switch (pDoc->type) {
        case XML_DOCUMENT_NODE:
        case XML_HTML_DOCUMENT_NODE:
            printChildren( pRoot->children );
            break;
        default:
            fprintf( outFp, z_not_doc, pDoc->type );
        }
    }

    xmlCleanupParser();
    return 0;
}


static char *
loadFile(FILE * fp, size_t * pzSize)
{
    size_t  asz = CHUNK_SZ;
    size_t  usz = 0;
    char *  mem = malloc( asz );

    for (;;) {

        if ((usz + CHUNK_SZ) > asz) {
            asz += CHUNK_SZ;
            mem = realloc( mem, asz );
        }

        if (mem == NULL) {
            fprintf(stderr, "Cannot allocate %d byte bufer\n", (int)asz);
            exit( EXIT_FAILURE );
        }

        {
            size_t rdct = fread(mem + usz, (size_t)1, (size_t)CHUNK_SZ, fp);
            usz += rdct;
            if (rdct < CHUNK_SZ)
                break;
        }
    }

    *pzSize = usz;
    return mem;
}


static void
emitIndentation( void )
{
    int indent = level * 2;
    while (--indent >= 0) fputc( ' ', outFp );
}


static char *
trim(char const * pzSrc, size_t * pSz)
{
    static char   zNil[1] = "";
    static char * pzData  = NULL;
    static size_t dataLen = 0;
    size_t        strSize;

    if (pzSrc == NULL) {
        if (pSz != NULL) *pSz = 0;
        return zNil;
    }

    /*
     *  Trim leading and trailing white space.
     */
    while (isspace( *pzSrc ))  pzSrc++;

    {
        char const * pzEnd = pzSrc + strlen( pzSrc );
        while ((pzEnd > pzSrc) && isspace( pzEnd[-1] ))  pzEnd--;

        if (pzEnd <= pzSrc) {
            if (pSz != NULL) *pSz = 0;
            return zNil;
        }
        strSize = (size_t)(pzEnd - pzSrc);
    }

    /*
     *  Count the extra backslashes required and ensure our buffer is
     *  big enough to hold the newly formed string.
     */
    {
        char const * pz = pzSrc;
        for (;;) {
            pz += strcspn( pz, "'\\" );
            if (*(pz++) == NUL)
                break;
            strSize++;
        }
    }

    if (dataLen <= strSize) {
        size_t sz = (strSize + 0x1000) & ~0x0FFFUL;
        if (pzData == NULL)
             pzData = malloc( sz );
        else pzData = realloc( pzData, sz );
        if (pzData == NULL) {
            fprintf( stderr, "ENOMEM allocating 0x%X bytes", (unsigned)sz );
            exit( EXIT_FAILURE );
        }
        dataLen = sz;
    }

    /*
     *  Copy the data, adding backslashes in front of
     *  single quotes and backslashes.
     */
    {
        char * pzDest = pzData;
        for (;;) {
            switch (*(pzDest++) = *(pzSrc++)) {
            case '\'': pzDest[-1]  = '\\'; *(pzDest++) = '\''; break;
            case '\\': *(pzDest++) = '\\'; break;
            case NUL:  goto set_size;
            }
            if (pzDest == pzData + strSize)
                break;
        }

        *pzDest = '\0';
    }

 set_size:
    if (pSz != NULL) *pSz = strSize;
    return pzData;
}

static xmlNodePtr
printHeader(xmlDocPtr pDoc)
{
    static char const zDef[] = "AutoGen Definitions %s%s;\n";
    char const * pzSfx = ".tpl";

    xmlNodePtr pRootNode = xmlDocGetRootElement( pDoc );
    xmlChar *  pTpl = NULL;
    xmlChar *  pzTpl;

    if (pRootNode == NULL) {
        fprintf( stderr, "Root node not found\n" );
        exit( EXIT_FAILURE );
    }

    if (HAVE_OPT( OVERRIDE_TPL )) {
        if (strchr( OPT_ARG( OVERRIDE_TPL ), '.' ) != NULL)
            pzSfx = "";
        pzTpl = (xmlChar *)VOIDP(OPT_ARG( OVERRIDE_TPL ));
    }
    else {
        pTpl = xmlGetProp(pRootNode, (xmlChar *)VOIDP("template"));
        if (pTpl == NULL) {
            fprintf( stderr, "No template was specified.\n" );
            exit( EXIT_FAILURE );
        }

        pzTpl = pTpl;
        if (strchr( (char *)pzTpl, '.' ) != NULL)
            pzSfx = "";
    }

    fprintf( outFp, zDef, pzTpl, pzSfx );
    if (pTpl != NULL)
        free( pTpl );

    if (pDoc->name != NULL)
        fprintf( outFp, "XML-name = '%s';\n", TRIM( pDoc->name, NULL ));

    if (pDoc->version != NULL)
        fprintf( outFp, "XML-version = '%s';\n", TRIM( pDoc->version, NULL ));

    if (pDoc->encoding != NULL)
        fprintf( outFp, "XML-encoding = '%s';\n", TRIM( pDoc->encoding, NULL ));

    if (pDoc->URL != NULL)
        fprintf( outFp, "XML-URL = '%s';\n", TRIM( pDoc->URL, NULL ));

    if (pDoc->standalone)
        fputs( "XML-standalone = true;\n", outFp );

    return pRootNode;
}

static void
printAttrs(xmlAttrPtr pAttr)
{
    while (pAttr != NULL) {
        char * pzCont = (char *)pAttr->children->content;

        emitIndentation();
        fputs( (char *)VOIDP(pAttr->name), outFp );
        fputs( " = ", outFp );
        if (pAttr->children->children == NULL)
            fprintf( outFp, "'%s';\n", TRIM( pzCont, NULL ));
        else {
            fputs( "{\n", outFp );
            level++;
            if (pzCont != NULL) {
                emitIndentation();
                fprintf( outFp, zTextFmt, TRIM( pzCont, NULL ));
            }
            printChildren( pAttr->children->children );
            level--;
            emitIndentation();
            fputs( "};\n", outFp );
        }

        pAttr = pAttr->next;
    }
}


static void
printNode(xmlNodePtr pNode)
{
    switch (pNode->type) {
    case XML_ELEMENT_NODE:
    {
        size_t sz;
        char * pzTxt;
        emitIndentation();
        fputs( (char *)VOIDP(pNode->name), outFp );
        pzTxt = TRIM( pNode->content, &sz );

        if (  (pNode->properties == NULL)
           && (pNode->children == NULL)) {

            if (sz == 0)
                 fputs( ";\n", outFp );
            else fprintf( outFp, " = '%s';\n", pzTxt );
            break;
        }

        fputs( " = {\n", outFp );
        level++;
        emitIndentation();
        fprintf( outFp, "content = '%s';\n", pzTxt );
        printAttrs( pNode->properties );
        printChildren( pNode->children );
        level--;
        emitIndentation();
        fputs( "};\n", outFp );
        break;
    }

    case XML_ATTRIBUTE_NODE:
        fputs( "Misplaced attribute\n", outFp );
        exit( EXIT_FAILURE );

    case XML_TEXT_NODE:
    {
        size_t sz;
        char * pzTxt = TRIM( pNode->content, &sz );
        if (sz == 0)
            break;
        emitIndentation();
        fprintf( outFp, zTextFmt, pzTxt );
        break;
    }

    case XML_COMMENT_NODE:
    {
        size_t sz;
        char * pzTxt = TRIM( pNode->content, &sz );
        if (sz == 0)
            break;

        emitIndentation();
        fputs( "/* ", outFp );
        for (;;) {
            char * pz = strstr( pzTxt, "*/" );
            if (pz == NULL)
                break;
            fwrite(pzTxt, (size_t)((pz - pzTxt) + 1), (size_t)1, outFp);
            pzTxt = pz+1;
            fputc( ' ', outFp );
        }
        fprintf( outFp, "%s */\n", pzTxt );
        break;
    }

    case XML_CDATA_SECTION_NODE:
    case XML_ENTITY_REF_NODE:
    case XML_ENTITY_NODE:
    case XML_PI_NODE:

    case XML_DOCUMENT_NODE:
    case XML_HTML_DOCUMENT_NODE:
    case XML_DOCUMENT_TYPE_NODE:
    case XML_DOCUMENT_FRAG_NODE:
    case XML_NOTATION_NODE:
    case XML_DTD_NODE:
    case XML_ELEMENT_DECL:
    case XML_ATTRIBUTE_DECL:
    case XML_ENTITY_DECL:
    case XML_NAMESPACE_DECL:
    case XML_XINCLUDE_START:
    case XML_XINCLUDE_END:
        emitIndentation();
        fprintf( outFp, "/* Unsupported XML node type:  %s */\n",
                typeName[ pNode->type ]);
        break;

    default:
        emitIndentation();
        fprintf( outFp, "/* Unknown XML node type %d */\n", pNode->type );
        break;
    }
}


static void
printChildren( xmlNodePtr pNode )
{
    while (pNode != NULL) {
        printNode( pNode );
        pNode = pNode->next;
    }
}

/*
 * Local Variables:
 * mode: C
 * c-file-style: "stroustrup"
 * indent-tabs-mode: nil
 * End:
 * end of xml2ag/xml2ag.c */
