/*
 * (C) Copyright IBM Corp. 1997-1998  All rights reserved.
 *
 * US Government Users Restricted Rights Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 *
 * The program is provided "as is" without any warranty express or
 * implied, including the warranty of non-infringement and the implied
 * warranties of merchantibility and fitness for a particular purpose.
 * IBM will not be liable for any damages suffered by you as a result
 * of using the Program. In no event will IBM be liable for any
 * special, indirect or consequential damages or lost profits even if
 * IBM has been advised of the possibility of their occurrence. IBM
 * will not be liable for any third party claims against you.
 */

package com.ibm.xml.parser;

import org.w3c.dom.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.UnsupportedEncodingException;

/**
 * The TXText class implements the Text interface as defined by the Document Object Model (DOM).
 * <p>The Text object contains the non-markup content of an Element. If there is no markup
 * inside an Element's content, the text will be contained in a single Text object that is the child
 * of the Element. Any markup will parse into Child Elements that are siblings of the Text Nodes
 * on either side of it, and whose content will be represented as Text Node children of the
 * markup Element. 
 *
 * @version 1.1
 * @author TAMURA Kent &lt;kent@trl.ibm.co.jp&gt;
 * @see org.w3c.dom.Text 
 * @see com.ibm.xml.parser.TXCDATASection
 */
public class TXText extends Child implements Text {
    
           String  data                     = null;
           boolean isIgnorableWhitespace    = false;

    /**
     * Constructor.
     * @param data      The actual content of the Text Node.
     */
    public TXText(String data) {
        this.data = data;
    }

    /**
     * Clone this Text Node using the appropriate factory.  
     * <p>This method is defined by Child.
     * @return          Cloned Text Node.
     * @see com.ibm.xml.parser.Child#clone
     */
    public synchronized Object clone() {
        checkFactory();
        TXText t = factory.createText(this.data);
        t.setFactory(getFactory());
        t.setIsIgnorableWhitespace(getIsIgnorableWhitespace());
        return t;
    }
    
    /**
     * Returns that this object is a Text Node. 
     * <p>This method is defined by DOM.
     * @return          Text Node indicator.
     */
    public int getNodeType() {
        return Node.TEXT;
    }
    
    /**
     * Returns the actual content of the Text Node.
     * @return          The actual content of the Text Node, or <var>""</var> if no content.
     * @see #setData         
     */
    public String getData() {
        return this.data;
    }

    /**
     * Sets the actual content of the Text Node.
     * @param data      The actual content of the Text Node.
     * @see #getData         
     */
    public void setData(String data) {
        this.data = data;
        clearDigest();
    }
    
    /**
     * Returns all text associated with this Node without considering entities.                                           
     * <p>This method is defined by Child.
     * @return          Text associated with this object, or <var>""</var> if no Text.
     * @see com.ibm.xml.parser.Child#toString
     * @see #getData
     */
    public String getText() {                   
        return getData();
    }

    /**
     * Returns, at the Text level, whether space is to be preserved.  
     * This value is used, for example, to determine if space is to be preserved
     * in Text Nodes during print() operations.
     * @return                  <code>=true</code> space is to be preserved; 
     *                          <code>=false</code> space is to be ignored.
     * @see #setIsIgnorableWhitespace
     * @see com.ibm.xml.parser.Parser#setPreserveSpace
     * @see com.ibm.xml.parser.TXElement#setPreserveSpace
     */
    public boolean getIsIgnorableWhitespace() {
        return this.isIgnorableWhitespace;
    }

    /**
     * Sets, at the Text level, whether space is to be preserved.  
     * This value is used, for example, to determine if space is to be preserved
     * in Text Nodes during print() operations.
     * @return isIgnorableWhitespace    <code>=true</code> space is to be preserved; 
     *                                  <code>=false</code> space is to be ignored.
     * @see #getIsIgnorableWhitespace
     * @see com.ibm.xml.parser.Parser#setPreserveSpace
     * @see com.ibm.xml.parser.TXElement#setPreserveSpace
     */
    public void setIsIgnorableWhitespace(boolean isIgnorableWhitespace) {
        this.isIgnorableWhitespace = isIgnorableWhitespace;
    }

    /**
     * Returns the XML language ID (the value of the <code>xml:lang</code> attribute) in 
     * use for this Text Node.  If this Text Node has no explicitly defined language attribute,
     * Parent Nodes will be recursively checked.
     * @return          The XML language ID, or <var>null</var> if all Parents have no language ID.
     */
    public String getLanguage() {
        if (null == parent)  return null;
        if (parent instanceof TXElement)
            return ((TXElement)parent).getLanguage();
        if (parent instanceof GeneralReference)
            return ((GeneralReference)parent).getLanguage();
        return null;
    }
    
    /**
     * Append <var>data</var> to the end of the character data in this Text Node.
     * <p>This method is defined by DOM.
     * @param data      Data to append to existing character data in this Text Node.
     * @see #insert
     * @see #replace
     */
    public synchronized void append(String data) {
        StringBuffer sb = new StringBuffer(this.data.length()+data.length());
        sb.append(this.data);
        sb.append(data);
        this.data = sb.toString();
        clearDigest();
    }
    
    /**
     * Insert <var>data</var> at the specified character offset. 
     * <p>This method is defined by DOM.
     * @param offset    0-based offset into existing character data in this Text Node.
     * @param data      Data to insert into existing character data in this Text Node.
     * @see #append
     * @see #replace
    */
    public synchronized void insert(int offset, String data) {
        StringBuffer sb = new StringBuffer(this.data+data.length());
        sb.append(this.data);
        sb.insert(offset, data);
        this.data = sb.toString();
        clearDigest();
    }
    
    /**
     * Delete characters at the specified character offset. 
     * <p>This method is defined by DOM.
     * @param offset    0-based offset into existing character data in this Text Node.
     * @param count     Number of characters to delete.
     * @see #append
     * @see #replace
     */
    public synchronized void delete(int offset, int count) {
        this.data = this.data.substring(0, offset)+this.data.substring(offset+count);
        clearDigest();
    }
    
    /**
     * Replace the characters starting at the specified character offset with the specified string.     
     * <p>This method is defined by DOM.
     * @param offset    0-based offset into existing character data in this Text Node.
     * @param count     Number of existing characters to replace.
     * @param data      Data to insert into existing character data in this Text Node.
     * @see #append
     * @see #insert 
     * @see #delete
     */
    public synchronized void replace(int offset, int count, String data) {
        this.data = this.data.substring(0, offset)+data+this.data.substring(offset+count);
        clearDigest();
    }
    
    /**
     * Insert the specified Element as a sibling of this Text Node. The result
     * of this operation may be the creation of up to 2 new Text Nodes: the character
     * data specified by the offset and count will form one Text Node that will
     * become the Child of the inserted Element, and the remainder of the character
     * data (after the offset and count) will form another Text Node, becoming a sibling
     * of this Text Node.  
     * <p>This method is defined by DOM.
     * @param element   The Element to insert in the tree; this instance must be an instance of TXElement.
     * @param offset    0-based offset into existing character data in this Text Node.
     * @param count     Number of characters to copy to Child Text Node of Element.
     * @exception IllegalArgumentException Thrown if <var>element</var> is not a TXElement.
     * @exception RuntimeException Thrown if this Text Node has no Parent.
     */
    public synchronized void splice(Element element, int offset, int count) throws IllegalArgumentException, RuntimeException {
        if (!(element instanceof TXElement))
            throw new IllegalArgumentException("com.ibm.xml.parser.TXText#splice(): An instance of TXElement is required.");
        String second = this.data.substring(offset, count);
        String third = this.data.substring(offset+count);
        this.data = this.data.substring(0, offset);
        clearDigest();
        Parent parent = (Parent)getParentNode();
        if (null == parent)
            throw new RuntimeException("com.ibm.xml.parser.TXText#splice(): This Text Node has no parent and can not be spliced.");
        element.insertBefore(null == factory ? new TXText(second) : factory.createText(second),
                             null);
        parent.insertAfter(element, this);
        if (0 < third.length())
            parent.insertAfter(null == factory ? new TXText(third) : factory.createText(third),
                               this);
    }

    /**
     * Trim all leading and trailing whitespace in the specified String.  All strings of white space are 
     * replaced by a single space character (#x20).
     * @param   string  String to be trimmed.
     * @return          The trimmed string.
    */
    public static String trim(String string) {
        return trim(string, true, true);
    }
    
    /**
     * Conditionally trim all leading and trailing whitespace in the specified String.  All strings of white space are 
     * replaced by a single space character (#x20).
     * @param   string      String to be trimmed.
     * @param   trimHead    Trim leading whitespace?
     * @param   trimTail    Trim trailing whitespace?
     * @return              The trimmed string.
    */
    public static String trim(String string, boolean trimHead, boolean trimTail) {
        char[] buf = string.toCharArray();
        int len = buf.length;
        boolean edit = false;
        int s;
        for (s = 0;  s < len;  s ++) {
            if (com.ibm.xml.parser.XMLChar.isSpace(buf[s]))  break;
        }
        /* replace S to ' '. and ' '+ -> single ' '. */
        int d = s;
        boolean pres = false;
        for ( ;  s < len;  s ++) {
            char c = buf[s];
            if (com.ibm.xml.parser.XMLChar.isSpace(c)) {
                if (!pres) {
                    if (' ' != c)  edit = true;
                    buf[d++] = ' ';
                } else
                    edit = true;
                pres = true;
            } else {
                buf[d++] = c;
                pres = false;
            }
        }
        if (trimTail && 1 <= d && ' ' == buf[d-1]) {
            edit = true;
            d --;
        }
        int start = 0;
        if (trimHead && 0 < d && ' ' == buf[0]) {
            edit = true;
            start ++;
        }
        return edit ? new String(buf, start, d-start) : string;
    }

    /**
     * Translate escape sequences in the specified string into printable characters.  
     * For example, <var>\r</var> is translated into <var>\\r</var>.
     * @param   string      String to translate.
     * @return              The translated string.
     */
    public static String makePrintable(String string) {
        StringBuffer sb = new StringBuffer(string.length()*2);
        for (int i = 0;  i < string.length();  i ++) {
            int ch = string.charAt(i);
            if ('\r' == ch) {
                sb.append("\\r");
            } else if ('\n' == ch) {
                sb.append("\\n");
            } else if ('\t' == ch) {
                sb.append("\\t");
            } else if ('\b' == ch) {
                sb.append("\\b");
            } else if ('\f' == ch) {
                sb.append("\\f");
            } else if ('"' == ch) {
                sb.append("\\\"");
            } else if ('\\' == ch) {
                sb.append("\\\\");
            } else {
                sb.append((char)ch);
            }
        }
        return sb.toString();
    }

    static boolean isText(Object obj) {
        return !(obj instanceof TXCDATASection) && obj instanceof TXText;
    }
}
