001/****************************************************************************
002 * Copyright/Copyleft: 
003 * 
004 * For this source the LGPL Lesser General Public License, 
005 * published by the Free Software Foundation is valid.
006 * It means:
007 * 1) You can use this source without any restriction for any desired purpose.
008 * 2) You can redistribute copies of this source to everybody.
009 * 3) Every user of this source, also the user of redistribute copies 
010 *    with or without payment, must accept this license for further using.
011 * 4) But the LPGL ist not appropriate for a whole software product,
012 *    if this source is only a part of them. It means, the user 
013 *    must publish this part of source,
014 *    but don't need to publish the whole source of the own product.
015 * 5) You can study and modify (improve) this source 
016 *    for own using or for redistribution, but you have to license the
017 *    modified sources likewise under this LGPL Lesser General Public License.
018 *    You mustn't delete this Copyright/Copyleft inscription in this source file.    
019 *
020 * @author Hartmut Schorrig, Germany, Pinzberg
021 * @version 2009-07-02  (year-month-day)
022 * list of changes: 
023 * 2010-01-05 Hartmut The Topic-labels are searched in the current document first, see resolveInternalTopicHref(...)
024 * 2009-12-12 Hartmut Correction of paths to files with sOutRefDirectory. Not the output files can placed at any location.
025 * 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.
026 * 2009-07-02 Hartmut: A second wildcard is supported in labels.
027 * 2006-05-00 Hartmut: creation
028 *
029 ****************************************************************************/
030package org.vishia.xml.docuGen;
031
032import java.io.File;
033//import java.nio.charset.Charset;
034import java.text.ParseException;
035import java.util.ArrayList;
036import java.util.Iterator;
037import java.util.LinkedList;
038import java.util.List;
039import java.util.ListIterator;
040import java.util.Map;
041import java.util.TreeMap;
042
043import org.jdom.Attribute;
044import org.jdom.Element;
045import org.jdom.Namespace;
046import org.vishia.util.FileSystem;
047import org.vishia.util.SortedList;
048import org.vishia.xmlSimple.WikistyleTextToSimpleXml;
049import org.vishia.xmlSimple.XmlException;
050import org.vishia.xmlSimple.XmlNode;
051import org.vishia.xml.XmlExtensions;
052import org.vishia.xml.XmlMReaderJdomSaxon;
053import org.vishia.xml.XmlNodeJdom;
054import org.vishia.xml.XslTransformer;
055import org.vishia.xml.XslTransformer.FileTypeIn;
056
057import org.vishia.mainCmd.MainCmd;
058import org.vishia.mainCmd.MainCmd_ifc;
059import org.vishia.mainCmd.Report;
060//import vishia.sortedList.SortedList;
061//import vishia.xslt.XslTransformer;
062
063
064
065
066/** This class contains methods to correct hyperlink labels 
067 * according requirements of XML documentation generation.
068 */
069public class CorrectHref
070{
071        private static final String sVersion = "2009-12-08"; 
072    
073        MainCmd_ifc console;
074        
075        /** Special Namespace for XhtmlPre. */
076  private final Namespace nsPre = Namespace.getNamespace("pre", "http://www.vishia.de/2006/XhtmlPre"); 
077        
078  /**Instance to process input files. This instance holds informations about input files with several reading conditions 
079   * and contains a xml parser call.
080   */ 
081  final XmlMReaderJdomSaxon xmlMReader = new XmlMReaderJdomSaxon();
082
083  
084  private static final class AnchorAndChapter
085  { /**The anchor itself. */
086    final Element xmlAnchor;
087    /** The chapter to the anchor. */
088    final Element xmlChapter;
089    
090    /**Construct it:
091     * @param xmlChapter The chapter to the anchor.
092     * @param xmlAnchor The anchor itself.
093     */
094    public AnchorAndChapter(Element xmlAnchor, Element xmlChapter)
095    { this.xmlChapter = xmlChapter;
096      this.xmlAnchor = xmlAnchor;
097    }
098    //public String toString(){ return xml
099  }
100  
101  
102  /**Index of all available anchors for hrefs inside this document.
103         * This Index is built in method {@link catchAllAnchors(Element)}
104         */
105        protected TreeMap<String, AnchorAndChapter> listAnchors = new TreeMap<String, AnchorAndChapter>(); 
106  
107  /**The actual label for back href,it may be "", than use the chapter href.
108   * 
109   */  
110        String sActLabel = "";
111  
112        /**Index of all hyperlink associations sorted to sLeft for fast search. 
113         * All Hyperlink associations are readed from input file on -i option,
114         * this list is built in method {@link setLabelAssociations(Element)}.
115         * All elements of this list are from type HyperlinkAssociation.
116         * A TreeMap can't be use because more as one element with the same key may be existing. 
117         */
118  protected final SortedList listHyperlinkAssociationLeft 
119  = new SortedList(new ArrayList<HyperlinkAssociation>(100))
120  { @Override
121  public String getKey(Object item)
122                { return ((HyperlinkAssociation)(item)).sLeft;
123                }
124  };
125        
126  
127  Map<String, List<HyperlinkAssociation>> xxxindexHyperlinkAssociationLeft = new TreeMap<String, List<HyperlinkAssociation>>();
128  
129  
130  static class HyperlinkAssociation
131  {
132        private String sLeft;
133        private String sMiddle;
134    private String sRight;
135    private String sLeftDst;
136    private String sMiddleDst;
137    private String sRightDst;
138        
139        /** A possible content label. See ...*/
140        private String sContent;
141        
142  }
143  
144        /**Index of all hyperlinks with cross reference entry
145         * All elements of this list are from type CrossHref. 
146         */
147  protected final SortedList listHref 
148  = new SortedList(new ArrayList<CrossHref>(100))
149  { @Override
150  public String getKey(Object item)
151                { return ((CrossHref)(item)).sHref;
152                }
153  };
154        
155  
156  /** Class for one href, founded not in actual document.
157   * 
158   */
159  private class CrossHref
160  {
161        /** Key string*/
162        private final String sHref;
163
164        /**This element is assembled in the tree of HrefRoot for input for additional XSL script.
165         */
166        private final Element xmlHrefEntry;
167        
168        /** The target element in the document, may be null */
169        //private Element xmlHrefTarget;
170        //private Element xmlBackref;
171        
172        
173        //private String sContent;
174        
175        private final List<Element> xmlHrefs;
176        
177        public CrossHref(String sHref, Element xmlHref, Element xmlHrefTarget, String sContent)
178        { this.sHref = sHref;
179          //this.xmlHref = xmlHref;
180          //this.xmlHrefTarget = xmlHrefTarget;
181          xmlHrefs = new LinkedList<Element>();
182          xmlHrefEntry = new Element("HrefEntry", nsPre);
183          xmlHrefEntry.setAttribute("name", sHref);
184          if(xmlHrefTarget == null)
185          { xmlHrefEntry.setAttribute("isHrefTarget", "true");
186          }
187          xmlHrefRoot.addContent(xmlHrefEntry);
188        }
189        
190        
191        
192        void addBackref(Element xmlHref, Element xmlChapter)
193        { Element xmlBackref = new Element("Backref");
194          xmlHrefs.add(xmlHref);
195          xmlHrefEntry.addContent(xmlBackref);
196          try
197          {
198                  String sChapterLabel = xmlChapter.getAttributeValue("id");
199        xmlBackref.setAttribute("chapter-href", sChapterLabel);
200        if(sActLabel.length() >0)
201        { xmlBackref.setAttribute("div-href", sActLabel);
202          xmlBackref.setAttribute("href", sActLabel);
203        }
204        else
205        { xmlBackref.setAttribute("href", sChapterLabel);
206        }
207        Element xmlChapterTitle = xmlChapter.getChild("title", nsPre);
208                  if(xmlChapterTitle != null)
209                  { String sChapterTitle = xmlChapterTitle.getText();
210                    xmlBackref.setAttribute("chapter-title", sChapterTitle);
211                  }  
212          }
213          catch(Exception exc)
214          { //may be null pointer because not available child or attribute in xmlChapter
215                xmlBackref.setAttribute("chapter-nonCompleteInfo", "true");
216          }
217        }
218  }
219  
220  /**Root Element of all href positions to create cross refs and back refs:
221   * This Element will be the input for XSL translation to generate crossRefLists and Backrefs.
222   * The root Element and the direct children have the name &lt;pre:Href>. 
223   * The children have attributes href and some 
224   * children &lt;Backref> with attributes chapter-href and chapter-title.
225   * */
226  Element xmlHrefRoot = new Element("HrefRoot", nsPre);
227  
228  
229  /**The root element for cross reference XSLT input.
230   * At least the inputfile is member of.
231   */ 
232  Element xmlRoot;
233  
234  /** List of chapter elements, which are destinations for cross ref informations.
235   * 
236   */
237  private final List<Element> xmlCrossRefElements = new LinkedList<Element>();
238  
239        
240  /** The xml file to correct hrefs.*/
241  //String sFileInput;
242  
243  /** The xml output file with corrected hrefs.*/
244  String sFileOutput;
245  
246  /** The control.xml file containing Element HyperlinkAssociations at second level.*/
247  List<String> listFileCtrl = new LinkedList<String>();
248  
249  /** The additional XSL File to add cross reference hints. */
250  String sFileXsl = null;
251  
252  /**The reference directory for the output file. Only the deepness of the directory is used
253   * in respect to a relative label given in any label association. It is set by <code>-O:</code>.
254   */
255  String sOutRefDirectory;
256  
257  /**The ident of the document to search the HyperlinkAssociation in the document part of all control files.
258   * It is set by <code>-D:</code>
259   */
260  String sIdentDocument;
261  
262  File fileXsl = null;
263
264  /**The additional inputs for cross References, set on multiple -e options. */
265  private final List<FileTypeIn> listFileIn = new LinkedList<FileTypeIn>();
266
267  
268
269  
270  boolean readHyperlinkAssociations(String sFileHyperAssociations)
271  {
272    boolean bOk = true;
273    File fileCtrl = new File(sFileHyperAssociations);
274    Element xmlCtrl = null;
275    console.writeInfoln("org.vishia.xml.docuGen.CorrectHref " + sVersion);
276    console.reportln(Report.fineInfo, "Parameter: out=" + sFileOutput + ", ctrl=" + listFileCtrl.toString() 
277        + ", sOutRefDir=" + sOutRefDirectory + ", sIdentDocu=" + sIdentDocument);
278    try { xmlCtrl = XmlExtensions.readXmlFile(fileCtrl); } 
279    catch (XmlException e)
280    { System.err.println("CorrectHref: File not found: " + fileCtrl.getAbsolutePath());
281      bOk = false;
282    } 
283    if(bOk)
284    { Element xmlHyperlinkAssociations = xmlCtrl.getChild("HyperlinkAssociations");
285      if(xmlHyperlinkAssociations == null)
286      { console.writeInfoln("CorrectHref: no <HyperlinkAssociations> in file:" + fileCtrl.getAbsolutePath());
287      }
288      else
289      { storeHyperlinkAssociation(xmlHyperlinkAssociations);
290      }
291      if(sIdentDocument != null){
292        
293        List<Element> listXmlDocu = xmlCtrl.getChildren("document");
294        Iterator<Element> iterXmlDocu = listXmlDocu.iterator();
295        boolean bDocuFound = false;
296        Element xmlHyperLinksDocu = null;
297        while(!bDocuFound && iterXmlDocu.hasNext()){
298          Element xmlDocu =  iterXmlDocu.next();
299          Attribute xmlIdent = xmlDocu.getAttribute("ident");
300          if(xmlIdent != null && xmlIdent.getValue().equals(sIdentDocument)){
301            bDocuFound = true;
302            xmlHyperLinksDocu = xmlDocu.getChild("HyperlinkAssociations"); //may be null
303          }
304        }
305        if(xmlHyperLinksDocu != null){
306          storeHyperlinkAssociation(xmlHyperLinksDocu);
307        }
308      }
309    }
310    return bOk;
311  }    
312  
313  
314  boolean readAllHyperlinkAssociations()
315  {
316    boolean bOk = true;
317    for(String sFileCtrl: listFileCtrl){
318      bOk = readHyperlinkAssociations(sFileCtrl);
319    }  
320    return bOk;
321  }
322  
323  
324  /**sets the label association from a given xml tree. 
325   * The xml element should contain elements like followed:
326   * <pre>
327   * &lt;Association href="name">value&lt;/Association>
328   * &lt;Association href="name">value&lt;/Association>
329   * </pre>
330   * The name of the top level element may be other as shown, it is not tested.
331   * @param xmlLabelAssignment The input xml tree with label assignment.
332   */
333  @SuppressWarnings("unchecked")
334  private void storeHyperlinkAssociation(Element xmlLabelAssignment)
335  { ListIterator iter = xmlLabelAssignment.getChildren("Association").listIterator();
336        while(iter.hasNext())
337        { Element xmlAssociation = (Element)(iter.next());
338                  String sLabel = xmlAssociation.getAttributeValue("href");
339      if(sLabel.startsWith("/"))
340        stop();
341      int posWildcard = sLabel.indexOf('*');
342                  if(posWildcard == 0)
343                  {
344                        
345                  }
346                  else
347                  { //no wildcard or wildcard not left.
348                        if(posWildcard < 0){ posWildcard = sLabel.length(); }
349                    HyperlinkAssociation item = new HyperlinkAssociation();
350                    item.sLeft = sLabel.substring(0, posWildcard);
351                    if(posWildcard < (sLabel.length()-1))
352                    { int posWildcard2 = sLabel.indexOf('*', posWildcard+1);
353          if(posWildcard2 <0)
354          { item.sRight = sLabel.substring(posWildcard +1);
355            item.sMiddle = null;
356          }
357          else
358          { if(posWildcard2 < (sLabel.length()-1))
359            { item.sMiddle = sLabel.substring(posWildcard +1, posWildcard2);
360              item.sRight = sLabel.substring(posWildcard2 +1);
361            }
362            else
363            { item.sMiddle = sLabel.substring(posWildcard +1, posWildcard2);
364              item.sRight = null;
365            }
366          }
367                    }
368                    else 
369                    { item.sRight = null; 
370                      item.sMiddle = null;
371        }
372                          
373                    String sNewValue = xmlAssociation.getAttributeValue("dst");
374                          posWildcard = sNewValue.indexOf('*');
375                          if(posWildcard < 0) { posWildcard = sNewValue.length(); }
376                          item.sLeftDst = sNewValue.substring(0, posWildcard);
377                          if(posWildcard < (sNewValue.length()-1))
378                          { int posWildcard2 = sNewValue.indexOf('*', posWildcard+1);
379          if(posWildcard2 <0)
380          { item.sRightDst = sNewValue.substring(posWildcard +1); 
381            item.sMiddleDst = "";
382          }
383          else
384          { if(posWildcard2 < (sNewValue.length()-1))
385            { item.sMiddleDst = sNewValue.substring(posWildcard +1, posWildcard2);
386              item.sRightDst = sNewValue.substring(posWildcard2 +1);
387            }
388            else
389            { item.sMiddleDst = sNewValue.substring(posWildcard +1, posWildcard2);
390              item.sRightDst = "";
391            }
392          }
393        }
394                          else 
395                          { item.sRightDst = ""; 
396          item.sMiddleDst = ""; 
397        }
398                          item.sContent = xmlAssociation.getAttributeValue("content");
399                          
400                          final int ixAss = listHyperlinkAssociationLeft.search(item.sLeft);
401                          if(ixAss < 0){
402                            //not yet in list
403                            listHyperlinkAssociationLeft.add(item);  //add sorted to item.sLeft because it is a sorted list
404                          }
405                          else {
406                            boolean bFound =false;
407          //there is the equivalent sLeft-item in list:
408                            int ix = ixAss;
409                            HyperlinkAssociation assoc;
410                            while(!bFound && --ix >=0 
411                                 && (assoc = (HyperlinkAssociation)listHyperlinkAssociationLeft.get(ix)).sLeft.equals(item.sLeft)
412                                 ){
413                              bFound = checkAndReplaceItem(assoc, item);
414                            }
415                            ix = ixAss;
416                            int ixMax = listHyperlinkAssociationLeft.size();
417                            while(!bFound && ix < ixMax
418               && (assoc = (HyperlinkAssociation)listHyperlinkAssociationLeft.get(ix)).sLeft.equals(item.sLeft)
419               ){
420            bFound = checkAndReplaceItem(assoc, item);
421            ix +=1;
422          }
423          if(!bFound){
424            //not found in existing entries: add it, it will be sorted in at any position in range of same sLeft. 
425            listHyperlinkAssociationLeft.add(item);  //add sorted to item.sLeft because it is a sorted list
426          }
427                          }
428                  }
429        }  
430  }
431
432  
433  
434  
435  
436  boolean checkAndReplaceItem(HyperlinkAssociation a1, HyperlinkAssociation item)
437  {
438    boolean bFound = (a1.sMiddle == item.sMiddle || a1.sMiddle != null && item.sMiddle != null && a1.sMiddle.equals(item.sMiddle))
439           && (a1.sRight == item.sRight    || a1.sRight  != null && item.sRight  != null && a1.sRight.equals(item.sRight))
440            ;
441    if(bFound){
442            /**Found the same entry, replace the entry in list by newer content. */
443      a1.sLeftDst = item.sLeftDst;
444      a1.sMiddleDst = item.sMiddleDst;
445      a1.sRightDst = item.sRightDst;
446      console.reportln(Report.fineInfo, "CorrectHref: " + item.sLeft 
447          + "... replace " + a1.sLeftDst + "*" + a1.sMiddleDst + "*" + a1.sRightDst 
448          + " with " + item.sLeftDst + "*" + item.sMiddleDst + "*" + item.sRightDst);     
449    }
450    return bFound;
451  }
452            
453  
454  
455  @SuppressWarnings("unchecked")
456  void catchAllAnchors(Element xmlData, Element xmlChapter, String sChapterNr, int nChapterNr)
457  {
458    Attribute attrIdChapter;
459    if(xmlData.getName().equals("chapter"))
460    { xmlChapter = xmlData;
461      String sChapterNrAct = sChapterNr + nChapterNr;
462      xmlData.setAttribute("chapternr", sChapterNrAct);
463      attrIdChapter = xmlData.getAttribute("id");
464      if(attrIdChapter == null)
465      { //it should always have an id attribute!
466        xmlData.setAttribute("id", sChapterNrAct);
467      }
468      sActLabel = "";  //on chapter level without inner structures the additional label is emtpy.
469      //for nested chapters:
470      sChapterNr = sChapterNr + nChapterNr + ".";  
471      nChapterNr =0;
472    }
473        Attribute attrAnchor = xmlData.getAttribute("id");
474    if(attrAnchor != null)
475    { String sAnchor = attrAnchor.getValue();
476        listAnchors.put(sAnchor, new AnchorAndChapter(xmlData, xmlChapter));
477    }
478    if(xmlData.getName().equals("a"))
479    { attrAnchor = xmlData.getAttribute("name");
480      if(attrAnchor != null)
481      { listAnchors.put(attrAnchor.getValue(), new AnchorAndChapter(xmlData, xmlChapter));
482      }
483    }  
484    if(xmlData.getName().equals("anchor"))
485    { attrAnchor = xmlData.getAttribute("label");
486      if(attrAnchor != null)
487      { listAnchors.put(attrAnchor.getValue(), new AnchorAndChapter(xmlData, xmlChapter));
488      }
489    }  
490    { ListIterator<Element> childs = xmlData.getChildren().listIterator();
491                  while(childs.hasNext())
492                  { Element xmlChild = childs.next(); 
493        if(xmlChild.getName().equals("chapter"))
494        { nChapterNr +=1; 
495        }
496        catchAllAnchors(xmlChild, xmlChapter, sChapterNr, nChapterNr);
497                  }
498                }
499  }
500  
501  
502  
503  /**associates the label. 
504  * The href may contain one asterisk on begin, end or inside as wildcard symbol. 
505  * Than the determined begin, end or both is tested (startsWith, endsWith),
506  * and the value deticated to the asterisk is saved and used for asterisk replacement
507  * in the value.<br>
508  * Example: With the specification <pre>
509  *   &lt;Association href="pre*post">../MyFile/#*TheNewPost&lt;/Association>
510  * <pre>
511  * all labels preMyLabelpost, preSecondpost will be translated to
512  * <code>../MyFile/#MyLabelTheNewPost</code>, 
513  * <code>../MyFile/#MyLabelTheNewPost</code> and so on.
514  */ 
515  @SuppressWarnings("unchecked")
516  void processInputTree(Element xmlData, Element xmlLastChapter)
517  { Attribute attrId;
518    if(xmlData.getName().equals("chapter"))
519    { xmlLastChapter = xmlData;
520      attrId = xmlData.getAttribute("id");
521      sActLabel = "";  //on chapter level without inner structures the additional label is emtpy.
522    }
523    else if( (attrId = xmlData.getAttribute("id")) != null)
524    {
525      sActLabel = attrId.getValue();  //The Label for link.
526    }
527    if(xmlData.getAttribute("crossRefContent")!=null)
528    { xmlCrossRefElements.add(xmlData);
529    }
530
531    Attribute attrHref = xmlData.getAttribute("href");
532        if(attrHref != null)
533        { String sHref = attrHref.getValue();
534                  if(sHref.startsWith("#=>XMI_(Wikipedia)"))
535        stop();
536      /* * commented/
537                  { String sElement = xmlData.getName();
538        if(sElement.equals("area"))
539          stop();
540      }
541                  /* */
542      if(sHref.startsWith("#Topic"))
543      { sHref = resolveInternalTopicHref(xmlData, sHref);  //TODO: not tested, other concept used.
544      }
545      if(sHref != null && sHref.startsWith("#"))
546      { String sLabel = sHref.substring(1);
547                        HyperlinkAssociation hrefAssociation = null;
548                        if(sHref.startsWith("#thisDownload:_"))
549                                stop();
550        if(sHref.startsWith("#/"))
551          stop();
552        int ix = listHyperlinkAssociationLeft.search(sLabel);
553                        if(ix < 0){ ix = -ix-2; }
554                        //ix is -1 if a label is given lower as the first entry.
555                        if(ix >=0)
556                        { boolean found = false;
557                          boolean neverFound = false;
558                                int ix1 = ix;
559                                int posMiddle = -2;  //-2 is not used.
560                          while(!found && !neverFound && ix < listHyperlinkAssociationLeft.size())
561                            { hrefAssociation = (HyperlinkAssociation)listHyperlinkAssociationLeft.get(ix);
562                                        if( !sLabel.startsWith(hrefAssociation.sLeft))
563                                        { neverFound = true; //abort the loop because it is outside the range in list.
564                                        }
565                                        else
566                                        { int end1 = hrefAssociation.sLeft.length();
567                                          if( ( hrefAssociation.sRight == null || sLabel.endsWith(hrefAssociation.sRight))
568                                               &&( hrefAssociation.sMiddle == null 
569                                                 || (posMiddle = sLabel.indexOf(hrefAssociation.sMiddle, end1)) >=0
570                                               ) )
571                              { found = true;
572                              }
573                              else
574                              { ix += 1;
575                              }
576                                        }
577                            } 
578                          if(!found)
579                          { ix = ix1 -1;
580                            neverFound = false;
581                                while(!found && !neverFound && ix >= 0)
582                                    { hrefAssociation = (HyperlinkAssociation)listHyperlinkAssociationLeft.get(ix);
583                                                if( !sLabel.startsWith(hrefAssociation.sLeft))
584                                                { neverFound = true; //abort the loop because it is outside the range in list.
585                                                }
586                                        else if( ( hrefAssociation.sRight == null || sLabel.endsWith(hrefAssociation.sRight))
587                     &&( hrefAssociation.sMiddle == null 
588                       || (posMiddle = sLabel.indexOf(hrefAssociation.sMiddle)) >=0
589                     ) )
590              { found = true;
591                                      }
592                                      else
593                                      { ix -= 1;
594                                      }
595                                    }
596                          }
597                          if(found)
598                          {
599                                        //dst label without given start and end part from src,
600                                  final String sLeftDst2;
601                                  final String sLeftDst1;
602                            if(hrefAssociation.sLeftDst.startsWith("$")){
603                                    sLeftDst1 = replaceDstRoot(hrefAssociation.sLeftDst);
604                            } else {
605                              sLeftDst1 = hrefAssociation.sLeftDst;
606                            }
607                            if(sLeftDst1 == null){
608                              console.reportln(Report.fineInfo, hrefAssociation.sLeft + " = " + hrefAssociation.sLeftDst + ": no environment found.");
609                              removeHref(xmlData);
610                            } else {  
611                            /*
612                              if(sLeftDst1.startsWith("#") || sLeftDst1.startsWith("http")){
613                              sLeftDst2 = sLeftDst1;
614                            } else {
615                              //sLeftDst2 = replaceDirectoryDeepness(sLeftDst1);
616                              sLeftDst2 = FileSystem.relativatePath(sLeftDst1, sOutRefDirectory);
617                            }
618                            */
619                            int start = hrefAssociation.sLeft != null ? hrefAssociation.sLeft.length() : 0; 
620                                  int end =  sLabel.length() - (hrefAssociation.sRight != null ? hrefAssociation.sRight.length() : 0); 
621                              if(posMiddle < 0)
622                              {                       
623                sHref = sLeftDst1 + sLabel.substring(start, end) //may be ""
624                      + hrefAssociation.sRightDst;
625                              }
626                              else
627                              {
628                              int posStart2 = posMiddle + hrefAssociation.sMiddle.length();
629                //dst label with start and end part from dst.
630                                  sHref = sLeftDst1 + sLabel.substring(start, posMiddle) 
631                                        + hrefAssociation.sMiddleDst + sLabel.substring(posStart2, end) //may be ""
632                                        + hrefAssociation.sRightDst;
633                              }  
634                        sHref = checkAndReplaceHrefText(xmlData, sHref);
635                            xmlData.setAttribute("href", sHref);
636                            }
637                            
638          }
639                                if(!found)
640                                { hrefAssociation = null;  //do not use by hazard! 
641                                }
642                        }
643                        //sHref may be changed if association is found! 
644                        if(sHref != null && sHref.startsWith("#")) //test the resulting sHref.
645                                { /**due to hyperlink associations the href is not associated to an external file.
646                                   * test whether the label is found in actual document:
647                                   */ 
648                                        sLabel = sHref.substring(1);
649                                AnchorAndChapter xmlAnchor = listAnchors.get(sLabel);  
650                                  String sCrossrefContent = (hrefAssociation != null ? hrefAssociation.sContent : null);
651                                        if(xmlAnchor != null){
652                                    addInternHref(sLabel, xmlData, xmlLastChapter, xmlAnchor.xmlAnchor, sCrossrefContent);
653                                        }
654                                        if( xmlAnchor == null && sCrossrefContent == null)
655                                        { //wether an anchor inside the document nor the request to create a content for cross references:
656                                                console.reportln(Report.fineInfo,"CorrectHref: removed href: \"" + sHref + "\"");
657                                          removeHref(xmlData);
658                                        }
659                                }
660          }  
661        }
662        { //for nested chapters
663                ListIterator<Element> childs = xmlData.getChildren().listIterator();
664          while(childs.hasNext())
665          { Element xmlChild = childs.next(); 
666                processInputTree(xmlChild, xmlLastChapter);
667          }
668        }
669  }
670
671  
672  
673  
674  
675  
676  /**Replaces the left part of input with the content of the environment variable,
677   * which name is given in this left part. 
678   * @param sLeftDstInput input, it starts with '$'. The left part is the part to the first /.
679   *        This left part builds a name of an environment variable.
680   * @return replaces environment variable with the content, or null if the environment variable isn't found.
681   *        In the second case the label shouldn't be replaced. 
682   */
683  private String replaceDstRoot(String sLeftDstInput)
684  {
685    int pos2 = sLeftDstInput.indexOf('/');
686    String sRootLabel = sLeftDstInput.substring(1, pos2);  //after $
687    String sRootDst = System.getenv(sRootLabel);
688    if(sRootDst == null || sRootDst.length() == 0){
689      return null;
690    } else {
691      String sLeftDst = sRootDst + sLeftDstInput.substring(pos2);
692      return sLeftDst;
693    }  
694  }
695  
696  
697  
698  
699  /**TODO: test usage of {@link org.vishia.util.FileSystem#relativatePath(String, String)}
700   * 
701   * @param sInput
702   * @return
703   */
704  private String replaceDirectoryDeepness(String sInput)
705  { int posHtmlRef =0;
706    int posInput = 0;
707    while(posHtmlRef >=0 && sInput.length() >= (posInput+3) && sInput.substring(posInput, posInput+3).equals("../")){
708      //relative path to left
709      if(sOutRefDirectory.substring(posHtmlRef, posHtmlRef+3).equals("../")){
710         posHtmlRef +=3;  //both files are more left, delete 1 level of  ../
711         posInput +=3;
712      }
713      else {
714        int posSlash = sOutRefDirectory.indexOf('/', posHtmlRef);
715        if(posSlash >=0){
716          posHtmlRef =posSlash +1;  //after '/'
717          posInput -=3;  //go to left, because the output file is more right.
718        }
719        else {
720          posHtmlRef = -1; //to break.
721        }
722        while(posInput < 0){
723          posInput +=3;
724          sInput = "../" + sInput; //it may be errornuoes because the input is more left as a root.
725        }
726      }
727    }
728    String sOutput = sInput.substring(posInput);
729    return sOutput;
730  }
731  
732  
733  
734  void removeHref(Element xmlData)
735  {
736    xmlData.removeAttribute("href");
737    xmlData.setName("span");  //instead <a href=...>
738    xmlData.setAttribute("class", "removedHref");
739          
740  }
741  
742  
743  
744  
745  /**Replaces some special texts in the hyperlink.
746   * <ul>
747   * <li>$chapter: Replace with chapter text.
748   * </ul>
749   * @param xmlNode
750   */
751  private String checkAndReplaceHrefText(Element xmlNode, String sHref)
752  { String sHrefRet = sHref;
753    String sHrefText = xmlNode.getTextNormalize();
754    if(sHref.equals("#Topic:.orgVishiaXmlDocu.href.hrefExchg."))
755      stop();
756    final AnchorAndChapter xmlAnchor;
757    if(sHref.startsWith("#")){
758      xmlAnchor = listAnchors.get(sHref.substring(1));
759      if(xmlAnchor == null){
760        console.reportln(Report.fineInfo,"removed href: \"" + sHref + "\"");
761        xmlNode.removeAttribute("href");
762        xmlNode.setName("span");  //instead <a href=...>
763        xmlNode.setAttribute("class", "removedHref");
764      } else {
765        //anchor found:
766        int posChapter = sHrefText.indexOf("$chapter");
767        if(posChapter >=0){
768          stop();
769        }
770      }
771    }
772    else{
773      /**Possible: Search known anchors in other documents, using a hyper file. */
774      xmlAnchor = null;
775      if( !sHref.startsWith("http") && !sHref.startsWith("!")){
776        /*Handle all relative paths to other files. */
777        //sLeftDst2 = replaceDirectoryDeepness(sLeftDst1);
778        if(sHref.startsWith("XRPG/"))
779          stop();
780        sHrefRet = FileSystem.relativatePath(sHref, sOutRefDirectory);
781      } else {
782        sHrefRet = sHref;
783      }
784    }  
785    if(xmlAnchor != null){
786      int posChapter = sHrefText.indexOf("$chapter");
787      if(posChapter >=0){
788        /**Replace with the number and text of chapter. */
789        if(sHref.startsWith("#")){
790          /**Located in current document: */
791          final String sChapterTitle = xmlAnchor.xmlChapter.getChildTextNormalize("title", nsPre);
792          final String sChapterNr = xmlAnchor.xmlChapter.getAttributeValue("chapternr");
793          final String sHrefTextNew = sHrefText.substring(0, posChapter) 
794                                    + sChapterNr + " " + sChapterTitle 
795                                    + sHrefText.substring(posChapter + 8);
796          sHrefRet = "#" + xmlAnchor.xmlChapter.getAttributeValue("id");
797          xmlNode.setText(sHrefTextNew);
798        } else {
799          /**Label in another document: */
800          String sText = sHrefText.substring(0, posChapter) + " " + sHref +" " + sHrefText.substring(posChapter+8); 
801          xmlNode.setText(sText); //replace unuseable $chapter with the hyperlink, no other informations.
802        }
803      }  
804    }  
805    return sHrefRet;
806  }
807  
808  
809  
810  /**adds one entry of href.
811   * 
812   * @param sHref Label to search quickly
813   * @param xmlAct The actual element, it contains the href
814   * @param xmlLastChapter The Link to the chapter
815   * @param xmlHrefTarget may be null, than the reference is not performed.
816   * @param sContent String for creation cross reference content. 
817   */
818  void addInternHref(String sHref, Element xmlAct, Element xmlLastChapter, Element xmlHrefTarget, String sContent)
819  { CrossHref hrefEntry = (CrossHref)listHref.get(sHref);
820    if(hrefEntry == null)
821    { hrefEntry = new CrossHref(sHref, xmlAct, xmlHrefTarget, sContent);
822      listHref.add(hrefEntry);
823    }
824    hrefEntry.addBackref(xmlAct, xmlLastChapter);
825  }  
826  
827  
828  
829  /**Tries to resolve a "#Topic..."-reference with an existing topic in the own document.
830   * If the Topic-label is found internally, and a chapter is associated to the label, 
831   * the hyperlink-reference will be redirected to the start of the chapter. 
832   * If the hyperlink-text starts with "Topic:" (normally, if no special text is given), than
833   * the hyperlink-text will be changed to "Chapter: CHAPTERTITLE" of the found chapter.
834   *  
835   * @param xmlHref The element which contains the sHref. It will be changed if it is a inner href.
836   * 
837   * @param sHref starts with '#Topic'. A topic reference. 
838   * It may be start with "#Topic:_", #Topic:" or "#Topic:."
839   * 
840   * @return null if the sHref is processed as internal href, else the input sHref. 
841   */
842  private String resolveInternalTopicHref(Element xmlHref, String sHref)
843  { String sHref1;
844    if(sHref.charAt(7) == '_' || sHref.charAt(7) == '.') {
845      //Variants Topic:_ or Topic:.
846      //Internally a Topic is written "Topic.A.B."
847      sHref1 = "Topic." + sHref.substring(8);
848    } else {
849      sHref1 = "Topic." + sHref.substring(7);
850    }
851    if(!sHref1.endsWith(".")){
852      sHref1 += "."; //The internal Topic.xxx. label has a dot at last. 
853    }
854    AnchorAndChapter anchor = listAnchors.get(sHref1);
855    if(anchor != null){
856      if(anchor.xmlChapter != null){
857        String sId = anchor.xmlChapter.getAttributeValue("id");
858        if(sId != null){
859          xmlHref.setAttribute("href", "#" + sId);
860          sHref1 = null;  //return null.
861        }
862        String sHrefText = xmlHref.getText();
863        final String sChapterTitle = anchor.xmlChapter.getChildTextNormalize("title", nsPre);
864        if(sChapterTitle != null){
865          final String sChapterNr = anchor.xmlChapter.getAttributeValue("chapternr");
866          final String sHrefTextNew;
867          int posChapter = sHrefText.indexOf("$chapter");
868          if(posChapter >=0){ 
869            //The HrefText contains a placeholder for chapter-title
870            sHrefTextNew = sHrefText.substring(0, posChapter) 
871                         + sChapterNr + " " + sChapterTitle 
872                         + sHrefText.substring(posChapter + 8);
873            xmlHref.setText(sHrefTextNew);
874          } else if(sHrefText.startsWith("Topic:")){ 
875            //The HrefText contains the same Topic label 
876            sHrefTextNew = "Chapter: " 
877                         + sChapterNr + " " + sChapterTitle; 
878            xmlHref.setText(sHrefTextNew);
879          }
880        }
881      } 
882      if(sHref1 != null) {
883        xmlHref.setAttribute("href", sHref1);
884        sHref1 = null;
885      }
886    } else {
887      //Topic not found in same document, handle it outside.
888      sHref1 = sHref;
889    }
890    return sHref1;
891  }
892  
893  
894  
895  //adds one entry for cross refernce
896  boolean xxxaddCrossReferenceEntry(String sHref, Element xmlLastChapter)
897  { boolean bFound = false;
898        CrossHref crossEntry = (CrossHref)listHref.get(sHref);
899    if(crossEntry != null)
900    { bFound = true;
901        
902    }
903    else if(fileXsl != null)
904    { //additinal XSL script is given to generate absent hyperlink targets.
905          xmlRoot.setAttribute("crossRefContent", sHref);
906                  //Try to create a XML tree via XSLT
907                        Element xmlCrossOutput = null;
908                        Element xmlCrossBackref = null;
909                  try
910                  { xmlCrossOutput = XmlExtensions.xslTransformXml(xmlRoot, fileXsl);}
911                  catch(XmlException exception)
912                  { console.writeError("transform Exception", exception);
913                  }
914        if(xmlCrossOutput != null && xmlCrossOutput.getName().equals("noCrossRefContent"))
915        { xmlCrossOutput = null;
916        }
917        else
918        { xmlCrossBackref = searchXmlCrossBackref(xmlCrossOutput);
919        }
920        //may be with null as xmlCrossOutput:
921        crossEntry = new CrossHref(sHref, xmlCrossOutput, xmlCrossBackref, null);  
922      listHref.add(crossEntry);
923      bFound = (xmlCrossOutput != null);
924    }
925    if(crossEntry != null)
926    { crossEntry.addBackref(xmlLastChapter, null);
927    }
928        return bFound;
929  }
930  
931
932  
933  
934  @SuppressWarnings("unchecked")
935  private static Element searchXmlCrossBackref(Element parent)
936        { Element xmlCrossBackref = null;
937                if(parent.getAttribute("crossBackref")!= null)
938                { xmlCrossBackref = parent;
939                }
940                else
941                { List<org.jdom.Element> children = parent.getChildren();
942                  if(children != null)
943                  { Iterator<org.jdom.Element> iter = children.iterator();
944                    while(xmlCrossBackref == null && iter.hasNext())
945                    { //recursively
946                        xmlCrossBackref = searchXmlCrossBackref(iter.next());
947                    }
948                  }
949                }  
950                return xmlCrossBackref;  //may be null if not found.
951        }
952  
953
954        /**For every detect crossRefContent Element a XSL translation is called, 
955         * the output is ranged as children in the crossRefContent Element.  
956         */
957  @SuppressWarnings("unchecked")
958  void processCrossReferences()
959  {
960        Iterator<Element> iter = xmlCrossRefElements.iterator();
961        while(iter.hasNext())
962        { Element xmlCrossRefOutputElement = iter.next();
963          String sContent = xmlCrossRefOutputElement.getAttributeValue("crossRefContent");
964          //Information for XSL, to detect which content is to be create:
965          xmlRoot.setAttribute("crossRefContent", sContent);
966                  //Try to create a XML tree via XSLT
967                        xmlRoot.addContent(xmlHrefRoot);  //add to the input tree to do somewhat with,
968                  // and all other data are also available.
969                        Element xmlCrossOutput = null;
970                  try{ xmlCrossOutput = XmlExtensions.xslTransformXml(xmlRoot, fileXsl);}
971                  catch(XmlException exception)
972                  { console.writeError("transform Exception", exception);
973                  }
974                  if(xmlCrossOutput != null)
975                  { Iterator<Element> iterOut = xmlCrossOutput.getChildren().iterator();
976                    List<Element> list = new LinkedList<Element>();
977                    //park it in a simple list because otherwise a ConcurrentListModification exception is thrown
978                    while(iterOut.hasNext())
979                    { list.add(iterOut.next());  
980                    }
981                    iterOut = list.iterator();
982                    while(iterOut.hasNext())
983                    { Element xmlOut = iterOut.next();
984                      xmlOut.detach();
985                          xmlCrossRefOutputElement.addContent(xmlOut);
986                    }
987                  }
988        }  
989  }
990  
991
992  /** Add back refs as &lt;li> on every Element with attribute addBackRefs="LABEL"
993   * 
994   *
995   */ 
996  void addBackRefs()
997  {
998        
999  }
1000  
1001  
1002  
1003  
1004  
1005  /** First the arguments sFileXml and sFileCtrl have to be setted.
1006   * @throws XmlException 
1007   * 
1008   *
1009   */  
1010        void execute(MainCmd_ifc console) throws XmlException
1011        { this.console = console;
1012                boolean bOk = true;
1013                bOk = readAllHyperlinkAssociations();
1014    Element xmlData = null;  
1015    if(bOk)
1016          { Element xmlRoot = new Element("root");
1017      int nrofFiles = xmlMReader.readInputsToJdomElement(xmlRoot);
1018      if(nrofFiles <= 0)
1019      { System.err.println("Input File not found: ");
1020                          bOk = false;
1021                        }
1022      else
1023      { xmlData = (Element)xmlRoot.getChildren().get(0);
1024        WikistyleTextToSimpleXml wikiFormat = new WikistyleTextToSimpleXml();
1025        XmlNode nodeTop = new XmlNodeJdom(xmlData);  //this class wraps xmlData, xmlData will be converted itself.      
1026        wikiFormat.testXmlTreeAndConvert(nodeTop);        
1027        //wikiFormat.testXmlTreeAndConvert(xmlData);
1028      }
1029          }
1030    if(bOk)
1031          { if(sFileXsl != null)
1032            { fileXsl = new File(sFileXsl);
1033                xmlRoot = new Element("root");
1034                  xmlData.detach();  //from its document.
1035                    xmlRoot.addContent(xmlData);
1036                    Iterator<FileTypeIn> iter = listFileIn.iterator();
1037                    while(iter.hasNext())
1038                    { XslTransformer.FileTypeIn input = iter.next(); 
1039                      try
1040                      { Element xmlInput = input.readXmlFile(); 
1041                              xmlInput.detach();
1042                              xmlRoot.addContent(xmlInput);
1043                      }
1044                            catch(Exception exc)                        
1045                            { System.err.println("File problem found: " + input.getAbsolutePath());
1046                                          bOk = false;
1047                                        } 
1048                    }
1049            }
1050          }
1051    if(bOk)
1052    { catchAllAnchors(xmlData, xmlData, "", 0);
1053                /**core routine. */
1054      processInputTree(xmlData, xmlData);
1055          }
1056    
1057    if(bOk && fileXsl != null)
1058    { processCrossReferences();
1059    }
1060    if(bOk)
1061          { File fileXmlOut = new File(sFileOutput);
1062            XmlExtensions.XmlMode mode = new XmlExtensions.XmlMode();
1063            mode.setXmlIso8859(); 
1064            xmlData.detach();  //from the input Document
1065            try {       XmlExtensions.writeXmlFile(xmlData, fileXmlOut, mode); } 
1066                  catch (Exception e)
1067                        { System.err.println("File not writeable: " + fileXmlOut.getAbsolutePath() + e.getMessage());
1068                          bOk = false;
1069                        } 
1070    }
1071                
1072        }
1073  
1074        
1075        
1076        
1077        
1078        
1079        
1080  /**It's a debug helper. The method is empty, but it is a mark to set a breakpoint. */
1081  void stop()
1082  { //only for debug
1083  }
1084  
1085  
1086  /**Main-Programm called as command
1087   * <pre>
1088      super.addAboutInfo("CorrectHref");
1089      super.addAboutInfo("made by Hartmut Schorrig, 2007-03-02 / " + sVersion);
1090      super.addHelpInfo("Corrects hyperlink references");
1091      super.addHelpInfo("param: -i:INPUT -y:OUTPUT {-c:CTRL} [{-e:XML} -t:XSL] [-o:HTML] [-d:IDENT]");
1092      super.addHelpInfo("-i:INPUT  the input xml file to correct hrefs.");
1093      super.addHelpInfo("-y:OUTPUT the output xml file with corrected hrefs.");
1094      super.addHelpInfo("-c:CTRL the control.xml file containing Element HyperlinkAssociations at second level.");
1095      super.addHelpInfo("-e:XML  an additional XML file containing additional for the cross references, see -tXSL.");
1096      super.addHelpInfo("-t:XSL  an additional XSL file to generate cross references to external sources.");
1097      super.addHelpInfo("-o:HTML The output file to generate, only the deepness of directory is used.");
1098      super.addHelpInfo("-d:IDENT The ident of the document inside all of the CTRL-Files, than its HyperlinkAssociation is used.");
1099      * </pre>
1100   * 
1101   * @param argc cmd-line-parameter
1102   */
1103  public static void main(String[] argc)
1104  { CorrectHref exec = new CorrectHref(); 
1105        Main console = exec.new Main(argc);
1106    if(console.evaluateCmdLineArgs())
1107    { exec.xmlMReader.setReport(console);
1108      try{ exec.execute(console); }
1109      catch(XmlException exc)
1110      { console.writeError("unexpected", exc);
1111        console.setExitErrorLevel(Report.exitWithErrors);
1112      }
1113    }
1114    console.exit();
1115  }
1116  
1117  /**Main-class to organize cmd line parsing.
1118   */
1119  private class Main extends MainCmd
1120  {
1121    
1122    
1123    Main(String[] args)
1124          { super(args);
1125            //super.addHelpInfo(getAboutInfo());
1126            super.addAboutInfo("CorrectHref");
1127            super.addAboutInfo("made by Hartmut Schorrig, 2007-03-02 / " + sVersion);
1128            super.addHelpInfo("Corrects hyperlink references");
1129            super.addHelpInfo("param: -i:INPUT -y:OUTPUT {-c:CTRL} [{-e:XML} -t:XSL] [-o:HTML] [-d:IDENT]");
1130            super.addHelpInfo("-i:INPUT  the input xml file to correct hrefs.");
1131            super.addHelpInfo("-y:OUTPUT the output xml file with corrected hrefs.");
1132            super.addHelpInfo("-c:CTRL the control.xml file containing Element HyperlinkAssociations at second level.");
1133            super.addHelpInfo("-e:XML  an additional XML file containing additional for the cross references, see -tXSL.");
1134            super.addHelpInfo("-t:XSL  an additional XSL file to generate cross references to external sources.");
1135      super.addHelpInfo("-o:HTML The output file to generate, only the deepness of directory is used.");
1136      super.addHelpInfo("-d:IDENT The ident of the document inside all of the CTRL-Files, than its HyperlinkAssociation is used.");
1137      super.addStandardHelpInfo();
1138          }
1139        
1140    /** processes the cmd line arguments. */
1141    private boolean evaluateCmdLineArgs()
1142    { boolean bOk = true;
1143      try{ super.parseArguments(); }
1144      catch(Exception exception)
1145      { setExitErrorLevel(MainCmd_ifc.exitWithArgumentError);
1146        bOk = false;
1147      }
1148        return bOk;
1149    }
1150    
1151                @Override
1152    protected boolean checkArguments()
1153                {
1154                        // TODO Auto-generated method stub
1155                        return true;
1156                }
1157
1158                @Override
1159    protected boolean testArgument(String argc, int nArg) //throws ParseException
1160          { boolean bOk = true;  //set to false if the argc is not passed
1161                        int posArg = (argc.length()>=2 && argc.charAt(2)==':') ? 3 : 2; //with or without :
1162      
1163            //if(argc.startsWith("-i"))     { sFileInput   = getArgument(2); }
1164      if(argc.startsWith("-i")) { xmlMReader.addInputFile(getArgument(posArg),XmlMReaderJdomSaxon.mExpandWikiFormat); }
1165            else if(argc.startsWith("-y")){ sFileOutput   = getArgument(posArg); }
1166            else if(argc.startsWith("-c")){ listFileCtrl.add(getArgument(posArg)); }
1167            else if(argc.startsWith("-e")){ listFileIn.add(new XslTransformer.FileTypeIn(getArgument(posArg),0));}
1168      else if(argc.startsWith("-t")){ sFileXsl  = getArgument(posArg); }
1169      else if(argc.startsWith("-o")){ sOutRefDirectory = getArgument(posArg).replace('\\', '/'); }
1170      else if(argc.startsWith("-d")){ sIdentDocument = getArgument(posArg); }
1171      else bOk=false;
1172        
1173            return bOk;
1174          }
1175        
1176                
1177  }
1178
1179}