/****************************************************************************/
/* Copyright/Copyleft:
 *
 * For this source the LGPL Lesser General Public License,
 * published by the Free Software Foundation is valid.
 * It means:
 * 1) You can use this source without any restriction for any desired purpose.
 * 2) You can redistribute copies of this source to everybody.
 * 3) Every user of this source, also the user of redistribute copies
 *    with or without payment, must accept this license for further using.
 * 4) But the LPGL ist not appropriate for a whole software product,
 *    if this source is only a part of them. It means, the user
 *    must publish this part of source,
 *    but don't need to publish the whole source of the own product.
 * 5) You can study and modify (improve) this source
 *    for own using or for redistribution, but you have to license the
 *    modified sources likewise under this LGPL Lesser General Public License.
 *    You mustn't delete this Copyright/Copyleft inscription in this source file.
 *
 * @author www.vishia.de/Java
 * @version 2006-06-15  (year-month-day)
 * list of changes:
 * 2006-05-00: www.vishia.de creation
 *
 ****************************************************************************/

package org.vishia.xml;


import java.util.*;
import java.io.*;
import java.nio.charset.Charset;

import org.jdom.*;
//import org.jdom.input.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.*;
import org.jdom.transform.*;          //JDOMInput, JdomOutput, Transformer contens not here!
import javax.xml.transform.*;         //Transformer
import javax.xml.transform.stream.*;  //StreamSource

import org.vishia.mainCmd.Report;
import org.vishia.xmlSimple.XmlException;

/** The class XmlExtensions contains several methods to deal with XML data.
 * It uses JDOM as basic access to XML. The JDOM library downloadable from www.jdom.org is necessary.
 * <br>
 * They are three groups of tasks of this class:<br>
 * <ul><li>Access to inner parts of an element tree: This feature is implemented in the methods
 *   {@link getChildren(Element, String)}, {@link getTextFromPath(Element, String, boolean)}
 *   and {@link setTextToPath(Element, String, String )}. With this methods a textual controlled access 
 *   to any content inside Elements is possible, without calling a XSL-translator, but directly.
 *   The textual given pathes are comparably to XPATH, but not equivalent.</li>
 * <li>Read and write whole XML trees from files and to files. The beautification and handling of
 *   white spaces are topics of the appropriate methods read...() and write...().</li>
 * <li>Wrapper to call the XSL translator {@link xslTransformString(Element, File)} and {@link xslTransformXml(Element, File)}.
 *   The cause of this wrappers are catch of exceptions and supply detached xml elements (not bind to a document)
 *   as result of XSL translation.</li>
 * </ul>
 * <br>
 * The beautification and white space processing followes the necessities of document text processing.
 * It is a own problem of XML, that spaces and new lines are determined by output formats (beautification) and content,
 * both is not distinguishably. From there, in some XML formats no beautification is made. But such XML files are
 * arduously readable by human. But in document texts, no linefeed should be present because linefeeds 
 * are not parts of the information itself, they are parts of the appearance of the information at printing.
 * This is adequate to the spaces: Only one space in series may be a part of the information itself. White spaces are not.
 * <br>
 * From there any input with white spaces may be reduced to one space after reading from file. The routing 
 * {@link replaceWhiteSpaceWith1Space(Element, boolean)} implements this feature. The reduced input xml tree may be processed
 * by any XSL translation or other XML processing in Java. The output may be beautificated for better readability by human.
 * But the spaces of text content may be beware. The beautification is only made respecitvely to the superior organisation elements.
 * The methods {@link beautificationBewareTextContent(Element)} and the {@link writeXmlBeautificatedTextFile(Element, File)} do so. 
 * <br>
 * <br>
 * Most of the methods are static, because no internal data are necessary. The output methods
 * uses settings of some properties: encoding, indent mode, so they are based on a instance of XmlExtensions.
 * A instance of XmlExtensions will be created by the static methods {@link create(Charset)},
 * the constructors are private. This is a design attempt ajar to factory patterns. 
 * The using of <code>new XmlExtension(...)</code> instead <code>create(...)</code> will be 
 * adequate, but here create() is the decision.  
 *
<hr/>
<pre>
date       who        change
2007-01-02 JcHartmut  create() with Charset, some methods will be non static.
2006-03-07 JcHartmut  beautification, replaceWhiteSpaceWith1Space.
2005-06-00 JcHartmut  initial revision
*
</pre>
<hr/>
 */
public class XmlExtensions
{
  /** The data held in the subclass.*/
  //private XmlMode mode;

  /** The standard charset of this instance.*/
  //private static String sEncodingStandard = "ISO-8859-1";

  /** The xhtml namespace with prefix xhtml: xmlns:xhtml="http://www.w3.org/1999/xhtml" */
  //public final static Namespace xhtml = Namespace.getNamespace("xhtml","http://www.w3.org/1999/xhtml");
  
  /** Class hold some properties of transformation.  
   */
  public static class XmlMode
  {
    private static final int kConvertText = 1;

    /** If this bit is setted in convertMode, a XML-output,
     * not textual output ist produced.
     */
    private static final int mConvertXml        = 0x10;

    /** The XML-output-conversion should be written in UFT8-Form.
     * @deprecated
     * */
    private static final int kConvertXmlUTF8    = mConvertXml + 0x0;

    /** The XML-output-conversion should be written in ISO8859-Form.
     * @deprecated
     * */
    private static final int kConvertXmlIso8859 = mConvertXml + 0x1;

    /** The XML-output-conversion should be written in ISO8859-Form. 
     * @deprecated
     * */
    private static final int kConvertXmlASCII = mConvertXml + 0x2;

    /** The encoding charset of this instance.*/
    private String sEncoding = "ISO-8859-1";
    

    private int convertMode = kConvertText;

    /** The indent, may be "" or null if no wrapping. */
    private String sIndent = "  ";

    /**
     * Sets the indent for wrapping.
     * @param sIndent null if no wrapping, "" if wrapping without indent.
     */
    public void setIndent(String sIndent){ this.sIndent = sIndent; }

    public void setText()      { convertMode = kConvertText; }
    
    /**Sets the encoding to the given encoding.
     */
    public void setEncoding(Charset encoding)   
    { this.sEncoding=encoding.name(); 
    }
   
    /**Sets the encoding to UTF8. UTF8 is a standard supporting all char sets.
     */
    public void setXmlUTF8()   { convertMode = kConvertXmlUTF8; sEncoding="UTF-8"; }
   
    
    /**Sets the encoding to ISO-8849-1. This is the standard char set on windows systems in west european.
     */
    public void setXmlIso8859(){ convertMode = kConvertXmlIso8859; sEncoding = "ISO-8859-1";}
    
    /**Sets the encoding to US-ASCII. This is the minimal standard char set, using only 7-bit-chars.
     */
    public void setXmlASCII()  { convertMode = kConvertXmlASCII; sEncoding = "US-ASCII"; }

    public boolean isText(){ return convertMode == kConvertText; }
    public boolean isXml(){ return (convertMode & mConvertXml) == mConvertXml; }
    public boolean isIndent(){ return sIndent != null; }
    public String getIndent(){ return sIndent; }

    /** Returns the encoding as input for XmlExtensions.writeXmlFile(xmlTree, sFileName, <b>encoding<b>)
     * @deprecated
    */
    public int xxxgetEncoding()
    throws XmlException
    { switch(convertMode)
      { case kConvertXmlUTF8   : return XmlExtensions.kEncoding_UTF_8;
        case kConvertXmlIso8859: return XmlExtensions.kEncoding_ISO_8859_1;
        default                : throw new XmlException("getEncoding? - but unknown"); //, Report.exitWithArgumentError);
      }
    }
    
    public String getEncoding()
    { return sEncoding;
    }
  }



  /** Internal constant for encoding the xml file. 
   * It was an idea to code the encoding not with a string, but with a constant.
   * But this system is not extensible, the using of java.nio.charset.Charset 
   * is the better decision. See {@link create(Charset)}.
   * @deprecated
   */
  public final static int kEncoding_ISO_8859_1 = 1;
  
  /** Internal constant for encoding the xml file.  
   * @deprecated */
  public final static int kEncoding_UTF_8 = 2;
  /** Internal constant for encoding the xml file. 
   * @deprecated */
  public final static int kEncoding_ASCII = 3;






  /** Gets the text content from the adressed node relativ to the given Element.
   *  This method is useable conveniently if the path is given in textual form 
   *  outside the directly programming in java, but at example with control files (may be in XML)
   *  or from a conversion of XML data to a java routine (code generation).  
      <br>
      The Path, param sXmlPath, may be given in the followed form, 
      <br>
      example: "../element/@attribute" or "@name+$+{Parameter|@name|$}" <br>
      <table border="1"><tr><th>sample</th><th>meaning</th></tr>
      <tr><td><code>name             </code></td><td>Normalized text content of the element</td></tr>
      <tr><td><code>@name            </code></td><td>content of the attribute</td></tr>
      <tr><td><code>name/child       </code></td><td>Normalized text content of the child element</td></tr>
      <tr><td><code>name/@attr       </code></td><td>content of the attribute within the element</td></tr>
      <tr><td><code>name/name/@attr  </code></td><td>The path can be in any deepness</td></tr>
      <tr><td><code>../../name       </code></td><td>parent from parent</td></tr>
      <tr><td><code>"text"           </code></td>
        <td>the constant text between "". It may be a part of the path concating with other parts with +, 
        but it is also possible to return a text directly, without using the input xml element. 
        This choice is conveniently, because the behaviour may be determined outside of a java programming,
        and no special case will be produced if only a simple text is expected.</td>
      </tr>
      <tr><td><code>path+"text"+path </code></td><td>concation between the texts from pathes</td></tr>
      <tr><td><code>path+c+path      </code></td><td>1 char between + is a added separator, it is the same as <code>+"c"+</code></td></tr>
      <tr><td><code>path+c           </code></td><td>1 char on end after +, same as here <code>+"."</code></td></tr>
      <tr><td><code>{apath|vpath|c}  </code></td>
        <td>concation of all textes from <code><i>vpath</i></code> from all elements found with <code><i>apath</i></code>, 
        with <code><i>c</i></code> as separator between the concatenated result strings. <br>
        With using this expression as part of sXmlPath it is possible to concatenate the content of
        more as one elements without using of a extra programmed loop.
        </td>
      </tr>
      </table>
      @param xml The Element the path starts from.
      @param sXmlPath The path expression see above.
      @param bInvalidIsOk If true, than return "" on invalid path, if false than return null on invalid path.
      @return The requested text content, "" or null if the path is not valid.
  */
  @SuppressWarnings("unchecked")
  public static String getTextFromPath(Element xml, String sXmlPath, boolean bInvalidIsOk)
  {
    boolean bDebug = false; //sXmlPath.startsWith("..");
    StringBuffer sText = new StringBuffer(100);
    boolean bError = false;
    Element xmlChild = xml;
    if(bDebug) System.out.println(sXmlPath);
    int pos1 = 0;
    while(pos1 < sXmlPath.length())
    { //concat
      char cc;
      if( (cc = sXmlPath.charAt(pos1)) == '\'' || cc == '\"')
      {
        int posEnd = sXmlPath.indexOf(cc, pos1+1);   //same char either "..." or '...'
        if(posEnd < 0) posEnd = sXmlPath.length();
        sText.append(sXmlPath.substring(pos1+1, posEnd));
        pos1 = posEnd +1;
        if(pos1 < sXmlPath.length())
        { pos1 = sXmlPath.indexOf('+', pos1);     //search + as concation char
          if(pos1 < 0) pos1 = sXmlPath.length();  //no further concation.
        }
      }
      else if(pos1 < (sXmlPath.length()-1) && sXmlPath.charAt(pos1+1) == '+'
             || pos1 == (sXmlPath.length()-1)  //the last char
             )
      { //$+  simple char before concation is a concation char
        sText.append(sXmlPath.charAt(pos1));
        pos1 += 2;     //may be pos1 >= length()
      }
      else if(sXmlPath.charAt(pos1) == '{') pos1 = getMultiText(sText, xml, sXmlPath, pos1);
      else
      { //text from path
        int posEnd  = sXmlPath.indexOf('+', pos1);
        if(posEnd < 0) posEnd = sXmlPath.length();  //no concation: end of string
        int posSep = sXmlPath.lastIndexOf('/', posEnd-1);
        if(posSep > 0)
        { if(bDebug) System.out.println("  child:"+ sXmlPath.substring(pos1, posSep)+":");
          List listChildren = getChildren(xml, sXmlPath.substring(pos1, pos1 + posSep));
          xmlChild = (listChildren.size()>0 ? (Element)(listChildren.get(0)) : null);
          pos1 = posSep +1;
        }
        else xmlChild = xml;

        if(xmlChild == null) bError = true;   //1 error force return null if !bInvalidIsOk
        else
        {
          if(sXmlPath.charAt(pos1) == '@')
          { String sContent = xmlChild.getAttributeValue(sXmlPath.substring(pos1+1, posEnd));
            if(bDebug) System.out.println("              :" + sXmlPath.substring(pos1,posEnd) + ":" + xmlChild.getName() + "::" + sContent);
            if(sContent == null) bError = true;   //1 error force return null if !bInvalidIsOk
            else sText.append(sContent);
          }
          else
          { xmlChild = xmlChild.getChild(sXmlPath.substring(pos1, posEnd));
            if(xmlChild == null) bError = true;   //1 error force return null if !bInvalidIsOk
            else sText.append(xmlChild.getTextNormalize());
          }
        }
        pos1 = posEnd + 1;  //after posConcat or > length()
      }
    }
    if(bError && !bInvalidIsOk) return null;
    else return sText.toString();  //may be "" if invalid path
  }


  /** Gets a list of children, not only from the given xml Element, but also from a deeper level.
   *  This method is useable conveniently if the path is given in textual form 
   *  outside the directly programming in java, but at example with control files (may be in XML)
   *  or from a conversion of XML data to a java routine (code generation).  
      The Path, param sXmlPath, may be given in the followed form, 
      <br>
      examples: 
      <ul><li><code>"element1/name"</code>: children of element1 with tag name "name"</li>
          <li><code>"element1/*"</code>: all children of element1</li>
          <li><code>"../name"</code>: all siblings with name</li>
          <li><code>"../*"</code>: all siblings inclusive self</li>
          <li><code>".."</code>: Only the parent of self</li>
          <li><code>"../.."</code>: Only the parent of parent</li>
          <li><code>"."</code>: Only self (it is the param xml itself)</li>
      </ul>
      If the path ends with <code>".."</code> or <code>"."</code>, the list contains only the addressed element, 
      it is not a list getted with <code>org.jdom.Element.getChildren()</code>, it is a simple LinkedList 
      with this one element. 
      <br>
      But if the path ends with an named element or "*", the returned list is a list 
      getted with <code>org.jdom.Element.getChildren()</code>.
      It means, that all operations may be applied to the list, including add of further siblings
      using <code>thelist.listIterator().add(sibling);</code>     
   * 
   * @param xml The parent element.
   * @param sXmlPath Path relativ from parent to the element from which the children are listed.
   *        If the path is "." or empty, 
   * @return List of children or null if any error in path.
   */ 

  @SuppressWarnings("unchecked")
  public static List<org.jdom.Parent> getChildren(Element xml, String sXmlPath)
  { int pos1 = 0;
    boolean bPathFound = false;
    while(pos1 < sXmlPath.length() && !bPathFound)
    { int posSep = sXmlPath.indexOf('/', pos1);
      if(posSep >=0)
      { bPathFound = false;
        if(sXmlPath.substring(pos1, posSep).equals(".."))
        { if(xml != null) xml = xml.getParentElement();
        }
        else if(sXmlPath.substring(pos1, posSep).equals("."))
        { //nothing, "." is the actual element
        }
        else
        { //really a child
          if(xml != null) xml = xml.getChild(sXmlPath.substring(pos1, posSep));
        }
        pos1 = posSep +1;
      }
      else bPathFound = true;
    }
    List<org.jdom.Parent> listChildren;
    if(xml != null) 
    { if(sXmlPath.substring(pos1).equals(".."))
      { //the parent is the meant children
        listChildren = new LinkedList<org.jdom.Parent>();
        listChildren.add(xml.getParent());
      }
      else if(sXmlPath.substring(pos1).equals("."))
      { //the addressed element directly
        listChildren = new LinkedList<org.jdom.Parent>();
        listChildren.add(xml);
      }
      else
      { //jdom-like children list
        listChildren = xml.getChildren(sXmlPath.substring(pos1));
      }
    }
    else{ listChildren = null; }
    return listChildren;
  }





  private static int getMultiText(StringBuffer sText, Element xml, String sXmlPath, int pos1)
  { //boolean bOk = true;
    //{
    int posSepEnd = sXmlPath.indexOf('}', pos1);
    int posSep1 = sXmlPath.indexOf('|', pos1);
    int posSep2 = sXmlPath.indexOf('|', posSep1 +1);   //may be 0 if posSep1 = -1
    if(posSepEnd < 0 || posSep1 < 0 || posSep2 < 0)
    { sText.append("::ERROR::Syntay {||}:" + sXmlPath.substring(pos1));
      //bOk = false;
    }
    else
    { String sApath     = sXmlPath.substring(pos1+1, posSep1);
      String sVpath     = sXmlPath.substring(posSep1+1, posSep2);
      String sSeparator = sXmlPath.substring(posSep2+1, posSepEnd);
      //System.out.println("getMultiText:" + sApath + ":" + sVpath + ":" );
      List<org.jdom.Parent> listChildren = getChildren(xml, sApath);
      Iterator<org.jdom.Parent> iterChildren = listChildren.iterator();
      boolean bFirst = true;
      while(iterChildren.hasNext())
      { Element xmlChild = (Element)(iterChildren.next());
        //System.out.println("  -"+ xmlChild.getName());
        String sContent  = getTextFromPath(xmlChild, sVpath, true);
        if(bFirst) bFirst = false;
        else sText.append(sSeparator);
        sText.append(sContent);
      }
    }
    if(posSepEnd >0) return posSepEnd +1;
    else return sXmlPath.length();  //error: end not found
  }









  /** Sets the specified element with the given value.
      The Path may be given in the followed form (example): "../element/@attribute"
      @param xml Element within the content is setted
      @param sXmlPath Path selects the child within xml, into the sContent is setted.
             The path should be given in form "../../name/name/dst".
             The specification ".." means the parent, like XPATH.
             All childs are created, if they don't exist.<br/>
             The last element of the path may be a special specification:
             <table border=1>
             <tr><th>form</th><th>Description</th></tr>
             <tr><td>name</td><td>the child with the name</td></tr>
             <tr><td>.</td><td>The element itself (sXmlPath="." is the input element itself)</td></tr>
             <tr><td>@name</td><td>example: "tag/@name". Sets the content into the attribute name of the tag</td></tr>
             <tr><td>=</td><td>example: "tag/tag2/=". Sets the content into the element tag2,
                               otherwise the content is appended to name2</td></tr>
             <tr><td>!</td><td>example: "tag/tag2/!". Changes the name of the selected xml-Element tag2 to the sContent</td></tr>
             </table>
      @param sContent content set to the element, attribute or defines the name of the element.
      @return true if succesfull, false if the element is not found or other error (if no effect).
  */
  public static boolean setTextToPath(Element xml, String sXmlPath, String sContent)
  { boolean bOk = false;
    if(sContent == null) sContent = "XXX";
    Element xmlChild = xml;
    int pos1 = 0;
    int posSep;
    if(sXmlPath.length()>0 && sContent != null)
    { boolean bCont = true;
      do
      { posSep = sXmlPath.indexOf('/', pos1);
        if(posSep < 0) //the last part of the path:
        { posSep = sXmlPath.length();
          bCont = false;
        }
        if(sXmlPath.substring(pos1, posSep).equals(".."))
        { xmlChild = xmlChild.getParentElement();
        }
        else if(sXmlPath.substring(pos1, posSep).equals("."))
        { //nothing, "." is the actual element
        }
        else if(sXmlPath.substring(pos1).startsWith("!"))
        { //the rest is the new tag name, evaluate later.
          bCont = false;
        }
        else if(sXmlPath.substring(pos1).startsWith("@"))
        { //the rest is the atrribute name, evaluate later.
          bCont = false;
        }
        else if(sXmlPath.substring(pos1).startsWith("="))
        { //delete the content of the element before setting new.
          xmlChild.setText("");  //delete the textual content
        }
        else
        { //really a child
          Element xmlChildNew;
          xmlChildNew = xmlChild.getChild(sXmlPath.substring(pos1, posSep));
          if(xmlChildNew == null)
          { //no such child, create it:
            xmlChild = new Element(sXmlPath.substring(pos1, posSep));
          }
          else
          { xmlChild = xmlChildNew;
          } //it is it.
        }
        if(bCont) { pos1 = posSep +1; }
      } while(bCont && xmlChild != null);

      if(xmlChild != null)
      { if(sXmlPath.charAt(pos1) == '@')
        { xmlChild.setAttribute(sXmlPath.substring(pos1+1), sContent);
          bOk = true;
        }
        else if(sXmlPath.charAt(pos1) == '!')
        { xmlChild.setName(sContent);
          bOk = true;
        }
        else
        { xmlChild.addContent(sContent);
          bOk = true;
        }
      }
    }
    return bOk;
  }



  /** Reads a xml file and convert it to a internal xml tree. This is a simple frame arround SAXBuilder.
      On any problem an exception is thrown. This may be a file-not-found or a parsing problem with the content of the xml-file.

    @return The root element
  */
  public static Element readXmlFile(File file)
  throws XmlException
  { try
    { SAXBuilder builder = new SAXBuilder();
      Document doc = builder.build( file );
      return doc.getRootElement();
    }
    catch(JDOMException exception)
    { throw new XmlException("conversion readed xml-File " + file.getAbsolutePath() + exception.getMessage());
    }
    catch(IOException exception)
    { throw new XmlException("reading xml-File " + file.getAbsolutePath() + exception.getMessage());
    }
  }



  /** Reads a xml file and convert it to a internal xml tree. This is a more complex frame arround SAXBuilder.
   * The special solution is the followed:<br>
   * * By reading from file every newline is converted to a space char, except the first line.
   * It means, the file have only 2 lines, the head line and a very long second line
   * with the whole content.<br>
   * * After reading all whitespaces are converted to one space using {@link replaceWhiteSpaceWith1Space(Element, boolean)}.<br>
   * <br>
   * The effect is the followed: The textual content from the inputted XML file
   * doesn  't contain any whitespaces, or line feed. Every white space is converted to one spaces.
   * More as one space in serial doesn't exist. No line structure is given.
   * If a prior beautificated input is there, it is now non-beatificated, simple.
   * This is a format appropriately useable for document processing with document formatting structures.
   * <br>
   * @param file The file from which xml is readed.
   * @return The root element without whitespaces.
  */
  public static Element readXmlFileTrimWhiteSpace(File file)
  throws XmlException
  {


    class InputStreamSpecial extends InputStream
    {
      private final InputStream in;
      int cNext = -1;
      int lineCt = 0;

      InputStreamSpecial(File file)
      { InputStream in;
        try{ in = new FileInputStream(file);}
        catch(FileNotFoundException ex){ in = null;}
        this.in = in;
      }

      public int read() throws IOException
      {
        if(in != null)
        { int cc;
          if(cNext >0)
          { cc = cNext;
          }
          else
          {
          }
          cc = in.read();
          if(cc == 0x0d && lineCt >0)
          { cc = 0x20;
          }
          if(cc == 0x0a)
          { if(lineCt >0)  //first line: no change!
            { cc = 0x20;
            }
            lineCt +=1;
          }
          return cc;
        }
        else return -1;
      }

      public boolean isReadable(){ return in != null; }
    }//InputStreamSpecial

    //use the special reader inside this method:
    InputStreamSpecial input = new InputStreamSpecial(file);
    if(!input.isReadable())
    { throw new XmlException("file not found: " + file.getAbsolutePath());
    }
    try
    { SAXBuilder builder = new SAXBuilder();
      Document doc = builder.build( input );
      //Document doc = builder.build( file );
      Element root = doc.getRootElement();
      replaceWhiteSpaceWith1Space(root, false);
      return root;
    }
    catch(Exception exception)
    { throw new XmlException("conversion given xml-String " + exception.getMessage());
    }
  }


  /** Replaces white spaces of all text()-content with one space, by keeping
   * the inner leading and trailing spaces.
   * The original behaviour of XML outputter is the possibility of replacement
   * of all whitespace with one space, but only in combination with trimming
   * all leading and trailing spaces with no replacement with one spaces.
   * This property sometimes is not useable, at example in a combination of
   * "text &lt;b>This is bold &lt;/b>The last space is a bold space."
   * The trimming ignores the space and the text would be corrupted.
   * @param xml The element to be white-spaces-trimmed.<br/>
   * @param bContendText Normally let it false by calling outside!
   *        If false, than test of containing text(), if it contains text() than
   *        delete the first leading and the last trailing whitespace
   *        and call recursively for inner elements with true.<br/>
   *        If true than normalize, but keep one space
   *        instead of possible leading or trainling white spaces.
   *        This is the correct choice for inner (recursively) calling.<br/>
   */

  @SuppressWarnings("unchecked")
  public static void replaceWhiteSpaceWith1Space(Element xml, boolean bContendText)
  { boolean bNewContendText = false;
    if(!bContendText)
    { //it is a textual paragraph if the element is a <p>-tag.
      String sTest = xml.getTextNormalize();  //get all text
      if(sTest.length()>0)
      { bContendText = true;
        bNewContendText = true;
      }
      //bContendText = xml.getName().equals("p");
    }
    { List listChild = xml.getContent();
      //List listChildNew = new LinkedList();
      Iterator iterChild = listChild.iterator();
      bNewContendText = false;  //test: what is with preserving the first and last space anyway

      boolean bFirst = bNewContendText;  //only on first element of first Text
      while(iterChild.hasNext())
      { Content xmlChild = (Content)iterChild.next();
        //xmlChild.detach();
        if(xmlChild instanceof org.jdom.Text && bContendText)
        { org.jdom.Text text = ((org.jdom.Text)(xmlChild));
          String sContent = text.getText();
          int nChars = sContent.length();
          if(nChars>0)
          { char char1 = sContent.charAt(0);
            char char9 = sContent.charAt(nChars-1);
            //:NOTE: \r\n is changed to \n on input parser.
            sContent = org.jdom.Text.normalizeString(sContent);
            //without leading and trailing spaces, only one space instead whitespace.
            if(sContent.startsWith("dieses hier"))
            {
              sContent = "dieses hier ";
            }
            if(char1 == ' ' || char1 == '\n' || char9 == ' ' || char9 == '\n' )
            { //only of any reason to change, fill sContent new.
              sContent = ( (!bFirst && char1 == ' ') ? " " : "")
                       + ( (!bFirst && char1 == '\n') ? " " : "")
                       + sContent
                       + (( ( iterChild.hasNext()  //not the last element
                            || !bNewContendText    //but always on inner elements
                            )
                          &&(nChars >1)          //input at least 2 chars
                          && sContent.length()>0 //any content else
                          && (char9 == ' '|| char1 == '\n') //last char is white space
                          )
                         ? " "                   //than append space.
                         : ""
                         )
                       ;
            }
          }
          text.setText(sContent);
          //listChildNew.add(new org.jdom.Text(sContent));
        }
        else if(xmlChild instanceof org.jdom.Element)
        { //process it recursively
          replaceWhiteSpaceWith1Space((org.jdom.Element)xmlChild, bContendText);
          //listChildNew.add(xmlChild);
        }
        else
        { //listChildNew.add(xmlChild);  //unchanged.
        }
        bFirst = false;
      }
    }
  }



  /**Beautificates the content of the Element with respecting of textual content preserving.
   * Copies the Element content inclusive childs into a new Element and returns the new Element.
   * If an element (child element) contents text(), than the content is copied without changes,
   * like xml:space="preserve".
   * <br>
   * If no text() is containing, all empty (only whitespace)-text()-
   * elements are ignored, and between elements, linefeed and indent is added.
   * So the output written with org.jdom.output.Format.getRawFormat() is beautificated.
   * This method is called recursively if child Elements are present.
   * <br>
   * Example: if the input xml tree contains:
   * <pre>&lt;sampleTag>&lt;innerTag>&lt;p>This is text&lt;b> with bold &lt;/b>and i&lt;b>nn&lt;/b>er bold text&lt;/p>&lt;/innerTag>&lt;/sampleTag>
   * </pre>
   * The output will be:<pre>
   * &lt;sampleTag>
   *   &lt;innerTag>
   *     &lt;p>This is text&lt;b> with bold &lt;/b>and i&lt;b>nn&lt;/b>er bold text&lt;/p>
   *   &lt;/innerTag>
   * &lt;/sampleTag>
   * </pre>
   * <br>
   * Another way to get beautificated output may be using the formatting possibilities of JDOM,
   * but they take no consideration of inner textual elements. If beautification is choiced,
   * all elements are beautificated and the original space structure may be corrupted.
   * The above example will written in form
   * <pre>
   * &lt;sampleTag>
   *   &lt;innerTag>
   *     &lt;p>This is text
   *       &lt;b> with bold
   *       &lt;/b>
   *       and i
   *       &lt;b>nn
   *       &lt;/b>
   *       er bold text
   *     &lt;/p>
   *   &lt;/innerTag>
   * &lt;/sampleTag>
   * </pre>
   * In this sample always a whitespace is produced between elements in output,
   * even if the user will not have a space.
   * <br>
   * Note: The identation is limited to approximately a half page width (length of sIndent).
   * <br>
   * Note: other content as Element and Text are not supported yet (:TODO:).
   *
   *
   * @param xml The Input element tree.
   * @return A new XML tree with beautificated content.
   */
  public static Element beautificationBewareTextContent(Element xml)
  { //first call
    return BeautificationNoTextContent.beautificationNoTextContent(xml, false, 0);
  }

  /** Inner class for Beautification */
  private static class BeautificationNoTextContent
  {

    /** constant String to realize indent.*/
    static final String sIndent = "\n                                                               ";

    /** Maximal number of indentation, determined by length of sIndent.*/
    static final int nMaxIndent = sIndent.length()/2 -2;


    /** Inner Recursively call variant of the public method.
     *
     * @param xml Input Element
     * @param bContendText True, than text()-content is present in xml Input or parents.
     * @param nLevel level of indentation, count by every recursively call.
     * @return
     */
    @SuppressWarnings("unchecked")
    private static Element beautificationNoTextContent(Element xml, boolean bContendText, int nLevel)
    { Element xmlOut = new Element(xml.getName(), xml.getNamespace());

      { //copy Attributes
        { List attributes = xml.getAttributes();
          Iterator iter = attributes.iterator();
          while(iter.hasNext())
          { Attribute attrib = ((Attribute)(iter.next()));
            xmlOut.setAttribute(attrib.getName(), attrib.getValue());
          }
        }
      }

      { //copy AdditionalNamespaces
        List listAddNs = xml.getAdditionalNamespaces();
        if(listAddNs.size() >0)
        { Iterator iter = listAddNs.iterator();
          while(iter.hasNext())
          { Namespace ns = (Namespace)(iter.next());
            xmlOut.addNamespaceDeclaration(ns);
          }
        }
      }

      if(nLevel > nMaxIndent ){ nLevel = nMaxIndent; } //no further indent.
      if(false && !bContendText)
      { //test of containment of text(), than set bContendText, with effect also to childs.
        String sTest = xml.getTextNormalize();  //get all text with trimmed spaces.
        if(sTest.length()>0)
        {  bContendText = true;
        }
        //bContendText = xml.getName().equals("p");  //older test
      }
      { List listChild = xml.getContent();
        //List listChildNew = new LinkedList();
        Iterator iterChild = listChild.iterator();
        while(iterChild.hasNext())
        { Content xmlChild = (Content)iterChild.next();
          //xmlChild.detach();  //:NOTE: detach causes a Iterator exception!
          if(xmlChild instanceof org.jdom.Text) // && bContendText)
          { org.jdom.Text text = ((org.jdom.Text)(xmlChild));
            if(bContendText)
            { //if a textual content was always detected before, copy exactly.
            	String sContent = text.getText();
              xmlOut.addContent(sContent);  //add it.
            }
            else
            { //not textual content before:
            	String sContent = text.getText();
            	int posStart = 0; int posEnd = sContent.length();
            	while(!bContendText && posStart < posEnd)
            	{ char cc = sContent.charAt(posStart);
            		if("\r\n \t".indexOf(cc) >=0){ posStart+=1; }
            		else { bContendText = true; }
            	}
              if(bContendText)
            	{ //really a text
            	  xmlOut.addContent(sContent); //.substring(posStart));  //add it.
            	}
              else
              { //ignore the text, if no textual content before and the text is empty. 
              	//It is a beautification from input.
              	//The beautification will be added in a own kind. 
              }
            }
          }
          else if(xmlChild instanceof Element)
          { //other element:
            if(!bContendText)
            { //insert indent before:
              xmlOut.addContent(sIndent.substring(0, 1+2*nLevel));
            }
            //call recursively this method, after it add the result element.
            xmlOut.addContent(beautificationNoTextContent((Element)xmlChild, bContendText, nLevel+1));
          }

        }

        if(false && !bContendText && nLevel >0)
        { //insert line feed and indent after the element:
          xmlOut.addContent(sIndent.substring(0, 1+2*(nLevel-1)));
        }
      }
      return xmlOut;
    }
  }//class BeautificationNoTextContent

  /** Write the content of the xml-node in the report
  */
  public static void reportContentElement(Element xml, Report report)
  {
    report.reportln(Report.info, "reportContentElement: " + xml.getName());
  }


  /**Transform a xml tree to a new tree. The input tree is started from an detached xml Element.
     @param xmlOutResult instance of Result to accumalate the output.
  */
  private static void xslTransform(Element xmlInput, File fXsl, Result xmlOutResult)
  throws XmlException
  {
    if(!fXsl.exists())
    { throw new XmlException("xslTransformation: xsl-file not found: " + fXsl.getName());
    }
    Transformer xslTransformer = null;
    try
    {
      xslTransformer = TransformerFactory.newInstance().newTransformer(new StreamSource(fXsl));
    }
    catch (TransformerException exception)
    { throw new XmlException("xslTransformation: error in xsl-file: " + fXsl.getName() + exception.getMessage());
    }
    Document docIn = new Document();
    xmlInput.detach(); //it may have used in a transformation before.
    docIn.setRootElement(xmlInput);
    try{ xslTransformer.transform(new JDOMSource(docIn), xmlOutResult); }
    catch (TransformerException exception)
    { throw new XmlException("xslTransformation: error in xsl-file: " + fXsl.getName() + exception.getMessage());
    }

  }



  /**XSL-Transformation of a xml tree to a new tree. The input tree is started from an detached xml Element.
   * Internally javax.xml is used.
     @return The new xml tree. The returned root element has not a parent, it is detached.
             So it can be added to any other tree.
  */
  public static Element xslTransformXml(Element xmlInput, File xslFile)
  throws XmlException
  {
    JDOMResult xmlOutResult = new JDOMResult();
    xslTransform(xmlInput, xslFile, xmlOutResult);
    if(!xmlOutResult.getDocument().hasRootElement())
    { throw new XmlException("xslTransformationXml: no root element produced");
    }
    //if success than return the detached root element from conversion document.
    //The document is further unnecessary and will be deleted by the garbage collector.
    Element xmlOut = xmlOutResult.getDocument().getRootElement();
    xmlOut.detach();
    return xmlOut;
  }




  /**Transform a xml tree to a string. The input tree is started from an detached xml Element.
     @return the string.
  */
  public static String xslTransformString(Element xmlInput, File xslFile)
  throws XmlException
  {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(10000);  //increased if necessary
    Result xmlOutResult = new StreamResult(outputStream);

    xslTransform(xmlInput, xslFile, xmlOutResult);
    return outputStream.toString();
  }


  /**Output a xml tree to a file with beautification of the output,
   * but beware all spaces inside textual content. It calls internally {@link beautificationNoTextContent(Element)}
   * <br>
   * @param xmlRoot The root element
   * @param fileOut The file to write out. The file will be created or replaced.
   * @throws FileNotFoundException If the fileOut doesn't match.
  */
  public static void writeXmlBeautificatedTextFile(Element xmlRoot, File fileOut, Charset encoding)
  throws XmlException, FileNotFoundException
  { Element xmlRootBeautificated = beautificationBewareTextContent(xmlRoot);
    XmlMode mode = new XmlMode(); 
    mode.setEncoding(encoding);
    mode.setIndent(null);
    writeXmlFile(xmlRootBeautificated, fileOut, mode);
  }



  /**Output a xml tree to a file.
   * @throws FileNotFoundException if the sFileOut doesn't match
   * @deprecated
  */
  public static void writeXmlFile(Element xmlRoot, String sFileOut)
  throws XmlException, FileNotFoundException
  { XmlMode mode = new XmlMode();
  	writeXmlFile(xmlRoot, new File(sFileOut), mode);
  }







  
  
  
  
  
  


  /**Output a xml tree to a file.
   * if setIndent() is called with null-Argument, than write without wrapping.
   *  Otherwise, wrap with the given indent.
   *  If the output is written with indent, it is revitalize readable. Some times, the text should be written
   *  with exactly spaces, not with white spaces, from there setIndent(null) should be called before.
   *  The output will be written with the charset defined by {@link create(Charset)} or {@link setEncoding(int)}.
   *  @param xmlRoot The root Element of the created output XML-File.
   *  @param fileOut This file will be created or replaced.
   * @exception  FileNotFoundException  if the file exists but is a directory
   *                   rather than a regular file, does not exist but cannot
   *                   be created, or cannot be opened for any other reason
   * @throws IOException if any error on writing at file system
   * @exception  SecurityException  if a security manager exists and its
   *               <code>checkWrite</code> method denies write access
   *               to the file.
  */
  public void writeXmlFile(Element xmlRoot, File fileOut)
  throws XmlException, FileNotFoundException
  { FileOutputStream fOut = null;
    //try
    { fOut = new FileOutputStream(fileOut);
      Document docu = new Document();
      docu.setRootElement(xmlRoot);
      XMLOutputter writerXml = new XMLOutputter();
      org.jdom.output.Format format = org.jdom.output.Format.getRawFormat();
      //format.setNewlines(bIndent);
      //xmlFormat.setIndent("  ");
      //format.setIndent("  ");
      //format.setLineSeparator("\n");
      //format.setExpandEmptyElements(true);
      writerXml.setFormat(format);
      try
      { writerXml.output( docu,  fOut );
        fOut.close();
      }
      catch(IOException exc)
      { throw new XmlException("Any error writing file:" + exc.getMessage());
      }
    }
    //catch(IOException exception){ throw new XmlException("write xml-output-file: " + fileOut.getAbsolutePath()); }
  }



  /**Output a xml tree to a file.
   *  @param xmlRoot The root Element of the created output XML-File.
   *  @param fileOut This file will be created or replaced.
   *  @param sEncoding The encoding, typicall "ISO-8859-1"
   * @exception  FileNotFoundException  if the file exists but is a directory
   *                   rather than a regular file, does not exist but cannot
   *                   be created, or cannot be opened for any other reason
   * @throws IOException if any error on writing at file system
   * @exception  SecurityException  if a security manager exists and its
   *               <code>checkWrite</code> method denies write access
   *               to the file.
  */
  public static void writeXmlFile(Element xmlRoot, File fileOut, XmlMode mode)
  throws XmlException, FileNotFoundException
  { FileOutputStream fOut = null;
    //try
    { fOut = new FileOutputStream(fileOut);
      Document docu = new Document();
      docu.setRootElement(xmlRoot);
      XMLOutputter writerXml = new XMLOutputter();
      org.jdom.output.Format format = org.jdom.output.Format.getRawFormat();
      format.setEncoding(mode.getEncoding());
      //format.setExpandEmptyElements(true);
      writerXml.setFormat(format);
      try
      { writerXml.output( docu,  fOut );
        fOut.close();
      }
      catch(IOException exc)
      { throw new XmlException("Any error writing file:" + exc.getMessage());
      }
    }
    //catch(IOException exception){ throw new XmlException("write xml-output-file: " + fileOut.getAbsolutePath()); }
  }




  /**Output a xml tree to a file.
   *  @param xmlRoot The root Element of the created output XML-File.
   *  @param fileOut This file will be created or replaced.
   *  @param sEncoding The encoding, typicall "ISO-8859-1"
   * @exception  FileNotFoundException  if the file exists but is a directory
   *                   rather than a regular file, does not exist but cannot
   *                   be created, or cannot be opened for any other reason
   * @throws IOException if any error on writing at file system
   * @exception  SecurityException  if a security manager exists and its
   *               <code>checkWrite</code> method denies write access
   *               to the file.
  */
  public static void writeXmlDirect(Element xml, File fileOut, String sEncoding)
  throws XmlException, FileNotFoundException
  { FileWriter out = null;
    //try
    { try
			{
				out = new FileWriter(fileOut);
        writeXmlDirect(xml, out, sEncoding);
			} catch (IOException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    }
  }  
  /**Output a xml tree to a file.
   *  @param xmlRoot The root Element of the created output XML-File.
   *  @param fileOut This file will be created or replaced.
   *  @param sEncoding The encoding, typicall "ISO-8859-1"
   * @exception  FileNotFoundException  if the file exists but is a directory
   *                   rather than a regular file, does not exist but cannot
   *                   be created, or cannot be opened for any other reason
   * @throws IOException if any error on writing at file system
   * @exception  SecurityException  if a security manager exists and its
   *               <code>checkWrite</code> method denies write access
   *               to the file.
  */
  @SuppressWarnings("unchecked")
  public static void writeXmlDirect(Element xml, Writer out, String sEncoding)
  throws XmlException, IOException
  { //try
    out.write("<" + xml.getName());
  	{ //copy Attributes
      { List attributes = xml.getAttributes();
        Iterator iter = attributes.iterator();
        while(iter.hasNext())
        { Attribute attrib = ((Attribute)(iter.next()));
          //xmlOut.setAttribute(attrib.getName(), attrib.getValue());
          out.write(" " + attrib.getName() + "=" + "\"" + attrib.getValue() + "\"");
        }
      }
    }
    out.write(">");
  	
    { //copy AdditionalNamespaces
      List listAddNs = xml.getAdditionalNamespaces();
      if(listAddNs.size() >0)
      { Iterator iter = listAddNs.iterator();
        while(iter.hasNext())
        { Namespace ns = (Namespace)(iter.next());
          out.write(" xmlns:" + ns.getPrefix() + "=\"" + ns.getURI() + "\"");           
        }
      }
    }

    { List listChild = xml.getContent();
      //List listChildNew = new LinkedList();
      Iterator iterChild = listChild.iterator();
      while(iterChild.hasNext())
      { Content xmlChild = (Content)iterChild.next();
        //xmlChild.detach();  //:NOTE: detach causes a Iterator exception!
        if(xmlChild instanceof org.jdom.Text) // && bContendText)
        { org.jdom.Text text = ((org.jdom.Text)(xmlChild));
          String sContent = text.getText();
          //if(sContent.indexOf('<')>0 || )
          //if(sContent)
          out.write(sContent);
        }
        else if(xmlChild instanceof Element)
        { //other element:
          //call recursively this method, after it add the result element.
          writeXmlDirect((Element)xmlChild, out, sEncoding);
        }

      }

    }
  }





}













                                