/* 
 * $Id: GLSDDocumentModule.cpp,v 1.18 2006/06/29 07:26:42 ozawa Exp $
 *
 * Copyright 2005- ONGS Inc. All rights reserved.
 * 
 * author: Masanori OZAWA (ozawa@ongs.co.jp)
 * version: $Revision: 1.18 $
 *
 * 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/GLSDDocumentModule.h"
#include "URLTool.h"
#include "xmlloader.h"
#include "XMLTool.h"

#include "modules/imports/ImportImage.h"
#include "modules/imports/ImportTextCSV.h"
#include "modules/imports/ImportTextCommandPrompt.h"
#include "modules/imports/ImportTextPlain.h"
#include "modules/imports/ImportTextSourcecode.h"
#include "modules/imports/ImportTextXML.h"

using namespace XDTP;

GLSDDocumentModule::GLSDDocumentModule()
{
#ifndef WITHOUT_GDK
    m_ImportModules.push_back(new ImportImage());
#endif
    m_ImportModules.push_back(new ImportTextCSV());
    m_ImportModules.push_back(new ImportTextCommandPrompt());
    m_ImportModules.push_back(new ImportTextPlain());
    m_ImportModules.push_back(new ImportTextSourcecode());
    m_ImportModules.push_back(new ImportTextXML());
}

GLSDDocumentModule::~GLSDDocumentModule()
{
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    for (; iter != end; iter++) {
        delete (*iter);
    }
    m_ImportModules.clear();
}

/**
 * 処理を開始する前に出力形式を設定します。
 * 
 * @param aType 出力形式
 */
void GLSDDocumentModule::setOutputType(const Glib::ustring& aType)
{
    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.setOutputType(aType);
    for (; iter != end; iter++) {
        (*iter)->setOutputType(aType);
    }
}

/**
 * 処理を開始する前にオプションを設定します。
 * 
 * @param anOptions オプション
 */
void GLSDDocumentModule::setOption(const StringList& anOptions)
{
    bool overwrite = false;
    Glib::ustring url;
    StringList::const_iterator striter = anOptions.begin();
    StringList::const_iterator strend = anOptions.end();
    
    m_AliasMap.clear();
    for (; striter != strend; striter++) {
        url.clear();
        if (0 == strncasecmp(striter->c_str(), "alias_file=", 11)) {
            url = striter->substr(11);
            if (0 < url.length()) {
                if (Glib::path_is_absolute(url)) {
                    url = "file:" + url;
                }
                else {
                    // 相対パスの場合は、カレントディレクトリからの相対とみなす。
                    url = "file:" + Glib::build_filename(Glib::get_current_dir(), url);
                }
                overwrite = true;
            }
        }
        else if (0 == strncasecmp(striter->c_str(), "alias_url=", 10)) {
            url = striter->substr(10);
            overwrite = false;
        }
        if (!url.empty()) {
            if (!addAliasMap(url, overwrite)) {
                THROW("Unable to parse the alias file. (" + url + ")");
            }
        }
    }

    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.setOption(anOptions);
    for (; iter != end; iter++) {
        (*iter)->setOption(anOptions);
    }
}

/**
 * 処理を開始する前に出力ファイル名を設定します。
 * 
 * @param anOutputFile 出力ファイル名
 */
void GLSDDocumentModule::setOutputFile(const Glib::ustring& anOutputFile)
{
    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.setOutputFile(anOutputFile);
    for (; iter != end; iter++) {
        (*iter)->setOutputFile(anOutputFile);
    }
}

/**
 * 処理を開始する前に変換元XMLファイル名を設定します。
 * 
 * @param aXMLFile 変換元XMLファイル名
 */
void GLSDDocumentModule::setXMLFile(const Glib::ustring& aXMLFile)
{
    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.setXMLFile(aXMLFile);
    for (; iter != end; iter++) {
        (*iter)->setXMLFile(aXMLFile);
    }
}

/**
 * 入力ファイルのエンコードを指定します。
 * XMLファイルはこの限りではありません。
 * 
 * @param anInputEncoding 入力ファイルのエンコード
 */
void GLSDDocumentModule::setInputEncoding(const Glib::ustring& anInputEncoding)
{
    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.setInputEncoding(anInputEncoding);
    for (; iter != end; iter++) {
        (*iter)->setInputEncoding(anInputEncoding);
    }
}

/**
 * 出力ファイルのエンコードを指定します。
 * 
 * @param anOutputEncoding 出力ファイルのエンコード
 */
void GLSDDocumentModule::setOutputEncoding(const Glib::ustring& anOutputEncoding)
{
    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.setOutputEncoding(anOutputEncoding);
    for (; iter != end; iter++) {
        (*iter)->setOutputEncoding(anOutputEncoding);
    }
}

/**
 * 現在のXDTPTransformモジュールを指定します。
 * 
 * @param aXDTPTransform 現在のXDTPTransformモジュール
 */
void GLSDDocumentModule::setXDTPTransform(const XDTPTransform *aXDTPTransform)
{
    XDTPModuleAdapter::setXDTPTransform(aXDTPTransform);

    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.setXDTPTransform(aXDTPTransform);
    for (; iter != end; iter++) {
        (*iter)->setXDTPTransform(aXDTPTransform);
    }
}

/**
 * ファイルをparseする直前に呼び出されます。
 * 
 * @param aFile parse対象のファイル名
 */
void GLSDDocumentModule::treatPreFile(const Glib::ustring& aFile)
{
    // XMLファイルのエンコーディングを解析
    XMLTool tool;
    std::string xmlencoding = tool.getEncoding(aFile);
    
    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.setXMLEncoding(xmlencoding);
    m_ImportTextGLSD.treatPreFile(aFile);
    for (; iter != end; iter++) {
        (*iter)->setXMLEncoding(xmlencoding);
        (*iter)->treatPreFile(aFile);
    }
}

/**
 * parseしたXML文章をXSL変換する直前に呼び出されます。
 * 
 * @param aDocument XML文章
 */
void GLSDDocumentModule::treatPreDocument(xmlDocPtr aDocument)
{
    // text/glsd-* のインポート処理のみ alias タグの置換処理に優先します。
    m_ImportTextGLSD.treatPreDocument(aDocument);

    // alias タグの置換処理
    if (!replaceAllAlias(aDocument)) {
        THROW("Unable to replace an alias.");
    }

    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    for (; iter != end; iter++) {
        (*iter)->treatPreDocument(aDocument);
    }
}

/**
 * XSL変換後のXML文章を保存する直前に呼び出されます。
 * 
 * @param aDocument XML文章
 */
void GLSDDocumentModule::treatPostDocument(xmlDocPtr aDocument)
{
    normalizeImport(aDocument);

    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.treatPostDocument(aDocument);
    for (; iter != end; iter++) {
        (*iter)->treatPostDocument(aDocument);
    }
}

/**
 * XML文章を保存した直後に呼び出されます。
 * 
 * @param aFile 保存したファイル名
 */
void GLSDDocumentModule::treatPostFile(const Glib::ustring& aFile)
{
    // import モジュール処理
    GLSDImportModuleList::iterator iter = m_ImportModules.begin();
    GLSDImportModuleList::iterator end = m_ImportModules.end();
    
    m_ImportTextGLSD.treatPostFile(aFile);
    for (; iter != end; iter++) {
        (*iter)->treatPostFile(aFile);
    }
}

/**
 * importタグを標準化します。
 * 主に以下の処理を行います。<br>
 * <ul>
 *  <li>importタグの前後の不要なpタグを削除します。
 *  <li>importタグへのアンカーを作成します。
 * </ul>
 * 
 * @param aDocument
 *              変換対象DOMドキュメント
 */
void GLSDDocumentModule::normalizeImport(xmlDocPtr aDocument)
{
    int count;
    XMLTool tool;
    xmlNodePtr node;
    xmlNodePtr test;
    xmlNodeSetPtr nodes;
    xmlXPathObjectPtr obj = tool.getNodeList(xmlDocGetRootElement(aDocument), "//import");
    const char *id;

    if (obj) {
        nodes = obj->nodesetval;
        
        for (count = 0; count < nodes->nodeNr; count++) {
            node = nodes->nodeTab[count];

            // importがp要素で直接囲まれているかチェックする。
            // これは、importが非構造モジュール要素であるため
            // 必然的に発生してしまうことが多い。
            if (node->parent && xmlStrcmp(node->parent->name, (const xmlChar*)"p") == 0) {
                for (test = node->prev; test; test = test->prev) {
                    if (tool.isElement(test)) break;
                }
                if (!test) {
                    for (test = node->next; test; test = test->next) {
                        if (tool.isElement(test)) break;
                    }
                    if (!test) {
                        // p要素で囲む必要がないため削除する。
                        xmlFreeNode(xmlReplaceNode(node->parent, node));
                    }
                }
            }
        }

        // id属性を a タグで置き換える
        for (count = 0; count < nodes->nodeNr; count++) {
            node = nodes->nodeTab[count];
            id = (const char*)xmlGetProp(node, (const xmlChar*)"id");
            if (id) {
                if (0 < strlen(id)) {
                    test = xmlNewNode(NULL, (const xmlChar*)"a");
                    xmlSetProp(test, (const xmlChar*)"name", (const xmlChar*)id);
                    if (!xmlAddPrevSibling(node, test)) {
                        // 構造上起り得ないエラー(メモリ不足?)
                        xmlFreeNode(test);
                        xmlXPathFreeObject(obj);
                        THROW("Unable to add element.");
                    }
                    /**
                     * 構造に影響が出るので、コメントアウト。 2005/09/30 ozawa
                    // <a/>の直後に改行を入れる。
                    xmlAddNextSibling(test, xmlNewText((const xmlChar*)LINE_SEP));
                    */
                }
                xmlFree((xmlChar*)id);
            }
        }
        
        xmlXPathFreeObject(obj);
    }
}

/**
 * alias 置換マップを構築します。
 * 
 * @param aURL
 *              alias置換マップファイルへのURL
 * @param anOverWrite
 *              alias置換マップに同じキーが登録されていた場合に、
 *              値を上書きする場合は true を指定します。
 * @return 構築に成功した場合は true を指定します。
 */
bool GLSDDocumentModule::addAliasMap(const Glib::ustring& aURL, bool anOverWrite)
{
    int len;
    char *buf;
    xmlDocPtr doc;
    
    if (!URLTool::getResource(aURL, &buf, &len)) {
#ifdef DEBUG
        fprintf(stderr, "%s: error: Unable to get the resource. (%s)" LINE_SEP, APP_NAME, aURL.c_str());
#endif
        return false;
    }
    
    doc = XMLLoader::loadMem(buf, len);
    free(buf);

    if (!doc) {
#ifdef DEBUG
        fprintf(stderr, "%s: error: Unable to parse the resource. (%s)" LINE_SEP, APP_NAME, aURL.c_str());
#endif
        return false;
    }

    bool bSuccess = true;
    bool keyset, valueset;
    int count;
    Glib::ustring key;
    Glib::ustring value;
    XMLTool tool;
    xmlNodePtr node;
    xmlNodePtr child;
    xmlNodeSetPtr nodes;
    xmlXPathObjectPtr obj = tool.getNodeList(xmlDocGetRootElement(doc), "/aliases/alias");

    if (obj) {
        nodes = obj->nodesetval;
        
        for (count = 0; bSuccess && count < nodes->nodeNr; count++) {
            node = nodes->nodeTab[count];
            child = node->children;
            
            key.clear();
            value.clear();
            keyset = valueset = false;
            for (; (!keyset || !valueset) && child; child = child->next) {
                if (0 == strcmp((const char*)child->name, "key")) {
                    key = tool.getText(child);
                    keyset = true;
                }
                else if (0 == strcmp((const char*)child->name, "value")) {
                    value = tool.getText(child);
                    valueset = true;
                }
            }

            if (!keyset || !valueset) {
                if (!keyset) fprintf(stderr, "%s: error: alias has no key. (value=%s)" LINE_SEP, APP_NAME, value.c_str());
                if (!valueset) fprintf(stderr, "%s: error: alias has no value. (key=%s)" LINE_SEP, APP_NAME, key.c_str());
                bSuccess = false;
            }
            else {
                if (anOverWrite || 0 == m_AliasMap.count(key)) {
                    m_AliasMap[key] = value;
                }
            }
        }
        
        xmlXPathFreeObject(obj);
    }
    
    xmlFreeDoc(doc);
    
    return bSuccess;
}

/**
 * alias の置換処理を行います。
 * 
 * @param aDocument
 *              変換対象DOMドキュメント
 * @return 置換に成功した場合は true を戻します。
 */
bool GLSDDocumentModule::replaceAllAlias(xmlDocPtr aDocument)
{
    bool bSuccess = true;
    int count;
    Glib::ustring key;
    Glib::ustring value;
    String2StringMap::iterator iter;
    XMLTool tool;
    xmlNodePtr node;
    xmlNodeSetPtr nodes;
    xmlXPathObjectPtr obj = tool.getNodeList(xmlDocGetRootElement(aDocument), "//p/alias");

    if (obj) {
        nodes = obj->nodesetval;
        
        for (count = 0; bSuccess && count < nodes->nodeNr; count++) {
            node = nodes->nodeTab[count];
            
            key = tool.getText(node);
            if (0 >= key.length()) {
                fprintf(stderr, "%s: error: alias has zero length keyword." LINE_SEP, APP_NAME);
                bSuccess = false;
                break;
            }
            
            iter = m_AliasMap.find(key);
            if (iter != m_AliasMap.end()) {
                value = iter->second;
            }
            else {
#if 1
                // 置換対象が見付からない場合はキーワードを置換文字列とする。
                value = key;
#else
                // 置換対象が見付からない場合に、警告を表示して alias 全体を削除。
                fprintf(stderr, "%s: warning: Unable to find the value of the alias. (%s)" LINE_SEP,
                    APP_NAME, key.c_str());
                xmlUnlinkNode(node);
                xmlFreeNode(node);
                continue;
#endif
            }

            xmlFreeNode(xmlReplaceNode(node, xmlNewText((const xmlChar*)value.c_str())));
        }
        
        xmlXPathFreeObject(obj);
    }
    
    return bSuccess;
}


syntax highlighted by Code2HTML, v. 0.9.1