//______________________________________________________________________________
//   |       |
//   | knipp |           Knipp Medien und Kommunikation GmbH
//    -------
//
//
//     Knipp RRI Toolkit
//
//
//     Copyright (C) 2005 by Knipp Medien und Kommunikation GmbH
//
//     This library is free software; you can redistribute it and/or
//     modify it under the terms of the GNU Lesser General Public License 
//     as published by the Free Software Foundation; either version 2.1 
//     of the License, or 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 Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser 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 
//
//
//     initial author:    Klaus Malorny
//
//     version:           1.4 (15127), 2011/06/03 14:52:49
//                        $Revision: 14936 $ 
//                        $Date: 2010-11-05 15:57:40 +0100 (Fri, 05 Nov 2010) $
//                        $Author: Klaus.Malorny $
//

package de.knipp.rritoolkit;

import javax.xml.namespace.QName;

import org.w3c.dom.Node;
import org.w3c.dom.Element;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DOMException;



/**
 *  helper class to simplify building DOM trees
 *
 *  @version  $Revision: 14936 $
 *  @author   Klaus Malorny
 */

public class XmlBuilder
{
  /** the base node */
  
  private Node baseNode;
  
  /** the current node, either the document node, document fragment node
      or an element */
  
  private Node curNode;

  
  /** the default namespace URI */
  
  private String defaultURI;
  
  
  /** the default namespace prefix */
  
  private String defaultPrefix;
  
  
  
  
  /**
   *  constructor. Creates a document fragment for the given document,
   *  which can be retrieved via {@code getBaseNode}.
   *
   *  @param doc                 the document
   *  @throws DOMException       if the creation of the fragment failed
   */
  
  public XmlBuilder (Document doc)
    throws DOMException
  {
    baseNode = doc.createDocumentFragment ();
    curNode = baseNode;
  }
  
  
  /**
   *  constructor. The given node is used as the starting point.
   *
   *  @param node                  the node
   */
  
  public XmlBuilder (Node node)
  {
    baseNode = node;
    curNode = node;
  }
  
  
  
  /**
   *  constructor. The given fragment is used as the starting point.
   *
   *  @param frag                the fragment
   */
  
  public XmlBuilder (DocumentFragment frag)
  {
    baseNode = frag;
    curNode = frag;
  }
  
  
  
  
  /**
   *  return the current node to which attributes, elements and texts are added
   *  to.
   *
   *  @return                      the node
   */
  
  public Node getCurrent ()
  {
    return curNode;
  }
  
  
  
  
  /**
   *  return the base node
   *
   *  @return                      the node
   */
  
  public Node getBase ()
  {
    return baseNode;
  }
  
  
  
  /**
   *  set the default namespace used for subsequent calls of the "simple"
   *  element creation methods. It is not used for attributes, though. This
   *  "default" namespace may not be mistaken for the XML default namespace.
   *
   *  @param uri                   the URI; if {@code null}, no namespace
   *                               is assigned to the subsequent elements
   *  @param prefix                the prefix, may be {@code null}
   *  @return                      this object, allowing call chaining
   */
  
  public XmlBuilder ns (String uri, String prefix)
  {
    defaultURI = uri;
    defaultPrefix = prefix;
    return this;
  }
  
  
  /**
   *  set the default namespace used for subsequent calls of the "simple"
   *  element creation methods. It is not used for attributes, though. This
   *  "default" namespace may not be mistaken for the XML default namespace.
   *
   *  @param uri                   the URI; if {@code null}, no namespace
   *                               is assigned to the subsequent elements
   *  @return                      this object, allowing call chaining
   */
  
  public XmlBuilder ns (String uri)
  {
    defaultURI = uri;
    defaultPrefix = null;
    return this;
  }
  
  
  
  /**
   *  clear the default namespace used for subsequent calls of the "simple"
   *  element creation methods. This "default" namespace may not be mistaken 
   *  for the XML default namespace.
   *
   *  @return                      this object, allowing call chaining
   */
  
  public XmlBuilder clearNS ()
  {
    defaultURI = null;
    defaultPrefix = null;
    return this;
  }
  
  
  
  /**
   *  create an element. If a default namespace has been defined, 
   *  it is used for the creation of the element, otherwise the element
   *  will neither have a namespace nor a prefix. The element is appended
   *  to the current node and becomes the new current node. The mnemonic "es"
   *  stands for "element start".
   *
   *  @param localName             the local part of the element name
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
 
  public XmlBuilder es (String localName)
    throws DOMException
  {
    Element el;
    
    if (defaultURI != null)
    {
      el = curNode.getOwnerDocument ().createElementNS (defaultURI, 
        defaultPrefix != null && defaultPrefix.length () > 0 ? 
        defaultPrefix + ":" + localName : localName);
    
    } else
    {
      el = curNode.getOwnerDocument ().createElement (localName);
    }
    
    curNode.appendChild (el);
    curNode = el;
    
    return this;
  }

  
  /**
   *  create an element. The element is appended to the current node and 
   *  becomes the new current node. The default name space is left unchanged.
   *  The mnemonic "es" stands for "element start".
   *
   *  @param uri                   the name space URI
   *  @param qName                 the qualified name, i.e. a name with or
   *                               without a prefix
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder es (String uri, String qName)
    throws DOMException
  {
    Element el = curNode.getOwnerDocument ().createElementNS (uri, qName);
    curNode.appendChild (el);
    curNode = el;
    
    return this;
  }
  

  /**
   *  create an element. The element is appended to the current node and 
   *  becomes the new current node. The default name space is left unchanged.
   *  The mnemonic "es" stands for "element start".
   *
   *  @param uri                   the name space URI
   *  @param prefix                the prefix, may be {@code null} or empty
   *  @param localName             the local element name
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder es (String uri, String prefix, String localName)
    throws DOMException
  {
    String qName = prefix != null && prefix.length () > 0 ?
      prefix + ":" + localName : localName;
    
    Element el = curNode.getOwnerDocument ().createElementNS (uri, qName);
    curNode.appendChild (el);
    curNode = el;
    
    return this;
  }
  
  
  /**
   *  create an element. The element is appended to the current node and 
   *  becomes the new current node. The default name space is left unchanged.
   *  The mnemonic "es" stands for "element start".
   *
   *  @param qName                 the qualified name (as an object)
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder es (QName qName)
    throws DOMException
  {
    Element el = curNode.getOwnerDocument ().createElementNS (
      qName.getNamespaceURI (), qName.getLocalPart ());
    
    el.setPrefix (qName.getPrefix ());
    curNode.appendChild (el);
    curNode = el;
    
    return this;
  }
  
  
  /**
   *  finish an element. The parent of the current element becomes the new
   *  current node. The mnemonic "ee" stands for "element end".
   *
   *  @return                      this object, allowing call chaining
   */
  
  public XmlBuilder ee ()
  {
    curNode = curNode.getParentNode ();
    return this;
  }
  
  
  
  /**
   *  add an attribute to the current node, if it is an element node. The
   *  string representation of the given value is added as the attribute value.
   *  If the value is {@code null} the attribute is not added at all.
   *  The mnemonic "attr" stands for "attribute".
   *
   *  @param name                  the name of the attribute
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder attr (String name, Object value)
    throws DOMException
  {
    if (!(curNode instanceof Element)) return this;
    if (value == null) return this;
  
    Element el = (Element) curNode;
    el.setAttribute (name, value.toString ());
    
    return this;
  }

  
  /**
   *  add an attribute to the current node, if it is an element node. The value
   *  of the attribute is either "true" or "false", depending on the parameter
   *  "value". The mnemonic "attf" stands for "attribute as true/false".
   *
   *  @param name                  the name of the attribute
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder attf (String name, boolean value)
    throws DOMException
  {
    return attr (name, value ? "true" : "false");
  }

  
  /**
   *  add an attribute to the current node, if it is an element node. The
   *  string representation of the given value is added as the attribute value.
   *  If the value is {@code null} the attribute is not added at all.
   *  The mnemonic "attr" stands for "attribute".
   *
   *  @param uri                   the name space URI
   *  @param qName                 the qualified name, i.e. a name with or
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder attr (String uri, String qName, Object value)
    throws DOMException
  {
    if (!(curNode instanceof Element)) return this;
    if (value == null) return this;
  
    Element el = (Element) curNode;
    el.setAttributeNS (uri, qName, value.toString ());
    
    return this;
  }

  
  
  
  /**
   *  add an attribute to the current node, if it is an element node. The value
   *  of the attribute is either "true" or "false", depending on the parameter
   *  "value". The mnemonic "attf" stands for "attribute as true/false".
   *
   *  @param uri                   the name space URI
   *  @param qName                 the qualified name, i.e. a name with or
   *                               without a prefix
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder attf (String uri, String qName, boolean value)
    throws DOMException
  {
    return attr (uri, qName, value ? "true" : "false");
  }

  
  /**
   *  add an attribute to the current node, if it is an element node. The
   *  string representation of the given value is added as the attribute value.
   *  If the value is {@code null} the attribute is not added at all.
   *  The mnemonic "attr" stands for "attribute".
   *
   *  @param uri                   the name space URI
   *  @param prefix                the prefix, may be {@code null} or empty
   *  @param localName             the local element name
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder attr (String uri, String prefix, 
    String localName, Object value)
    throws DOMException
  {
    if (!(curNode instanceof Element)) return this;
    if (value == null) return this;
  
    String qName = prefix != null && prefix.length () > 0 ?
      prefix + ":" + localName : localName;

    Element el = (Element) curNode;
    el.setAttributeNS (uri, qName, value.toString ());
    
    return this;
  }
  
  
  
  /**
   *  add an attribute to the current node, if it is an element node. The value
   *  of the attribute is either "true" or "false", depending on the parameter
   *  "value". The mnemonic "attf" stands for "attribute as true/false".
   *
   *  @param uri                   the name space URI
   *  @param prefix                the prefix, may be {@code null} or empty
   *  @param localName             the local element name
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder attf (String uri, String prefix, 
    String localName, boolean value)
    throws DOMException
  {
    return attr (uri, prefix, localName, value ? "true" : "false");
  }


  
  
  /**
   *  add an attribute to the current node, if it is an element node. The
   *  string representation of the given value is added as the attribute value.
   *  If the value is {@code null} the attribute is not added at all.
   *  The mnemonic "attr" stands for "attribute".
   *
   *  @param qName                 the qualified name
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder attr (QName qName, Object value)
    throws DOMException
  {
    if (!(curNode instanceof Element)) return this;
    if (value == null) return this;
  
    Element el = (Element) curNode;
    
    Attr attr = el.getOwnerDocument ().createAttributeNS (
      qName.getNamespaceURI (), qName.getLocalPart ());
    
    attr.setPrefix (qName.getPrefix ());
    attr.setValue (value.toString ());
    
    el.setAttributeNode (attr);
    
    return this;
  }
  
  
  
  /**
   *  add an attribute to the current node, if it is an element node. The value
   *  of the attribute is either "true" or "false", depending on the parameter
   *  "value". The mnemonic "attf" stands for "attribute as true/false".
   *
   *  @param qName                 the qualified name
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder attf (QName qName, boolean value)
    throws DOMException
  {
    return attr (qName, value ? "true" : "false");
  }
  
  /**
   *  add a text node to the current node. The given object is converted
   *  to a text string via {@code toString}. The mnemnonic "t" stands
   *  for "text".
   *
   *  @param text                  the text to add. If {@code null}
   *                               no node is created
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder t (Object text)
    throws DOMException
  {
    if (text == null) return this;
    
    curNode.appendChild (curNode.getOwnerDocument ().
      createTextNode (text.toString ()));
    
    return this;
  }

  
  
  
  /**
   *  add a text node to the current node. Depending on the value, either
   *  "true" or "false" is added. The mnemnonic "ttf" stands 
   *  for "text as true/false".
   *
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder ttf (boolean value)
    throws DOMException
  {
    return t (value ? "true" : "false");
  }
  
  
  
  /**
   *  add a comment node to the current node
   *
   *  @param text                  the text
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */

  public XmlBuilder comment (String text)
    throws DOMException
  {
    curNode.appendChild (curNode.getOwnerDocument ().createComment (text));
    return this;
  }
  
  /**
   *  convenience method that adds an element with the given value as text.
   *  If the value is {@code null}, the element is not added at all.
   *  The mnemonic "elt" stands for "element with text".
   *
   *  @param name                  the name of the element
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder elt (String name, Object value)
    throws DOMException
  {
    if (value != null)
    {
      es (name);
      t (value);
      ee ();
    }
    
    return this;
  }

  
  /**
   *  convenience method that adds an element with the given value as text.
   *  If the value is {@code null}, the element is not added at all.
   *  The mnemonic "elt" stands for "element with text".
   *
   *  @param uri                   the name space URI
   *  @param qName                 the qualified name, i.e. a name with or
   *                               without a prefix
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder elt (String uri, String qName, Object value)
    throws DOMException
  {
    if (value != null)
    {
      es (uri, qName);
      t (value);
      ee ();
    }
    
    return this;
  }
  
  
  /**
   *  convenience method that adds an element with the given value as text.
   *  If the value is {@code null}, the element is not added at all.
   *  The mnemonic "elt" stands for "element with text".
   *
   *  @param uri                   the name space URI
   *  @param prefix                the prefix, may be {@code null} or empty
   *  @param localName             the local element name
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder elt (String uri, String prefix, 
    String localName, Object value)
    throws DOMException
  {
    if (value != null)
    {
      es (uri, prefix, localName);
      t (value);
      ee ();
    }
    
    return this;
  }

  
  /**
   *  convenience method that adds an element with the given value as text.
   *  If the value is {@code null}, the element is not added at all.
   *  The mnemonic "elt" stands for "element with text".
   *
   *  @param qName                 the qualified name
   *  @param value                 the value
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder elt (QName qName, Object value)
    throws DOMException
  {
    if (value != null)
    {
      es (qName);
      t (value);
      ee ();
    }
    
    return this;
  }
  
  
  /**
   *  include the subtree created by the specified other instance.
   *  The mnemonic "inc" stands for "include".
   *
   *  @param xb                     the other builder
   *  @return                      this object, allowing call chaining
   *  @throws DOMException         if the creation failed
   */
  
  public XmlBuilder inc (XmlBuilder xb)
    throws DOMException
  {
    if (xb == this) 
      throw new IllegalArgumentException ("tried to include oneself");
    
    curNode.appendChild (xb.getBase ());
    return this;
  }
}


