/** **************************************************************************
 * simplexml.c
 * 
 * Copyright 2008 Bryan Ischo <bryan@ischo.com>
 * 
 * This file is part of libs3.
 * 
 * libs3 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, version 3 of the License.
 *
 * In addition, as a special exception, the copyright holders give
 * permission to link the code of this library and its programs with the
 * OpenSSL library, and distribute linked combinations including the two.
 *
 * libs3 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 version 3
 * along with libs3, in a file named COPYING.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 ************************************************************************** **/

#include <libxml/parser.h>
#include <string.h>
#include "simplexml.h"

// Use libxml2 for parsing XML.  XML is severely overused in modern
// computing.  It is useful for only a very small subset of tasks, but
// software developers who don't know better and are afraid to go against the
// grain use it for everything, and in most cases, it is completely
// inappropriate.  Usually, the document structure is severely under-specified
// as well, as is the case with S3.  We do our best by just caring about the
// most important aspects of the S3 "XML document" responses: the elements and
// their values.  The SAX API (just about the lamest API ever devised and
// proof that XML sucks - well, the real proof is how crappy all of the XML
// parsing libraries are, including libxml2 - but I digress) is used here
// because we don't need much from the parser and SAX is fast and low memory.
//
// Note that for simplicity we assume all ASCII here.  No attempts are made to
// detect non-ASCII sequences in utf-8 and convert them into ASCII in any way.
// S3 appears to only use ASCII anyway.


static xmlEntityPtr saxGetEntity(void *user_data, const xmlChar *name)
{
    (void) user_data;

    return xmlGetPredefinedEntity(name);
}


static void saxStartElement(void *user_data, const xmlChar *nameUtf8,
                            const xmlChar **attr)
{
    (void) attr;

    SimpleXml *simpleXml = (SimpleXml *) user_data;

    if (simpleXml->status != S3StatusOK) {
        return;
    }
    
    // Assume that name has no non-ASCII in it
    char *name = (char *) nameUtf8;

    // Append the element to the element path
    int len = strlen(name);

    if ((simpleXml->elementPathLen + len + 1) >= 
        (int) sizeof(simpleXml->elementPath)) {
        // Cannot handle this element, stop!
        simpleXml->status = S3StatusXmlParseFailure;
        return;
    }

    if (simpleXml->elementPathLen) {
        simpleXml->elementPath[simpleXml->elementPathLen++] = '/';
    }
    strcpy(&(simpleXml->elementPath[simpleXml->elementPathLen]), name);
    simpleXml->elementPathLen += len;
}


static void saxEndElement(void *user_data, const xmlChar *name)
{
    (void) name;

    SimpleXml *simpleXml = (SimpleXml *) user_data;

    if (simpleXml->status != S3StatusOK) {
        return;
    }

    // Call back with 0 data
    simpleXml->status = (*(simpleXml->callback))
        (simpleXml->elementPath, 0, 0, simpleXml->callbackData);

    while ((simpleXml->elementPathLen > 0) &&
           (simpleXml->elementPath[simpleXml->elementPathLen] != '/')) {
        simpleXml->elementPathLen--;
    }

    simpleXml->elementPath[simpleXml->elementPathLen] = 0;
}


static void saxCharacters(void *user_data, const xmlChar *ch, int len)
{
    SimpleXml *simpleXml = (SimpleXml *) user_data;

    if (simpleXml->status != S3StatusOK) {
        return;
    }

    simpleXml->status = (*(simpleXml->callback))
        (simpleXml->elementPath, (char *) ch, len, simpleXml->callbackData);
}


static void saxError(void *user_data, const char *msg, ...)
{
    (void) msg;

    SimpleXml *simpleXml = (SimpleXml *) user_data;

    if (simpleXml->status != S3StatusOK) {
        return;
    }

    simpleXml->status = S3StatusXmlParseFailure;
}


static struct _xmlSAXHandler saxHandlerG =
{
    0, // internalSubsetSAXFunc
    0, // isStandaloneSAXFunc
    0, // hasInternalSubsetSAXFunc
    0, // hasExternalSubsetSAXFunc
    0, // resolveEntitySAXFunc
    &saxGetEntity, // getEntitySAXFunc
    0, // entityDeclSAXFunc
    0, // notationDeclSAXFunc
    0, // attributeDeclSAXFunc
    0, // elementDeclSAXFunc
    0, // unparsedEntityDeclSAXFunc
    0, // setDocumentLocatorSAXFunc
    0, // startDocumentSAXFunc
    0, // endDocumentSAXFunc
    &saxStartElement, // startElementSAXFunc
    &saxEndElement, // endElementSAXFunc
    0, // referenceSAXFunc
    &saxCharacters, // charactersSAXFunc
    0, // ignorableWhitespaceSAXFunc
    0, // processingInstructionSAXFunc
    0, // commentSAXFunc
    0, // warningSAXFunc
    &saxError, // errorSAXFunc
    &saxError, // fatalErrorSAXFunc
    0, // getParameterEntitySAXFunc
    &saxCharacters, // cdataBlockSAXFunc
    0, // externalSubsetSAXFunc
    0, // initialized
    0, // _private
    0, // startElementNsSAX2Func
    0, // endElementNsSAX2Func
    0 // xmlStructuredErrorFunc serror;
};

void simplexml_initialize(SimpleXml *simpleXml, 
                          SimpleXmlCallback *callback, void *callbackData)
{
    simpleXml->callback = callback;
    simpleXml->callbackData = callbackData;
    simpleXml->elementPathLen = 0;
    simpleXml->status = S3StatusOK;
    simpleXml->xmlParser = 0;
}


void simplexml_deinitialize(SimpleXml *simpleXml)
{
    if (simpleXml->xmlParser) {
        xmlFreeParserCtxt(simpleXml->xmlParser);
    }
}


S3Status simplexml_add(SimpleXml *simpleXml, const char *data, int dataLen)
{
    if (!simpleXml->xmlParser &&
        (!(simpleXml->xmlParser = xmlCreatePushParserCtxt
           (&saxHandlerG, simpleXml, 0, 0, 0)))) {
        return S3StatusInternalError;
    }

    if (xmlParseChunk((xmlParserCtxtPtr) simpleXml->xmlParser, 
                      data, dataLen, 0)) {
        return S3StatusXmlParseFailure;
    }

    return simpleXml->status;
}
