/*****************************************************************************
 *
 * $Id$
 *
 * Copyright (C) 2009 - 2012  Richard Hacker <lerich@gmx.net>
 *                            Florian Pose <fp@igh-essen.com>
 *
 * This file is part of the PdCom library.
 *
 * The PdCom 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, either version 3 of the License, or (at your
 * option) any later version.
 *
 * The PdCom 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 the PdCom Library. If not, see <http://www.gnu.org/licenses/>.
 *
 ****************************************************************************/

#include <string.h> // memcpy() etc.

#include "MsrProtocolHandler.h"

#include "MsrException.h"
#include "MsrParam.h"
#include "MsrChannel.h"

#include "pdcom/Exception.h"
#include "pdcom/Process.h"

#include <cstdlib>
#include <algorithm>
#include <vector>
#include <sstream>

//#define DEBUG_XML
//#define DEBUG_STATEMACHINE

#define DEBUG 0

#if DEBUG
#include <iostream>
using std::cout;
using std::cerr;
using std::endl;
#endif

using namespace MSRProto;

/****************************************************************************/

PdCom::ProtocolHandler* ProtocolHandler::tryParse(
        const char *buf, size_t len, PdCom::Process* process,
        std::ostream* os)
{
    if (memcmp("<connected", buf, std::min(size_t(10), len))) {
        process->protocolLog(PdCom::Process::LogDebug,
                "MSR Protocol handler does not recognise the protocol.");
        return NULL;
    }
    else {
        process->protocolLog(PdCom::Process::LogInfo,
                "MSR Protocol handler recognises the protocol.");
        return new ProtocolHandler(process, os);
    }
}

/****************************************************************************/

ProtocolHandler::ProtocolHandler(
        PdCom::Process *process,
        std::ostream *os
        ):
    PdCom::ProtocolHandler(process, os)
{
    state = NotConnected;
    connected_state = Idle;

    xmlDepth = 0;
    admin = write = 0;
    maxParamIdx = maxChannelIdx = 0;

    if (!(xmlParser = XML_ParserCreate(NULL)))
        throw PdCom::ProtocolException("Could not create XML parser");

    XML_SetUserData(xmlParser, this);
    XML_SetElementHandler(xmlParser, ExpatInitStartTag, ExpatInitEndTag);

    parse("<xml>", 5);
}

/****************************************************************************/

ProtocolHandler::~ProtocolHandler()
{
#if DEBUG
    cerr << "deleting MSR ProtocolHandler" << endl;
#endif

    XML_ParserFree(xmlParser);
    for (std::vector<Param*>::iterator it = parameter.begin();
            it != parameter.end(); it++)
        delete (*it);
    for (std::vector<Channel*>::iterator it = channel.begin();
            it != channel.end(); it++)
        delete (*it);
}

/****************************************************************************/

std::map<std::string,std::string> ProtocolHandler::getInfo() const
{
    std::map<std::string,std::string> i;

    i["protocol"] = "MSR";
    i["features"] = featureStr;
    i["app"] = app;
    i["appversion"] = appVersion;

    std::stringstream str;
    str << version;
    i["version"] = str.str();

    return i;
}

/****************************************************************************/

bool ProtocolHandler::hasFeature(const std::string &f) const
{
    return feature.find(f) != feature.end();
}

/****************************************************************************/

size_t ProtocolHandler::parse(const char *buf, size_t len)
{
#if DEBUG
    cout << "==================================" << endl
        << std::string(buf,len) << endl
        << "===================================" << endl;
#endif
    if (XML_STATUS_OK != XML_Parse(xmlParser, buf, len, 0)) {
        throw PdCom::ProtocolException(
                std::string("Fatal XML parsing error: ")
                + XML_ErrorString(XML_GetErrorCode(xmlParser))
                );
    }

    return len;
}

/****************************************************************************/

void ProtocolHandler::sendBroadcast(
        const std::string &message,
        const std::string &attr
        )
{
    for (std::string::const_iterator it = attr.begin();
            it != attr.end(); it++) {
        if (!std::isalpha(*it)) {
            process->protocolLog(PdCom::Process::LogError,
                    __func__ + std::string("(): Invalid attribute ") + attr);
            return;
        }
    }

    *os << "<broadcast " << attr << "=\"" << xmlEscape(message) << "\"/>\n";
    os->flush();
}

/****************************************************************************
 * Wrapper to get from static function calls to class method calls
 */
void XMLCALL
ProtocolHandler::ExpatInitStartTag(void *data,
        const char *el, const char **attr)
{
    reinterpret_cast<ProtocolHandler*>(data)->initStartTag(el, attr);
}

/****************************************************************************
 * Wrapper to get from static function calls to class method calls
 */
void XMLCALL
ProtocolHandler::ExpatInitEndTag(void *data, const char *el)
{
    reinterpret_cast<ProtocolHandler*>(data)->initEndTag(el);
}

/****************************************************************************/
void ProtocolHandler::processInfoTag(const char **attr)
{
    double time = 0;
    const char* msg = 0;
    for (int i = 0; attr[i]; i += 2) {
        if (!strcmp(attr[i], "text")) {
            if (!admin and !strncmp(attr[i+1], "Adminmode", 9)) {
                admin = 1;
            }
            else if (!write and !strncmp(attr[i+1],
                        "write access", 12)) {
                write = 1;
            }
            msg = attr[i + 1];
        }
        else if (!strcmp(attr[i], "time")) {
            std::stringstream str;

            str.imbue(std::locale("C")); // use '.' as a decimal separator!
            str << attr[i + 1];
            str >> time;
        }
    }
    if (msg) {
        process->processMessage(time,
                PdCom::Process::LogInfo, 0, msg);
    }
}

/****************************************************************************/
void ProtocolHandler::processBroadcastTag(const char **attr)
{
    double time = 0;
    const char* msg = 0;
    for (int i = 0; attr[i]; i += 2) {
        if (!strcmp(attr[i], "text")) {
            msg = attr[i + 1];
        }
        else if (!strcmp(attr[i], "time")) {
            std::stringstream str;

            str.imbue(std::locale("C")); // use '.' as a decimal separator!
            str << attr[i + 1];
            str >> time;
        }
    }
    if (msg) {
        process->processMessage(time,
                PdCom::Process::LogInfo, 0, msg);
    }
}

/****************************************************************************/
void ProtocolHandler::initStartTag(const char *el, const char **attr)
{

#ifdef DEBUG_XML

#ifdef DEBUG_STATEMACHINE
    cerr << "---------- start tag <" << el << "> ";
    switch (state) {
        case NotConnected:
            cerr << "State: NotConnected";
            break;
        case Connected:
            cerr << "State: Connected";
            break;
        case ReadParam:
            cerr << "State: ReadParam";
            break;
        case ReadParamList:
            cerr << "State: ReadParamList";
            break;
        case ReadChan:
            cerr << "State: ReadChan";
            break;
        case ReadChanList:
            cerr << "State: ReadChanList";
            break;
    }
    cerr << " Depth " << xmlDepth << endl;
#endif // DEBUG_STATEMACHINE
    cerr << "Attributes:" << endl;
    for (int i = 0; attr[i]; i += 2) {
        cerr << "    " << attr[i] << " = " << attr[i+1] << endl;
    }
#endif // DEBUG_XML

    switch (xmlDepth) {
        case 0:
            process->protocolLog(PdCom::Process::LogInfo,
                    "Waiting for <connected> tag.");
            break;
        case 1:
            if (!strcmp(el, "info")) {
                processInfoTag(attr);
                break;
            }
            else if (!strcmp(el, "broadcast")) {
                processBroadcastTag(attr);
                break;
            }

            switch (state) {
                case NotConnected:
                    if (!strcmp(el, "connected")) {
                        state = Connected;
                        process->protocolLog(PdCom::Process::LogInfo,
                                "<connected> tag arrived.");
                        for (int i = 0; attr[i]; i += 2) {
                            if (!strcmp(attr[i], "name")) {
                                name = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "host")) {
                                host = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "app")) {
                                app = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "appversion")) {
                                appVersion = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "version")) {
                                version = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "features")) {
                                const char *c = attr[i+1];
                                const char *c1;

                                featureStr = c;

                                /* Parse comma separated features */
                                while (*c) {
                                    unsigned int len;
                                    if (',' == *c) {
                                        /* Ignore commas */
                                        c++;
                                        continue;
                                    }

                                    /* Find next ',' */
                                    c1 = strchr(c, ',');

                                    /* If no comma is found, c1 is NULL. */
                                    if (c1) {
                                        /* c1 points to a comma, so calculate
                                         * len and increment c1 */
                                        len = c1++ - c;
                                    } else {
                                        /* No comma found, so get length from
                                         * strlen() and let c1 point to the
                                         * end */
                                        len = strlen(c);
                                        c1 = c + len;
                                    }

                                    feature.insert(std::string(c, len));
                                    c = c1;
                                }
                            }
                            else if (!strcmp(attr[i], "recievebufsize")) {
                                recievebufsize = atoi(attr[i+1]);
                            }
                        }
                    }
                    break;

                case Connected:
                    break;

                case ReadParam:
                    if (!strcmp(el, "parameters")) {
                        state = ReadParamList;
                    }
                    break;

                case ReadChan:
                    if (!strcmp(el, "channels")) {
                        state = ReadChanList;
                    }
                    break;

                default:
                    break;
            }
            break;

        case 2:
            switch (state) {
                case ReadParamList:
                    if (!strcmp(el, "parameter")) {
                        const char* name = 0;
                        unsigned int index = 0;
                        unsigned int rnum = 1;
                        unsigned int cnum = 1;
                        const char* orientation = 0;
                        unsigned int flags = 0;
                        unsigned int datasize = 0;
                        const char* unit = 0;
                        const char* datatype = 0;

                        for (int i = 0; attr[i]; i += 2) {

                            if (!strcmp(attr[i], "name")) {
                                name = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "index")) {
                                index = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "flags")) {
                                flags = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "rnum")) {
                                rnum = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "cnum")) {
                                cnum = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "datasize")) {
                                datasize = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "orientation")) {
                                orientation = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "typ")) {
                                datatype = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "unit")) {
                                unit = attr[i+1];
                            }
                        }
                        if (index >= maxParamIdx) {
                            maxParamIdx = index + 1;
                            parameter.resize(maxParamIdx);
                        }

                        try {
                            parameter[index] = new Param(
                                    this, os, name, index, rnum, cnum,
                                    orientation, flags, datasize, unit,
                                    datatype);
                            process->protocolLog(PdCom::Process::LogDebug,
                                    std::string("Found Parameter: ") + name);
                        }
                        catch (MSRProto::Exception &e) {
                            process->protocolLog(PdCom::Process::LogError,
                                    std::string("Failed to create MsrParam ")
                                    + name + ": " + e.what());
                            parameter[index] = 0;
                        }
                    }
                    break;

                case ReadChanList:
                    if (!strcmp(el, "channel")) {
                        const char* name = 0;
                        const char* alias = "";
                        unsigned int index = 0;
                        unsigned int rnum = 1;
                        unsigned int cnum = 1;
                        const char* orientation = "SCALAR";
                        const char* datatype = 0;
                        unsigned int datasize = 0;
                        unsigned int bufsize = 0;
                        unsigned int freq = 0;
                        const char* unit = "";

                        for (int i = 0; attr[i]; i += 2) {

                            if (!strcmp(attr[i], "name")) {
                                name = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "alias")) {
                                alias = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "index")) {
                                index = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "rnum")) {
                                rnum = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "cnum")) {
                                cnum = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "orientation")) {
                                orientation = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "typ")) {
                                datatype = attr[i+1];
                            }
                            else if (!strcmp(attr[i], "datasize")) {
                                datasize = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "bufsize")) {
                                bufsize = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "HZ")) {
                                freq = atoi(attr[i+1]);
                            }
                            else if (!strcmp(attr[i], "unit")) {
                                unit = attr[i+1];
                            }
                        }
                        if (index >= maxChannelIdx) {
                            maxChannelIdx = index + 1;
                            channel.resize(maxChannelIdx);
                        }

                        try {
                            channel[index] = new Channel(
                                    this, os, name, alias, index,
                                    rnum, cnum, orientation,
                                    datatype, datasize, bufsize, freq, unit);
                            process->protocolLog(PdCom::Process::LogDebug,
                                    std::string("Found Channel: ") + name);
                        }
                        catch (MSRProto::Exception &e) {
                            process->protocolLog(PdCom::Process::LogError,
                                    std::string("Failed to create MsrChan ")
                                    + name + ": " + e.what());
                            channel[index] = 0;
                        }
                    }
                    break;

                default:
                    break;
            }

            break;
        default:
            break;
    }
    xmlDepth++;
}

/****************************************************************************/

void ProtocolHandler::initEndTag(const char *el)
{
    xmlDepth--;

#ifdef DEBUG_STATEMACHINE
    cerr << "========== end tag <" << el << "> ";
    switch (state) {
        case NotConnected:
            cerr << "State: NotConnected";
            break;
        case Connected:
            cerr << "State: Connected";
            break;
        case ReadParam:
            cerr << "State: ReadParam";
            break;
        case ReadParamList:
            cerr << "State: ReadParamList";
            break;
        case ReadChan:
            cerr << "State: ReadChan";
            break;
        case ReadChanList:
            cerr << "State: ReadChanList";
            break;
    }
    cerr << " Depth " << xmlDepth << endl;
#endif // DEBUG_STATEMACHINE

    switch (xmlDepth) {
        case 0:
            break;
        case 1:
            switch (state) {
                case Connected:
                    if (!strcmp(el, "connected")) {
                        login();
                        sendParameterList();
                        state = ReadParam;
                    }
                    break;
                case ReadParamList:
                    if (!strcmp(el, "parameters")) {
                        std::ostringstream msg;
                        msg << "Found " << maxParamIdx << " parameters.";
                        process->protocolLog(PdCom::Process::LogInfo,
                                msg.str());
                        sendChannelList();
                        state = ReadChan;
                    }
                    break;
                case ReadChanList:
                    if (!strcmp(el, "channels")) {
                        state = Ready;
                        std::ostringstream msg;
                        msg << "Found " << maxChannelIdx << " channels.";
                        process->protocolLog(PdCom::Process::LogInfo,
                                msg.str());
                        XML_SetElementHandler(xmlParser,
                                ExpatConnectedStartTag, ExpatConnectedEndTag);

                        // Tell the process that communication is ready
                        process->protocolLog(PdCom::Process::LogInfo,
                                "Protocol initialisation finished.");
                        initialised();
                    }
                    break;
                default:
                    break;
            }
            break;

        case 2:
            switch (state) {
                default:
                    break;
            }
        default:
            break;
    }
}

/****************************************************************************
 * Wrapper to get from static function calls to class method calls
 */
void XMLCALL
ProtocolHandler::ExpatConnectedStartTag(void *data,
        const char *el, const char **attr)
{
    reinterpret_cast<ProtocolHandler*>(data)->connectedStartTag(el, attr);
}

/****************************************************************************
 * Wrapper to get from static function calls to class method calls
 */
void XMLCALL
ProtocolHandler::ExpatConnectedEndTag(void *data, const char *el)
{
    reinterpret_cast<ProtocolHandler*>(data)->connectedEndTag(el);
}

/****************************************************************************/

void ProtocolHandler::connectedStartTag(const char *el, const char **attr)
{
#ifdef DEBUG_XML

#ifdef DEBUG_STATEMACHINE
    cerr << "---------- start tag <" << el << "> ";
    switch (state) {
        case NotConnected:
            cerr << "State: NotConnected";
            break;
        case Connected:
            cerr << "State: Connected";
            break;
        case ReadParam:
            cerr << "State: ReadParam";
            break;
        case ReadParamList:
            cerr << "State: ReadParamList";
            break;
        case ReadChan:
            cerr << "State: ReadChan";
            break;
        case ReadChanList:
            cerr << "State: ReadChanList";
            break;
    }
    cerr << " Depth " << xmlDepth << endl;
#endif // DEBUG_STATEMACHINE
    cerr << "Attributes:" << endl;
    for (int i = 0; attr[i]; i += 2) {
        cerr << "    " << attr[i] << " = " << attr[i + 1] << endl;
    }
#endif // DEBUG_XML

    switch (xmlDepth) {
        unsigned int i;
        case 1:
            switch (connected_state) {
                case Idle:
                    if (!strcmp(el, "data")) {
                        for (i = 0; attr[i]; i += 2) {
                            if (!strcmp(attr[i], "time")) {
                                // this is the time of the last value in the
                                // following tags!
                                dataTime = attr[i + 1];
                            }
                        }
                        connected_state = ReadData;
                    }
                    else if (!strcmp(el, "pu")) {
                        unsigned int param_update_idx = ~0U;

                        // Parameter update
                        for (i = 0; attr[i]; i += 2) {
                            if (strcmp(attr[i], "index") == 0) {
                                param_update_idx = atoi(attr[i + 1]);
                            }
                        }
                        if (param_update_idx < maxParamIdx
                                and parameter[param_update_idx]) {
                            parameter[param_update_idx]->parameterUpdate();
                        }
                    }
                    else if (!strcmp(el, "parameter")) {
                        const char* value = NULL;
                        const char* mtime = NULL;
                        unsigned int index = ~0U;

                        for (i = 0; attr[i]; i += 2) {
                            if (!strcmp(attr[i], "index")) {
                                index = atoi(attr[i + 1]);
                            }
                            else if (!strcmp(attr[i], "mtime")) {
                                mtime = attr[i + 1];
                            }
                            else if (!strcmp(attr[i], "value")) {
                                value = attr[i + 1];
                            }
                        }

                        if (index < maxParamIdx && value) {
                            parameter[index]->newValues(mtime, value);
                        } else {
                            std::string msg( "Invalid response to "
                                    "parameter update request: ");
                            if (!value) {
                                msg += "No value was supplied.";
                            }
                            else {
                                msg += "Array index exceeded.";
                            }

                            process->protocolLog(PdCom::Process::LogError,
                                    msg);
                        }
                    }
                    else if (!strcmp(el, "channel")) {
                        const char* value = NULL;
                        unsigned int index = ~0U;

                        for (i = 0; attr[i]; i += 2) {
                            if (!strcmp(attr[i], "index")) {
                                index = atoi(attr[i + 1]);
                            }
                            else if (!strcmp(attr[i], "value")) {
                                value = attr[i + 1];
                            }
                        }

                        if (index < maxChannelIdx && value) {
                            channel[index]->newPoll(value);
                        } else {
                            std::string msg( "Invalid response to channel poll "
                                    "request: ");
                            if (!value) {
                                msg += "No value was supplied.";
                            }
                            else {
                                msg += "Array index exceeded.";
                            }

                            process->protocolLog(PdCom::Process::LogError,
                                    msg);
                        }
                    }
                    else if (!strcmp(el, "info")) {
                        processInfoTag(attr);
                    }
                    else if (!strcmp(el, "broadcast")) {
                        processBroadcastTag(attr);
                    }
                    break;
                default:
                    break;
            }
            break;
        case 2:
            switch (connected_state) {
                case ReadData:
                    if (!strcmp(el, "F")) {
                        unsigned int index = ~0U;
                        const char *value = 0;

                        for (i = 0; attr[i]; i += 2) {
                            if (strcmp(attr[i], "c") == 0) {
                                index = atoi(attr[i + 1]);
                            }
                            else if (strcmp(attr[i], "d") == 0) {
                                value = attr[i + 1];
                            }
                        }
                        if (index <= maxChannelIdx && value) {
                            channel[index]->newValues(dataTime, value);
                        }
                    }
                    else if (!strcmp(el, "E")) {
                        unsigned int index = ~0U;
                        const char *value = 0;

                        for (i = 0; attr[i]; i += 2) {
                            if (strcmp(attr[i], "c") == 0) {
                                index = atoi(attr[i + 1]);
                            }
                            else if (strcmp(attr[i], "d") == 0) {
                                value = attr[i + 1];
                            }
                        }
                        if (index <= maxChannelIdx && value) {
                            channel[index]->newEvent(dataTime, value);
                        }
                    }
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
    xmlDepth++;
}

/****************************************************************************/

void ProtocolHandler::connectedEndTag(const char *el)
{
    xmlDepth--;

#ifdef DEBUG_STATEMACHINE
    cerr << "========== end tag <" << el << "> ";
    switch (state) {
        case NotConnected:
            cerr << "State: NotConnected";
            break;
        case Connected:
            cerr << "State: Connected";
            break;
        case ReadParam:
            cerr << "State: ReadParam";
            break;
        case ReadParamList:
            cerr << "State: ReadParamList";
            break;
        case ReadChan:
            cerr << "State: ReadChan";
            break;
        case ReadChanList:
            cerr << "State: ReadChanList";
            break;
    }
    cerr << " Depth " << xmlDepth << endl;
#endif // DEBUG_STATEMACHINE

    switch (xmlDepth) {
        case 0:
            break;
        case 1:
            switch (connected_state) {
                case ReadData:
                    if (!strcmp(el, "data"))
                        connected_state = Idle;
                    break;
                default:
                    break;
            }
            break;
        default:
            break;
    }
}

/****************************************************************************/

void ProtocolHandler::login() const
{
    std::list<PdCom::Process::ClientInteraction> prompt;
    PdCom::Process::ClientInteraction ci;
    std::string loginStr;

    ci.prompt = "Username";
    ci.response = "Username";
    prompt.push_back(ci);
    ci.prompt = "Hostname";
    ci.response = "Hostname";
    prompt.push_back(ci);
    ci.prompt = "Application";
    ci.response = "Application";
    prompt.push_back(ci);

    if(!process->clientInteraction("MSR Client Authentication:",
                "MSR protocol would like to know the following: ",
                "",
                prompt))
        return;

    loginStr += prompt.front().response;
    prompt.pop_front();
    loginStr += "@";
    loginStr += prompt.front().response;
    prompt.pop_front();

    *os << "<remote_host name=\"" << xmlEscape(loginStr)
        << "\" applicationname=\"" << xmlEscape(prompt.front().response)
        << "\" access=\"allow\" isadmin=\"true\" />\n";

    process->protocolLog(PdCom::Process::LogInfo,
            std::string( "Logging in as \"") + loginStr + "\"");
}

/****************************************************************************/

void ProtocolHandler::sendChannelList()  const
{
    *os << "<rk />\n";
    process->protocolLog(PdCom::Process::LogInfo,
            "Requesting channel list.");
}

/****************************************************************************/

void ProtocolHandler::sendParameterList()  const
{
    *os << "<rp />\n";
    process->protocolLog(PdCom::Process::LogInfo,
            "Requesting parameter list.");
}

/****************************************************************************/

std::string ProtocolHandler::xmlEscape(const std::string &str)
{
    std::stringstream ret;

    for (std::string::const_iterator it = str.begin();
            it != str.end(); it++) {
        switch (*it) {
            case '<': ret << "&lt;"; break;
            case '>': ret << "&gt;"; break;
            case '&': ret << "&amp;"; break;
            case '"': ret << "&quot;"; break;
            case '\'': ret << "&apos;"; break;
            default: ret << *it; break;
        }
    }

    return ret.str();
}

/****************************************************************************/
