/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CXmlNode.cpp  -  description
                             -------------------
    begin                : Sun May 22 2003
    based on the code    : Mikko Lahteenmaki <Laza@Flashmail.com>
    copyright            : (C) 2003-2005 by Alexey S.Parshin
    email                : alexeyp@gmail.com
 ***************************************************************************/

/***************************************************************************
   This library is free software; you can redistribute it and/or modify it 
   under the terms of the GNU Library General Public License as published by
   the Free Software Foundation; either version 2 of the License, or (at 
   your option) any later version.

   This library is distributed in the hope that it will be useful, but 
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library
   General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; if not, write to the Free Software Foundation, 
   Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

   Please report all bugs and problems to "alexeyp@gmail.com"
 ***************************************************************************/

#include <sptk3/xml/CXmlNode.h>
#include <sptk3/xml/CXmlParser.h>

CXmlAttribute& CXmlAttribute::operator = (int v) { 
   char buff[64]; 
   sprintf(buff,"%i",v); 
   assign(buff); 
   return *this; 
}

CXmlAttribute& CXmlAttribute::operator = (unsigned v)  { 
   char buff[64]; 
   sprintf(buff,"%u",v); 
   assign(buff); 
   return *this; 
}

CXmlAttribute& CXmlAttribute::operator = (double v) { 
   char buff[64]; 
   sprintf(buff,"%f",v); 
   assign(buff); 
   return *this; 
}

CXmlAttributes& CXmlAttributes::operator = (const CXmlAttributes& s)
{
   CXmlAttributes::const_iterator it = s.begin();
   for(; it != s.end(); it++) {
      (*this)[it->first] = it->second;
   }
   return *this;
}

CXmlAttribute CXmlAttributes::getAttribute(std::string attr,const char *defaultValue) const { 
   const_iterator itor = find(attr.c_str());
   if (itor != end()) return itor->second;
   CXmlAttribute rc;
   if (defaultValue)  rc = defaultValue;
   return rc;  
}

void CXmlAttributes::setAttribute(std::string attr,CXmlAttribute value,const char *defaultValue) { 
   iterator itor = find(attr.c_str());
   if (defaultValue && value == defaultValue) {
      if (itor != end())
         erase(itor);
      return;
   }
   if (itor != end())
      itor->second = value;
   else
      (*this)[attr] = value;
}

void CXmlNodeList::free_item(CXmlNode *item) {
   delete item;
}

CXmlNode::CXmlNode(CXmlDoc *doc) : m_document(doc)
{
   m_nodetype = 0;    
   m_parent = 0;
}

CXmlNode::~CXmlNode()
{
   if(parent()) parent()->remove(this);
   clear();
}

void CXmlNode::clear()
{
   for(unsigned n=0; n<children(); n++) {
      CXmlNode *np = child(n);
      np->parent(0);
      delete np;
   }
   m_child_nodes.clear();
}

void CXmlNode::text(std::string &ret)
{
   if(isText() || isCDataSection())
      ret += value();

   for(unsigned n=0; n<children(); n++) {
      CXmlNode *np = child(n);
      np->text(ret);
   }    
}

CXmlNode *CXmlNode::cloneNode(bool deep)
{
   CXmlNode *node = new CXmlNode(m_name, m_nodetype, m_document);
   node->m_attributes   = m_attributes;

   if(deep) {
      for(unsigned n=0; n<children(); n++) {
         CXmlNode *np = child(n);
         CXmlNode *cloned = np->cloneNode(true);
         node->add(cloned);
      }
   }

   return node;
}

// currently no path-like childname can be passed
CXmlNode *CXmlNode::child(const char *name, bool recursive) const
{
    // search for first occurance of node
   for(unsigned n=0; n < children(); n++) {
      CXmlNode *np = child(n);
      if(np->name() == name) {
         return np;
      }
      if(recursive) {
         CXmlNode *sub_ch = np->child(name, recursive);
         if(sub_ch) return sub_ch;
      }
   };

    // no valid child found
   return 0;
}

// currently no path-like childname can be passed
CXmlNodeList CXmlNode::nodes(const char *name)
{
   CXmlNodeList nlist;

    // search for all occurances of nodename and insert them into the new list
   for(unsigned n=0; n<children(); n++) {
      CXmlNode *np = child(n);
      if(np->name() == name)
         nlist.append(np);
   }

   return nlist;
}

// Adds the node 'new_child' to the end of the list 
// of children of this node. If the 'new_child' is 
// already in the tree, it is first removed. 
CXmlNode *CXmlNode::add(CXmlNode *new_child)
{
   // Remove, if already in list
   if(m_child_nodes.indexOf(new_child)>-1)
      m_child_nodes.remove(new_child); 

   // Append to last
   m_child_nodes.append(new_child);
   new_child->parent(this);

   return new_child;
}

// Removes the child node indicated by 'old_child' 
// from the list of children, and returns it. 
CXmlNode *CXmlNode::remove(CXmlNode *old_child)
{
   if(!old_child || childNodes().indexOf(old_child)==-1) {
      return 0;
   }

   m_child_nodes.remove(old_child);
   return old_child;
}

// Replaces the child node 'old_child' with 'new_child'
// in the list of children, and returns the 'old_child' node. 
// If the 'new_child' is already in the tree, it is first removed. 
CXmlNode *CXmlNode::replace(CXmlNode *new_child, CXmlNode *old_child)
{
   int index = childNodes().indexOf(old_child);
   if(index==-1) return 0;

   remove(new_child);

   m_child_nodes.replace(index, new_child);
   new_child->parent(this);

   return old_child;
}

// Inserts the node newChild before the existing child node refChild.
// If refChild is null, insert newChild at the end of the list of children.
// --
// If newChild is a DocumentFragment object, all of its children are inserted, 
// in the same order, before refChild. If the newChild is already 
// in the tree, it is first removed. Returns the node being inserted. 
CXmlNode *CXmlNode::insertBefore(CXmlNode *new_child, CXmlNode *ref_child)
{
   int index = ref_child ? childNodes().indexOf(ref_child) : childNodes().size()-1;
   if(index==-1) return 0;

   remove(new_child);

   m_child_nodes.insert(index, new_child);
   new_child->parent(this);

   return new_child;
}

//#define WRITE_INDENT() if(indent > 0) { tmp = CString(' ', indent); buffer.append(tmp); }
//#define WRITE(text) tmp=(text); buffer.append(tmp)

void CXmlNode::save(CBuffer &buffer, int indent) const {
   std::string tmp;

   // output indendation spaces
   if (indent > 0)
      buffer.append(std::string(indent,' '));

   if (isElement()) {
        // Output tag name
      buffer.append("<" + name());
        // Output attributes
      CXmlAttributes::const_iterator it = attributes().begin();
      for (; it != attributes().end(); it++) {
         std::string real_id, real_val;
         if (!document()->docType().encodeEntities(it->first.c_str(), real_id))
            real_id = it->first;
         if (!document()->docType().encodeEntities(it->second.c_str(), real_val))
            real_val = it->second;

         buffer.append(" " + real_id + "=\"" + real_val + "\"");
      }
   }

   // depending on the nodetype, do output
   switch(m_nodetype)
   {
      case DOM_PI: {
            buffer.append("<?" + name() + " " + value() + "?>\n");
         }
         break;

      case DOM_TEXT: {
            std::string text;
            if(!document()->docType().encodeEntities(value().c_str(), text)) {
               buffer.append(value());
            } else {
               buffer.append(text);
            }
         }
         break;

      case DOM_CDATA_SECTION:
         // output all subnodes
         buffer.append("<![CDATA[" + value() + "]]>\n");
         break;

      case DOM_COMMENT:
         // output all subnodes
         buffer.append("<!-- " + value() + " -->\n");
         break;

      case DOM_ELEMENT: {

            if (m_child_nodes.size()) {

               bool only_cdata;
               if (children()==1 && child(0)->isText()) {
                  only_cdata = true;
                  buffer.append(">",1);
               } else {
                  only_cdata = false;
                  buffer.append(">\n",2);
               }

               // output all subnodes
               for(unsigned n=0; n<children(); n++) {
                  CXmlNode *np = child(n);
                  if (only_cdata)
                     np->save(buffer, -1);
                  else {
                     np->save(buffer, indent + CXmlDoc::indentSpaces());
                     if(buffer.data()[buffer.bytes()-1] != '\n')
                        buffer.append('\n');
                  }
               }
               // output indendation spaces
               if (!only_cdata && indent > 0)
                  buffer.append(std::string(indent,' '));

               // output closing tag
               buffer.append("</" + name() + ">\n");

            } else {
               //LEAF
               buffer.append("/>\n",3);
            }
         }
         break;

      default:
        // unknown nodetype
         break;
   }
}
