/* 
 * $Id: ImportTextGLSD.cpp,v 1.6 2006/07/13 15:45:39 ozawa Exp $
 *
 * Copyright 2006- ONGS Inc. All rights reserved.
 * 
 * author: Masanori OZAWA (ozawa@ongs.co.jp)
 * version: $Revision: 1.6 $
 *
 * 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.
 * 
 * THIS SOFTWARE IS PROVIDED BY ONGS INC ``AS IS'' AND ANY EXPRESS 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 ONGS INC OR 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.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the ONGS Inc.
 * 
 */

#include "xdtp.h"
#include "modules/GLSDModuleChecker.h"
#include "modules/GLSDDocumentModuleChecker.h"
#include "modules/imports/ImportTextGLSD.h"
#include "URI.h"
#include "URLTool.h"
#include "Utilities.h"
#include "xmlloader.h"
#include "XMLTool.h"

using namespace XDTP;

#define DEFAULT_MAX_NEST_LEVEL  6
#define MAX_NEST_LEVEL_KEY      "text/glsd-document:max-nest-level"

ImportTextGLSD::ImportTextGLSD()
{
}

ImportTextGLSD::~ImportTextGLSD()
{
}

/**
 * parseしたXML文章をXSL変換する直前に呼び出されます。
 * 
 * @param aDocument XML文章
 */
void ImportTextGLSD::treatPreDocument(xmlDocPtr aDocument)
{
    GLSDImportModuleAdapter::treatPreDocument(aDocument);

    int count;
    int nestcnt;
    int nestlevel = DEFAULT_MAX_NEST_LEVEL;
    XMLTool tool;
    xmlNodePtr root = xmlDocGetRootElement(aDocument);
    xmlNodeSetPtr nodes;
    xmlXPathObjectPtr obj;

    if (getOptionInt(MAX_NEST_LEVEL_KEY, nestcnt)) {
        nestlevel = nestcnt;
    }

    for (nestcnt = 0; nestcnt < nestlevel; nestcnt++) {
        obj = tool.getNodeList(root, "//import[@type=\"text/glsd-document\"]");
        if (!obj) break;

        nodes = obj->nodesetval;
        
        // 置換対象が見付からない場合は処理を終了
        if (0 == nodes->nodeNr) {
            xmlXPathFreeObject(obj);
            break;
        }
        
        for (count = 0; count < nodes->nodeNr; count++) {
            if (!transform(aDocument, nodes->nodeTab[count])) {
                THROW("ImportTextGLSD: transform failed.");
            }
        }
        
        xmlXPathFreeObject(obj);
    }
}

bool ImportTextGLSD::transform(xmlDocPtr aDocument, xmlNodePtr aNode)
{
    Glib::ustring ref;
    Glib::ustring caption;
    Glib::ustring encoding; // encoding 属性は不要なため無視します

    if (!getImportAttributes(aNode, ref, caption, encoding)) {
        return false;
    }
    
    if ("html" == m_OutputType || "xhtml" == m_OutputType ||
        "text" == m_OutputType) {
        return glsdInclude(aDocument, aNode, ref, caption, encoding);
    }
    // otherwise, do not operation.
    
    return true;
}

bool ImportTextGLSD::glsdInclude
    (xmlDocPtr aDocument, xmlNodePtr aNode,
     const Glib::ustring& aRef,
     const Glib::ustring& aCaption,
     const Glib::ustring& anEncoding)
{
    // ファイルを読み込み、ノードを置換する
    bool bSuccess = false;
    std::string tmpfile;
    XMLTool tool;
    xmlDocPtr newdoc = NULL;
    xmlXPathObjectPtr obj = NULL;

    try {
        GLSDModuleChecker gmChecker;
        GLSDDocumentModuleChecker gmdChecker;
        Glib::RefPtr<Glib::IOChannel> ioc = getIOChannel(aRef, tmpfile);
        ioc->set_encoding(); // binary mode
        
        newdoc = xmlReadFd(g_io_channel_unix_get_fd(ioc->gobj()), aRef.c_str(), NULL, 0);
        ioc->close();
        
        // check
        if (!getXDTPTransform()->isXMLCheck()) {
            if (!gmChecker.checkFormat(newdoc) || !gmdChecker.checkFormat(newdoc)) {
                THROW("checkFormat failed. (" + aRef + ")");
            }
        }
        
        // import の相対参照を絶対参照に変換
        if (!absoluteImportRef(aRef, newdoc)) {
            THROW("Unable to fix the reference URL of 'import'.");
        }
        
        // id 属性値を一意に識別できるよう調整
        if (!normalizeImportID(aNode, newdoc)) {
            THROW("Unable to normalize the attribute of id.");
        }

        // 置換ノードの取得
        xmlNodePtr root = xmlDocGetRootElement(newdoc);
        xmlNodePtr newdocgroup = NULL;
        xmlNodePtr parent = aNode->parent;
        xmlNodeSetPtr nodes;
        obj = tool.getNodeList(root, "/document/docgroup");

        if (obj) {
            nodes = obj->nodesetval;
            if (1 == nodes->nodeNr) {
                newdocgroup = xmlDocCopyNode(nodes->nodeTab[0], aDocument, 1);
                if (!newdocgroup) {
                    THROW("xmlDocCopyNodeList failed. Out of memory?");
                }
            }
        }
        
        // 置換
        if (newdocgroup) {
            if (parent && 0 == strcmp("p", (const char*)parent->name)) {
                xmlFreeNode(xmlReplaceNode(parent, newdocgroup));
            }
            else {
                xmlFreeNode(xmlReplaceNode(aNode, newdocgroup));
            }
        }
        else {
            xmlUnlinkNode(aNode);
            xmlFreeNode(aNode);
        }
        
        bSuccess = true;
    }
    catch (Exception exception) {
        exception.print(stderr);
    }
    catch (Glib::Error error) {
        std::string msg = Glib::locale_from_utf8(error.what());
        fprintf(stderr, "%s: %s (%s)" LINE_SEP, APP_NAME, msg.c_str(), aRef.c_str());
    }
    catch (std::exception exception) {
        fprintf(stderr, "%s: %s (%s)" LINE_SEP, APP_NAME, exception.what(), aRef.c_str());
    }
    catch (...) {
        fprintf(stderr, "%s: ImportTextSourcecode: unknown error occured. (%s)" LINE_SEP,
            APP_NAME, aRef.c_str());
    }
    
    if (obj) {
        xmlXPathFreeObject(obj);
    }
    if (newdoc) {
        xmlFreeDoc(newdoc);
    }
    if (0 < tmpfile.length()) {
        remove(tmpfile.c_str());
    }
    
    return bSuccess;
}

/**
 * import の相対参照を絶対参照に変換します。
 * 基準となる参照位置を元に、基準となる絶対的な参照位置を決定します。
 * 次に import タグ内の相対参照を、基準の絶対的な参照位置を元に修正します。
 * XDTPは file スキーマ以外で相対参照を認めないため、file スキーマ以外の
 * 相対参照は修正しません。
 * 
 * @param aRef
 *          基準となる参照位置
 * @param aDocument
 *          変換対象の import タグを保有するXMLドキュメント
 * @return 変換に成功した場合は true を戻します。
 */
bool ImportTextGLSD::absoluteImportRef(const Glib::ustring& aRef, xmlDocPtr aDocument)
{
    // 基準位置を決定
    RefPtr<URI> uri = URI::parse(normalizeURL(aRef));
    Glib::ustring basepath = uri->getPath();
    Glib::ustring::size_type pos;
    
    // 基準を決めるにあたって、file スキーマ以外はホスト名の記述が必須。
    if (!uri->isFileScheme() && 0 >= uri->getHostname().length()) {
        return false;
    }
    
    pos = basepath.rfind('/');
    if (Glib::ustring::npos == pos || 1 == pos) {
        return false;
    }
    if (0 == pos) {
        basepath = "";
    }
    else {
        basepath = basepath.substr(0, pos);
        if ('/' != basepath.at(0)) {
            basepath = "/" + basepath;
        }
    }

    // 相対参照を修正
    bool result = true;
    int count;
    char buf[32];
    Glib::ustring ref;
    Glib::ustring tmp1, tmp2;
    Glib::ustring path;
    Glib::ustring newref;
    RefPtr<URI> imref;
    XMLTool tool;

    xmlNodePtr root = xmlDocGetRootElement(aDocument);
    xmlNodePtr node;
    xmlNodeSetPtr nodes;
    xmlXPathObjectPtr obj = tool.getNodeList(root, "//import");

    if (obj) {
        nodes = obj->nodesetval;
        for (count = 0; count < nodes->nodeNr; count++) {
            node = nodes->nodeTab[count];
            
            if (!getImportAttributes(node, ref, tmp1, tmp2)) {
                result = false;
                break;
            }

            imref = URI::parse(ref);
            
            if (!imref->isFileScheme()) {
                if (0 < imref->getScheme().length()) {
                    result = false;
                    break;
                }
                else {
                    path = ref;
                }
            }
            else {
                path = imref->getPath();
            }

            if (0 >= path.length()) {
                result = false;
                break;
            }

            newref = uri->getScheme() + "://";
            
            if (!uri->isFileScheme()) {
                if (0 < uri->getUsername().length()) {
                    newref += uri->getUsername() + "@";
                }
                newref += uri->getHostname();
                if (0 < uri->getPort()) {
                    bzero(buf, sizeof(buf));
                    snprintf(buf, sizeof(buf) -1, ":%d", uri->getPort());
                    newref += buf;
                }
            }
            
            if ('/' == path.at(0)) {
                newref += path;
            }
            else {
                newref += basepath + "/" + path;
            }
            
            xmlSetProp(node, (const xmlChar *)"ref", (const xmlChar *)newref.c_str());
        }

        xmlXPathFreeObject(obj);
    }
    
    return result;
}

/**
 * id 属性値を一意に識別できるよう正規化します。
 * 
 * @param aParent
 *          元となるimportエレメント
 * @param aDocument
 *          変換対象の import タグを保有するXMLドキュメント
 * @return 変換に成功した場合は true を戻します。
 */
bool ImportTextGLSD::normalizeImportID(xmlNodePtr aParent, xmlDocPtr aDocument)
{
    // 元の id 属性値を取得
    Glib::ustring id;
    const char *pId = (const char*)xmlGetProp(aParent, (const xmlChar*)"id");
    id = Utilities::strTrim((pId ? pId : ""));
    if (pId) xmlFree((xmlChar*)pId);
    
    if (0 >= id.length()) {
        // 元となる id 属性値がない場合は何もしません。
        return true;
    }
    
    id += "-";
    
    // docgroup, import, bibitem 正規化
    int count;
    Glib::ustring newid;
    XMLTool tool;
    xmlNodePtr root = xmlDocGetRootElement(aDocument);
    xmlNodePtr node;
    xmlNodeSetPtr nodes;
    xmlXPathObjectPtr obj = tool.getNodeList(root, "//*[@id]");

    if (obj) {
        nodes = obj->nodesetval;
        for (count = 0; count < nodes->nodeNr; count++) {
            node = nodes->nodeTab[count];
            
            // 対象エレメント以外は無視
            if (0 != strcmp("docgroup", (const char*)node->name) &&
                0 != strcmp("import", (const char*)node->name) &&
                0 != strcmp("bibitem", (const char*)node->name)) {
                continue;
            }
            
            // 正規化
            pId = (const char*)xmlGetProp(node, (const xmlChar*)"id");
            newid = id + Utilities::strTrim((pId ? pId : ""));
            if (pId) xmlFree((xmlChar*)pId);
            xmlSetProp(node, (const xmlChar*)"id", (const xmlChar*)newid.c_str());
        }
        xmlXPathFreeObject(obj);
    }

    // access 正規化
    const char *pRef;
    Glib::ustring newref;

    obj = tool.getNodeList(root, "//access");
    if (obj) {
        nodes = obj->nodesetval;
        for (count = 0; count < nodes->nodeNr; count++) {
            node = nodes->nodeTab[count];
            
            pRef = (const char*)xmlGetProp(node, (const xmlChar*)"ref");
            newref = Utilities::strTrim((pRef ? pRef : ""));
            if (pRef) xmlFree((xmlChar*)pRef);
            
            if (5 <= newref.length() && 0 == newref.substr(0, 5).compare("glsd:")) {
                // 正規化
                newref = "glsd:" + id + newref.substr(5);
                xmlSetProp(node, (const xmlChar*)"ref", (const xmlChar*)newref.c_str());
            }
        }
        xmlXPathFreeObject(obj);
    }

    return true;
}


syntax highlighted by Code2HTML, v. 0.9.1