/* 
 * $Id: GLSDImportModuleAdapter.cpp,v 1.19 2006/07/13 13:31:18 ozawa Exp $
 *
 * Copyright 2005- ONGS Inc. All rights reserved.
 * 
 * author: Masanori OZAWA (ozawa@ongs.co.jp)
 * version: $Revision: 1.19 $
 *
 * 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/imports/GLSDImportModuleAdapter.h"

#include "URI.h"
#include "URLTool.h"
#include "Utilities.h"
#include "XMLTool.h"

#define TIP_TAG     "___XDTP_GLSD_IMPORT_TAG___"

using namespace XDTP;

GLSDImportModuleAdapter::GLSDImportModuleAdapter()
{
    m_OutputType = DEFAULT_OUTPUT_TYPE;
    m_OutputEncoding = DEFAULT_OUTPUT_ENCODING;
    m_pXDTPTransform = NULL;
}

GLSDImportModuleAdapter::~GLSDImportModuleAdapter()
{
}

/**
 * 現在のXDTPTransformモジュールを取得します。
 * 
 * @return 現在のXDTPTransformモジュール
 */
const XDTPTransform * GLSDImportModuleAdapter::getXDTPTransform()
{
    return m_pXDTPTransform;
}

/**
 * 処理を開始する前に出力形式を設定します。
 * 
 * @param aType 出力形式
 */
void GLSDImportModuleAdapter::setOutputType(const Glib::ustring& aType)
{
    m_OutputType = aType;
}

/**
 * 処理を開始する前にオプションを設定します。
 * 
 * @param anOptions オプション
 */
void GLSDImportModuleAdapter::setOption(const StringList& anOptions)
{
    m_OptionList = anOptions;
}

/**
 * 処理を開始する前に出力ファイル名を設定します。
 * 
 * @param anOutputFile 出力ファイル名
 */
void GLSDImportModuleAdapter::setOutputFile(const Glib::ustring& anOutputFile)
{
    m_OutputFile = anOutputFile;
}

/**
 * 処理を開始する前に変換元XMLファイル名を設定します。
 * 
 * @param aXMLFile 変換元XMLファイル名
 */
void GLSDImportModuleAdapter::setXMLFile(const Glib::ustring& aXMLFile)
{
    m_XMLFile = aXMLFile;
}

/**
 * 入力ファイルのエンコードを指定します。
 * XMLファイルはこの限りではありません。
 * 
 * @param anInputEncoding 入力ファイルのエンコード
 */
void GLSDImportModuleAdapter::setInputEncoding(const Glib::ustring& anInputEncoding)
{
    m_InputEncoding = anInputEncoding;
}

/**
 * 出力ファイルのエンコードを指定します。
 * 
 * @param anOutputEncoding 出力ファイルのエンコード
 */
void GLSDImportModuleAdapter::setOutputEncoding(const Glib::ustring& anOutputEncoding)
{
    m_OutputEncoding = anOutputEncoding;
}

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

/**
 * XMLファイルのエンコーディングを指定します。
 * 
 * @param anEncoding XMLファイルのエンコーディング
 */
void GLSDImportModuleAdapter::setXMLEncoding(const std::string& anEncoding)
{
    m_XMLEncoding = anEncoding;
}

/**
 * ファイルをparseする直前に呼び出されます。
 * 
 * @param aFile parse対象のファイル名
 */
void GLSDImportModuleAdapter::treatPreFile(const Glib::ustring& aFile)
{
    // no operation
}

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

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

/**
 * XML文章を保存した直後に呼び出されます。
 * 
 * @param aFile 保存したファイル名
 */
void GLSDImportModuleAdapter::treatPostFile(const Glib::ustring& aFile)
{
    // no operation
}

/**
 * ファイルからインポート処理対象の部分を抽出し、置換処理を
 * 行う際に呼び出されます。
 * 
 * @param aRef インポート先のファイル
 * @param aCaption キャプション
 * @param anEncoding インポート先のファイルのエンコード
 * @param aNewData 置換すべき文字列を応答します。
 * @return 処理結果を応答します。
 */
TIP_STATUS GLSDImportModuleAdapter::transformImportPointCallback
    (const Glib::ustring& aRef, const Glib::ustring& aCaption,
     const Glib::ustring& anEncoding, Glib::ustring& aNewData)
{
    return TIP_SKIP;
}

/**
 * インポート処理対象を識別するノードを構築します。
 * 
 * @param aType 処理対象の種類
 * @param aRef インポート先のファイル
 * @param aCaption キャプション
 * @param anEncoding インポート先のファイルのエンコード
 * @return インポート処理対象を識別するノード
 */
xmlNodePtr GLSDImportModuleAdapter::createNewImportPointNode
     (const Glib::ustring& aType, const Glib::ustring& aRef,
      const Glib::ustring& aCaption, const Glib::ustring& anEncoding)
{
    Glib::ustring comment = TIP_TAG LINE_SEP;
    
    comment += "type=" + aType + LINE_SEP;
    comment += "ref=" + aRef + LINE_SEP;
    comment += "caption=" + aCaption + LINE_SEP;
    comment += "encoding=" + anEncoding + LINE_SEP;
    
    return xmlNewComment((const xmlChar*)comment.c_str());
}

/**
 * ファイルからインポート処理対象の部分を抽出し、置換処理を行います。
 * 
 * @param aFile 処理対象のファイル
 * @param aType 処理対象の種類
 * @return 成功した場合は true を戻します。
 */
bool GLSDImportModuleAdapter::transformImportPoint
    (const Glib::ustring& aFile, const Glib::ustring& aType)
{
    bool result = false;

    // 一時ファイルを構築
    std::string tmpfile = "_xdtp_temp.XXXXXX";
    int fd = Glib::mkstemp(tmpfile);
    
    if (0 > fd) {
        fprintf(stderr, "%s: Unable to create temp file." LINE_SEP, APP_NAME);
        return false;
    }
    close(fd);

    // インポート置換処理
    try {
        Glib::ustring line, data, newdata;
        Glib::ustring type, ref, caption, encoding;
        Glib::ustring::size_type pos, posRet;
        Glib::RefPtr<Glib::IOChannel> iioc =
            Glib::IOChannel::create_from_file(aFile.c_str(), "r");
        Glib::RefPtr<Glib::IOChannel> oioc =
            Glib::IOChannel::create_from_file(tmpfile.c_str(), "w");
        iioc->set_encoding(m_OutputEncoding);
        oioc->set_encoding(m_OutputEncoding);
        
        while (iioc->read_line(line) == Glib::IO_STATUS_NORMAL) {
            pos = line.find("<!--" TIP_TAG);
            if (Glib::ustring::npos != pos) {
                if (0 < pos) {
                    oioc->write(line.substr(0, pos));
                }
                data = line.substr(pos);
                pos = Glib::ustring::npos;
                while (iioc->read_line(line) == Glib::IO_STATUS_NORMAL) {
                    pos = line.find("-->");
                    if (Glib::ustring::npos != pos) {
                        data += line.substr(0, pos +3);
                        break;
                    }
                    data += line;
                }
                if (Glib::ustring::npos == pos)
                    THROW("Unable to find the terminate position of import.");
                
                line = line.substr(pos +3);
                
                pos = data.find("type=");
                posRet = data.find(LINE_SEP, pos);
                if (Glib::ustring::npos == pos ||
                    Glib::ustring::npos == posRet) {
                    THROW("Internal error!");
                }
                pos += 5;
                type = data.substr(pos, posRet - pos);
                
                pos = data.find("ref=");
                posRet = data.find(LINE_SEP, pos);
                if (Glib::ustring::npos == pos ||
                    Glib::ustring::npos == posRet) {
                    THROW("Internal error!");
                }
                pos += 4;
                ref = data.substr(pos, posRet - pos);

                pos = data.find("caption=");
                posRet = data.find(LINE_SEP, pos);
                if (Glib::ustring::npos == pos ||
                    Glib::ustring::npos == posRet) {
                    THROW("Internal error!");
                }
                pos += 8;
                caption = data.substr(pos, posRet - pos);
                
                pos = data.find("encoding=");
                posRet = data.find(LINE_SEP, pos);
                if (Glib::ustring::npos == pos ||
                    Glib::ustring::npos == posRet) {
                    THROW("Internal error!");
                }
                pos += 9;
                encoding = data.substr(pos, posRet - pos);
                
                if (type == aType) {
                    newdata = "";
                    switch (transformImportPointCallback(ref, caption, encoding, newdata)) {
                    case TIP_SUCCESS:
                        oioc->write(newdata);
                        break;
                    case TIP_SKIP:
                        oioc->write(data);
                        break;
                    default:
                    case TIP_FAILED:
                        THROW("Unable to transform the import point.");
                        break;
                    }
                }
                else {
                    oioc->write(data);
                }
            }

            oioc->write(line);
        }
        
        iioc->close();
        oioc->close();
        result = true;
    }
    catch (Exception exception) {
        exception.print(stderr);
    }
    catch (Glib::Error error) {
        std::string msg = Glib::locale_from_utf8(error.what());
        fprintf(stderr, "%s: %s" LINE_SEP, APP_NAME, msg.c_str());
    }
    catch (std::exception exception) {
        fprintf(stderr, "%s: %s" LINE_SEP, APP_NAME, exception.what());
    }
    catch (...) {
        fprintf(stderr, "%s: ImportTextSourcecode: unknown error occured." LINE_SEP, APP_NAME);
    }
    
    if (result) {
        result = Utilities::move(tmpfile, aFile);
    }
    if (!result) {
        remove(tmpfile.c_str());
    }
    
    return result;
}

/**
 * 任意タイプのスタイルシートを取得する。
 * 
 * @param aDocument
 *            XHTMLドキュメント
 * @param aType
 *            スタイルシートのタイプ
 *            常に小文字で指定する必要があります。
 * @return  取得したスタイルシート
 */
xmlNodePtr GLSDImportModuleAdapter::getStyleSheet
    (xmlDocPtr aDocument, const Glib::ustring& aType)
{
    if (0 >= aType.length()) {
        THROW("bad arguments.");
    }

    // スタイルシートのエレメントを取得
    int count;
    XMLTool tool;
    xmlNodePtr comment = NULL;
    xmlNodePtr node;
    xmlNodePtr root = xmlDocGetRootElement(aDocument);
    xmlNodeSetPtr nodes;
    xmlXPathObjectPtr obj = tool.getNodeList(root, "/html/head/style");
    const char *pTmp;
    Glib::ustring value;
    
    if (obj) {
        nodes = obj->nodesetval;

        for (count = 0; count < nodes->nodeNr; count++) {
            node = nodes->nodeTab[count];
            pTmp = (const char*)xmlGetProp(node, (const xmlChar*)"type");
            value = (pTmp ? pTmp : "");
            if (pTmp) xmlFree((xmlChar*)pTmp);

            value = value.lowercase();
            if (value == aType) {
                break;
            }
        }
        
        if (count < nodes->nodeNr) {
            // スタイルシートのコメント部を取得
            for (node = node->children; node; node = node->next) {
                if (tool.isComment(node)) {
                    comment = node;
                    break;
                }
            }
        }
        
        xmlXPathFreeObject(obj);
    }

    // 取得できない場合は新規に作成する
    if (!comment) {
        xmlNodePtr style = NULL;
        
        obj = tool.getNodeList(root, "/html/head/style");
        
        if (obj) {
            nodes = obj->nodesetval;
            for (count = 0; count < nodes->nodeNr; count++) {
                pTmp = (const char*)xmlGetProp(nodes->nodeTab[count], (const xmlChar*)"type");
                value = (pTmp ? pTmp : "");
                if (pTmp) xmlFree((xmlChar*)pTmp);
                
                value = value.lowercase();
                if (value == aType) {
                    style = nodes->nodeTab[count];
                    break;
                }
            }
            xmlXPathFreeObject(obj);
        }
        if (!style) {
            obj = tool.getNodeList(root, "/html/head");
            
            if (!obj) return NULL;
            
            nodes = obj->nodesetval;
            node = nodes->nodeTab[0];
            xmlXPathFreeObject(obj);
            style = xmlNewNode(NULL, (const xmlChar*)"style");
            xmlSetProp(style, (const xmlChar*)"type", (const xmlChar*)aType.c_str());
            xmlAddChild(node, style);
        }
        if (!style) THROW("Out of memory!");
        
        comment = xmlNewComment((const xmlChar*)"");
        if (!comment) THROW("Out of memory!");
        
        xmlAddChild(style, comment);
    }

    return comment;
}

/**
 * Importタグの各種属性値を取得します。
 * 
 * @param aNode Importノード
 * @param aRef リソースのURL
 * @param aCaption キャプション
 * @param anEncoding リソースのエンコーディング
 * @return 取得に成功した場合は true を戻す。
 */
bool GLSDImportModuleAdapter::getImportAttributes(xmlNodePtr aNode,
    Glib::ustring& aRef, Glib::ustring& aCaption, Glib::ustring& anEncoding)
{
    const char *pRef = (const char*)xmlGetProp(aNode, (const xmlChar*)"ref");
    if (!pRef) return false;

    const char *pCaption = (const char*)xmlGetProp(aNode, (const xmlChar*)"caption");
    const char *pEncoding = (const char*)xmlGetProp(aNode, (const xmlChar*)"encoding");
    
    aRef = Utilities::strTrim(pRef);
    aCaption = Utilities::strTrim((pCaption ? pCaption : ""));
    anEncoding = Utilities::strTrim((pEncoding ? pEncoding : ""));

    xmlFree((xmlChar*)pRef);
    if (pCaption) xmlFree((xmlChar*)pCaption);
    if (pEncoding) xmlFree((xmlChar*)pEncoding);
    
    if (getenv(XDTP_URI_TRANSITION)) {
        // 誤ったURIを修正する
        RefPtr<URI> uri = URI::parse(aRef);
        aRef = uri->toString().c_str();
    }

    return true;
}

/**
 * URIで参照するリソースへのIOChannelを取得します。
 * 取得できない場合は XDTP::Exception を throw します。
 * 
 * @param aRef URI
 * @param aTmpFile 一時ファイルを生成した場合は、一時ファイルへのパスを応答します。
 *                 生成しない場合は、空文字列を応答します。
 * @return リソースへのIOChannel
 */
Glib::RefPtr<Glib::IOChannel> GLSDImportModuleAdapter::getIOChannel
    (const Glib::ustring& aRef, std::string& aTmpFile)
{
    Glib::RefPtr<Glib::IOChannel> result;
    
    try {
        std::string target;
        RefPtr<URI> uri = URI::parse(normalizeURL(aRef));

        if (0 >= uri->getScheme().length() || uri->isFileScheme()) {
            target = uri->getPath();
        }
        else {
            aTmpFile = "_xdtp_temp.XXXXXX";
            int fd = Glib::mkstemp(aTmpFile);
            if (0 > fd) {
                aTmpFile = "";
                THROW("Unable to create temp file.");
            }
            if (!URLTool::getResource2File(aRef, fd)) {
                close(fd);
                THROW("Unable to read the resource. " + aRef);
            }
            close(fd);
            target = aTmpFile;
        }
        
        result = Glib::IOChannel::create_from_file(target.c_str(), "r");
    }
    catch (Exception exception) {
        THROW(exception.what() + " (" + aRef + ")");
    }
    catch (Glib::Error error) {
        std::string msg = Glib::locale_from_utf8(error.what());
        THROW(msg + " (" + aRef + ")");
    }
    catch (std::exception exception) {
        std::string msg = exception.what();
        THROW(msg + " (" + aRef + ")");
    }
    catch (...) {
        THROW("GLSDImportModuleAdapter: Unknown error occured. (" + aRef + ")");
    }
    
    return result;
}

/**
 * ローカルファイルを参照するURLを絶対パスのURLに展開する。
 */
Glib::ustring GLSDImportModuleAdapter::normalizeURL(const Glib::ustring& url)
{
    bool froceFileScheme = false;
    RefPtr<URI> uri = URI::parse(url);
    Glib::ustring result = url;
    
    if (0 >= uri->getScheme().length()) {
        if (0 >= url.length() ||
            (2 <= url.length() && "//" == url.substr(0, 2)))
        {
            THROW("Bad URI. (" + url + ")");
        }
        
        froceFileScheme = true;
    }
    
    if (froceFileScheme || uri->isFileScheme()) {
        if (0 >= uri->getPath().length()) {
            // パスが存在しないURLはエラーとする。
            if (7 < url.length() && "file://" == url.substr(0, 7).lowercase()) {
                THROW("Bad URL. (" + url + ")" LINE_SEP
                      "\tThis problem may be able to be solved by setting up" LINE_SEP
                      "\tenvironment variable XDTP_URI_TRANSITION.");
            }
            THROW("Bad URL. (" + url + ")");
        }
        else if (!Glib::path_is_absolute(uri->getPath())) {
            result = uri->getPath();
            
            Glib::ustring::size_type pos = m_XMLFile.rfind(FILE_SEP);
            if (Glib::ustring::npos != pos) {
                result = m_XMLFile.substr(0, pos) + FILE_SEP + result;
#ifdef WIN32
                result = Utilities.strReplaceAll(result, FILE_SEP, "/");
#endif
            }
            if (!Glib::path_is_absolute(result)) {
                result = Glib::build_filename(Glib::get_current_dir(), result);
            }

            result = "file:" + result;
        }
    }
    
    return result;
}

/**
 * ファイルの読み込み用エンコードを適切に設定する。
 * 
 * @param anIOC エンコードを設定するIOChannel
 * @param aDocument XMLドキュメント
 * @param anEncoding 優先的に設定するエンコード。不明な場合は空文字列を指定する。
 */
void GLSDImportModuleAdapter::setEncoding(Glib::RefPtr<Glib::IOChannel>& anIOC,
                                          const Glib::ustring& anEncoding)
{
    if (!anEncoding.empty()) {
        anIOC->set_encoding(anEncoding);
    }
    else if (!m_InputEncoding.empty()) {
        anIOC->set_encoding(m_InputEncoding);
    }
    else if (!m_XMLEncoding.empty()) {
        anIOC->set_encoding(m_XMLEncoding);
    }
    else {
        std::string encoding;
        Glib::get_charset(encoding);
        anIOC->set_encoding(encoding.c_str());
    }
}

/**
 * XMLドキュメント用に特殊文字をエスケープします。
 * 
 * @param aString 特殊文字を含む文字列
 * @return エスケープ後の文字列
 */
Glib::ustring GLSDImportModuleAdapter::escapeString(const Glib::ustring& aString)
{
    // エスケープ処理
    Glib::ustring result =aString;

    result = Utilities::strReplaceAll(result, "&", "&amp;");  // &
    result = Utilities::strReplaceAll(result, "<", "&lt;");   // <
    result = Utilities::strReplaceAll(result, ">", "&gt;");   // >
    result = Utilities::strReplaceAll(result, " ", "&#160;"); // スペース
    result = Utilities::strReplaceAll(result, "\\", "&#92;"); // バックスラッシュ
    
    return result;
}

/**
 * Importタグをテキスト形式で保持します。
 */
bool GLSDImportModuleAdapter::preserveImport(xmlDocPtr aDocument, xmlNodePtr aNode,
                                             const Glib::ustring& aRef,
                                             const Glib::ustring& aCaption,
                                             const Glib::ustring& anEncoding)
{
    const char *pType = (const char*)xmlGetProp(aNode, (const xmlChar*)"type");
    Glib::ustring type = (pType ? pType : "");
    Glib::ustring text = "<import type=\"" + type + "\" ref=\"" + aRef + "\" ";
    text += "caption=\"" + aCaption + "\" encoding=\"" + anEncoding + "\">";
    
    if (pType) xmlFree((xmlChar*)pType);

    xmlNodePtr import = xmlNewText((const xmlChar*)text.c_str());
    if (!import) return false;
    xmlFreeNode(xmlReplaceNode(aNode, import));
    
    return true;
}

/**
 * 指定のキーで識別されるオプションの値を取得します。
 * 
 * @param aKey オプションを一意に識別する値
 * @param aValue 取得したオプションの値を格納する領域
 * @return オプションが見付かり正しく取得できた場合は true を戻します。
 */
bool GLSDImportModuleAdapter::getOptionInt(const Glib::ustring& aKey, int &aValue)
{
    long val = 0;
    char *endptr = NULL;
    Glib::ustring tmp;
    
    if (getOptionString(aKey, tmp) && 0 < tmp.length()) {
        val = strtol(tmp.c_str(), &endptr, 10);
        if ('\0' == *endptr) {
            aValue = (int)val;
            return true;
        }
    }
    
    return false;
}

bool GLSDImportModuleAdapter::getOptionString(const Glib::ustring& aKey, Glib::ustring& aValue)
{
    bool result = false;
    Glib::ustring key = aKey + "=";
    StringList::const_iterator iter = m_OptionList.begin();
    StringList::const_iterator end = m_OptionList.end();
    
    for (; iter != end; iter++) {
        if (key.length() <= iter->length() &&
            key.compare(iter->substr(0, key.length())) == 0) {
            aValue = iter->substr(key.length());
            result = true;
            break;
        }
    }
    
    return result;
}


syntax highlighted by Code2HTML, v. 0.9.1