/**
 * @file llxmlparser.cpp
 * @brief LLXmlParser implementation
 *
 * $LicenseInfo:firstyear=2002&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

// llxmlparser.cpp
//
// copyright 2002, linden research inc


#include "linden_common.h"

#include "llxmlparser.h"
#include "llerror.h"


LLXmlParser::LLXmlParser()
    :
    mParser( NULL ),
    mDepth( 0 )
{
    mAuxErrorString = "no error";

    // Override the document's declared encoding.
    mParser = XML_ParserCreate(NULL);

    XML_SetUserData(mParser, this);
    XML_SetElementHandler(                  mParser,    startElementHandler, endElementHandler);
    XML_SetCharacterDataHandler(            mParser,    characterDataHandler);
    XML_SetProcessingInstructionHandler(    mParser,    processingInstructionHandler);
    XML_SetCommentHandler(                  mParser,    commentHandler);

    XML_SetCdataSectionHandler(             mParser,    startCdataSectionHandler, endCdataSectionHandler);

    // This sets the default handler but does not inhibit expansion of internal entities.
    // The entity reference will not be passed to the default handler.
    XML_SetDefaultHandlerExpand(            mParser,    defaultDataHandler);

    XML_SetUnparsedEntityDeclHandler(       mParser,    unparsedEntityDeclHandler);
}

LLXmlParser::~LLXmlParser()
{
    XML_ParserFree( mParser );
}


bool LLXmlParser::parseFile(const std::string &path)
{
    llassert( !mDepth );

    bool success = true;

    LLFILE* file = LLFile::fopen(path, "rb");       /* Flawfinder: ignore */
    if( !file )
    {
        mAuxErrorString = llformat( "Couldn't open file %s", path.c_str());
        success = false;
    }
    else
    {
        S32 bytes_read = 0;

        fseek(file, 0L, SEEK_END);
        S32 buffer_size = ftell(file);
        fseek(file, 0L, SEEK_SET);

        void* buffer = XML_GetBuffer(mParser, buffer_size);
        if( !buffer )
        {
            mAuxErrorString = llformat( "Unable to allocate XML buffer while reading file %s", path.c_str() );
            success = false;
            goto exit_label;
        }

        bytes_read = (S32)fread(buffer, 1, buffer_size, file);
        if( bytes_read <= 0 )
        {
            mAuxErrorString = llformat( "Error while reading file  %s", path.c_str() );
            success = false;
            goto exit_label;
        }

        if( !XML_ParseBuffer(mParser, bytes_read, true ) )
        {
            mAuxErrorString = llformat( "Error while parsing file  %s", path.c_str() );
            success = false;
        }

exit_label:
        fclose( file );
    }


    if( success )
    {
        llassert( !mDepth );
    }
    mDepth = 0;

    if( !success )
    {
        LL_WARNS() << mAuxErrorString << LL_ENDL;
    }

    return success;
}


//  Parses some input. Returns 0 if a fatal error is detected.
//  The last call must have isFinal true;
//  len may be zero for this call (or any other).
S32 LLXmlParser::parse( const char* buf, int len, int isFinal )
{
    return XML_Parse(mParser, buf, len, isFinal);
}

const char* LLXmlParser::getErrorString()
{
    const char* error_string = XML_ErrorString(XML_GetErrorCode( mParser ));
    if( !error_string )
    {
        error_string = mAuxErrorString.c_str();
    }
    return error_string;
}

S32 LLXmlParser::getCurrentLineNumber()
{
    return XML_GetCurrentLineNumber( mParser );
}

S32 LLXmlParser::getCurrentColumnNumber()
{
    return XML_GetCurrentColumnNumber(mParser);
}

///////////////////////////////////////////////////////////////////////////////
// Pseudo-private methods.  These are only used by internal callbacks.

// static
void LLXmlParser::startElementHandler(
     void *userData,
     const XML_Char *name,
     const XML_Char **atts)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->startElement( name, atts );
    self->mDepth++;
}

// static
void LLXmlParser::endElementHandler(
    void *userData,
    const XML_Char *name)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->mDepth--;
    self->endElement( name );
}

// s is not 0 terminated.
// static
void LLXmlParser::characterDataHandler(
    void *userData,
    const XML_Char *s,
    int len)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->characterData( s, len );
}

// target and data are 0 terminated
// static
void LLXmlParser::processingInstructionHandler(
    void *userData,
    const XML_Char *target,
    const XML_Char *data)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->processingInstruction( target, data );
}

// data is 0 terminated
// static
void LLXmlParser::commentHandler(void *userData, const XML_Char *data)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->comment( data );
}

// static
void LLXmlParser::startCdataSectionHandler(void *userData)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->mDepth++;
    self->startCdataSection();
}

// static
void LLXmlParser::endCdataSectionHandler(void *userData)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->endCdataSection();
    self->mDepth++;
}

//  This is called for any characters in the XML document for
//  which there is no applicable handler.  This includes both
//  characters that are part of markup which is of a kind that is
//  not reported (comments, markup declarations), or characters
//  that are part of a construct which could be reported but
//  for which no handler has been supplied. The characters are passed
//  exactly as they were in the XML document except that
//  they will be encoded in UTF-8.  Line boundaries are not normalized.
//  Note that a byte order mark character is not passed to the default handler.
//  There are no guarantees about how characters are divided between calls
//  to the default handler: for example, a comment might be split between
//  multiple calls.

// static
void LLXmlParser::defaultDataHandler(
    void *userData,
    const XML_Char *s,
    int len)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->defaultData( s, len );
}

//  This is called for a declaration of an unparsed (NDATA)
//  entity.  The base argument is whatever was set by XML_SetBase.
//  The entityName, systemId and notationName arguments will never be null.
//  The other arguments may be.
// static
void LLXmlParser::unparsedEntityDeclHandler(
    void *userData,
    const XML_Char *entityName,
    const XML_Char *base,
    const XML_Char *systemId,
    const XML_Char *publicId,
    const XML_Char *notationName)
{
    LLXmlParser* self = (LLXmlParser*) userData;
    self->unparsedEntityDecl( entityName, base, systemId, publicId, notationName );
}




////////////////////////////////////////////////////////////////////
// Test code.

/*
class LLXmlDOMParser : public LLXmlParser
{
public:

    LLXmlDOMParser() {}
    virtual ~LLXmlDOMParser() {}

    void tabs()
    {
        for ( int i = 0; i < getDepth(); i++)
        {
            putchar(' ');
        }
    }

    virtual void startElement(const char *name, const char **atts)
    {
        tabs();
        printf("startElement %s\n", name);

        S32 i = 0;
        while( atts[i] && atts[i+1] )
        {
            tabs();
            printf( "\t%s=%s\n", atts[i], atts[i+1] );
            i += 2;
        }

        if( atts[i] )
        {
            tabs();
            printf( "\ttrailing attribute: %s\n", atts[i] );
        }
    }

    virtual void endElement(const char *name)
    {
        tabs();
        printf("endElement %s\n", name);
    }

    virtual void characterData(const char *s, int len)
    {
        tabs();

        char* str = new char[len+1];
        strncpy( str, s, len );
        str[len] = '\0';
        printf("CharacterData %s\n", str);
        delete str;
    }

    virtual void processingInstruction(const char *target, const char *data)
    {
        tabs();
        printf("processingInstruction %s\n", data);
    }
    virtual void comment(const char *data)
    {
        tabs();
        printf("comment %s\n", data);
    }

    virtual void startCdataSection()
    {
        tabs();
        printf("startCdataSection\n");
    }

    virtual void endCdataSection()
    {
        tabs();
        printf("endCdataSection\n");
    }

    virtual void defaultData(const char *s, int len)
    {
        tabs();

        char* str = new char[len+1];
        strncpy( str, s, len );
        str[len] = '\0';
        printf("defaultData %s\n", str);
        delete str;
    }

    virtual void unparsedEntityDecl(
        const char *entityName,
        const char *base,
        const char *systemId,
        const char *publicId,
        const char *notationName)
    {
        tabs();

        printf(
            "unparsed entity:\n"
                "\tentityName %s\n"
                "\tbase %s\n"
                "\tsystemId %s\n"
                "\tpublicId %s\n"
                "\tnotationName %s\n",
            entityName,
            base,
            systemId,
            publicId,
            notationName );
    }
};


int main()
{
  char buf[1024];

  LLFILE* file = LLFile::fopen("test.xml", "rb");
  if( !file )
  {
      return 1;
  }

  LLXmlDOMParser parser;
  int done;
  do {
    size_t len = fread(buf, 1, sizeof(buf), file);
    done = len < sizeof(buf);
    if( 0 == parser.parse( buf, len, done) )
    {
      fprintf(stderr,
          "%s at line %d\n",
          parser.getErrorString(),
          parser.getCurrentLineNumber() );
      return 1;
    }
  } while (!done);

  fclose( file );
  return 0;
}
*/