/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Xerces" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache\@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation, and was
 * originally based on software copyright (c) 1999, International
 * Business Machines, Inc., http://www.ibm.com .  For more information
 * on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

//=============================================================================
/**
 *  @file    JGXmlParser.cpp
 *
 *  @author  Fukasawa Mitsuo
 *
 *
 *    Copyright (C) 2001-2003 BEE Co.,Ltd. All rights reserved.
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
//=============================================================================

#define BEE_BUILD_DLL

// ---------------------------------------------------------------------------
//  Includes
// ---------------------------------------------------------------------------
#include "JGXmlParser.h"


// ---------------------------------------------------------------------------
//  Local data
//
//  encodingName
//      The encoding we are to output in. If not set on the command line,
//      then it is defaulted to LATIN1.
//
//  xmlFile
//      The path to the file to parser. Set via command line.
//
//  valScheme
//      Indicates what validation scheme to use. It defaults to 'auto', but
//      can be set via the -v= command.
//
//  expandNamespaces
//      Indicates if the output should expand the namespaces Alias with
//      their URI's, defaults to false, can be set via the command line -e
// ---------------------------------------------------------------------------
static XMLFormatter::UnRepFlags unRepFlags = XMLFormatter::UnRep_CharRef;
static SAX2XMLReader::ValSchemes valScheme = SAX2XMLReader::Val_Auto;
static const char * encodingName = "shift_jis";
static char * _xmlFile         = "recipe_se3000.xml";
static bool expandNamespaces   = false ;
static bool doNamespaces       = true;
static bool doSchema           = false;  // if true, parser is very slow
static bool schemaFullChecking = false;
static bool namespacePrefixes  = false;


// ---------------------------------------------------------------------------
//  Local helper methods
// ---------------------------------------------------------------------------
static void usage()
{
    cout << "\nUsage:\n"
            "    XMLParser [options] <XML file>\n\n"
            "This program invokes the SAX2XMLReader, and then prints the\n"
            "data returned by the various SAX2 handlers for the specified\n"
            "XML file.\n\n"
            "Options:\n"
             "    -u=xxx      Handle unrepresentable chars [fail | rep | ref*].\n"
             "    -v=xxx      Validation scheme [always | never | auto*].\n"
             "    -e          Expand Namespace Alias with URI's. Defaults to off.\n"
             "    -x=XXX      Use a particular encoding for output (LATIN1*).\n"
             "    -f          Enable full schema constraint checking processing. Defaults to off.\n"
             "    -p          Enable namespace-prefixes feature. Defaults to off.\n"
             "    -n          Disable namespace processing. Defaults to on.\n"
             "                NOTE: THIS IS OPPOSITE FROM OTHER SAMPLES.\n"
             "    -s          Disable schema processing. Defaults to on.\n"
             "                NOTE: THIS IS OPPOSITE FROM OTHER SAMPLES.\n"
             "    -?          Show this help.\n\n"
             "  * = Default if not provided explicitly.\n\n"
             "The parser has intrinsic support for the following encodings:\n"
             "    UTF-8, USASCII, ISO8859-1, UTF-16[BL]E, UCS-4[BL]E,\n"
             "    WINDOWS-1252, IBM1140, IBM037.\n"
         <<  endl;
}

// ---------------------------------------------------------------------------
//  Program entry point
// ---------------------------------------------------------------------------
int JGXmlParser::init(int argC, char* argV[])
{
    // Initialize the XML4C2 system
    try
    {
         XMLPlatformUtils::Initialize();
    }

    catch (const XMLException& toCatch)
    {
        cerr << "Error during initialization! :\n"
             << StrX(toCatch.getMessage()) << endl;
        return -1;
    }

    // Check command line and extract arguments.
    if (argC < 2)
    {
        usage();
        //XMLPlatformUtils::Terminate();
        return -1;
    }

    int parmInd;
    for (parmInd = 1; parmInd < argC; parmInd++)
    {
        // Break out on first parm not starting with a dash
        if (argV[parmInd][0] != '-')
            break;

        // Watch for special case help request
        if (!strcmp(argV[parmInd], "-?"))
        {
            usage();
            //XMLPlatformUtils::Terminate();
            return -2;
        }
         else if (!strncmp(argV[parmInd], "-v=", 3)
              ||  !strncmp(argV[parmInd], "-V=", 3))
        {
            const char* const parm = &argV[parmInd][3];

            if (!strcmp(parm, "never"))
                valScheme = SAX2XMLReader::Val_Never;
            else if (!strcmp(parm, "auto"))
                valScheme = SAX2XMLReader::Val_Auto;
            else if (!strcmp(parm, "always"))
                valScheme = SAX2XMLReader::Val_Always;
            else
            {
                cerr << "Unknown -v= value: " << parm << endl;
                //XMLPlatformUtils::Terminate();
                return -2;
            }
        }
        else if (!strcmp(argV[parmInd], "-e")
              ||  !strcmp(argV[parmInd], "-E"))
        {
            expandNamespaces = true;
        }
        else if (!strncmp(argV[parmInd], "-x=", 3)
              ||  !strncmp(argV[parmInd], "-X=", 3))
        {
           // Get out the encoding name
            encodingName = &argV[parmInd][3];
        }
        else if (!strncmp(argV[parmInd], "-u=", 3)
              ||  !strncmp(argV[parmInd], "-U=", 3))
        {
            const char* const parm = &argV[parmInd][3];

            if (!strcmp(parm, "fail"))
                unRepFlags = XMLFormatter::UnRep_Fail;
            else if (!strcmp(parm, "rep"))
                unRepFlags = XMLFormatter::UnRep_Replace;
            else if (!strcmp(parm, "ref"))
                unRepFlags = XMLFormatter::UnRep_CharRef;
            else
            {
                cerr << "Unknown -u= value: " << parm << endl;
                //XMLPlatformUtils::Terminate();
                return -2;
            }
        }
        else if (!strcmp(argV[parmInd], "-n")
              ||  !strcmp(argV[parmInd], "-N"))
        {
            doNamespaces = false;
        }
        else if (!strcmp(argV[parmInd], "-s")
              ||  !strcmp(argV[parmInd], "-S"))
        {
            doSchema = false;
        }
        else if (!strcmp(argV[parmInd], "-f")
              ||  !strcmp(argV[parmInd], "-F"))
        {
            schemaFullChecking = true;
        }
        else if (!strcmp(argV[parmInd], "-p")
              ||  !strcmp(argV[parmInd], "-P"))
        {
            namespacePrefixes = true;
        }
        else
        {
            cerr << "Unknown option '" << argV[parmInd]
                 << "', ignoring it\n" << endl;
        }
    }

    //
    //  And now we have to have only one parameter left and it must be
    //  the file name.
    //
    if (parmInd + 1 != argC)
    {
        usage();
        //XMLPlatformUtils::Terminate();
        return -1;
    }
    _xmlFile = argV[parmInd];

    return 0;
}

//-----------------------------------------------------------------------------
int JGXmlParser::parseSax(JGSaxHandler& saxHandler, const string& xmlFile)
{
    // Initialize the XML4C2 system
    try
    {
         XMLPlatformUtils::Initialize();
    }

    catch (const XMLException& toCatch)
    {
        cerr << "Error during initialization! :\n"
             << StrX(toCatch.getMessage()) << endl;
        return -1;
    }
    //  Create a SAX parser object. Then, according to what we were told on
    //  the command line, set it to validate or not.
    //
    SAX2XMLReader * parser = XMLReaderFactory::createXMLReader();

    //
    //  Then, according to what we were told on
    //  the command line, set it to validate or not.
    //
    if (valScheme == SAX2XMLReader::Val_Auto)
    {
        parser->setFeature(XMLUni::fgSAX2CoreValidation, true);
        parser->setFeature(XMLUni::fgXercesDynamic, true);
    }

    if (valScheme == SAX2XMLReader::Val_Never)
    {
        parser->setFeature(XMLUni::fgSAX2CoreValidation, false);
    }

    if (valScheme == SAX2XMLReader::Val_Always)
    {
        parser->setFeature(XMLUni::fgSAX2CoreValidation, true);
        parser->setFeature(XMLUni::fgXercesDynamic, false);
    }

    parser->setFeature(XMLUni::fgSAX2CoreNameSpaces, doNamespaces);
    parser->setFeature(XMLUni::fgXercesSchema, doSchema);
    parser->setFeature(XMLUni::fgXercesSchemaFullChecking, schemaFullChecking);
    parser->setFeature(XMLUni::fgSAX2CoreNameSpacePrefixes, namespacePrefixes);

    //
    //  Create the handler object and install it as the document and error
    //  handler for the parser. Then parse the file and catch any exceptions
    //  that propogate out
    //

    int errorCount = 0;
    try
    {
        parser->setContentHandler(&saxHandler);
        parser->setErrorHandler(&saxHandler);
        parser->parse(xmlFile.c_str());
        errorCount = parser->getErrorCount();
    }

    catch (const XMLException& toCatch)
    {
        cerr << "\nAn error occurred\n  Error: "
             << StrX(toCatch.getMessage())
             << "\n" << endl;
        //XMLPlatformUtils::Terminate();
        return -4;
    }

    //
    //  Delete the parser itself.  Must be done prior to calling Terminate, below.
    //
    delete parser;

    return 0;
}

//-----------------------------------------------------------------------------
// Parse xml file
//-----------------------------------------------------------------------------
int JGXmlParser::parseDom(JGDomParser& parser, const string& xmlFile)
{
    TRACE_FUNCTION(TRL_LOW, "JGXmlParser::parseDom");
    int result;

    try
    {
        parser.setValidationScheme(DOMParser::Val_Always);     // optional
        parser.setDoNamespaces(true);                          // optional
        parser.setDoSchema(true);                              // optional
        parser.setValidationSchemaFullChecking(true);          // optional
        parser.parse(xmlFile.c_str());
    }
    catch (const XMLException& toCatch)
    {
        TRACE_ERROR(("Xerces-c Parsing exception message: %s.\n",
                     StrX(toCatch.getMessage()).localForm()));
        return BEE_ERROR;
    }
    catch (...)
    {
        TRACE_ERROR(("Xerces-c Parsing exception message: Unexpected Exception.\n"));
        return BEE_ERROR;
    }

    // Traverse xml tree
    DOMDocument * doc = parser.getDocument();
    if (doc != NULL)
    {
        result = parser.parseElements((DOMNode *)doc->getDocumentElement());
        if (result < 0)
        {
            return result;
        }
    }
    else
    {
        TRACE_ERROR(("Parsed document is null.\n"));
        return BEE_ERROR;
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Finish parsing xml
//-----------------------------------------------------------------------------
int JGXmlParser::fini()
{
    // And call the termination method
    XMLPlatformUtils::Terminate();
    return 0;
}

///////////////////////////////////////////////////////////////////////////////
//
// DOM Parser
//
//-----------------------------------------------------------------------------
// Insert element's parser
//-----------------------------------------------------------------------------
void JGDomParser::insert(JGXmlElementParser * elmParser)
{
    string tagName = elmParser->m_tagName;
    m_tagmap.insert(JGXmlElementPair(tagName, elmParser));
}

//-----------------------------------------------------------------------------
// Find element's parser
//-----------------------------------------------------------------------------
JGXmlElementParser * JGDomParser::find(const string& tagName)
{
    JGXmlElementParser * elmParser;
    JGXmlElementMap::iterator iter = m_tagmap.find(tagName);
    if (iter == m_tagmap.end())
    {
        return NULL;
    }
    elmParser = (*iter).second;
    return elmParser;
}

//-----------------------------------------------------------------------------
// Parse element
//-----------------------------------------------------------------------------
int JGDomParser::parseElements(DOMNode * node)
{
    TRACE_FUNCTION(TRL_LOW, "JGDomParser::parseElements");
    int result = 0;
    DOMNode * child;
    if (node == NULL)
    {
        TRACE_ERROR((_TX("Node is null.\n")));
        return -1;
    }

    string nameStr = StrX(node->getNodeName()).localForm();
    if (nameStr != m_rootag)
    {
        TRACE_ERROR(("Illegal node name %s.\n", nameStr.c_str()));
        return -1;
    }

    // Attributes: nothing

    int nodeType = node->getNodeType();
    switch (nodeType)
    {
    case DOMNode::ELEMENT_NODE:
        {
            // Parse for the presence of children.
            for (child = node->getFirstChild(); child != NULL;
                 child = child->getNextSibling())
            {
                switch (child->getNodeType())
                {
                case DOMNode::ELEMENT_NODE:
                    {
                        nameStr = StrX(child->getNodeName()).localForm();
                        JGXmlElementParser * elmParser = this->find(nameStr);
                        if (elmParser != NULL)
                        {
                            result = this->parseElement(elmParser, node, child);
                            if (result < 0)
                            {
                                return result;
                            }
                        }
                        else
                        {
                            TRACE_ERROR(("Ignore element name %s.\n",
                                         nameStr.c_str()));
                            // return -1;   // ignore
                        }
                    }
                    break;

                case DOMNode::TEXT_NODE:
                case DOMNode::PROCESSING_INSTRUCTION_NODE:
                case DOMNode::DOCUMENT_NODE:
                case DOMNode::ENTITY_REFERENCE_NODE:
                case DOMNode::CDATA_SECTION_NODE:
                case DOMNode::COMMENT_NODE:
                case DOMNode::DOCUMENT_TYPE_NODE:
                case DOMNode::ENTITY_NODE:
                    break;
                default:
                    break;
                }
            }
        }
        break;

    case DOMNode::TEXT_NODE:
    case DOMNode::PROCESSING_INSTRUCTION_NODE:
    case DOMNode::DOCUMENT_NODE:
    case DOMNode::ENTITY_REFERENCE_NODE:
    case DOMNode::CDATA_SECTION_NODE:
    case DOMNode::COMMENT_NODE:
    case DOMNode::DOCUMENT_TYPE_NODE:
    case DOMNode::ENTITY_NODE:
        break;
    default:
        break;
    }

    return result;
}


//-----------------------------------------------------------------------------
// Parse element
//-----------------------------------------------------------------------------
int JGDomParser::parseElement(JGXmlElementParser * userData, DOMNode * parent,
                              DOMNode * node)
{
    TRACE_FUNCTION(TRL_LOW, "JGXmlParser::parseElement");
    DOMNode * child;
    string nameStr;
    string valStr;

    if (! sameNodeName(parent, userData->m_parentName))
    {
        TRACE_ERROR(("Invalid parent name(%s).\n", userData->m_tagName.c_str()));
        return -1;
    }

    int result = userData->begin(this);
    if (result < 0)
    {
        TRACE_ERROR(("Error when begin element(%s).\n", userData->m_tagName.c_str()));
        return result;
    }

    DOMNamedNodeMap * attributes = node->getAttributes();
    int attrCount = attributes->getLength();
    for (int i = 0; i < attrCount; i++)
    {
        DOMNode * attribute = attributes->item(i);
        nameStr = StrX(attribute->getNodeName()).localForm();
        valStr  = StrX(attribute->getNodeValue()).localForm();

        result = userData->parseAttribute(this, nameStr, valStr);
        if (result < 0)
        {
            TRACE_ERROR(("Invalid attribute name(%s).\n",
                         nameStr.c_str()));
            return BEE_ERROR;
        }
    }

    result = userData->endAttribute(this);
    if (result < 0)
    {
        TRACE_ERROR(("Error when end attribute(%s).\n", userData->m_tagName.c_str()));
        return result;
    }

    // Parse for the presence of children.
    string elmStr;
    for (child = node->getFirstChild(); child != NULL;
         child = child->getNextSibling())
    {
        int nodeType = child->getNodeType();
        switch (nodeType)
        {
        case DOMNode::ELEMENT_NODE:
            {
                elmStr = StrX(child->getNodeName()).localForm();
                JGXmlElementParser * childElement = this->find(elmStr);
                if (childElement != NULL)
                {
                    this->parseElement(childElement, node, child);
                }
                else
                {
                    TRACE_ERROR(("Invalid element name %s.\n",
                                 elmStr.c_str()));
                    //# IGNORE:    return BEE_ERROR;
                }
            }
            break;

        case DOMNode::TEXT_NODE:
            valStr = StrX(child->getNodeValue()).localForm();
            result = userData->parseText(this, valStr);
            break;

        case DOMNode::CDATA_SECTION_NODE:
            valStr = StrX(child->getNodeValue()).localForm();
            result = userData->parseCData(this, valStr);
            break;

        case DOMNode::COMMENT_NODE:
        case DOMNode::PROCESSING_INSTRUCTION_NODE:
        case DOMNode::DOCUMENT_NODE:
        case DOMNode::ENTITY_REFERENCE_NODE:
        case DOMNode::DOCUMENT_TYPE_NODE:
        case DOMNode::ENTITY_NODE:
            break;
        default:
            break;
        }
    }

    result = userData->end(this);
    if (result < 0)
    {
        TRACE_ERROR(("Error when end element(%s).\n", userData->m_tagName.c_str()));
        return result;
    }
    return BEE_SUCCESS;
}

//-----------------------------------------------------------------------------
// Check node name
//-----------------------------------------------------------------------------
bool JGDomParser::sameNodeName(DOMNode * node, const string& nodeName)
{
    string tagName = StrX(node->getNodeName()).localForm();
    if (nodeName.size() == 0)
    {
        return true;               // ignore checking
    }
    return (tagName == nodeName);
}

