/* 
 * $Id: XMLTool.cpp,v 1.3 2006/06/20 10:11:06 ozawa Exp $
 *
 * Copyright 2005- ONGS Inc. All rights reserved.
 * 
 * author: Masanori OZAWA (ozawa@ongs.co.jp)
 * version: $Revision: 1.3 $
 *
 * 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 "XMLTool.h"

#include "Utilities.h"

#include "libxml/xmlmemory.h"
#include "libxml/xmlIO.h"

using namespace XDTP;

XMLTool::XMLTool()
{
    static bool init = false;
    
    if (!init) {
        xmlInitParser();
        xmlSubstituteEntitiesDefault(1);
        xmlLoadExtDtdDefaultValue = 1;
        init = true;
    }
}

XMLTool::~XMLTool()
{
}

/**
 * 複数階層に跨るElementを一度に生成します。
 * 既存のElementが存在する場合は、存在しないElementのみを生成します。
 * 
 * @param aDocument
 *            生成対象のDOM オブジェクト
 * @param aXPath
 *            複数階層に跨るElementを識別するXPath。
 *            1つの階層に複数のElementを指定するようなことはできません。(書式: /e1/e2/e3/e4 )
 * @return 作成に成功したときは最下層の Element。失敗したときは null。
 */
xmlNodePtr XMLTool::createElements(xmlDocPtr aDocument, const Glib::ustring& aXPath)
{
    int count = 0;
    gchar **tokens = g_strsplit(aXPath.c_str(), "/", 0);
    xmlNodePtr node = NULL;
    xmlNodePtr parent = xmlDocGetRootElement(aDocument);
    
    if (!tokens) return NULL;
    
    if (!tokens[0] || !tokens[1]) {
        g_strfreev(tokens);
        return NULL;
    }
    
    if (xmlStrcmp(parent->name, (const xmlChar *)tokens[1])) {
        g_strfreev(tokens);
        return NULL;
    }
    
    node = parent->xmlChildrenNode;

    for (count = 2; tokens[count]; count++) {
        while (node && xmlStrcmp(node->name, (const xmlChar *)tokens[count])) {
            node = node->next;
        }
        
        if (node) {
            parent = node;
            node = parent->xmlChildrenNode;
        }
        else {
            // 新規に構築
            node = xmlNewDocNode(aDocument, NULL, (xmlChar*)tokens[count], NULL);
            xmlAddChild(parent, node);
            parent = node;
            node = NULL;
        }
    }
    
    if (tokens[count]) {
        parent = NULL;
    }

    g_strfreev(tokens);

    return parent;
}

/**
 * XMLファイルのエンコーディングを取得します。
 * 
 * @param aFile XMLファイル名
 * @return エンコーディング
 */
std::string XMLTool::getEncoding(const Glib::ustring& aFile)
{
    std::string result;
    
    // XML文章のエンコードを取得する
    try {
        Glib::ustring line;
        Glib::ustring::size_type pos;
        Glib::RefPtr<Glib::IOChannel> ioc =
            Glib::IOChannel::create_from_file(aFile.c_str(), "r");
        ioc->set_encoding("ASCII");

        while (ioc->read_line(line) == Glib::IO_STATUS_NORMAL) {
            if (!line.empty()) {
                line = Utilities::strTrim(line);
                line = line.lowercase();
                if ("<?" == line.substr(0, 2)) {
                    pos = line.find("encoding=\"");
                    
                    if (Glib::ustring::npos != pos) {
                        line = line.substr(pos +10);
                        pos = line.find("\"");
                        
                        if (Glib::ustring::npos != pos) {
                            result = line.substr(0, pos);
                        }
                    }
                }
                break;
            }
        }
        
        ioc->close();
    }
    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 (...) {
        fprintf(stderr, "%s: XMLTool: Unable to read the XML file. (%s)" LINE_SEP,
            APP_NAME, aFile.c_str());
    }
    
    return result;
}

/**
 * Nodeからの相対XPath指定で得られるNodeの一覧を取得します。
 * 
 * @param aNode
 *            探索元のNode
 * @param aXPath
 *            Nodeからの相対XPath
 * @return 取得したNodeの一覧を保持するオブジェクト。
 *         不要になった段階で、xmlXPathFreeObjectする必要があります。
 */
xmlXPathObjectPtr XMLTool::getNodeList(xmlNodePtr aNode, const Glib::ustring& aXPath)
{
    if (!aNode) return NULL;
    
    xmlXPathContextPtr xpathCtx = xmlXPathNewContext(aNode->doc);
    xmlXPathObjectPtr xpathObj;

    if (!xpathCtx) return NULL;
    xpathCtx->node = aNode; // set the current node
    
    xpathObj = xmlXPathEvalExpression((const xmlChar*)aXPath.c_str(), xpathCtx);
    xmlXPathFreeContext(xpathCtx);
    
    if (!xpathObj) {
        return NULL;
    }
    
    if (xmlXPathNodeSetIsEmpty(xpathObj->nodesetval)) {
        xmlXPathFreeObject(xpathObj);
        return NULL;
    }
    
    return xpathObj;
}

/**
 * Nodeの直下に存在するTextを取得します。
 * 
 * @param aNode
 *            Textを含んだNode
 * @return 取得したTextの文字列。Textが存在しない場合は空文字列。
 */
Glib::ustring XMLTool::getText(xmlNodePtr aNode)
{
    Glib::ustring result;
    
    xmlChar *text = xmlNodeListGetString(aNode->doc, aNode->xmlChildrenNode, 1);
    if (text) {
        result = (char*)text;
        xmlFree(text);
    }
    
    return result;
}

/**
 * NodeがAttributeかチェックします。
 * 
 * @param aNode
 *            チェック対象のNode
 * @return Attributeの場合は true
 */
bool XMLTool::isAttribute(xmlNodePtr aNode)
{
    return (XML_ATTRIBUTE_NODE == aNode->type);
}

/**
 * NodeがCommentかチェックします。
 * 
 * @param aNode
 *            チェック対象のNode
 * @return Commentの場合は true
 */
bool XMLTool::isComment(xmlNodePtr aNode)
{
    return (XML_COMMENT_NODE == aNode->type);
}

/**
 * NodeがElementかチェックします。
 * 
 * @param aNode
 *            チェック対象のNode
 * @return Elementの場合は true
 */
bool XMLTool::isElement(xmlNodePtr aNode)
{
    return (XML_ELEMENT_NODE == aNode->type);
}

/**
 * NodeがTextかチェックします。
 * 
 * @param aNode
 *            チェック対象のNode
 * @return Textの場合は true
 */
bool XMLTool::isText(xmlNodePtr aNode)
{
    return (XML_TEXT_NODE == aNode->type);
}

/**
 * Nodeの直下にTextを設定します。
 * 
 * @param aNode
 *            Textを設定するNode
 * @param aText
 *            設定するText
 * @return 設定に成功した場合は true
 */
bool XMLTool::setText(xmlNodePtr aNode, const Glib::ustring& aText)
{
    bool result = false;
    xmlNodePtr node = aNode->children;

    while (node) {
        if (isText(node)) {
            xmlNodeSetContent(node, (const xmlChar *)aText.c_str());
            result = true;
            break;
        }
        node = node->next;
    }

    if (!result) {
        // Textノードを新規に構築
        node = xmlNewDocText(aNode->doc, (xmlChar*)aText.c_str());
        result = (xmlAddChild(aNode, node) ? true : false);
    }

    return result;
}

/**
 * DOMドキュメントを、XSLTを用いて変換します。
 * 
 * @param aDocument
 *            変換元のDOMドキュメント
 * @param aXSLFile
 *            XSLファイル
 * @return 変換後のDOMドキュメント。不要になった段階でxmlFreeDocする必要があります。
 */
xmlDocPtr XMLTool::transform(xmlDocPtr aDocument, const Glib::ustring& aXSLFile)
{
    xsltStylesheetPtr xsl;
    xmlDocPtr result;

    xsl = xsltParseStylesheetFile((const xmlChar *)aXSLFile.c_str());
    result = transform(aDocument, xsl);

    xsltFreeStylesheet(xsl);
    
    return result;
}

/**
 * DOMドキュメントを、XSLTを用いて変換します。
 * 
 * @param aDocument
 *            変換元のDOMドキュメント
 * @param aXSL
 *            XSLオブジェクト
 * @return 変換後のDOMドキュメント。不要になった段階でxmlFreeDocする必要があります。
 */
xmlDocPtr XMLTool::transform(xmlDocPtr aDocument, xsltStylesheetPtr aXSL)
{
    return xsltApplyStylesheet(aXSL, aDocument, NULL);
}


syntax highlighted by Code2HTML, v. 0.9.1