1 package jdiff; 2 3 import com.sun.javadoc.*; 4 import com.sun.javadoc.Parameter; 5 import com.sun.javadoc.ParameterizedType; 6 import com.sun.javadoc.Type; 7 8 import java.util.*; 9 import java.io.*; 10 import java.lang.reflect.*; 11 12 /** 13 * Converts a Javadoc RootDoc object into a representation in an 14 * XML file. 15 * 16 * See the file LICENSE.txt for copyright details. 17 * @author Matthew Doar, mdoar@pobox.com 18 */ 19 public class RootDocToXML { 20 21 /** Default constructor. */ RootDocToXML()22 public RootDocToXML() { 23 } 24 25 /** 26 * Write the XML representation of the API to a file. 27 * 28 * @param root the RootDoc object passed by Javadoc 29 * @return true if no problems encountered 30 */ writeXML(RootDoc root)31 public static boolean writeXML(RootDoc root) { 32 String tempFileName = outputFileName; 33 if (outputDirectory != null) { 34 tempFileName = outputDirectory; 35 if (!tempFileName.endsWith(JDiff.DIR_SEP)) 36 tempFileName += JDiff.DIR_SEP; 37 tempFileName += outputFileName; 38 } 39 40 try { 41 FileOutputStream fos = new FileOutputStream(tempFileName); 42 outputFile = new PrintWriter(fos); 43 System.out.println("JDiff: writing the API to file '" + tempFileName + "'..."); 44 if (root.specifiedPackages().length != 0 || root.specifiedClasses().length != 0) { 45 RootDocToXML apiWriter = new RootDocToXML(); 46 apiWriter.emitXMLHeader(); 47 apiWriter.logOptions(); 48 apiWriter.processPackages(root); 49 apiWriter.emitXMLFooter(); 50 } 51 outputFile.close(); 52 } catch(IOException e) { 53 System.out.println("IO Error while attempting to create " + tempFileName); 54 System.out.println("Error: " + e.getMessage()); 55 System.exit(1); 56 } 57 // If validation is desired, write out the appropriate api.xsd file 58 // in the same directory as the XML file. 59 if (XMLToAPI.validateXML) { 60 writeXSD(); 61 } 62 return true; 63 } 64 65 /** 66 * Write the XML Schema file used for validation. 67 */ writeXSD()68 public static void writeXSD() { 69 String xsdFileName = outputFileName; 70 if (outputDirectory == null) { 71 int idx = xsdFileName.lastIndexOf('\\'); 72 int idx2 = xsdFileName.lastIndexOf('/'); 73 if (idx == -1 && idx2 == -1) { 74 xsdFileName = ""; 75 } else if (idx == -1 && idx2 != -1) { 76 xsdFileName = xsdFileName.substring(0, idx2); 77 } else if (idx != -1 && idx2 == -1) { 78 xsdFileName = xsdFileName.substring(0, idx); 79 } else if (idx != -1 && idx2 != -1) { 80 int max = idx2 > idx ? idx2 : idx; 81 xsdFileName = xsdFileName.substring(0, max); 82 } 83 } else { 84 xsdFileName = outputDirectory; 85 if (!xsdFileName.endsWith(JDiff.DIR_SEP)) 86 xsdFileName += JDiff.DIR_SEP; 87 } 88 xsdFileName += "api.xsd"; 89 try { 90 FileOutputStream fos = new FileOutputStream(xsdFileName); 91 PrintWriter xsdFile = new PrintWriter(fos); 92 // The contents of the api.xsd file 93 xsdFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>"); 94 xsdFile.println("<xsd:schema xmlns:xsd=\"https://www.w3.org/2001/XMLSchema\">"); 95 xsdFile.println(""); 96 xsdFile.println("<xsd:annotation>"); 97 xsdFile.println(" <xsd:documentation>"); 98 xsdFile.println(" Schema for JDiff API representation."); 99 xsdFile.println(" </xsd:documentation>"); 100 xsdFile.println("</xsd:annotation>"); 101 xsdFile.println(); 102 xsdFile.println("<xsd:element name=\"api\" type=\"apiType\"/>"); 103 xsdFile.println(""); 104 xsdFile.println("<xsd:complexType name=\"apiType\">"); 105 xsdFile.println(" <xsd:sequence>"); 106 xsdFile.println(" <xsd:element name=\"package\" type=\"packageType\" minOccurs='1' maxOccurs='unbounded'/>"); 107 xsdFile.println(" </xsd:sequence>"); 108 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 109 xsdFile.println(" <xsd:attribute name=\"jdversion\" type=\"xsd:string\"/>"); 110 xsdFile.println("</xsd:complexType>"); 111 xsdFile.println(); 112 xsdFile.println("<xsd:complexType name=\"packageType\">"); 113 xsdFile.println(" <xsd:sequence>"); 114 xsdFile.println(" <xsd:choice maxOccurs='unbounded'>"); 115 xsdFile.println(" <xsd:element name=\"class\" type=\"classType\"/>"); 116 xsdFile.println(" <xsd:element name=\"interface\" type=\"classType\"/>"); 117 xsdFile.println(" </xsd:choice>"); 118 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 119 xsdFile.println(" </xsd:sequence>"); 120 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 121 xsdFile.println("</xsd:complexType>"); 122 xsdFile.println(); 123 xsdFile.println("<xsd:complexType name=\"classType\">"); 124 xsdFile.println(" <xsd:sequence>"); 125 xsdFile.println(" <xsd:element name=\"implements\" type=\"interfaceTypeName\" minOccurs='0' maxOccurs='unbounded'/>"); 126 xsdFile.println(" <xsd:element name=\"constructor\" type=\"constructorType\" minOccurs='0' maxOccurs='unbounded'/>"); 127 xsdFile.println(" <xsd:element name=\"method\" type=\"methodType\" minOccurs='0' maxOccurs='unbounded'/>"); 128 xsdFile.println(" <xsd:element name=\"field\" type=\"fieldType\" minOccurs='0' maxOccurs='unbounded'/>"); 129 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 130 xsdFile.println(" </xsd:sequence>"); 131 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 132 xsdFile.println(" <xsd:attribute name=\"extends\" type=\"xsd:string\" use='optional'/>"); 133 xsdFile.println(" <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>"); 134 xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>"); 135 xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>"); 136 xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>"); 137 xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>"); 138 xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>"); 139 xsdFile.println("</xsd:complexType>"); 140 xsdFile.println(); 141 xsdFile.println("<xsd:complexType name=\"interfaceTypeName\">"); 142 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 143 xsdFile.println("</xsd:complexType>"); 144 xsdFile.println(); 145 xsdFile.println("<xsd:complexType name=\"constructorType\">"); 146 xsdFile.println(" <xsd:sequence>"); 147 xsdFile.println(" <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>"); 148 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 149 xsdFile.println(" </xsd:sequence>"); 150 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 151 xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\" use='optional'/>"); 152 xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>"); 153 xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>"); 154 xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>"); 155 xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>"); 156 xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>"); 157 xsdFile.println("</xsd:complexType>"); 158 xsdFile.println(); 159 xsdFile.println("<xsd:complexType name=\"paramsType\">"); 160 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 161 xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>"); 162 xsdFile.println("</xsd:complexType>"); 163 xsdFile.println(); 164 xsdFile.println("<xsd:complexType name=\"exceptionType\">"); 165 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 166 xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>"); 167 xsdFile.println("</xsd:complexType>"); 168 xsdFile.println(); 169 xsdFile.println("<xsd:complexType name=\"methodType\">"); 170 xsdFile.println(" <xsd:sequence>"); 171 xsdFile.println(" <xsd:element name=\"param\" type=\"paramsType\" minOccurs='0' maxOccurs='unbounded'/>"); 172 xsdFile.println(" <xsd:element name=\"exception\" type=\"exceptionType\" minOccurs='0' maxOccurs='unbounded'/>"); 173 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 174 xsdFile.println(" </xsd:sequence>"); 175 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 176 xsdFile.println(" <xsd:attribute name=\"return\" type=\"xsd:string\" use='optional'/>"); 177 xsdFile.println(" <xsd:attribute name=\"abstract\" type=\"xsd:boolean\"/>"); 178 xsdFile.println(" <xsd:attribute name=\"native\" type=\"xsd:boolean\"/>"); 179 xsdFile.println(" <xsd:attribute name=\"synchronized\" type=\"xsd:boolean\"/>"); 180 xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>"); 181 xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>"); 182 xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>"); 183 xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>"); 184 xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>"); 185 xsdFile.println("</xsd:complexType>"); 186 xsdFile.println(); 187 xsdFile.println("<xsd:complexType name=\"fieldType\">"); 188 xsdFile.println(" <xsd:sequence>"); 189 xsdFile.println(" <xsd:element name=\"doc\" type=\"xsd:string\" minOccurs='0' maxOccurs='1'/>"); 190 xsdFile.println(" </xsd:sequence>"); 191 xsdFile.println(" <xsd:attribute name=\"name\" type=\"xsd:string\"/>"); 192 xsdFile.println(" <xsd:attribute name=\"type\" type=\"xsd:string\"/>"); 193 xsdFile.println(" <xsd:attribute name=\"transient\" type=\"xsd:boolean\"/>"); 194 xsdFile.println(" <xsd:attribute name=\"volatile\" type=\"xsd:boolean\"/>"); 195 xsdFile.println(" <xsd:attribute name=\"value\" type=\"xsd:string\" use='optional'/>"); 196 xsdFile.println(" <xsd:attribute name=\"src\" type=\"xsd:string\" use='optional'/>"); 197 xsdFile.println(" <xsd:attribute name=\"static\" type=\"xsd:boolean\"/>"); 198 xsdFile.println(" <xsd:attribute name=\"final\" type=\"xsd:boolean\"/>"); 199 xsdFile.println(" <xsd:attribute name=\"deprecated\" type=\"xsd:string\"/>"); 200 xsdFile.println(" <xsd:attribute name=\"visibility\" type=\"xsd:string\"/>"); 201 xsdFile.println("</xsd:complexType>"); 202 xsdFile.println(); 203 xsdFile.println("</xsd:schema>"); 204 xsdFile.close(); 205 } catch(IOException e) { 206 System.out.println("IO Error while attempting to create " + xsdFileName); 207 System.out.println("Error: " + e.getMessage()); 208 System.exit(1); 209 } 210 } 211 212 /** 213 * Write the options which were used to generate this XML file 214 * out as XML comments. 215 */ logOptions()216 public void logOptions() { 217 // TODO: Get options from javadoc if possible. 218 // outputFile.print("<!-- "); 219 // outputFile.print(" Command line arguments = " + Options.cmdOptions); 220 // outputFile.println(" -->"); 221 } 222 223 /** 224 * Process each package and the classes/interfaces within it. 225 * 226 * @param pd an array of PackageDoc objects 227 */ processPackages(RootDoc root)228 public void processPackages(RootDoc root) { 229 PackageDoc[] specified_pd = root.specifiedPackages(); 230 Map pdl = new TreeMap(); 231 for (int i = 0; specified_pd != null && i < specified_pd.length; i++) { 232 pdl.put(specified_pd[i].name(), specified_pd[i]); 233 } 234 235 // Classes may be specified separately, so merge their packages into the 236 // list of specified packages. 237 ClassDoc[] cd = root.specifiedClasses(); 238 // This is lists of the specific classes to document 239 Map classesToUse = new HashMap(); 240 for (int i = 0; cd != null && i < cd.length; i++) { 241 PackageDoc cpd = cd[i].containingPackage(); 242 if (cpd == null && !packagesOnly) { 243 // If the RootDoc object has been created from a jar file 244 // this duplicates classes, so we have to be able to disable it. 245 // TODO this is still null? 246 cpd = root.packageNamed("anonymous"); 247 } 248 String pkgName = cpd.name(); 249 String className = cd[i].name(); 250 if (trace) System.out.println("Found package " + pkgName + " for class " + className); 251 if (!pdl.containsKey(pkgName)) { 252 if (trace) System.out.println("Adding new package " + pkgName); 253 pdl.put(pkgName, cpd); 254 } 255 256 // Keep track of the specific classes to be used for this package 257 List classes; 258 if (classesToUse.containsKey(pkgName)) { 259 classes = (ArrayList) classesToUse.get(pkgName); 260 } else { 261 classes = new ArrayList(); 262 } 263 classes.add(cd[i]); 264 classesToUse.put(pkgName, classes); 265 } 266 267 PackageDoc[] pd = (PackageDoc[]) pdl.values().toArray(new PackageDoc[0]); 268 for (int i = 0; pd != null && i < pd.length; i++) { 269 String pkgName = pd[i].name(); 270 271 // Check for an exclude tag in the package doc block, but not 272 // in the package.htm[l] file. 273 if (!shownElement(pd[i], null)) 274 continue; 275 276 if (trace) System.out.println("PROCESSING PACKAGE: " + pkgName); 277 outputFile.println("<package name=\"" + pkgName + "\">"); 278 279 int tagCount = pd[i].tags().length; 280 if (trace) System.out.println("#tags: " + tagCount); 281 282 List classList; 283 if (classesToUse.containsKey(pkgName)) { 284 // Use only the specified classes in the package 285 System.out.println("Using the specified classes"); 286 classList = (ArrayList) classesToUse.get(pkgName); 287 } else { 288 // Use all classes in the package 289 classList = new LinkedList(Arrays.asList(pd[i].allClasses())); 290 } 291 Collections.sort(classList); 292 ClassDoc[] classes = new ClassDoc[classList.size()]; 293 classes = (ClassDoc[])classList.toArray(classes); 294 processClasses(classes, pkgName); 295 296 addPkgDocumentation(root, pd[i], 2); 297 298 outputFile.println("</package>"); 299 } 300 } // processPackages 301 302 /** 303 * Process classes and interfaces. 304 * 305 * @param cd An array of ClassDoc objects. 306 */ processClasses(ClassDoc[] cd, String pkgName)307 public void processClasses(ClassDoc[] cd, String pkgName) { 308 if (cd.length == 0) 309 return; 310 if (trace) System.out.println("PROCESSING CLASSES, number=" + cd.length); 311 for (int i = 0; i < cd.length; i++) { 312 String className = cd[i].name(); 313 if (trace) System.out.println("PROCESSING CLASS/IFC: " + className); 314 // Only save the shown elements 315 if (!shownElement(cd[i], classVisibilityLevel)) 316 continue; 317 boolean isInterface = false; 318 if (cd[i].isInterface()) 319 isInterface = true; 320 if (isInterface) { 321 outputFile.println(" <!-- start interface " + pkgName + "." + className + " -->"); 322 outputFile.print(" <interface name=\"" + className + "\""); 323 } else { 324 outputFile.println(" <!-- start class " + pkgName + "." + className + " -->"); 325 outputFile.print(" <class name=\"" + className + "\""); 326 } 327 // Add attributes to the class element 328 Type parent = cd[i].superclassType(); 329 if (parent != null) 330 outputFile.println(" extends=\"" + buildEmittableTypeString(parent) + "\""); 331 outputFile.println(" abstract=\"" + cd[i].isAbstract() + "\""); 332 addCommonModifiers(cd[i], 4); 333 outputFile.println(">"); 334 // Process class members. (Treat inner classes as members.) 335 processInterfaces(cd[i].interfaceTypes()); 336 processConstructors(cd[i].constructors()); 337 processMethods(cd[i], cd[i].methods()); 338 processFields(cd[i].fields()); 339 340 addDocumentation(cd[i], 4); 341 342 if (isInterface) { 343 outputFile.println(" </interface>"); 344 outputFile.println(" <!-- end interface " + pkgName + "." + className + " -->"); 345 } else { 346 outputFile.println(" </class>"); 347 outputFile.println(" <!-- end class " + pkgName + "." + className + " -->"); 348 } 349 // Inner classes have already been added. 350 /* 351 ClassDoc[] ic = cd[i].innerClasses(); 352 for (int k = 0; k < ic.length; k++) { 353 System.out.println("Inner class " + k + ", name = " + ic[k].name()); 354 } 355 */ 356 }//for 357 }//processClasses() 358 359 /** 360 * Add qualifiers for the program element as attributes. 361 * 362 * @param ped The given program element. 363 */ addCommonModifiers(ProgramElementDoc ped, int indent)364 public void addCommonModifiers(ProgramElementDoc ped, int indent) { 365 addSourcePosition(ped, indent); 366 // Static and final and visibility on one line 367 for (int i = 0; i < indent; i++) outputFile.print(" "); 368 outputFile.print("static=\"" + ped.isStatic() + "\""); 369 outputFile.print(" final=\"" + ped.isFinal() + "\""); 370 // Visibility 371 String visibility = null; 372 if (ped.isPublic()) 373 visibility = "public"; 374 else if (ped.isProtected()) 375 visibility = "protected"; 376 else if (ped.isPackagePrivate()) 377 visibility = "package"; 378 else if (ped.isPrivate()) 379 visibility = "private"; 380 outputFile.println(" visibility=\"" + visibility + "\""); 381 382 // Deprecation on its own line 383 for (int i = 0; i < indent; i++) outputFile.print(" "); 384 boolean isDeprecated = false; 385 Tag[] ta = ((Doc)ped).tags("deprecated"); 386 if (ta.length != 0) { 387 isDeprecated = true; 388 } 389 if (ta.length > 1) { 390 System.out.println("JDiff: warning: multiple @deprecated tags found in comments for " + ped.name() + ". Using the first one only."); 391 System.out.println("Text is: " + ((Doc)ped).getRawCommentText()); 392 } 393 if (isDeprecated) { 394 String text = ta[0].text(); // Use only one @deprecated tag 395 if (text != null && text.compareTo("") != 0) { 396 int idx = endOfFirstSentence(text); 397 if (idx == 0) { 398 // No useful comment 399 outputFile.print("deprecated=\"deprecated, no comment\""); 400 } else { 401 String fs = null; 402 if (idx == -1) 403 fs = text; 404 else 405 fs = text.substring(0, idx+1); 406 String st = API.hideHTMLTags(fs); 407 outputFile.print("deprecated=\"" + st + "\""); 408 } 409 } else { 410 outputFile.print("deprecated=\"deprecated, no comment\""); 411 } 412 } else { 413 outputFile.print("deprecated=\"not deprecated\""); 414 } 415 416 } //addQualifiers() 417 418 /** 419 * Insert the source code details, if available. 420 * 421 * @param ped The given program element. 422 */ addSourcePosition(ProgramElementDoc ped, int indent)423 public void addSourcePosition(ProgramElementDoc ped, int indent) { 424 if (!addSrcInfo) 425 return; 426 if (JDiff.javaVersion.startsWith("1.1") || 427 JDiff.javaVersion.startsWith("1.2") || 428 JDiff.javaVersion.startsWith("1.3")) { 429 return; // position() only appeared in J2SE1.4 430 } 431 try { 432 // Could cache the method for improved performance 433 Class c = ProgramElementDoc.class; 434 Method m = c.getMethod("position", (Class[]) null); 435 Object sp = m.invoke(ped, (Object[]) null); 436 if (sp != null) { 437 for (int i = 0; i < indent; i++) outputFile.print(" "); 438 outputFile.println("src=\"" + sp + "\""); 439 } 440 } catch (NoSuchMethodException e2) { 441 System.err.println("Error: method \"position\" not found"); 442 e2.printStackTrace(); 443 } catch (IllegalAccessException e4) { 444 System.err.println("Error: class not permitted to be instantiated"); 445 e4.printStackTrace(); 446 } catch (InvocationTargetException e5) { 447 System.err.println("Error: method \"position\" could not be invoked"); 448 e5.printStackTrace(); 449 } catch (Exception e6) { 450 System.err.println("Error: "); 451 e6.printStackTrace(); 452 } 453 } 454 455 /** 456 * Process the interfaces implemented by the class. 457 * 458 * @param ifaces An array of ClassDoc objects 459 */ processInterfaces(Type[] ifaces)460 public void processInterfaces(Type[] ifaces) { 461 if (trace) System.out.println("PROCESSING INTERFACES, number=" + ifaces.length); 462 for (int i = 0; i < ifaces.length; i++) { 463 String ifaceName = buildEmittableTypeString(ifaces[i]); 464 if (trace) System.out.println("PROCESSING INTERFACE: " + ifaceName); 465 outputFile.println(" <implements name=\"" + ifaceName + "\"/>"); 466 }//for 467 }//processInterfaces() 468 469 /** 470 * Process the constructors in the class. 471 * 472 * @param ct An array of ConstructorDoc objects 473 */ processConstructors(ConstructorDoc[] ct)474 public void processConstructors(ConstructorDoc[] ct) { 475 if (trace) System.out.println("PROCESSING CONSTRUCTORS, number=" + ct.length); 476 for (int i = 0; i < ct.length; i++) { 477 String ctorName = ct[i].name(); 478 if (trace) System.out.println("PROCESSING CONSTRUCTOR: " + ctorName); 479 // Only save the shown elements 480 if (!shownElement(ct[i], memberVisibilityLevel)) 481 continue; 482 outputFile.print(" <constructor name=\"" + ctorName + "\""); 483 484 Parameter[] params = ct[i].parameters(); 485 boolean first = true; 486 if (params.length != 0) { 487 outputFile.print(" type=\""); 488 for (int j = 0; j < params.length; j++) { 489 if (!first) 490 outputFile.print(", "); 491 emitType(params[j].type()); 492 first = false; 493 } 494 outputFile.println("\""); 495 } else 496 outputFile.println(); 497 addCommonModifiers(ct[i], 6); 498 outputFile.println(">"); 499 500 // Generate the exception elements if any exceptions are thrown 501 processExceptions(ct[i].thrownExceptions()); 502 503 addDocumentation(ct[i], 6); 504 505 outputFile.println(" </constructor>"); 506 }//for 507 }//processConstructors() 508 509 /** 510 * Process all exceptions thrown by a constructor or method. 511 * 512 * @param cd An array of ClassDoc objects 513 */ processExceptions(ClassDoc[] cd)514 public void processExceptions(ClassDoc[] cd) { 515 if (trace) System.out.println("PROCESSING EXCEPTIONS, number=" + cd.length); 516 for (int i = 0; i < cd.length; i++) { 517 String exceptionName = cd[i].name(); 518 if (trace) System.out.println("PROCESSING EXCEPTION: " + exceptionName); 519 outputFile.print(" <exception name=\"" + exceptionName + "\" type=\""); 520 emitType(cd[i]); 521 outputFile.println("\"/>"); 522 }//for 523 }//processExceptions() 524 525 /** 526 * Process the methods in the class. 527 * 528 * @param md An array of MethodDoc objects 529 */ processMethods(ClassDoc cd, MethodDoc[] md)530 public void processMethods(ClassDoc cd, MethodDoc[] md) { 531 if (trace) System.out.println("PROCESSING " +cd.name()+" METHODS, number = " + md.length); 532 for (int i = 0; i < md.length; i++) { 533 String methodName = md[i].name(); 534 if (trace) System.out.println("PROCESSING METHOD: " + methodName); 535 // Skip <init> and <clinit> 536 if (methodName.startsWith("<")) 537 continue; 538 // Only save the shown elements 539 if (!shownElement(md[i], memberVisibilityLevel)) 540 continue; 541 outputFile.print(" <method name=\"" + methodName + "\""); 542 com.sun.javadoc.Type retType = md[i].returnType(); 543 if (retType.qualifiedTypeName().compareTo("void") == 0) { 544 // Don't add a return attribute if the return type is void 545 outputFile.println(); 546 } else { 547 outputFile.print(" return=\""); 548 emitType(retType); 549 outputFile.println("\""); 550 } 551 outputFile.print(" abstract=\"" + md[i].isAbstract() + "\""); 552 outputFile.print(" native=\"" + md[i].isNative() + "\""); 553 outputFile.println(" synchronized=\"" + md[i].isSynchronized() + "\""); 554 addCommonModifiers(md[i], 6); 555 outputFile.println(">"); 556 // Generate the parameter elements, if any 557 Parameter[] params = md[i].parameters(); 558 for (int j = 0; j < params.length; j++) { 559 outputFile.print(" <param name=\"" + params[j].name() + "\""); 560 outputFile.print(" type=\""); 561 emitType(params[j].type()); 562 outputFile.println("\"/>"); 563 } 564 565 // Generate the exception elements if any exceptions are thrown 566 processExceptions(md[i].thrownExceptions()); 567 568 addDocumentation(md[i], 6); 569 570 outputFile.println(" </method>"); 571 }//for 572 }//processMethods() 573 574 /** 575 * Process the fields in the class. 576 * 577 * @param fd An array of FieldDoc objects 578 */ processFields(FieldDoc[] fd)579 public void processFields(FieldDoc[] fd) { 580 if (trace) System.out.println("PROCESSING FIELDS, number=" + fd.length); 581 for (int i = 0; i < fd.length; i++) { 582 String fieldName = fd[i].name(); 583 if (trace) System.out.println("PROCESSING FIELD: " + fieldName); 584 // Only save the shown elements 585 if (!shownElement(fd[i], memberVisibilityLevel)) 586 continue; 587 outputFile.print(" <field name=\"" + fieldName + "\""); 588 outputFile.print(" type=\""); 589 emitType(fd[i].type()); 590 outputFile.println("\""); 591 outputFile.print(" transient=\"" + fd[i].isTransient() + "\""); 592 outputFile.println(" volatile=\"" + fd[i].isVolatile() + "\""); 593 /* JDK 1.4 and later */ 594 /* 595 String value = fd[i].constantValueExpression(); 596 if (value != null) 597 outputFile.println(" value=\"" + value + "\""); 598 */ 599 addCommonModifiers(fd[i], 6); 600 outputFile.println(">"); 601 602 addDocumentation(fd[i], 6); 603 604 outputFile.println(" </field>"); 605 606 }//for 607 }//processFields() 608 609 /** 610 * Emit the type name. Removed any prefixed warnings about ambiguity. 611 * The type maybe an array. 612 * 613 * @param type A Type object. 614 */ emitType(com.sun.javadoc.Type type)615 public void emitType(com.sun.javadoc.Type type) { 616 String name = buildEmittableTypeString(type); 617 if (name == null) 618 return; 619 outputFile.print(name); 620 } 621 622 /** 623 * Build the emittable type name. The type may be an array and/or 624 * a generic type. 625 * 626 * @param type A Type object 627 * @return The emittable type name 628 */ buildEmittableTypeString(com.sun.javadoc.Type type)629 private String buildEmittableTypeString(com.sun.javadoc.Type type) { 630 if (type == null) { 631 return null; 632 } 633 // type.toString() returns the fully qualified name of the type 634 // including the dimension and the parameters we just need to 635 // escape the generic parameters brackets so that the XML 636 // generated is correct 637 String name = type.toString().replaceAll("<", "<").replaceAll(">", ">"); 638 if (name.startsWith("<<ambiguous>>")) { 639 name = name.substring(13); 640 } 641 return name; 642 } 643 644 /** 645 * Emit the XML header. 646 */ emitXMLHeader()647 public void emitXMLHeader() { 648 outputFile.println("<?xml version=\"1.0\" encoding=\"iso-8859-1\" standalone=\"no\"?>"); 649 outputFile.println("<!-- Generated by the JDiff Javadoc doclet -->"); 650 outputFile.println("<!-- (" + JDiff.jDiffLocation + ") -->"); 651 outputFile.println("<!-- on " + new Date() + " -->"); 652 outputFile.println(); 653 /* No need for this any longer, since doc block text is in an CDATA element 654 outputFile.println("<!-- XML Schema is used, but XHTML transitional DTD is needed for nbsp -->"); 655 outputFile.println("<!-- entity definitions etc.-->"); 656 outputFile.println("<!DOCTYPE api"); 657 outputFile.println(" PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\""); 658 outputFile.println(" \"" + baseURI + "/TR/xhtml1/DTD/xhtml1-transitional.dtd\">"); 659 */ 660 outputFile.println("<api"); 661 outputFile.println(" xmlns:xsi='" + baseURI + "/2001/XMLSchema-instance'"); 662 outputFile.println(" xsi:noNamespaceSchemaLocation='api.xsd'"); 663 outputFile.println(" name=\"" + apiIdentifier + "\""); 664 outputFile.println(" jdversion=\"" + JDiff.version + "\">"); 665 outputFile.println(); 666 } 667 668 /** 669 * Emit the XML footer. 670 */ emitXMLFooter()671 public void emitXMLFooter() { 672 outputFile.println(); 673 outputFile.println("</api>"); 674 } 675 676 /** 677 * Determine if the program element is shown, according to the given 678 * level of visibility. 679 * 680 * @param ped The given program element. 681 * @param visLevel The desired visibility level; "public", "protected", 682 * "package" or "private". If null, only check for an exclude tag. 683 * @return boolean Set if this element is shown. 684 */ shownElement(Doc doc, String visLevel)685 public boolean shownElement(Doc doc, String visLevel) { 686 // If a doc block contains @exclude or a similar such tag, 687 // then don't display it. 688 if (doExclude && excludeTag != null && doc != null) { 689 String rct = doc.getRawCommentText(); 690 if (rct != null && rct.indexOf(excludeTag) != -1) { 691 return false; 692 } 693 } 694 if (visLevel == null) { 695 return true; 696 } 697 ProgramElementDoc ped = null; 698 if (doc instanceof ProgramElementDoc) { 699 ped = (ProgramElementDoc)doc; 700 } 701 if (visLevel.compareTo("private") == 0) 702 return true; 703 // Show all that is not private 704 if (visLevel.compareTo("package") == 0) 705 return !ped.isPrivate(); 706 // Show all that is not private or package 707 if (visLevel.compareTo("protected") == 0) 708 return !(ped.isPrivate() || ped.isPackagePrivate()); 709 // Show all that is not private or package or protected, 710 // i.e. all that is public 711 if (visLevel.compareTo("public") == 0) 712 return ped.isPublic(); 713 return false; 714 } //shownElement() 715 716 /** 717 * Strip out non-printing characters, replacing them with a character 718 * which will not change where the end of the first sentence is found. 719 * This character is the hash mark, '#'. 720 */ stripNonPrintingChars(String s, Doc doc)721 public String stripNonPrintingChars(String s, Doc doc) { 722 if (!stripNonPrintables) 723 return s; 724 char[] sa = s.toCharArray(); 725 for (int i = 0; i < sa.length; i++) { 726 char c = sa[i]; 727 // TODO still have an issue with Unicode: 0xfc in java.lang.String.toUpperCase comments 728 // if (Character.isDefined(c)) 729 if (Character.isLetterOrDigit(c)) 730 continue; 731 // There must be a better way that is still platform independent! 732 if (c == ' ' || 733 c == '.' || 734 c == ',' || 735 c == '\r' || 736 c == '\t' || 737 c == '\n' || 738 c == '!' || 739 c == '?' || 740 c == ';' || 741 c == ':' || 742 c == '[' || 743 c == ']' || 744 c == '(' || 745 c == ')' || 746 c == '~' || 747 c == '@' || 748 c == '#' || 749 c == '$' || 750 c == '%' || 751 c == '^' || 752 c == '&' || 753 c == '*' || 754 c == '-' || 755 c == '=' || 756 c == '+' || 757 c == '_' || 758 c == '|' || 759 c == '\\' || 760 c == '/' || 761 c == '\'' || 762 c == '}' || 763 c == '{' || 764 c == '"' || 765 c == '<' || 766 c == '>' || 767 c == '`' 768 ) 769 continue; 770 /* Doesn't seem to return the expected values? 771 int val = Character.getNumericValue(c); 772 // if (s.indexOf("which is also a test for non-printable") != -1) 773 // System.out.println("** Char " + i + "[" + c + "], val =" + val); //DEBUG 774 // Ranges from https://www.unicode.org/unicode/reports/tr20/ 775 // Should really replace 0x2028 and 0x2029 with <br/> 776 if (val == 0x0 || 777 inRange(val, 0x2028, 0x2029) || 778 inRange(val, 0x202A, 0x202E) || 779 inRange(val, 0x206A, 0x206F) || 780 inRange(val, 0xFFF9, 0xFFFC) || 781 inRange(val, 0xE0000, 0xE007F)) { 782 if (trace) { 783 System.out.println("Warning: changed non-printing character " + sa[i] + " in " + doc.name()); 784 } 785 sa[i] = '#'; 786 } 787 */ 788 // Replace the non-printable character with a printable character 789 // which does not change the end of the first sentence 790 sa[i] = '#'; 791 } 792 return new String(sa); 793 } 794 795 /** Return true if val is in the range [min|max], inclusive. */ inRange(int val, int min, int max)796 public boolean inRange(int val, int min, int max) { 797 if (val < min) 798 return false; 799 if (val > max) 800 return false; 801 return true; 802 } 803 804 /** 805 * Add at least the first sentence from a doc block to the API. This is 806 * used by the report generator if no comment is provided. 807 * Need to make sure that HTML tags are not confused with XML tags. 808 * This could be done by stuffing the < character to another string 809 * or by handling HTML in the parser. This second option seems neater. Note that 810 * XML expects all element tags to have either a closing "/>" or a matching 811 * end element tag. Due to the difficulties of converting incorrect HTML 812 * to XHTML, the first option is used. 813 */ addDocumentation(ProgramElementDoc ped, int indent)814 public void addDocumentation(ProgramElementDoc ped, int indent) { 815 String rct = ((Doc)ped).getRawCommentText(); 816 if (rct != null) { 817 rct = stripNonPrintingChars(rct, (Doc)ped); 818 rct = rct.trim(); 819 if (rct.compareTo("") != 0 && 820 rct.indexOf(Comments.placeHolderText) == -1 && 821 rct.indexOf("InsertOtherCommentsHere") == -1) { 822 int idx = endOfFirstSentence(rct); 823 if (idx == 0) 824 return; 825 for (int i = 0; i < indent; i++) outputFile.print(" "); 826 outputFile.println("<doc>"); 827 for (int i = 0; i < indent; i++) outputFile.print(" "); 828 String firstSentence = null; 829 if (idx == -1) 830 firstSentence = rct; 831 else 832 firstSentence = rct.substring(0, idx+1); 833 boolean checkForAts = false; 834 if (checkForAts && firstSentence.indexOf("@") != -1 && 835 firstSentence.indexOf("@link") == -1) { 836 System.out.println("Warning: @ tag seen in comment: " + 837 firstSentence); 838 } 839 String firstSentenceNoTags = API.stuffHTMLTags(firstSentence); 840 outputFile.println(firstSentenceNoTags); 841 for (int i = 0; i < indent; i++) outputFile.print(" "); 842 outputFile.println("</doc>"); 843 } 844 } 845 } 846 847 /** 848 * Add at least the first sentence from a doc block for a package to the API. This is 849 * used by the report generator if no comment is provided. 850 * The default source tree may not include the package.html files, so 851 * this may be unavailable in many cases. 852 * Need to make sure that HTML tags are not confused with XML tags. 853 * This could be done by stuffing the < character to another string 854 * or by handling HTML in the parser. This second option is neater. Note that 855 * XML expects all element tags to have either a closing "/>" or a matching 856 * end element tag. Due to the difficulties of converting incorrect HTML 857 * to XHTML, the first option is used. 858 */ addPkgDocumentation(RootDoc root, PackageDoc pd, int indent)859 public void addPkgDocumentation(RootDoc root, PackageDoc pd, int indent) { 860 String rct = null; 861 String filename = pd.name(); 862 try { 863 // See if the source path was specified as part of the 864 // options and prepend it if it was. 865 String srcLocation = null; 866 String[][] options = root.options(); 867 for (int opt = 0; opt < options.length; opt++) { 868 if ((options[opt][0]).compareTo("-sourcepath") == 0) { 869 srcLocation = options[opt][1]; 870 break; 871 } 872 } 873 filename = filename.replace('.', JDiff.DIR_SEP.charAt(0)); 874 if (srcLocation != null) { 875 // Make a relative location absolute 876 if (srcLocation.startsWith("..")) { 877 String curDir = System.getProperty("user.dir"); 878 while (srcLocation.startsWith("..")) { 879 srcLocation = srcLocation.substring(3); 880 int idx = curDir.lastIndexOf(JDiff.DIR_SEP); 881 curDir = curDir.substring(0, idx+1); 882 } 883 srcLocation = curDir + srcLocation; 884 } 885 filename = srcLocation + JDiff.DIR_SEP + filename; 886 } 887 // Try both ".htm" and ".html" 888 filename += JDiff.DIR_SEP + "package.htm"; 889 File f2 = new File(filename); 890 if (!f2.exists()) { 891 filename += "l"; 892 } 893 FileInputStream f = new FileInputStream(filename); 894 BufferedReader d = new BufferedReader(new InputStreamReader(f)); 895 String str = d.readLine(); 896 // Ignore everything except the lines between <body> elements 897 boolean inBody = false; 898 while(str != null) { 899 if (!inBody) { 900 if (str.toLowerCase().trim().startsWith("<body")) { 901 inBody = true; 902 } 903 str = d.readLine(); // Get the next line 904 continue; // Ignore the line 905 } else { 906 if (str.toLowerCase().trim().startsWith("</body")) { 907 inBody = false; 908 continue; // Ignore the line 909 } 910 } 911 if (rct == null) 912 rct = str + "\n"; 913 else 914 rct += str + "\n"; 915 str = d.readLine(); 916 } 917 } catch(java.io.FileNotFoundException e) { 918 // If it doesn't exist, that's fine 919 if (trace) 920 System.out.println("No package level documentation file at '" + filename + "'"); 921 } catch(java.io.IOException e) { 922 System.out.println("Error reading file \"" + filename + "\": " + e.getMessage()); 923 System.exit(5); 924 } 925 if (rct != null) { 926 rct = stripNonPrintingChars(rct, (Doc)pd); 927 rct = rct.trim(); 928 if (rct.compareTo("") != 0 && 929 rct.indexOf(Comments.placeHolderText) == -1 && 930 rct.indexOf("InsertOtherCommentsHere") == -1) { 931 int idx = endOfFirstSentence(rct); 932 if (idx == 0) 933 return; 934 for (int i = 0; i < indent; i++) outputFile.print(" "); 935 outputFile.println("<doc>"); 936 for (int i = 0; i < indent; i++) outputFile.print(" "); 937 String firstSentence = null; 938 if (idx == -1) 939 firstSentence = rct; 940 else 941 firstSentence = rct.substring(0, idx+1); 942 String firstSentenceNoTags = API.stuffHTMLTags(firstSentence); 943 outputFile.println(firstSentenceNoTags); 944 for (int i = 0; i < indent; i++) outputFile.print(" "); 945 outputFile.println("</doc>"); 946 } 947 } 948 } 949 950 /** 951 * Find the index of the end of the first sentence in the given text, 952 * when writing out to an XML file. 953 * This is an extended version of the algorithm used by the DocCheck 954 * Javadoc doclet. It checks for @tags too. 955 * 956 * @param text The text to be searched. 957 * @return The index of the end of the first sentence. If there is no 958 * end, return -1. If there is no useful text, return 0. 959 * If the whole doc block comment is wanted (default), return -1. 960 */ endOfFirstSentence(String text)961 public static int endOfFirstSentence(String text) { 962 return endOfFirstSentence(text, true); 963 } 964 965 /** 966 * Find the index of the end of the first sentence in the given text. 967 * This is an extended version of the algorithm used by the DocCheck 968 * Javadoc doclet. It checks for @tags too. 969 * 970 * @param text The text to be searched. 971 * @param writingToXML Set to true when writing out XML. 972 * @return The index of the end of the first sentence. If there is no 973 * end, return -1. If there is no useful text, return 0. 974 * If the whole doc block comment is wanted (default), return -1. 975 */ endOfFirstSentence(String text, boolean writingToXML)976 public static int endOfFirstSentence(String text, boolean writingToXML) { 977 if (saveAllDocs && writingToXML) 978 return -1; 979 int textLen = text.length(); 980 if (textLen == 0) 981 return 0; 982 int index = -1; 983 // Handle some special cases 984 int fromindex = 0; 985 int ellipsis = text.indexOf(". . ."); // Handles one instance of this 986 if (ellipsis != -1) 987 fromindex = ellipsis + 5; 988 // If the first non-whitespace character is an @, go beyond it 989 int i = 0; 990 while (i < textLen && text.charAt(i) == ' ') { 991 i++; 992 } 993 if (text.charAt(i) == '@' && fromindex < textLen-1) 994 fromindex = i + 1; 995 // Use the brute force approach. 996 index = minIndex(index, text.indexOf("? ", fromindex)); 997 index = minIndex(index, text.indexOf("?\t", fromindex)); 998 index = minIndex(index, text.indexOf("?\n", fromindex)); 999 index = minIndex(index, text.indexOf("?\r", fromindex)); 1000 index = minIndex(index, text.indexOf("?\f", fromindex)); 1001 index = minIndex(index, text.indexOf("! ", fromindex)); 1002 index = minIndex(index, text.indexOf("!\t", fromindex)); 1003 index = minIndex(index, text.indexOf("!\n", fromindex)); 1004 index = minIndex(index, text.indexOf("!\r", fromindex)); 1005 index = minIndex(index, text.indexOf("!\f", fromindex)); 1006 index = minIndex(index, text.indexOf(". ", fromindex)); 1007 index = minIndex(index, text.indexOf(".\t", fromindex)); 1008 index = minIndex(index, text.indexOf(".\n", fromindex)); 1009 index = minIndex(index, text.indexOf(".\r", fromindex)); 1010 index = minIndex(index, text.indexOf(".\f", fromindex)); 1011 index = minIndex(index, text.indexOf("@param", fromindex)); 1012 index = minIndex(index, text.indexOf("@return", fromindex)); 1013 index = minIndex(index, text.indexOf("@throw", fromindex)); 1014 index = minIndex(index, text.indexOf("@serial", fromindex)); 1015 index = minIndex(index, text.indexOf("@exception", fromindex)); 1016 index = minIndex(index, text.indexOf("@deprecate", fromindex)); 1017 index = minIndex(index, text.indexOf("@author", fromindex)); 1018 index = minIndex(index, text.indexOf("@since", fromindex)); 1019 index = minIndex(index, text.indexOf("@see", fromindex)); 1020 index = minIndex(index, text.indexOf("@version", fromindex)); 1021 if (doExclude && excludeTag != null) 1022 index = minIndex(index, text.indexOf(excludeTag)); 1023 index = minIndex(index, text.indexOf("@vtexclude", fromindex)); 1024 index = minIndex(index, text.indexOf("@vtinclude", fromindex)); 1025 index = minIndex(index, text.indexOf("<p>", 2)); // Not at start 1026 index = minIndex(index, text.indexOf("<P>", 2)); // Not at start 1027 index = minIndex(index, text.indexOf("<blockquote", 2)); // Not at start 1028 index = minIndex(index, text.indexOf("<pre", fromindex)); // May contain anything! 1029 // Avoid the char at the start of a tag in some cases 1030 if (index != -1 && 1031 (text.charAt(index) == '@' || text.charAt(index) == '<')) { 1032 if (index != 0) 1033 index--; 1034 } 1035 1036 /* Not used for jdiff, since tags are explicitly checked for above. 1037 // Look for a sentence terminated by an HTML tag. 1038 index = minIndex(index, text.indexOf(".<", fromindex)); 1039 if (index == -1) { 1040 // If period-whitespace etc was not found, check to see if 1041 // last character is a period, 1042 int endIndex = text.length()-1; 1043 if (text.charAt(endIndex) == '.' || 1044 text.charAt(endIndex) == '?' || 1045 text.charAt(endIndex) == '!') 1046 index = endIndex; 1047 } 1048 */ 1049 return index; 1050 } 1051 1052 /** 1053 * Return the minimum of two indexes if > -1, and return -1 1054 * only if both indexes = -1. 1055 * @param i an int index 1056 * @param j an int index 1057 * @return an int equal to the minimum index > -1, or -1 1058 */ minIndex(int i, int j)1059 public static int minIndex(int i, int j) { 1060 if (i == -1) return j; 1061 if (j == -1) return i; 1062 return Math.min(i,j); 1063 } 1064 1065 /** 1066 * The name of the file where the XML representing the API will be 1067 * stored. 1068 */ 1069 public static String outputFileName = null; 1070 1071 /** 1072 * The identifier of the API being written out in XML, e.g. 1073 * "SuperProduct 1.3". 1074 */ 1075 public static String apiIdentifier = null; 1076 1077 /** 1078 * The file where the XML representing the API will be stored. 1079 */ 1080 private static PrintWriter outputFile = null; 1081 1082 /** 1083 * The name of the directory where the XML representing the API will be 1084 * stored. 1085 */ 1086 public static String outputDirectory = null; 1087 1088 /** 1089 * Do not display a class with a lower level of visibility than this. 1090 * Default is to display all public and protected classes. 1091 */ 1092 public static String classVisibilityLevel = "protected"; 1093 1094 /** 1095 * Do not display a member with a lower level of visibility than this. 1096 * Default is to display all public and protected members 1097 * (constructors, methods, fields). 1098 */ 1099 public static String memberVisibilityLevel = "protected"; 1100 1101 /** 1102 * If set, then save the entire contents of a doc block comment in the 1103 * API file. If not set, then just save the first sentence. Default is 1104 * that this is set. 1105 */ 1106 public static boolean saveAllDocs = true; 1107 1108 /** 1109 * If set, exclude program elements marked with whatever the exclude tag 1110 * is specified as, e.g. "@exclude". 1111 */ 1112 public static boolean doExclude = false; 1113 1114 /** 1115 * Exclude program elements marked with this String, e.g. "@exclude". 1116 */ 1117 public static String excludeTag = null; 1118 1119 /** 1120 * The base URI for locating necessary DTDs and Schemas. By default, this 1121 * is "https://www.w3.org". A typical value to use local copies of DTD files 1122 * might be "file:///C:/jdiff/lib" 1123 */ 1124 public static String baseURI = "https://www.w3.org"; 1125 1126 /** 1127 * If set, then strip out non-printing characters from documentation. 1128 * Default is that this is set. 1129 */ 1130 static boolean stripNonPrintables = true; 1131 1132 /** 1133 * If set, then add the information about the source file and line number 1134 * which is available in J2SE1.4. Default is that this is not set. 1135 */ 1136 static boolean addSrcInfo = false; 1137 1138 /** 1139 * If set, scan classes with no packages. 1140 * If the source is a jar file this may duplicates classes, so 1141 * disable it using the -packagesonly option. Default is that this is 1142 * not set. 1143 */ 1144 static boolean packagesOnly = false; 1145 1146 /** Set to enable increased logging verbosity for debugging. */ 1147 private static boolean trace = false; 1148 1149 } //RootDocToXML 1150