/***************************************************************************
                          SIMPLY POWERFUL TOOLKIT (SPTK)
                          CXmlNode.h  -  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"
 ***************************************************************************/

#ifndef __CXMLNODE_H__
#define __CXMLNODE_H__

#include <sptk3/CBuffer.h>
#include <sptk3/CDateTime.h>
#include <sptk3/xml/CXmlNodeList.h>

#include <string>
#include <map>

class CXmlDoc;
class CXmlNode;

/// @addtogroup XML
/// @{

/// A workaround for MS VC++ bug
typedef std::string xstring;

/// @brief XML node attribute
///
/// A single node attribute that contains a name and a value
class SP_EXPORT CXmlAttribute : public xstring {

public:
   /// Default constructor
   /// Creates an empty XML attribute
   CXmlAttribute() {}

   /// Constructor
   /// Creates an XML attribute from character string
   CXmlAttribute(const char *v) : xstring(v) {}

   /// Constructor
   /// Creates an XML attribute from std::string
   CXmlAttribute(std::string v) : xstring(v) {}

   /// Constructor
   /// Creates an XML attribute from integer
   CXmlAttribute(int v)         { operator = (v); }

   /// Constructor
   /// Creates an XML attribute from double
   CXmlAttribute(double v)      { operator = (v); }

   /// Constructor
   /// Creates an XML attribute from CDateTime
   CXmlAttribute(CDateTime v)   : xstring(v) {}

   /// Assignment of the value
   /// @param v int, a new value
   CXmlAttribute& operator = (int v);

   /// Assignment of the value
   /// @param v unsigned, a new value
   CXmlAttribute& operator = (unsigned v);

   /// Assignment of the value
   /// @param v double, a new value
   CXmlAttribute& operator = (double v);

   /// Assignment of the value
   /// @param s const std::string, a new value
   CXmlAttribute& operator = (const std::string& s) { assign(s); return *this; }

   /// Assignment of the value
   /// @param s const char *, a new value
   CXmlAttribute& operator = (const char *s)        { assign(s); return *this; }

   /// Assignment of the value
   /// @param s CDateTime, a new value
   CXmlAttribute& operator = (CDateTime s)          { assign(s); return *this; }

   /// Returns the value with the conversion
   operator unsigned () const                       { return atoi(c_str()); }

   /// Returns the value with the conversion
   operator int () const                            { return atoi(c_str()); }

   /// Returns the value with the conversion
   operator double () const                         { return atof(c_str()); }
};

/// @brief XML node attributes
///
/// The CXmlAttributes class is map for node attributes.
class SP_EXPORT CXmlAttributes : public std::map<std::string,CXmlAttribute> {
public:

   /// Constructs empty attribute map
   CXmlAttributes() { }

   /// Assign operator. Makes deep copy of map to another.
   /// @param src as copy source
   CXmlAttributes& operator = (const CXmlAttributes& src);

   /// Searches for named attribute.
   /// Returns true, if given attribute is found.
   /// @param attr std::string, name of attribute to search
   bool hasAttribute(std::string attr) const { return find(attr) != end(); }

   /// Returns attribute value for given attribute.
   /// If not found, empty string is returned.
   /// HTML tags can have empty attributes, for those you should use has_attribute() method.
   /// @param attr std::string, name of attribute
   /// @param defaultValue const char *, a default value. If attribute doesn't exist then default value is returned.
   CXmlAttribute getAttribute(std::string attr,const char *defaultValue=0L) const;

   /// Sets attribute value for given attribute.
   /// If not found, empty string is returned.
   /// HTML tags can have empty attributes, for those you should use has_attribute() method.
   /// @param attr std::string, name of attribute
   /// @param value CXmlAttribute, an attribute value. See CXmlAttribute class description for data convertions.
   /// @param defaultValue const char *, a default value. If attribute value is matching default value than attribute isn't stored (or removed if it existed).
   void setAttribute(std::string attr,CXmlAttribute value,const char *defaultValue=0L);
};

/// @brief XML node
///
/// XML node with the attributes and possible child XML nodes
class SP_EXPORT CXmlNode {    
public:
   /// Node type enumeration
   enum NodeTypes {     
      DOM_DOCUMENT = 1,    ///< Document node 
      DOM_ELEMENT,         ///< Normal element node, can contain subnodes 
      DOM_PI,              ///< Processing Instruction node 
      DOM_TEXT,            ///< Cdata where all default entities MUST be escaped. 
      DOM_CDATA_SECTION,   ///< Cdata section, which can contain preformatted char data. 
      DOM_COMMENT          ///< Comment node 
   };

   /// Destructor
   virtual ~CXmlNode();

   /// Deletes all children.
   virtual void clear();

   /// Returns parent node of this node.
   /// For CXmlDocument this returns NULL.
   CXmlNode *parent() const { return m_parent; }

   /// Returns document context associated with this node.
   CXmlDoc *document() const { return m_document; }

   /// Returns type of node.
   int type() const { return m_nodetype; }

   /// Returns true node is document type 
   bool isDocument()      const { return (type()==DOM_DOCUMENT);  }

   /// Returns true node is element type
   bool isElement()    const { return (type()==DOM_ELEMENT); }

   /// Returns true node is element type, but has no children */
   bool isLeaf()       const { return (type()==DOM_ELEMENT) && m_child_nodes.size() == 0; }

   /// Returns true node is processing instruction (PI) type
   bool isPI()         const { return (type()==DOM_PI); }

   /// Returns true node is comment type
   bool isComment()    const { return (type()==DOM_COMMENT); }

   /// Returns true node is text type
   bool isText()       const { return (type()==DOM_TEXT); }

   /// Returns true node is cdata-section type
   bool isCDataSection() const { return (type()==DOM_CDATA_SECTION); }

   /** 
    * Returns the node name.
     * The meaning of the value depends on the node type: 
    <table border=0 bgcolor=#e8e8e8>
     <tr><td>DOM_DOCUMENT</td>      <td>#document</td>
     <tr><td>DOM_ELEMENT</td>    <td>The tag name</td>
     <tr><td>DOM_PI</td>         <td>The target of the processing instruction</td>
     <tr><td>DOM_COMMENT</td>    <td>#comment</td>
     <tr><td>DOM_TEXT</td>       <td>#text</td>
     <tr><td>DOM_CDATA_SECTION</td><td>#cdata-section</td>
    </table>
   */
   const std::string &name() const { return m_name; }

   /// Sets the new name for the node
   /// @param name const std::string &, new node name
   void name(const std::string &name) { m_name = name; }

   /// Sets new name for node
   /// @param name const char *, new node name
   void name(const char *name) { m_name = name; }

   /**
    * Returns the value of the node.
    * The meaning of the value depends on the node type:
    <table border=0 bgcolor=#e8e8e8>
     <tr><td>DOM_DOCUMENT</td>      <td>none</td>
     <tr><td>DOM_ELEMENT</td>    <td>none</td>
     <tr><td>DOM_PI</td>         <td>The data of the processing intruction</td>
     <tr><td>DOM_COMMENT</td>    <td>The comment</td>
     <tr><td>DOM_TEXT</td>       <td>The text</td>
     <tr><td>DOM_CDATA_SECTION</td><td>The data of the cdata-section</td>
    </table>
    */
   std::string &value() { return m_value; }

   /// Returns the value of the node.
   /// The meaning of the value depends on the node type:
   const std::string &value() const { return m_value; }

   /// Sets new value to node.
   /// @param new_value const std::string &, new value
   /// @see value()
   void value(const std::string &new_value) { m_value = new_value; }

   /// Sets new value to node.
   /// @param new_value const char *, value to set
   /// @see value()
   void value(const char *new_value) { m_value = new_value; }

   /// Returns cdatas combined from children.
   /// e.g. "Some <tag>text</tag> here" becomes: "Some text here"
   /// @param ret std::string &, string is stored here
   void text(std::string &ret);

   /// Returns the first occurence of child with the given name.
   /// Recursive is true, it will search throught children.
   /// @param name const char *, name to be searched
   /// @param recursive bool, recursive search mode
   CXmlNode *child(const char *name, bool recursive=true) const;

   /// Returns list of all subnodes with give name.
   /// @param name to add list
   CXmlNodeList nodes(const char *name);

   /// Creates a deepcopy of the node. 
   /// If deep is true, then the cloning is done recursively which means that all the 
   /// node's children are deep copied too. If deep is false only the node itself is copied and the copy will have no child nodes. 
   CXmlNode *cloneNode(bool deep=false);

   /// Removes the child node indicated by 'old_child' 
   /// from the list of children, and returns it. 
   /// @param child CXmlNode *, child node to remove
   CXmlNode *remove(CXmlNode *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. 
   /// @param newChild CXmlNode *, new child node
   /// @param oldChild CXmlNode *, old child node
   CXmlNode *replace(CXmlNode *newChild, CXmlNode *oldChild);

   /// Adds the node 'newChild' to the end of the list 
   /// of children of this node. If the 'new_child' is 
   /// already in the tree, it is first removed. 
   /// @param newChild CXmlNode *, new child node
   CXmlNode *add(CXmlNode *newChild);

   /// Inserts the node 'new_child' before the existing child node 'ref_child'.
   /// If 'ref_child' is null, insert 'new_child' at the end of the list of children.
   /// If the 'new_child' is already in the tree, it is first removed. 
   /// Returns the node being inserted. 
   CXmlNode *insertBefore(CXmlNode *new_child, CXmlNode *ref_child);                                         

   /// Returns list of child nodes.
   const CXmlNodeList &childNodes() const { return m_child_nodes; }

   /// Returns number of child nodes
   unsigned children() const { return m_child_nodes.size(); }

   /// Return child for given index, NULL if out of range.
   CXmlNode *child(int index) const { return m_child_nodes.item(index); }

   /// Returns reference to attribute map.
   /// @see CXmlAttributes
   CXmlAttributes &attributes() { return m_attributes; }

   /// Returns const reference to attribute map.
   /// @see CXmlAttributes
   const CXmlAttributes &attributes() const { return m_attributes; }

   /// Returns true, if node has any attributes
   bool hasAttributes() const { return (m_attributes.size()>0); }

   /// Returns true, if given attribute is found
   /// @param attr const char *, attribute to search
   bool hasAttribute(const char *attr) const { return m_attributes.hasAttribute(attr); }

   /// Returns attribute value for given attribute. 
   /// HTML tags can have empty attributes, for those you should use has_attribute() method.
   /// @param attr name of attribute
   /// @param defaultValue const char *, a default value. If attribute doesn't exist then default value is returned.
   CXmlAttribute getAttribute(std::string attr,const char *defaultValue=0L) const { return m_attributes.getAttribute(attr,defaultValue); }

   /// Sets new value to attribute 'attr'.
   /// If attribute is not found, it's added to map.
   /// @param attr attribute name
   /// @param value attribute value
   /// @param defaultValue const char *, a default value. If attribute value is matching default value than attribute isn't stored (or removed if it existed).
   void setAttribute(const char *attr, CXmlAttribute value,const char *defaultValue=0L) { m_attributes.setAttribute(attr,value,defaultValue); }

   /// Saves node to buffer.
   /// @param buffer to save
   /// @param indent how many indent spaces at start
   virtual void save(CBuffer &buffer, int indent=0) const;

protected:

   std::string    m_name;               ///< Node name
   std::string    m_value;              ///< Node value
   CXmlAttributes m_attributes;         ///< Node attributes
   unsigned char  m_nodetype;           ///< Node type
   CXmlDoc       *m_document;           ///< Document pointer
   CXmlNode      *m_parent;             ///< Parent node pointer
   CXmlNodeList   m_child_nodes;        ///< List of subnodes

   /// Constructor - for derived classes
   CXmlNode(const std::string &name, int type, CXmlDoc *doc) { 
      m_name = name; 
      m_nodetype = (unsigned char)type; 
      m_document = doc; 
      m_parent = 0; 
   }

private:
   friend class CXmlParser;
   friend class CXmlDoc;

   /// Sets parent node - for CXmlParser
   void parent(CXmlNode *p) { m_parent = p; }

   /// Sets node type
   void type(int type) { m_nodetype = (unsigned char)type; }

   /// Sets parent document
   CXmlNode(CXmlDoc *doc); 
};

/// XML Element 
class CXmlElement : public CXmlNode {
public:	
   /// Constructor
   /// @param parent CXmlNode &, a parent node.
   /// @param tagname const char *, a name of XML tag
   CXmlElement(CXmlNode& parent,const char *tagname) 
   : CXmlNode(tagname, DOM_ELEMENT, parent.document()) {
      parent.add(this);
   }
};

/// XML Text 
class CXmlText : public CXmlNode {
public:	
   /// Constructor
   /// @param parent CXmlNode &, a parent node.
   /// @param data const char *, a text 
   CXmlText(CXmlNode& parent,const char *data) 
   : CXmlNode("#text", DOM_TEXT, parent.document()) {
      value(data);
      parent.add(this);
   }

   /// Constructor
   /// @param parent CXmlNode &, a parent node.
   /// @param data const std::string&, a text 
   CXmlText(CXmlNode& parent,const std::string& data) 
   : CXmlNode("#text", DOM_TEXT, parent.document()) {
      value(data.c_str());
      parent.add(this);
   }
};

/// XML comment
class CXmlComment : public CXmlNode {
public:	
   /// Constructor
   /// @param parent CXmlNode &, a parent node. 
   /// @param data const char *, a comment 
   CXmlComment(CXmlNode& parent,const char *data) 
   : CXmlNode("#comment", DOM_COMMENT, parent.document()) {
      value(data);
      parent.add(this);
   }
};

/// XML CData section
class CXmlCDataSection : public CXmlNode {
public:	
   /// Constructor
   /// @param parent CXmlNode &, a parent node.
   /// @param data const char *, a data 
   CXmlCDataSection(CXmlNode& parent,const char *data) 
   : CXmlNode("#cdata-section", DOM_CDATA_SECTION, parent.document()) {
      value(data);
      parent.add(this);
   }

   /// Constructor
   /// @param parent CXmlNode &, a parent node. 
   /// @param data const std::string&, a data 
   CXmlCDataSection(CXmlNode& parent,const std::string& data) 
   : CXmlNode("#cdata-section", DOM_CDATA_SECTION, parent.document()) {
      value(data.c_str());
      parent.add(this);
   }
};

/// XML processing instructions (PI)
class CXmlPI : public CXmlNode {
public:	
   /// Constructor
   /// @param parent CXmlNode &, a parent node. Make sure it's a pointer to the existing node.
   /// @param target std::string, a target tag name 
   /// @param data const char *, a data 
   CXmlPI(CXmlNode& parent,std::string target,const char *data) 
   : CXmlNode(target.c_str(), DOM_PI, parent.document()) {
      value(data);
      parent.add(this);
   }

   /// Constructor
   /// @param parent CXmlNode &, a parent node
   /// @param target std::string, a target tag name 
   /// @param data const std::string&, a data 
   CXmlPI(CXmlNode& parent,std::string target,const std::string& data) 
   : CXmlNode(target.c_str(), DOM_PI, parent.document()) {
      value(data.c_str());
      parent.add(this);
   }
};

/// @}

#define XML_TYPE_DOC             CXmlNode::DOM_DOCUMENT
#define XML_TYPE_ELEMENT         CXmlNode::DOM_ELEMENT
#define XML_TYPE_PI              CXmlNode::DOM_PI
#define XML_TYPE_TEXT            CXmlNode::DOM_TEXT
#define XML_TYPE_CDATA_SECTION   CXmlNode::DOM_CDATA_SECTION
#define XML_TYPE_COMMENT         CXmlNode::DOM_COMMENT

#endif
