/* 
 * $Id: ImportTextCSV.cpp,v 1.21 2006/06/23 06:33:15 ozawa Exp $
 *
 * Copyright 2005- ONGS Inc. All rights reserved.
 * 
 * author: Masanori OZAWA (ozawa@ongs.co.jp)
 * version: $Revision: 1.21 $
 *
 * 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/ImportTextCSV.h"
#include "CSVAnalyzer.h"
#include "Utilities.h"
#include "URLTool.h"
#include "XMLTool.h"

using namespace XDTP;

#define COL_INFO_ALIGN_LEFT		0x00000000
#define COL_INFO_ALIGN_RIGHT		0x00000001
#define COL_INFO_ALIGN_CENTER		0x00000002
#define COL_INFO_ALIGN_RESERVED	0x00000003
#define COL_INFO_ALIGN_MASK		0x00000003

#define GET_COL_INFO(info, mask)	(info & mask)
#define SET_COL_INFO(info, val, mask)	(info = (info & ~mask) | (val & mask))

ImportTextCSV::ImportTextCSV()
{
}

ImportTextCSV::~ImportTextCSV()
{
}

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

    int count;
    XMLTool tool;
    xmlNodePtr root = xmlDocGetRootElement(aDocument);
    xmlNodeSetPtr nodes;
    xmlXPathObjectPtr obj = tool.getNodeList(root, "//import[@type=\"text/csv\"]");

    if (obj) {
        nodes = obj->nodesetval;
        
        for (count = 0; count < nodes->nodeNr; count++) {
            if (!transform(aDocument, nodes->nodeTab[count])) {
            	THROW("ImportTextCSV: transform failed.");
            }
        }
        
        xmlXPathFreeObject(obj);
    }
}

bool ImportTextCSV::transform(xmlDocPtr aDocument, xmlNodePtr aNode)
{
    Glib::ustring ref;
    Glib::ustring caption;
    Glib::ustring encoding;

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

bool ImportTextCSV::csvTo_nHTML
    (bool isHTML, xmlDocPtr aDocument, xmlNodePtr aNode,
     const Glib::ustring& aRef,
     const Glib::ustring& aCaption,
     const Glib::ustring& anEncoding)
{
    // スタイルシートを取得
    xmlNodePtr comment = getStyleSheet(aDocument, "text/css");

    if (comment) {
        // スタイルシートにイメージのスタイルの指定があるか確認
        Glib::ustring content = (comment->content ? (const char*)comment->content : "");
        Glib::ustring::size_type pos = content.find("table.csv");
        
        if (Glib::ustring::npos == pos) {
            // スタイルシートにイメージのスタイルを追加
            content += LINE_SEP;
            content += "   table.csv {" LINE_SEP;
            content += "     border-collapse: collapse;" LINE_SEP;
            content += "     margin: 1.5mm auto 3mm auto;" LINE_SEP;
            content += "     empty-cells: show;" LINE_SEP;
            content += "     caption-side: top;" LINE_SEP;
            content += "     width: 90%;" LINE_SEP;
            content += "   }" LINE_SEP;
            content += "   table.csv tr th," LINE_SEP;
            content += "   table.csv tr td {" LINE_SEP;
            content += "     border: 2px solid gray;" LINE_SEP;
            content += "   }" LINE_SEP;
            content += "   caption.csv {" LINE_SEP;
            content += "     margin-top: 3mm;" LINE_SEP;
            content += "   }" LINE_SEP;
            xmlNodeSetContent(comment, (const xmlChar*)content.c_str());
        }
    }
    
    // テーブルエレメント作成
    xmlNodePtr table = xmlNewNode(NULL, (const xmlChar*)"table");
    xmlSetProp(table, (const xmlChar*)"border", (const xmlChar*)"1");
    xmlSetProp(table, (const xmlChar*)"class", (const xmlChar*)"csv");

    // 表題作成
    xmlNodePtr caption = xmlNewNode(NULL, (const xmlChar*)"caption");
    xmlSetProp(caption, (const xmlChar*)"class", (const xmlChar*)"csv");
    if (!aCaption.empty()) {
        xmlAddChild(caption, xmlNewText((const xmlChar*)aCaption.c_str()));
    }
    xmlAddChild(table, caption);

    // csvの解析とテーブル構造の構築
    IntVector info;
    if (!createTable(table, info, aRef, anEncoding)) {
    	xmlFreeNode(table);
    	return false;
    }

	// align処理
	bool result = true;

	try {
		const int cols = info.size();
		int count, count2;
        char buf[64];
        Glib::ustring align;
	    XMLTool tool;
	    xmlXPathObjectPtr obj;
	    xmlNodeSetPtr nodes;

        for (count = 0; count < cols; count++) {
        	switch (GET_COL_INFO(info[count], COL_INFO_ALIGN_MASK)) {
        	case COL_INFO_ALIGN_RIGHT:
        		align = "right";
        		break;
        	case COL_INFO_ALIGN_CENTER:
        		align = "center";
        		break;
        	default:
        		continue;
        	}

			bzero(buf, sizeof(buf));
			snprintf(buf, sizeof(buf) -1, "tr/td[%d]", count +1);
			obj = tool.getNodeList(table, buf);
		    if (obj) {
		        nodes = obj->nodesetval;
		        for (count2 = 0; count2 < nodes->nodeNr; count2++) {
		            xmlSetProp(nodes->nodeTab[count2],
		            	(const xmlChar*)"align", (const xmlChar*)align.c_str());
		        }

		        xmlXPathFreeObject(obj);
		    }
        }
	}
    catch (std::exception exception) {
        fprintf(stderr, "%s: %s (%s)" LINE_SEP, APP_NAME, exception.what(), aRef.c_str());
        result = false;
    }
    catch (...) {
        fprintf(stderr, "%s: ImportTextCSV: unknown error occured. (%s)" LINE_SEP,
            APP_NAME, aRef.c_str());
        result = false;
    }

    if (result) {
	    // import タグと table を交換
	    xmlFreeNode(xmlReplaceNode(aNode, table));
    }
    else {
    	xmlFreeNode(table);
    }

    return result;
}

bool ImportTextCSV::createTable(xmlNodePtr aTable, IntVector& anInfo,
								 const Glib::ustring& aRef,
								 const Glib::ustring& anEncoding)
{
    bool result = true;
    int cols, count;
    std::string tmpfile;

    try {
        Glib::RefPtr<Glib::IOChannel> ioc = getIOChannel(aRef, tmpfile);
        setEncoding(ioc, anEncoding);
        
        Glib::ustring line;
        StringList list;
        StringList::iterator iter, end;
        CSVAnalyzer analyzer;
        xmlNodePtr tr, th, td;
        
        // 一行目は列題名として扱う
        if (ioc->read_line(line) == Glib::IO_STATUS_NORMAL) {
            line = Utilities::strTrimCRLF(line);
            if (!analyzer.analyzeLine(line, list)) {
                ioc->close();
                THROW("CSV data is broken. : " + aRef + " : " LINE_SEP  + line);
            }
            
            tr = xmlNewNode(NULL, (const xmlChar*)"tr");
            xmlAddChild(aTable, tr);

            end = list.end();
            for (iter = list.begin(); iter != end; iter++) {
                th = xmlNewNode(NULL, (const xmlChar*)"th");
                xmlAddChild(th, xmlNewText((const xmlChar*)(*iter).c_str()));
                xmlAddChild(tr, th);
            }
        }
        
        cols = list.size();
        anInfo.resize(cols, COL_INFO_ALIGN_RIGHT);

        // 二行目以降の処理
        while (ioc->read_line(line) == Glib::IO_STATUS_NORMAL) {
            line = Utilities::strTrimCRLF(line);

            if (!analyzer.analyzeLine(line, list)) {
                ioc->close();
                THROW("CSV data is broken. : " + aRef + " : " LINE_SEP + line);
            }

			if (cols < (int)list.size()) {
                ioc->close();
                THROW("CSV data is broken. (too long column) : " + aRef + " : " LINE_SEP  + line);
			}

            tr = xmlNewNode(NULL, (const xmlChar*)"tr");
            xmlAddChild(aTable, tr);

            end = list.end();
            for (iter = list.begin(), count = 0; iter != end; iter++, count++) {
                td = xmlNewNode(NULL, (const xmlChar*)"td");
                xmlAddChild(tr, td);

                // 内容物が数値でない場合は左寄せで出力するように処理
                if (!isNumber((*iter))) {
            		SET_COL_INFO(anInfo[count], COL_INFO_ALIGN_LEFT, COL_INFO_ALIGN_MASK);
                }

                // 内容物がハイパーリンクの対象である場合ハイパーリンク
                // 指定で出力するように処理
            	if (isHyperLink((*iter))) {
                    // ','や', 'で区切られている場合、複数のリンク
                    // や複数の文字列から成っているものとして処理
                    // する必要がある。よって, ここで文字列を ','
                    // によって分割し、分割したものそれぞれに対して
                    // 処理を行う。
                    StringList tmplist;
                    StringList::iterator tmpbegin, tmpiter, tmpend;
                    xmlNodePtr a;
                    if (analyzer.analyzeLine((*iter), tmplist)) {
                        tmpend = tmplist.end();
                        for (tmpbegin = tmpiter = tmplist.begin(); tmpiter != tmpend; tmpiter++) {
                            if (tmpiter != tmpbegin) {
                                xmlAddChild(td, xmlNewText((const xmlChar*)", "));
                            }
                            if (isHyperLink((*tmpiter))) {
                                a = xmlNewNode(NULL, (const xmlChar*)"a");
                                xmlAddChild(a, xmlNewText((const xmlChar*)(*tmpiter).c_str()));
                                xmlSetProp(a, (const xmlChar*)"href", (const xmlChar*)(*tmpiter).c_str());
                                xmlAddChild(td, a);
                            }
                            else {
                                xmlAddChild(td, xmlNewText((const xmlChar*)(*tmpiter).c_str()));
                            }
                        }
                        continue;
                    }
                }

                xmlAddChild(td, xmlNewText((const xmlChar*)(*iter).c_str()));
            }
        }

        ioc->close();
    }
    catch (Exception exception) {
        exception.print(stderr);
        result = false;
    }
    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());
        result = false;
    }
    catch (std::exception exception) {
        fprintf(stderr, "%s: %s (%s)" LINE_SEP, APP_NAME, exception.what(), aRef.c_str());
        result = false;
    }
    catch (...) {
        fprintf(stderr, "%s: ImportTextCSV: unknown error occured. (%s)" LINE_SEP,
            APP_NAME, aRef.c_str());
        result = false;
    }
    
    if (0 < tmpfile.length()) {
        remove(tmpfile.c_str());
    }
    
    return result;
}

bool ImportTextCSV::isNumber(const Glib::ustring& aString)
{
    Glib::ustring str = aString.lowercase();
    
    // 先頭の +/- チェック
    if (0 >= str.length()) return false;
    if ("+" == str.substr(0, 1) || "-" == str.substr(0, 1)) {
        str = str.substr(1);
    }
    
    // 数値チェック
    if (0 >= str.length()) return false;

    bool bDot = false;
    bool bOK = false;
    Glib::ustring::iterator iter = str.begin();
    Glib::ustring::iterator end = str.end();
    
    for (; iter != end; iter++) {
        if (('0' <= *iter && '9' >= *iter) ||
            (0xb0a3 <= *iter && 0xb9a3 >= *iter))
        {
            bOK = true;
        }
        else if ('.' == *iter || 0xa5a1 == *iter) {
            if (bDot) return false;
            bDot = true;
        }
        else if (',' == *iter || 0xa4a1 == *iter) {
            // カンマは無視。ただし、小数点以降のカンマは拒否。
            if (bDot) return false;
        }
        else if (0xc1a1 == *iter) {
            // 終端の'〜'は許可
            iter++;
            if (iter != end) return false;
            break;
        }
        else {
            return false;
        }
    }
    
    return bOK;
}

bool ImportTextCSV::isHyperLink(const Glib::ustring& aString)
{
    int len = aString.length();
    Glib::ustring str = aString.lowercase();
    
    if (6 < len && "ftp://" == str.substr(0, 6)) return true;
    else if (7 < len && "http://" == str.substr(0, 7)) return true;
    else if (8 < len && "https://" == str.substr(0, 8)) return true;
    
    return false;
}


syntax highlighted by Code2HTML, v. 0.9.1