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 <pre:Href>. 223 * The children have attributes href and some 224 * children <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 * <Association href="name">value</Association> 328 * <Association href="name">value</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 * <Association href="pre*post">../MyFile/#*TheNewPost</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 <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}