/****************************************************************************
 * 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 Hartmut Schorrig, Germany, Pinzberg
 * @version 2009-07-02  (year-month-day)
 * list of changes: 
 * 2010-01-05 Hartmut The Topic-labels are searched in the current document first, see resolveInternalTopicHref(...)
 * 2009-12-12 Hartmut Correction of paths to files with sOutRefDirectory. Not the output files can placed at any location.
 * 2009-12-12 Hartmut Hyperlink-Associations where searched in the current document part, than in the genCtrl-file and than in a referenced hyperlink file.
 * 2009-07-02 Hartmut: A second wildcard is supported in labels.
 * 2006-05-00 Hartmut: creation
 *
 ****************************************************************************/
package org.vishia.xml.docuGen;

import java.io.File;
//import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;

import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.Namespace;
import org.vishia.util.FileSystem;
import org.vishia.util.SortedList;
import org.vishia.xmlSimple.WikistyleTextToSimpleXml;
import org.vishia.xmlSimple.XmlException;
import org.vishia.xmlSimple.XmlNode;
import org.vishia.xml.XmlExtensions;
import org.vishia.xml.XmlMReaderJdomSaxon;
import org.vishia.xml.XmlNodeJdom;
import org.vishia.xml.XslTransformer;
import org.vishia.xml.XslTransformer.FileTypeIn;

import org.vishia.mainCmd.MainCmd;
import org.vishia.mainCmd.MainCmd_ifc;
import org.vishia.mainCmd.Report;
//import vishia.sortedList.SortedList;
//import vishia.xslt.XslTransformer;




/** This class contains methods to correct hyperlink labels 
 * according requirements of XML documentation generation.
 */
public class CorrectHref
{
	private static final String sVersion = "2009-12-08"; 
    
	MainCmd_ifc console;
	
	/** Special Namespace for XhtmlPre. */
  private final Namespace nsPre = Namespace.getNamespace("pre", "http://www.vishia.de/2006/XhtmlPre"); 
	
  /**Instance to process input files. This instance holds informations about input files with several reading conditions 
   * and contains a xml parser call.
   */ 
  final XmlMReaderJdomSaxon xmlMReader = new XmlMReaderJdomSaxon();

  
  private static final class AnchorAndChapter
  { /**The anchor itself. */
    final Element xmlAnchor;
    /** The chapter to the anchor. */
    final Element xmlChapter;
    
    /**Construct it:
     * @param xmlChapter The chapter to the anchor.
     * @param xmlAnchor The anchor itself.
     */
    public AnchorAndChapter(Element xmlAnchor, Element xmlChapter)
    { this.xmlChapter = xmlChapter;
      this.xmlAnchor = xmlAnchor;
    }
    //public String toString(){ return xml
  }
  
  
  /**Index of all available anchors for hrefs inside this document.
	 * This Index is built in method {@link catchAllAnchors(Element)}
	 */
	protected TreeMap<String, AnchorAndChapter> listAnchors = new TreeMap<String, AnchorAndChapter>(); 
  
  /**The actual label for back href,it may be "", than use the chapter href.
   * 
   */  
	String sActLabel = "";
  
	/**Index of all hyperlink associations sorted to sLeft for fast search. 
	 * All Hyperlink associations are readed from input file on -i option,
	 * this list is built in method {@link setLabelAssociations(Element)}.
	 * All elements of this list are from type HyperlinkAssociation.
	 * A TreeMap can't be use because more as one element with the same key may be existing. 
	 */
  protected final SortedList listHyperlinkAssociationLeft 
  = new SortedList(new ArrayList<HyperlinkAssociation>(100))
  { @Override
  public String getKey(Object item)
		{ return ((HyperlinkAssociation)(item)).sLeft;
		}
  };
	
  
  Map<String, List<HyperlinkAssociation>> xxxindexHyperlinkAssociationLeft = new TreeMap<String, List<HyperlinkAssociation>>();
  
  
  static class HyperlinkAssociation
  {
  	private String sLeft;
  	private String sMiddle;
    private String sRight;
    private String sLeftDst;
    private String sMiddleDst;
    private String sRightDst;
  	
  	/** A possible content label. See ...*/
  	private String sContent;
  	
  }
  
	/**Index of all hyperlinks with cross reference entry
	 * All elements of this list are from type CrossHref. 
	 */
  protected final SortedList listHref 
  = new SortedList(new ArrayList<CrossHref>(100))
  { @Override
  public String getKey(Object item)
		{ return ((CrossHref)(item)).sHref;
		}
  };
	
  
  /** Class for one href, founded not in actual document.
   * 
   */
  private class CrossHref
  {
  	/** Key string*/
  	private final String sHref;

  	/**This element is assembled in the tree of HrefRoot for input for additional XSL script.
  	 */
  	private final Element xmlHrefEntry;
  	
  	/** The target element in the document, may be null */
  	//private Element xmlHrefTarget;
  	//private Element xmlBackref;
  	
  	
  	//private String sContent;
  	
  	private final List<Element> xmlHrefs;
  	
  	public CrossHref(String sHref, Element xmlHref, Element xmlHrefTarget, String sContent)
  	{ this.sHref = sHref;
  	  //this.xmlHref = xmlHref;
  	  //this.xmlHrefTarget = xmlHrefTarget;
  	  xmlHrefs = new LinkedList<Element>();
  	  xmlHrefEntry = new Element("HrefEntry", nsPre);
  	  xmlHrefEntry.setAttribute("name", sHref);
  	  if(xmlHrefTarget == null)
  	  { xmlHrefEntry.setAttribute("isHrefTarget", "true");
  	  }
  	  xmlHrefRoot.addContent(xmlHrefEntry);
  	}
  	
  	
  	
  	void addBackref(Element xmlHref, Element xmlChapter)
  	{ Element xmlBackref = new Element("Backref");
  	  xmlHrefs.add(xmlHref);
  	  xmlHrefEntry.addContent(xmlBackref);
  	  try
  	  {
	  	  String sChapterLabel = xmlChapter.getAttributeValue("id");
        xmlBackref.setAttribute("chapter-href", sChapterLabel);
        if(sActLabel.length() >0)
        { xmlBackref.setAttribute("div-href", sActLabel);
          xmlBackref.setAttribute("href", sActLabel);
        }
        else
        { xmlBackref.setAttribute("href", sChapterLabel);
        }
        Element xmlChapterTitle = xmlChapter.getChild("title", nsPre);
	  	  if(xmlChapterTitle != null)
	  	  { String sChapterTitle = xmlChapterTitle.getText();
	  	    xmlBackref.setAttribute("chapter-title", sChapterTitle);
	  	  }  
  	  }
  	  catch(Exception exc)
  	  { //may be null pointer because not available child or attribute in xmlChapter
  	  	xmlBackref.setAttribute("chapter-nonCompleteInfo", "true");
  	  }
  	}
  }
  
  /**Root Element of all href positions to create cross refs and back refs:
   * This Element will be the input for XSL translation to generate crossRefLists and Backrefs.
   * The root Element and the direct children have the name &lt;pre:Href>. 
   * The children have attributes href and some 
   * children &lt;Backref> with attributes chapter-href and chapter-title.
   * */
  Element xmlHrefRoot = new Element("HrefRoot", nsPre);
  
  
  /**The root element for cross reference XSLT input.
   * At least the inputfile is member of.
   */ 
  Element xmlRoot;
  
  /** List of chapter elements, which are destinations for cross ref informations.
   * 
   */
  private final List<Element> xmlCrossRefElements = new LinkedList<Element>();
  
	
  /** The xml file to correct hrefs.*/
  //String sFileInput;
  
  /** The xml output file with corrected hrefs.*/
  String sFileOutput;
  
  /** The control.xml file containing Element HyperlinkAssociations at second level.*/
  List<String> listFileCtrl = new LinkedList<String>();
  
  /** The additional XSL File to add cross reference hints. */
  String sFileXsl = null;
  
  /**The reference directory for the output file. Only the deepness of the directory is used
   * in respect to a relative label given in any label association. It is set by <code>-O:</code>.
   */
  String sOutRefDirectory;
  
  /**The ident of the document to search the HyperlinkAssociation in the document part of all control files.
   * It is set by <code>-D:</code>
   */
  String sIdentDocument;
  
  File fileXsl = null;

  /**The additional inputs for cross References, set on multiple -e options. */
  private final List<FileTypeIn> listFileIn = new LinkedList<FileTypeIn>();

  

  
  boolean readHyperlinkAssociations(String sFileHyperAssociations)
  {
    boolean bOk = true;
    File fileCtrl = new File(sFileHyperAssociations);
    Element xmlCtrl = null;
    console.writeInfoln("org.vishia.xml.docuGen.CorrectHref " + sVersion);
    console.reportln(Report.fineInfo, "Parameter: out=" + sFileOutput + ", ctrl=" + listFileCtrl.toString() 
        + ", sOutRefDir=" + sOutRefDirectory + ", sIdentDocu=" + sIdentDocument);
    try { xmlCtrl = XmlExtensions.readXmlFile(fileCtrl); } 
    catch (XmlException e)
    { System.err.println("CorrectHref: File not found: " + fileCtrl.getAbsolutePath());
      bOk = false;
    } 
    if(bOk)
    { Element xmlHyperlinkAssociations = xmlCtrl.getChild("HyperlinkAssociations");
      if(xmlHyperlinkAssociations == null)
      { console.writeInfoln("CorrectHref: no <HyperlinkAssociations> in file:" + fileCtrl.getAbsolutePath());
      }
      else
      { storeHyperlinkAssociation(xmlHyperlinkAssociations);
      }
      if(sIdentDocument != null){
        
        List<Element> listXmlDocu = xmlCtrl.getChildren("document");
        Iterator<Element> iterXmlDocu = listXmlDocu.iterator();
        boolean bDocuFound = false;
        Element xmlHyperLinksDocu = null;
        while(!bDocuFound && iterXmlDocu.hasNext()){
          Element xmlDocu =  iterXmlDocu.next();
          Attribute xmlIdent = xmlDocu.getAttribute("ident");
          if(xmlIdent != null && xmlIdent.getValue().equals(sIdentDocument)){
            bDocuFound = true;
            xmlHyperLinksDocu = xmlDocu.getChild("HyperlinkAssociations"); //may be null
          }
        }
        if(xmlHyperLinksDocu != null){
          storeHyperlinkAssociation(xmlHyperLinksDocu);
        }
      }
    }
    return bOk;
  }    
  
  
  boolean readAllHyperlinkAssociations()
  {
    boolean bOk = true;
    for(String sFileCtrl: listFileCtrl){
      bOk = readHyperlinkAssociations(sFileCtrl);
    }  
    return bOk;
  }
  
  
  /**sets the label association from a given xml tree. 
   * The xml element should contain elements like followed:
   * <pre>
   * &lt;Association href="name">value&lt;/Association>
   * &lt;Association href="name">value&lt;/Association>
   * </pre>
   * The name of the top level element may be other as shown, it is not tested.
   * @param xmlLabelAssignment The input xml tree with label assignment.
   */
  @SuppressWarnings("unchecked")
  private void storeHyperlinkAssociation(Element xmlLabelAssignment)
  { ListIterator iter = xmlLabelAssignment.getChildren("Association").listIterator();
  	while(iter.hasNext())
  	{ Element xmlAssociation = (Element)(iter.next());
		  String sLabel = xmlAssociation.getAttributeValue("href");
      if(sLabel.startsWith("/"))
        stop();
      int posWildcard = sLabel.indexOf('*');
		  if(posWildcard == 0)
		  {
		  	
		  }
		  else
		  { //no wildcard or wildcard not left.
		  	if(posWildcard < 0){ posWildcard = sLabel.length(); }
		    HyperlinkAssociation item = new HyperlinkAssociation();
		    item.sLeft = sLabel.substring(0, posWildcard);
		    if(posWildcard < (sLabel.length()-1))
		    { int posWildcard2 = sLabel.indexOf('*', posWildcard+1);
          if(posWildcard2 <0)
          { item.sRight = sLabel.substring(posWildcard +1);
            item.sMiddle = null;
          }
          else
          { if(posWildcard2 < (sLabel.length()-1))
            { item.sMiddle = sLabel.substring(posWildcard +1, posWildcard2);
              item.sRight = sLabel.substring(posWildcard2 +1);
            }
            else
            { item.sMiddle = sLabel.substring(posWildcard +1, posWildcard2);
              item.sRight = null;
            }
          }
		    }
		    else 
		    { item.sRight = null; 
		      item.sMiddle = null;
        }
			  
		    String sNewValue = xmlAssociation.getAttributeValue("dst");
			  posWildcard = sNewValue.indexOf('*');
			  if(posWildcard < 0) { posWildcard = sNewValue.length(); }
			  item.sLeftDst = sNewValue.substring(0, posWildcard);
			  if(posWildcard < (sNewValue.length()-1))
			  { int posWildcard2 = sNewValue.indexOf('*', posWildcard+1);
          if(posWildcard2 <0)
          { item.sRightDst = sNewValue.substring(posWildcard +1); 
            item.sMiddleDst = "";
          }
          else
          { if(posWildcard2 < (sNewValue.length()-1))
            { item.sMiddleDst = sNewValue.substring(posWildcard +1, posWildcard2);
              item.sRightDst = sNewValue.substring(posWildcard2 +1);
            }
            else
            { item.sMiddleDst = sNewValue.substring(posWildcard +1, posWildcard2);
              item.sRightDst = "";
            }
          }
        }
			  else 
			  { item.sRightDst = ""; 
          item.sMiddleDst = ""; 
        }
			  item.sContent = xmlAssociation.getAttributeValue("content");
			  
			  final int ixAss = listHyperlinkAssociationLeft.search(item.sLeft);
			  if(ixAss < 0){
			    //not yet in list
			    listHyperlinkAssociationLeft.add(item);  //add sorted to item.sLeft because it is a sorted list
			  }
			  else {
			    boolean bFound =false;
          //there is the equivalent sLeft-item in list:
			    int ix = ixAss;
			    HyperlinkAssociation assoc;
			    while(!bFound && --ix >=0 
			         && (assoc = (HyperlinkAssociation)listHyperlinkAssociationLeft.get(ix)).sLeft.equals(item.sLeft)
			         ){
			      bFound = checkAndReplaceItem(assoc, item);
			    }
			    ix = ixAss;
			    int ixMax = listHyperlinkAssociationLeft.size();
			    while(!bFound && ix < ixMax
               && (assoc = (HyperlinkAssociation)listHyperlinkAssociationLeft.get(ix)).sLeft.equals(item.sLeft)
               ){
            bFound = checkAndReplaceItem(assoc, item);
            ix +=1;
          }
          if(!bFound){
            //not found in existing entries: add it, it will be sorted in at any position in range of same sLeft. 
            listHyperlinkAssociationLeft.add(item);  //add sorted to item.sLeft because it is a sorted list
          }
			  }
		  }
  	}  
  }

  
  
  
  
  boolean checkAndReplaceItem(HyperlinkAssociation a1, HyperlinkAssociation item)
  {
    boolean bFound = (a1.sMiddle == item.sMiddle || a1.sMiddle != null && item.sMiddle != null && a1.sMiddle.equals(item.sMiddle))
           && (a1.sRight == item.sRight    || a1.sRight  != null && item.sRight  != null && a1.sRight.equals(item.sRight))
            ;
    if(bFound){
            /**Found the same entry, replace the entry in list by newer content. */
      a1.sLeftDst = item.sLeftDst;
      a1.sMiddleDst = item.sMiddleDst;
      a1.sRightDst = item.sRightDst;
      console.reportln(Report.fineInfo, "CorrectHref: " + item.sLeft 
          + "... replace " + a1.sLeftDst + "*" + a1.sMiddleDst + "*" + a1.sRightDst 
          + " with " + item.sLeftDst + "*" + item.sMiddleDst + "*" + item.sRightDst);     
    }
    return bFound;
  }
            
  
  
  @SuppressWarnings("unchecked")
  void catchAllAnchors(Element xmlData, Element xmlChapter, String sChapterNr, int nChapterNr)
  {
    Attribute attrIdChapter;
    if(xmlData.getName().equals("chapter"))
    { xmlChapter = xmlData;
      String sChapterNrAct = sChapterNr + nChapterNr;
      xmlData.setAttribute("chapternr", sChapterNrAct);
      attrIdChapter = xmlData.getAttribute("id");
      if(attrIdChapter == null)
      { //it should always have an id attribute!
        xmlData.setAttribute("id", sChapterNrAct);
      }
      sActLabel = "";  //on chapter level without inner structures the additional label is emtpy.
      //for nested chapters:
      sChapterNr = sChapterNr + nChapterNr + ".";  
      nChapterNr =0;
    }
  	Attribute attrAnchor = xmlData.getAttribute("id");
    if(attrAnchor != null)
    { String sAnchor = attrAnchor.getValue();
    	listAnchors.put(sAnchor, new AnchorAndChapter(xmlData, xmlChapter));
    }
    if(xmlData.getName().equals("a"))
    { attrAnchor = xmlData.getAttribute("name");
      if(attrAnchor != null)
      { listAnchors.put(attrAnchor.getValue(), new AnchorAndChapter(xmlData, xmlChapter));
      }
    }  
    if(xmlData.getName().equals("anchor"))
    { attrAnchor = xmlData.getAttribute("label");
      if(attrAnchor != null)
      { listAnchors.put(attrAnchor.getValue(), new AnchorAndChapter(xmlData, xmlChapter));
      }
    }  
    { ListIterator<Element> childs = xmlData.getChildren().listIterator();
		  while(childs.hasNext())
		  { Element xmlChild = childs.next(); 
        if(xmlChild.getName().equals("chapter"))
        { nChapterNr +=1; 
        }
        catchAllAnchors(xmlChild, xmlChapter, sChapterNr, nChapterNr);
		  }
		}
  }
  
  
  
  /**associates the label. 
  * The href may contain one asterisk on begin, end or inside as wildcard symbol. 
  * Than the determined begin, end or both is tested (startsWith, endsWith),
  * and the value deticated to the asterisk is saved and used for asterisk replacement
  * in the value.<br>
  * Example: With the specification <pre>
  *   &lt;Association href="pre*post">../MyFile/#*TheNewPost&lt;/Association>
  * <pre>
  * all labels preMyLabelpost, preSecondpost will be translated to
  * <code>../MyFile/#MyLabelTheNewPost</code>, 
  * <code>../MyFile/#MyLabelTheNewPost</code> and so on.
  */ 
  @SuppressWarnings("unchecked")
  void processInputTree(Element xmlData, Element xmlLastChapter)
  { Attribute attrId;
    if(xmlData.getName().equals("chapter"))
    { xmlLastChapter = xmlData;
      attrId = xmlData.getAttribute("id");
      sActLabel = "";  //on chapter level without inner structures the additional label is emtpy.
    }
    else if( (attrId = xmlData.getAttribute("id")) != null)
    {
      sActLabel = attrId.getValue();  //The Label for link.
    }
    if(xmlData.getAttribute("crossRefContent")!=null)
    { xmlCrossRefElements.add(xmlData);
    }

    Attribute attrHref = xmlData.getAttribute("href");
  	if(attrHref != null)
  	{ String sHref = attrHref.getValue();
		  if(sHref.startsWith("#=>XMI_(Wikipedia)"))
        stop();
      /* * commented/
		  { String sElement = xmlData.getName();
        if(sElement.equals("area"))
          stop();
      }
		  /* */
      if(sHref.startsWith("#Topic"))
      { sHref = resolveInternalTopicHref(xmlData, sHref);  //TODO: not tested, other concept used.
      }
      if(sHref != null && sHref.startsWith("#"))
      { String sLabel = sHref.substring(1);
		  	HyperlinkAssociation hrefAssociation = null;
		  	if(sHref.startsWith("#thisDownload:_"))
		  		stop();
        if(sHref.startsWith("#/"))
          stop();
        int ix = listHyperlinkAssociationLeft.search(sLabel);
		  	if(ix < 0){ ix = -ix-2; }
		  	//ix is -1 if a label is given lower as the first entry.
		  	if(ix >=0)
		  	{ boolean found = false;
		  	  boolean neverFound = false;
		  		int ix1 = ix;
		  		int posMiddle = -2;  //-2 is not used.
		  	  while(!found && !neverFound && ix < listHyperlinkAssociationLeft.size())
			    { hrefAssociation = (HyperlinkAssociation)listHyperlinkAssociationLeft.get(ix);
		  			if( !sLabel.startsWith(hrefAssociation.sLeft))
		  			{ neverFound = true; //abort the loop because it is outside the range in list.
		  			}
		  			else
		  			{ int end1 = hrefAssociation.sLeft.length();
		  			  if( ( hrefAssociation.sRight == null || sLabel.endsWith(hrefAssociation.sRight))
		  			       &&( hrefAssociation.sMiddle == null 
		  			         || (posMiddle = sLabel.indexOf(hrefAssociation.sMiddle, end1)) >=0
		  			       ) )
  			      { found = true;
  			      }
  			      else
  			      { ix += 1;
  			      }
		  			}
			    } 
		  	  if(!found)
		  	  { ix = ix1 -1;
		  	    neverFound = false;
		  	  	while(!found && !neverFound && ix >= 0)
				    { hrefAssociation = (HyperlinkAssociation)listHyperlinkAssociationLeft.get(ix);
			  			if( !sLabel.startsWith(hrefAssociation.sLeft))
			  			{ neverFound = true; //abort the loop because it is outside the range in list.
			  			}
  			  		else if( ( hrefAssociation.sRight == null || sLabel.endsWith(hrefAssociation.sRight))
                     &&( hrefAssociation.sMiddle == null 
                       || (posMiddle = sLabel.indexOf(hrefAssociation.sMiddle)) >=0
                     ) )
              { found = true;
				      }
				      else
				      { ix -= 1;
				      }
				    }
		  	  }
		  	  if(found)
		  	  {
			  		//dst label without given start and end part from src,
			  	  final String sLeftDst2;
			  	  final String sLeftDst1;
		  	    if(hrefAssociation.sLeftDst.startsWith("$")){
			  	    sLeftDst1 = replaceDstRoot(hrefAssociation.sLeftDst);
		  	    } else {
		  	      sLeftDst1 = hrefAssociation.sLeftDst;
		  	    }
		  	    if(sLeftDst1 == null){
		  	      console.reportln(Report.fineInfo, hrefAssociation.sLeft + " = " + hrefAssociation.sLeftDst + ": no environment found.");
		  	      removeHref(xmlData);
		  	    } else {  
  		  	    /*
		  	      if(sLeftDst1.startsWith("#") || sLeftDst1.startsWith("http")){
  		  	      sLeftDst2 = sLeftDst1;
  		  	    } else {
  		  	      //sLeftDst2 = replaceDirectoryDeepness(sLeftDst1);
  		  	      sLeftDst2 = FileSystem.relativatePath(sLeftDst1, sOutRefDirectory);
  		  	    }
  		  	    */
  		  	    int start = hrefAssociation.sLeft != null ? hrefAssociation.sLeft.length() : 0; 
  		  		  int end =  sLabel.length() - (hrefAssociation.sRight != null ? hrefAssociation.sRight.length() : 0); 
  			      if(posMiddle < 0)
  			      {			      
                sHref = sLeftDst1 + sLabel.substring(start, end) //may be ""
                      + hrefAssociation.sRightDst;
  			      }
  			      else
  			      {
    			      int posStart2 = posMiddle + hrefAssociation.sMiddle.length();
                //dst label with start and end part from dst.
    		  		  sHref = sLeftDst1 + sLabel.substring(start, posMiddle) 
    		  		        + hrefAssociation.sMiddleDst + sLabel.substring(posStart2, end) //may be ""
    		  		        + hrefAssociation.sRightDst;
  			      }  
  		        sHref = checkAndReplaceHrefText(xmlData, sHref);
  		  	    xmlData.setAttribute("href", sHref);
		  	    }
		  	    
          }
			  	if(!found)
			  	{ hrefAssociation = null;  //do not use by hazard! 
			  	}
		  	}
		  	//sHref may be changed if association is found!	
		  	if(sHref != null && sHref.startsWith("#")) //test the resulting sHref.
				{ /**due to hyperlink associations the href is not associated to an external file.
				   * test whether the label is found in actual document:
				   */ 
					sLabel = sHref.substring(1);
		  		AnchorAndChapter xmlAnchor = listAnchors.get(sLabel);  
				  String sCrossrefContent = (hrefAssociation != null ? hrefAssociation.sContent : null);
					if(xmlAnchor != null){
				    addInternHref(sLabel, xmlData, xmlLastChapter, xmlAnchor.xmlAnchor, sCrossrefContent);
					}
					if( xmlAnchor == null && sCrossrefContent == null)
					{ //wether an anchor inside the document nor the request to create a content for cross references:
						console.reportln(Report.fineInfo,"CorrectHref: removed href: \"" + sHref + "\"");
					  removeHref(xmlData);
					}
				}
  	  }  
  	}
  	{ //for nested chapters
  		ListIterator<Element> childs = xmlData.getChildren().listIterator();
  	  while(childs.hasNext())
  	  { Element xmlChild = childs.next(); 
  	  	processInputTree(xmlChild, xmlLastChapter);
  	  }
  	}
  }

  
  
  
  
  
  /**Replaces the left part of input with the content of the environment variable,
   * which name is given in this left part. 
   * @param sLeftDstInput input, it starts with '$'. The left part is the part to the first /.
   *        This left part builds a name of an environment variable.
   * @return replaces environment variable with the content, or null if the environment variable isn't found.
   *        In the second case the label shouldn't be replaced. 
   */
  private String replaceDstRoot(String sLeftDstInput)
  {
    int pos2 = sLeftDstInput.indexOf('/');
    String sRootLabel = sLeftDstInput.substring(1, pos2);  //after $
    String sRootDst = System.getenv(sRootLabel);
    if(sRootDst == null || sRootDst.length() == 0){
      return null;
    } else {
      String sLeftDst = sRootDst + sLeftDstInput.substring(pos2);
      return sLeftDst;
    }  
  }
  
  
  
  
  /**TODO: test usage of {@link org.vishia.util.FileSystem#relativatePath(String, String)}
   * 
   * @param sInput
   * @return
   */
  private String replaceDirectoryDeepness(String sInput)
  { int posHtmlRef =0;
    int posInput = 0;
    while(posHtmlRef >=0 && sInput.length() >= (posInput+3) && sInput.substring(posInput, posInput+3).equals("../")){
      //relative path to left
      if(sOutRefDirectory.substring(posHtmlRef, posHtmlRef+3).equals("../")){
         posHtmlRef +=3;  //both files are more left, delete 1 level of  ../
         posInput +=3;
      }
      else {
        int posSlash = sOutRefDirectory.indexOf('/', posHtmlRef);
        if(posSlash >=0){
          posHtmlRef =posSlash +1;  //after '/'
          posInput -=3;  //go to left, because the output file is more right.
        }
        else {
          posHtmlRef = -1; //to break.
        }
        while(posInput < 0){
          posInput +=3;
          sInput = "../" + sInput; //it may be errornuoes because the input is more left as a root.
        }
      }
    }
    String sOutput = sInput.substring(posInput);
    return sOutput;
  }
  
  
  
  void removeHref(Element xmlData)
  {
    xmlData.removeAttribute("href");
    xmlData.setName("span");  //instead <a href=...>
    xmlData.setAttribute("class", "removedHref");
          
  }
  
  
  
  
  /**Replaces some special texts in the hyperlink.
   * <ul>
   * <li>$chapter: Replace with chapter text.
   * </ul>
   * @param xmlNode
   */
  private String checkAndReplaceHrefText(Element xmlNode, String sHref)
  { String sHrefRet = sHref;
    String sHrefText = xmlNode.getTextNormalize();
    if(sHref.equals("#Topic:.orgVishiaXmlDocu.href.hrefExchg."))
      stop();
    final AnchorAndChapter xmlAnchor;
    if(sHref.startsWith("#")){
      xmlAnchor = listAnchors.get(sHref.substring(1));
      if(xmlAnchor == null){
        console.reportln(Report.fineInfo,"removed href: \"" + sHref + "\"");
        xmlNode.removeAttribute("href");
        xmlNode.setName("span");  //instead <a href=...>
        xmlNode.setAttribute("class", "removedHref");
      } else {
        //anchor found:
        int posChapter = sHrefText.indexOf("$chapter");
        if(posChapter >=0){
          stop();
        }
      }
    }
    else{
      /**Possible: Search known anchors in other documents, using a hyper file. */
      xmlAnchor = null;
      if( !sHref.startsWith("http") && !sHref.startsWith("!")){
        /*Handle all relative paths to other files. */
        //sLeftDst2 = replaceDirectoryDeepness(sLeftDst1);
    	if(sHref.startsWith("XRPG/"))
    	  stop();
    	sHrefRet = FileSystem.relativatePath(sHref, sOutRefDirectory);
      } else {
        sHrefRet = sHref;
      }
    }  
    if(xmlAnchor != null){
      int posChapter = sHrefText.indexOf("$chapter");
      if(posChapter >=0){
        /**Replace with the number and text of chapter. */
        if(sHref.startsWith("#")){
          /**Located in current document: */
          final String sChapterTitle = xmlAnchor.xmlChapter.getChildTextNormalize("title", nsPre);
          final String sChapterNr = xmlAnchor.xmlChapter.getAttributeValue("chapternr");
          final String sHrefTextNew = sHrefText.substring(0, posChapter) 
                                    + sChapterNr + " " + sChapterTitle 
                                    + sHrefText.substring(posChapter + 8);
          sHrefRet = "#" + xmlAnchor.xmlChapter.getAttributeValue("id");
          xmlNode.setText(sHrefTextNew);
        } else {
          /**Label in another document: */
          String sText = sHrefText.substring(0, posChapter) + " " + sHref +" " + sHrefText.substring(posChapter+8); 
          xmlNode.setText(sText); //replace unuseable $chapter with the hyperlink, no other informations.
        }
      }  
    }  
    return sHrefRet;
  }
  
  
  
  /**adds one entry of href.
   * 
   * @param sHref Label to search quickly
   * @param xmlAct The actual element, it contains the href
   * @param xmlLastChapter The Link to the chapter
   * @param xmlHrefTarget may be null, than the reference is not performed.
   * @param sContent String for creation cross reference content. 
   */
  void addInternHref(String sHref, Element xmlAct, Element xmlLastChapter, Element xmlHrefTarget, String sContent)
  { CrossHref hrefEntry = (CrossHref)listHref.get(sHref);
    if(hrefEntry == null)
    { hrefEntry = new CrossHref(sHref, xmlAct, xmlHrefTarget, sContent);
      listHref.add(hrefEntry);
    }
    hrefEntry.addBackref(xmlAct, xmlLastChapter);
  }  
  
  
  
  /**Tries to resolve a "#Topic..."-reference with an existing topic in the own document.
   * If the Topic-label is found internally, and a chapter is associated to the label, 
   * the hyperlink-reference will be redirected to the start of the chapter. 
   * If the hyperlink-text starts with "Topic:" (normally, if no special text is given), than
   * the hyperlink-text will be changed to "Chapter: CHAPTERTITLE" of the found chapter.
   *  
   * @param xmlHref The element which contains the sHref. It will be changed if it is a inner href.
   * 
   * @param sHref starts with '#Topic'. A topic reference. 
   * It may be start with "#Topic:_", #Topic:" or "#Topic:."
   * 
   * @return null if the sHref is processed as internal href, else the input sHref. 
   */
  private String resolveInternalTopicHref(Element xmlHref, String sHref)
  { String sHref1;
    if(sHref.charAt(7) == '_' || sHref.charAt(7) == '.') {
      //Variants Topic:_ or Topic:.
      //Internally a Topic is written "Topic.A.B."
      sHref1 = "Topic." + sHref.substring(8);
    } else {
      sHref1 = "Topic." + sHref.substring(7);
    }
    if(!sHref1.endsWith(".")){
      sHref1 += "."; //The internal Topic.xxx. label has a dot at last. 
    }
    AnchorAndChapter anchor = listAnchors.get(sHref1);
    if(anchor != null){
      if(anchor.xmlChapter != null){
        String sId = anchor.xmlChapter.getAttributeValue("id");
        if(sId != null){
          xmlHref.setAttribute("href", "#" + sId);
          sHref1 = null;  //return null.
        }
        String sHrefText = xmlHref.getText();
        final String sChapterTitle = anchor.xmlChapter.getChildTextNormalize("title", nsPre);
        if(sChapterTitle != null){
          final String sChapterNr = anchor.xmlChapter.getAttributeValue("chapternr");
          final String sHrefTextNew;
          int posChapter = sHrefText.indexOf("$chapter");
          if(posChapter >=0){ 
            //The HrefText contains a placeholder for chapter-title
            sHrefTextNew = sHrefText.substring(0, posChapter) 
                         + sChapterNr + " " + sChapterTitle 
                         + sHrefText.substring(posChapter + 8);
            xmlHref.setText(sHrefTextNew);
          } else if(sHrefText.startsWith("Topic:")){ 
            //The HrefText contains the same Topic label 
            sHrefTextNew = "Chapter: " 
                         + sChapterNr + " " + sChapterTitle; 
            xmlHref.setText(sHrefTextNew);
          }
        }
      } 
      if(sHref1 != null) {
        xmlHref.setAttribute("href", sHref1);
        sHref1 = null;
      }
    } else {
      //Topic not found in same document, handle it outside.
      sHref1 = sHref;
    }
    return sHref1;
  }
  
  
  
  //adds one entry for cross refernce
  boolean xxxaddCrossReferenceEntry(String sHref, Element xmlLastChapter)
  { boolean bFound = false;
  	CrossHref crossEntry = (CrossHref)listHref.get(sHref);
    if(crossEntry != null)
    { bFound = true;
    	
    }
    else if(fileXsl != null)
    { //additinal XSL script is given to generate absent hyperlink targets.
  	  xmlRoot.setAttribute("crossRefContent", sHref);
		  //Try to create a XML tree via XSLT
			Element xmlCrossOutput = null;
			Element xmlCrossBackref = null;
		  try
		  { xmlCrossOutput = XmlExtensions.xslTransformXml(xmlRoot, fileXsl);}
		  catch(XmlException exception)
		  { console.writeError("transform Exception", exception);
		  }
    	if(xmlCrossOutput != null && xmlCrossOutput.getName().equals("noCrossRefContent"))
    	{ xmlCrossOutput = null;
    	}
    	else
    	{ xmlCrossBackref = searchXmlCrossBackref(xmlCrossOutput);
    	}
    	//may be with null as xmlCrossOutput:
    	crossEntry = new CrossHref(sHref, xmlCrossOutput, xmlCrossBackref, null);  
      listHref.add(crossEntry);
      bFound = (xmlCrossOutput != null);
    }
    if(crossEntry != null)
    { crossEntry.addBackref(xmlLastChapter, null);
    }
  	return bFound;
  }
  

  
  
  @SuppressWarnings("unchecked")
  private static Element searchXmlCrossBackref(Element parent)
	{ Element xmlCrossBackref = null;
		if(parent.getAttribute("crossBackref")!= null)
		{ xmlCrossBackref = parent;
		}
		else
		{ List<org.jdom.Element> children = parent.getChildren();
		  if(children != null)
		  { Iterator<org.jdom.Element> iter = children.iterator();
		    while(xmlCrossBackref == null && iter.hasNext())
		    { //recursively
		    	xmlCrossBackref = searchXmlCrossBackref(iter.next());
		    }
		  }
		}  
		return xmlCrossBackref;  //may be null if not found.
	}
  

	/**For every detect crossRefContent Element a XSL translation is called, 
	 * the output is ranged as children in the crossRefContent Element.  
	 */
  @SuppressWarnings("unchecked")
  void processCrossReferences()
  {
  	Iterator<Element> iter = xmlCrossRefElements.iterator();
  	while(iter.hasNext())
  	{ Element xmlCrossRefOutputElement = iter.next();
  	  String sContent = xmlCrossRefOutputElement.getAttributeValue("crossRefContent");
  	  //Information for XSL, to detect which content is to be create:
  	  xmlRoot.setAttribute("crossRefContent", sContent);
		  //Try to create a XML tree via XSLT
			xmlRoot.addContent(xmlHrefRoot);  //add to the input tree to do somewhat with,
		  // and all other data are also available.
			Element xmlCrossOutput = null;
		  try{ xmlCrossOutput = XmlExtensions.xslTransformXml(xmlRoot, fileXsl);}
		  catch(XmlException exception)
		  { console.writeError("transform Exception", exception);
		  }
		  if(xmlCrossOutput != null)
		  { Iterator<Element> iterOut = xmlCrossOutput.getChildren().iterator();
		    List<Element> list = new LinkedList<Element>();
		    //park it in a simple list because otherwise a ConcurrentListModification exception is thrown
		    while(iterOut.hasNext())
		    { list.add(iterOut.next());  
		    }
		    iterOut = list.iterator();
		    while(iterOut.hasNext())
		    { Element xmlOut = iterOut.next();
		      xmlOut.detach();
		  	  xmlCrossRefOutputElement.addContent(xmlOut);
		    }
		  }
  	}  
  }
  

  /** Add back refs as &lt;li> on every Element with attribute addBackRefs="LABEL"
   * 
   *
   */ 
  void addBackRefs()
  {
  	
  }
  
  
  
  
  
  /** First the arguments sFileXml and sFileCtrl have to be setted.
   * @throws XmlException 
   * 
   *
   */  
	void execute(MainCmd_ifc console) throws XmlException
	{ this.console = console;
		boolean bOk = true;
		bOk = readAllHyperlinkAssociations();
    Element xmlData = null;  
    if(bOk)
	  { Element xmlRoot = new Element("root");
      int nrofFiles = xmlMReader.readInputsToJdomElement(xmlRoot);
      if(nrofFiles <= 0)
      { System.err.println("Input File not found: ");
			  bOk = false;
			}
      else
      { xmlData = (Element)xmlRoot.getChildren().get(0);
        WikistyleTextToSimpleXml wikiFormat = new WikistyleTextToSimpleXml();
        XmlNode nodeTop = new XmlNodeJdom(xmlData);  //this class wraps xmlData, xmlData will be converted itself.      
        wikiFormat.testXmlTreeAndConvert(nodeTop);        
        //wikiFormat.testXmlTreeAndConvert(xmlData);
      }
	  }
    if(bOk)
	  { if(sFileXsl != null)
	    { fileXsl = new File(sFileXsl);
	    	xmlRoot = new Element("root");
  		  xmlData.detach();  //from its document.
		    xmlRoot.addContent(xmlData);
		    Iterator<FileTypeIn> iter = listFileIn.iterator();
		    while(iter.hasNext())
		    { XslTransformer.FileTypeIn input = iter.next(); 
		      try
		      { Element xmlInput = input.readXmlFile(); 
			      xmlInput.detach();
			      xmlRoot.addContent(xmlInput);
		      }
			    catch(Exception exc)			
			    { System.err.println("File problem found: " + input.getAbsolutePath());
					  bOk = false;
					} 
		    }
	    }
	  }
    if(bOk)
    { catchAllAnchors(xmlData, xmlData, "", 0);
	  	/**core routine. */
      processInputTree(xmlData, xmlData);
	  }
    
    if(bOk && fileXsl != null)
    { processCrossReferences();
    }
    if(bOk)
	  { File fileXmlOut = new File(sFileOutput);
	    XmlExtensions.XmlMode mode = new XmlExtensions.XmlMode();
	    mode.setXmlIso8859(); 
	    xmlData.detach();  //from the input Document
	    try {	XmlExtensions.writeXmlFile(xmlData, fileXmlOut, mode); } 
		  catch (Exception e)
			{ System.err.println("File not writeable: " + fileXmlOut.getAbsolutePath() + e.getMessage());
			  bOk = false;
			} 
    }
	  	
	}
  
	
	
	
	
	
	
  /**It's a debug helper. The method is empty, but it is a mark to set a breakpoint. */
  void stop()
  { //only for debug
  }
  
  
  /**Main-Programm called as command
   * <pre>
      super.addAboutInfo("CorrectHref");
      super.addAboutInfo("made by Hartmut Schorrig, 2007-03-02 / " + sVersion);
      super.addHelpInfo("Corrects hyperlink references");
      super.addHelpInfo("param: -i:INPUT -y:OUTPUT {-c:CTRL} [{-e:XML} -t:XSL] [-o:HTML] [-d:IDENT]");
      super.addHelpInfo("-i:INPUT  the input xml file to correct hrefs.");
      super.addHelpInfo("-y:OUTPUT the output xml file with corrected hrefs.");
      super.addHelpInfo("-c:CTRL the control.xml file containing Element HyperlinkAssociations at second level.");
      super.addHelpInfo("-e:XML  an additional XML file containing additional for the cross references, see -tXSL.");
      super.addHelpInfo("-t:XSL  an additional XSL file to generate cross references to external sources.");
      super.addHelpInfo("-o:HTML The output file to generate, only the deepness of directory is used.");
      super.addHelpInfo("-d:IDENT The ident of the document inside all of the CTRL-Files, than its HyperlinkAssociation is used.");
      * </pre>
   * 
   * @param argc cmd-line-parameter
   */
  public static void main(String[] argc)
  { CorrectHref exec = new CorrectHref(); 
  	Main console = exec.new Main(argc);
    if(console.evaluateCmdLineArgs())
    { exec.xmlMReader.setReport(console);
      try{ exec.execute(console); }
      catch(XmlException exc)
      { console.writeError("unexpected", exc);
        console.setExitErrorLevel(Report.exitWithErrors);
      }
    }
    console.exit();
  }
  
  /**Main-class to organize cmd line parsing.
   */
  private class Main extends MainCmd
  {
    
    
    Main(String[] args)
	  { super(args);
	    //super.addHelpInfo(getAboutInfo());
	    super.addAboutInfo("CorrectHref");
	    super.addAboutInfo("made by Hartmut Schorrig, 2007-03-02 / " + sVersion);
	    super.addHelpInfo("Corrects hyperlink references");
	    super.addHelpInfo("param: -i:INPUT -y:OUTPUT {-c:CTRL} [{-e:XML} -t:XSL] [-o:HTML] [-d:IDENT]");
	    super.addHelpInfo("-i:INPUT  the input xml file to correct hrefs.");
	    super.addHelpInfo("-y:OUTPUT the output xml file with corrected hrefs.");
	    super.addHelpInfo("-c:CTRL the control.xml file containing Element HyperlinkAssociations at second level.");
	    super.addHelpInfo("-e:XML  an additional XML file containing additional for the cross references, see -tXSL.");
	    super.addHelpInfo("-t:XSL  an additional XSL file to generate cross references to external sources.");
      super.addHelpInfo("-o:HTML The output file to generate, only the deepness of directory is used.");
      super.addHelpInfo("-d:IDENT The ident of the document inside all of the CTRL-Files, than its HyperlinkAssociation is used.");
      super.addStandardHelpInfo();
	  }
  	
    /** processes the cmd line arguments. */
    private boolean evaluateCmdLineArgs()
    { boolean bOk = true;
      try{ super.parseArguments(); }
      catch(Exception exception)
      { setExitErrorLevel(MainCmd_ifc.exitWithArgumentError);
        bOk = false;
      }
    	return bOk;
    }
    
		@Override
    protected boolean checkArguments()
		{
			// TODO Auto-generated method stub
			return true;
		}

		@Override
    protected boolean testArgument(String argc, int nArg) //throws ParseException
	  { boolean bOk = true;  //set to false if the argc is not passed
			int posArg = (argc.length()>=2 && argc.charAt(2)==':') ? 3 : 2; //with or without :
      
	    //if(argc.startsWith("-i"))     { sFileInput   = getArgument(2); }
      if(argc.startsWith("-i")) { xmlMReader.addInputFile(getArgument(posArg),XmlMReaderJdomSaxon.mExpandWikiFormat); }
	    else if(argc.startsWith("-y")){ sFileOutput   = getArgument(posArg); }
	    else if(argc.startsWith("-c")){ listFileCtrl.add(getArgument(posArg)); }
	    else if(argc.startsWith("-e")){ listFileIn.add(new XslTransformer.FileTypeIn(getArgument(posArg),0));}
      else if(argc.startsWith("-t")){ sFileXsl  = getArgument(posArg); }
      else if(argc.startsWith("-o")){ sOutRefDirectory = getArgument(posArg).replace('\\', '/'); }
      else if(argc.startsWith("-d")){ sIdentDocument = getArgument(posArg); }
      else bOk=false;
	
	    return bOk;
	  }
  	
		
  }

}
