Initial subversion import.
The following lines are to recognize contributions made before the switch
to Subversion.
Patch by: bruce, jgw, scottb, mmendez, ecc, hcc, knorton, haeberling, samgross, mat.gessel
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/build-tools/doctool/src/booklet.xsd b/build-tools/doctool/src/booklet.xsd
new file mode 100644
index 0000000..f1daf8f
--- /dev/null
+++ b/build-tools/doctool/src/booklet.xsd
@@ -0,0 +1,304 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- edited with XMLSpy v2005 rel. 3 U (http://www.altova.com) by Bruce Johnson (private) -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
+ <xs:element name="booklet">
+ <xs:annotation>
+ <xs:documentation>The documentation for the entire API</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:sequence>
+ <xs:group ref="sectionHead"/>
+ <xs:element ref="package" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="package">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:group ref="sectionHead"/>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="class"/>
+ <xs:element ref="interface"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="class">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:group ref="sectionHead"/>
+ <xs:group ref="access" minOccurs="0"/>
+ <xs:sequence>
+ <xs:element name="isAbstract" minOccurs="0"/>
+ <xs:element name="isStatic" minOccurs="0"/>
+ <xs:element name="isFinal" minOccurs="0"/>
+ </xs:sequence>
+ <xs:sequence>
+ <xs:element name="superclass" minOccurs="0">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="ref">
+ <xs:annotation>
+ <xs:documentation>The id of the type of the superclass</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element ref="superinterface" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="field"/>
+ <xs:element ref="method"/>
+ <xs:element ref="class"/>
+ <xs:element ref="interface"/>
+ <xs:element ref="constructor"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="interface">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:group ref="sectionHead"/>
+ <xs:group ref="access" minOccurs="0"/>
+ <xs:sequence>
+ <xs:element name="isAbstract" minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>Implied but harmless</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="isStatic" minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>Implied but harmless on a nested interface</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:sequence>
+ <xs:element ref="superinterface" minOccurs="0" maxOccurs="unbounded"/>
+ <xs:choice minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="field"/>
+ <xs:element ref="method"/>
+ <xs:element ref="class"/>
+ <xs:element ref="interface"/>
+ </xs:choice>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="method">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:group ref="sectionHead"/>
+ <xs:group ref="access" minOccurs="0"/>
+ <xs:choice minOccurs="0">
+ <xs:element name="isAbstract"/>
+ <xs:element name="isStatic">
+ <xs:annotation>
+ <xs:documentation>N/A when the method appears in an interface</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="isFinal">
+ <xs:annotation>
+ <xs:documentation>N/A when the method appears in an interface</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:choice>
+ <xs:element ref="flatSignature"/>
+ <xs:element name="isSynchronized" minOccurs="0"/>
+ <xs:element name="type">
+ <xs:annotation>
+ <xs:documentation>The method return type</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element ref="params"/>
+ <xs:element ref="throws" minOccurs="0"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="field">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:group ref="sectionHead"/>
+ <xs:group ref="access" minOccurs="0"/>
+ <xs:sequence>
+ <xs:element name="isStatic" minOccurs="0"/>
+ <xs:element name="isFinal" minOccurs="0"/>
+ </xs:sequence>
+ <xs:element name="type">
+ <xs:annotation>
+ <xs:documentation>The field type</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="link">
+ <xs:annotation>
+ <xs:documentation>An explicit link to an id in this same document format (relies on special treatment from XSLT)</xs:documentation>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:attribute name="ref">
+ <xs:annotation>
+ <xs:documentation>The link target id</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="superinterface">
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="ref">
+ <xs:annotation>
+ <xs:documentation>The id of the interface type</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="params">
+ <xs:annotation>
+ <xs:documentation>Method/constructor parameters in left-to-right order</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="param" minOccurs="0" maxOccurs="unbounded">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="type">
+ <xs:annotation>
+ <xs:documentation>The param type, as it should appear to the reader</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:simpleContent>
+ <xs:extension base="xs:string">
+ <xs:attribute name="ref">
+ <xs:annotation>
+ <xs:documentation>A reference to the type id of the param type</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:extension>
+ </xs:simpleContent>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="name" type="xs:string">
+ <xs:annotation>
+ <xs:documentation>The name of the param</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="flatSignature">
+ <xs:annotation>
+ <xs:documentation>A simple param signature enclosed in parentheses that would differentitate overloads in a table of mehod links, such as "(int, Widget)"</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:group name="sectionHead">
+ <xs:sequence>
+ <xs:element name="id">
+ <xs:annotation>
+ <xs:documentation>The id of this section, which can be the target of a link and which should be unique across all documents which might want to link to this section </xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="name">
+ <xs:annotation>
+ <xs:documentation>The name/title of this section; if a @title tag is found, it should be used rather than the programmatic name</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="location" minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>TODO - replace me with better XSLT - A sequence of links that indicate structure back to the logical parent of this component within the doc hierarchy</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element ref="link"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="lead">
+ <xs:annotation>
+ <xs:documentation>Simple HTML intro text (a single phrase or sentence) that might appear in an overview table</xs:documentation>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:any namespace="##any" processContents="skip"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="description">
+ <xs:annotation>
+ <xs:documentation>Detailed narrative related to the topic</xs:documentation>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:any namespace="##any" processContents="skip">
+ <xs:annotation>
+ <xs:documentation>HTML description; "link" elements inteded to be parsed and fixed up</xs:documentation>
+ </xs:annotation>
+ </xs:any>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="tags" minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>Arbitrary tags for which a particular XSLT might handle specially such as "param", "returns", "throws"; some well-known ones are included for reference</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:any namespace="##any" processContents="skip">
+ <xs:annotation>
+ <xs:documentation>@link or any out-of-band tag, such as those found in JavaDoc, go here for potential XSLT special treatment</xs:documentation>
+ </xs:annotation>
+ </xs:any>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:group>
+ <xs:group name="access">
+ <xs:choice>
+ <xs:element name="isPublic"/>
+ <xs:element name="isProtected"/>
+ <xs:element name="isPackagePrivate"/>
+ <xs:element name="isPrivate"/>
+ </xs:choice>
+ </xs:group>
+ <xs:element name="constructor">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:group ref="sectionHead"/>
+ <xs:group ref="access" minOccurs="0"/>
+ <xs:element ref="flatSignature"/>
+ <xs:element ref="params"/>
+ <xs:element ref="throws" minOccurs="0"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="throws">
+ <xs:annotation>
+ <xs:documentation>A list of exception that might be thrown</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:element name="throw">
+ <xs:annotation>
+ <xs:documentation>Indicates a method/ctor might throw this type</xs:documentation>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:attribute name="ref">
+ <xs:annotation>
+ <xs:documentation>A reference to the id of the class that can be thrown</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+</xs:schema>
diff --git a/build-tools/doctool/src/com/google/doctool/Booklet.java b/build-tools/doctool/src/com/google/doctool/Booklet.java
new file mode 100644
index 0000000..5ed1e03
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/Booklet.java
@@ -0,0 +1,944 @@
+// Copyright 2006 Google Inc. All Rights Reserved.
+package com.google.doctool;
+
+import com.google.doctool.LinkResolver.ExtraClassResolver;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.ConstructorDoc;
+import com.sun.javadoc.Doc;
+import com.sun.javadoc.DocErrorReporter;
+import com.sun.javadoc.ExecutableMemberDoc;
+import com.sun.javadoc.FieldDoc;
+import com.sun.javadoc.MemberDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.PackageDoc;
+import com.sun.javadoc.ParamTag;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.ProgramElementDoc;
+import com.sun.javadoc.RootDoc;
+import com.sun.javadoc.SeeTag;
+import com.sun.javadoc.SourcePosition;
+import com.sun.javadoc.Tag;
+import com.sun.javadoc.Type;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Stack;
+
+public class Booklet {
+
+ private static final String OPT_BKCODE = "-bkcode";
+ private static final String OPT_BKDOCPKG = "-bkdocpkg";
+ private static final String OPT_BKOUT = "-bkout";
+
+ private static Booklet sBooklet;
+
+ public static void main(String[] args) {
+ // Strip off our arguments at the beginning.
+ //
+ com.sun.tools.javadoc.Main.execute(args);
+ }
+
+ public static int optionLength(String option) {
+ if (option.equals(OPT_BKOUT))
+ return 2;
+ else if (option.equals(OPT_BKDOCPKG))
+ return 2;
+ else if (option.equals(OPT_BKCODE))
+ return 1;
+ return 0;
+ }
+
+ public static String slurpSource(SourcePosition position) {
+ FileReader fr = null;
+ try {
+ fr = new FileReader(position.file());
+ BufferedReader br = new BufferedReader(fr);
+ for (int i = 0, n = position.line() - 1; i < n; ++i)
+ br.readLine();
+
+ StringBuffer lines = new StringBuffer();
+ String line = br.readLine();
+ int braceDepth = 0;
+ int indent = -1;
+ boolean seenSemiColonOrBrace = false;
+ while (line != null) {
+ if (indent == -1) {
+ for (indent = 0; Character.isWhitespace(line.charAt(indent)); ++indent);
+ }
+
+ if (line.length() >= indent)
+ line = line.substring(indent);
+
+ lines.append(line + "\n");
+ for (int i = 0, n = line.length(); i < n; ++i) {
+ char c = line.charAt(i);
+ if (c == '{') {
+ seenSemiColonOrBrace = true;
+ ++braceDepth;
+ } else if (c == '}') {
+ --braceDepth;
+ } else if (c == ';') {
+ seenSemiColonOrBrace = true;
+ }
+ }
+
+ if (braceDepth > 0 || !seenSemiColonOrBrace) {
+ line = br.readLine();
+ } else {
+ break;
+ }
+ }
+
+ String code = lines.toString();
+ return code;
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ if (fr != null)
+ fr.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return "";
+ }
+
+ public static boolean start(RootDoc rootDoc) {
+ getBooklet().process(rootDoc);
+ return true;
+ }
+
+ public static boolean validOptions(String[][] options,
+ DocErrorReporter reporter) {
+ return getBooklet().analyzeOptions(options, reporter);
+ }
+
+ private static Booklet getBooklet() {
+ if (sBooklet == null)
+ sBooklet = new Booklet();
+ return sBooklet;
+ }
+
+ public Booklet() {
+ // Set up standard tags (to ignore during tag processing)
+ //
+ fStandardTagKinds.add("@see");
+ fStandardTagKinds.add("@serial");
+ fStandardTagKinds.add("@throws");
+ fStandardTagKinds.add("@param");
+ fStandardTagKinds.add("@id");
+ }
+
+ private boolean analyzeOptions(String[][] options, DocErrorReporter reporter) {
+ for (int i = 0, n = options.length; i < n; ++i) {
+ if (options[i][0].equals(OPT_BKOUT)) {
+ fOutputPath = options[i][1];
+ } else if (options[i][0].equals(OPT_BKDOCPKG)) {
+ String[] packages = options[i][1].split(";");
+ fPackagesToGenerate = new HashSet();
+ for (int packageIndex = 0; packageIndex < packages.length; ++packageIndex) {
+ fPackagesToGenerate.add(packages[packageIndex]);
+ }
+ } else if (options[i][0].equals(OPT_BKCODE)) {
+ fShowCode = true;
+ }
+ }
+
+ if (fOutputPath == null) {
+ reporter.printError("You must specify an output directory with "
+ + OPT_BKOUT);
+ return false;
+ }
+
+ return true;
+ }
+
+ private void begin(String tag) {
+ pw.print("<" + tag + ">");
+ fTagStack.push(tag);
+ }
+
+ private void begin(String tag, String attr, String value) {
+ pw.print("<" + tag + " " + attr + "='" + value + "'>");
+ fTagStack.push(tag);
+ }
+
+ private void beginCDATA() {
+ pw.print("<![CDATA[");
+ }
+
+ private void beginEndln(String tag) {
+ pw.println("<" + tag + "/>");
+ }
+
+ private void beginln(String tag) {
+ pw.println();
+ begin(tag);
+ }
+
+ private void beginln(String tag, String attr, String value) {
+ pw.println();
+ begin(tag, attr, value);
+ }
+
+ private void emitDescription(ClassDoc enclosing, Doc forWhat,
+ Tag[] leadInline, Tag[] descInline) {
+ emitJRELink(enclosing, forWhat);
+
+ beginln("lead");
+ processTags(leadInline);
+ endln();
+
+ beginln("description");
+ processTags(descInline);
+ endln();
+ }
+
+ private void emitIdentity(String id, String name) {
+ beginln("id");
+ text(id);
+ endln();
+
+ beginln("name");
+ text(name);
+ endln();
+ }
+
+ private void emitJRELink(ClassDoc enclosing, Doc doc) {
+ String jreLink = "http://java.sun.com/j2se/1.4.2/docs/api/";
+ if (doc instanceof ClassDoc) {
+ ClassDoc classDoc = (ClassDoc) doc;
+ String pkg = classDoc.containingPackage().name();
+ if (!pkg.startsWith("java.")) {
+ return;
+ }
+ String clazz = classDoc.name();
+
+ jreLink += pkg.replace('.', '/') + "/";
+ jreLink += clazz;
+ jreLink += ".html";
+ } else if (doc instanceof ExecutableMemberDoc) {
+ ExecutableMemberDoc execMemberDoc = (ExecutableMemberDoc) doc;
+ String pkg = enclosing.containingPackage().name();
+ if (!pkg.startsWith("java.")) {
+ return;
+ }
+ String clazz = enclosing.name();
+ String method = execMemberDoc.name();
+ String sig = execMemberDoc.signature();
+
+ jreLink += pkg.replace('.', '/') + "/";
+ jreLink += clazz;
+ jreLink += ".html";
+ jreLink += "#";
+ jreLink += method;
+ jreLink += sig;
+ } else if (doc instanceof PackageDoc) {
+ String pkg = doc.name();
+ if (!pkg.startsWith("java.")) {
+ return;
+ }
+ jreLink += pkg.replace('.', '/') + "/package-summary.html";
+ } else if (doc instanceof FieldDoc) {
+ FieldDoc fieldDoc = (FieldDoc) doc;
+ String pkg = enclosing.containingPackage().name();
+ if (!pkg.startsWith("java.")) {
+ return;
+ }
+ String clazz = fieldDoc.containingClass().name();
+ String field = fieldDoc.name();
+
+ jreLink += pkg.replace('.', '/') + "/";
+ jreLink += clazz;
+ jreLink += ".html";
+ jreLink += "#";
+ jreLink += field;
+ }
+
+ // Add the link.
+ //
+ beginln("jre");
+ text(jreLink);
+ endln();
+ }
+
+ private void emitLocation(Doc doc) {
+ Doc parent = getParentDoc(doc);
+ if (parent != null) {
+ beginln("location");
+ emitLocationLink(parent);
+ endln();
+ }
+ }
+
+ private void emitLocationLink(Doc doc) {
+ // Intentionally reverses the order.
+ //
+ String myId;
+ String myTitle;
+ if (doc instanceof MemberDoc) {
+ MemberDoc memberDoc = (MemberDoc) doc;
+ myId = getId(memberDoc);
+ myTitle = memberDoc.name();
+ } else if (doc instanceof ClassDoc) {
+ ClassDoc classDoc = (ClassDoc) doc;
+ myId = getId(classDoc);
+ myTitle = classDoc.name();
+ } else if (doc instanceof PackageDoc) {
+ PackageDoc pkgDoc = (PackageDoc) doc;
+ myId = getId(pkgDoc);
+ myTitle = pkgDoc.name();
+ } else if (doc instanceof RootDoc) {
+ myId = fRootDocId;
+ myTitle = fRootDoc.name();
+ } else {
+ throw new IllegalStateException(
+ "Expected only a member, type, or package");
+ }
+
+ Doc parent = getParentDoc(doc);
+ if (parent != null) {
+ emitLocationLink(parent);
+ }
+
+ beginln("link", "ref", myId);
+
+ Tag[] titleTag = doc.tags("@title");
+ if (titleTag.length > 0) {
+ myTitle = titleTag[0].text().trim();
+ }
+
+ if (myTitle == null || myTitle.length() == 0) {
+ myTitle = "[NO TITLE]";
+ }
+
+ text(myTitle);
+
+ endln();
+ }
+
+ private void emitModifiers(ProgramElementDoc doc) {
+ if (doc.isPrivate())
+ beginEndln("isPrivate");
+ else if (doc.isProtected())
+ beginEndln("isProtected");
+ else if (doc.isPublic())
+ beginEndln("isPublic");
+ else if (doc.isPackagePrivate())
+ beginEndln("isPackagePrivate");
+
+ if (doc.isStatic())
+ beginEndln("isStatic");
+
+ if (doc.isFinal())
+ beginEndln("isFinal");
+
+ if (doc instanceof MethodDoc) {
+ MethodDoc methodDoc = (MethodDoc) doc;
+
+ if (methodDoc.isAbstract())
+ beginEndln("isAbstract");
+
+ if (methodDoc.isSynchronized())
+ beginEndln("isSynchronized");
+ }
+ }
+
+ private void emitOutOfLineTags(Tag[] tags) {
+ beginln("tags");
+ processTags(tags);
+ endln();
+ }
+
+ private void emitType(Type type) {
+ ClassDoc typeAsClass = type.asClassDoc();
+
+ if (typeAsClass != null)
+ begin("type", "ref", getId(typeAsClass));
+ else
+ begin("type");
+
+ String typeName = type.typeName();
+ String dims = type.dimension();
+
+ text(typeName + dims);
+
+ end();
+ }
+
+ private void end() {
+ pw.print("</" + fTagStack.pop() + ">");
+ }
+
+ private void endCDATA() {
+ pw.print("]]>");
+ }
+
+ private void endln() {
+ end();
+ pw.println();
+ }
+
+ private MethodDoc findMatchingInterfaceMethodDoc(ClassDoc[] interfaces,
+ MethodDoc methodDoc) {
+ if (interfaces != null) {
+ // Look through the methods on superInterface for a matching methodDoc.
+ //
+ for (int intfIndex = 0; intfIndex < interfaces.length; ++intfIndex) {
+ ClassDoc currentIntfDoc = interfaces[intfIndex];
+ MethodDoc[] intfMethodDocs = currentIntfDoc.methods();
+ for (int methodIndex = 0; methodIndex < intfMethodDocs.length; ++methodIndex) {
+ MethodDoc intfMethodDoc = intfMethodDocs[methodIndex];
+ String methodDocName = methodDoc.name();
+ String intfMethodDocName = intfMethodDoc.name();
+ if (methodDocName.equals(intfMethodDocName)) {
+ if (methodDoc.signature().equals(intfMethodDoc.signature())) {
+ // It's a match!
+ //
+ return intfMethodDoc;
+ }
+ }
+ }
+
+ // Try the superinterfaces of this interface.
+ //
+ MethodDoc foundMethodDoc = findMatchingInterfaceMethodDoc(
+ currentIntfDoc.interfaces(), methodDoc);
+ if (foundMethodDoc != null)
+ return foundMethodDoc;
+ }
+ }
+
+ // Just didn't find it anywhere. Must not be based on an implemented
+ // interface.
+ //
+ return null;
+ }
+
+ private String getId(ClassDoc classDoc) {
+ return classDoc.qualifiedName();
+ }
+
+ private String getId(ExecutableMemberDoc memberDoc) {
+ // Use the mangled name to look up a unique id (based on its hashCode).
+ //
+ String clazz = memberDoc.containingClass().qualifiedName();
+ String id = clazz + "#" + memberDoc.name() + memberDoc.signature();
+ return id;
+ }
+
+ private String getId(FieldDoc fieldDoc) {
+ String clazz = fieldDoc.containingClass().qualifiedName();
+ String id = clazz + "#" + fieldDoc.name();
+ return id;
+ }
+
+ private String getId(MemberDoc memberDoc) {
+ if (memberDoc.isMethod())
+ return getId((MethodDoc) memberDoc);
+ else if (memberDoc.isConstructor())
+ return getId((ConstructorDoc) memberDoc);
+ else if (memberDoc.isField())
+ return getId((FieldDoc) memberDoc);
+ else
+ throw new RuntimeException("Unknown member type");
+ }
+
+ private String getId(PackageDoc packageDoc) {
+ return packageDoc.name();
+ }
+
+ private Doc getParentDoc(Doc doc) {
+ if (doc instanceof MemberDoc) {
+ MemberDoc memberDoc = (MemberDoc) doc;
+ return memberDoc.containingClass();
+ } else if (doc instanceof ClassDoc) {
+ ClassDoc classDoc = (ClassDoc) doc;
+ Doc enclosingClass = classDoc.containingClass();
+ if (enclosingClass != null) {
+ return enclosingClass;
+ } else {
+ return classDoc.containingPackage();
+ }
+ } else if (doc instanceof PackageDoc) {
+ return fRootDoc;
+ } else if (doc instanceof RootDoc) {
+ return null;
+ } else {
+ throw new IllegalStateException(
+ "Expected only a member, type, or package");
+ }
+ }
+
+ private boolean looksSynthesized(ExecutableMemberDoc memberDoc) {
+ SourcePosition memberPos = memberDoc.position();
+ int memberLine = memberPos.line();
+
+ SourcePosition classPos = memberDoc.containingClass().position();
+ int classLine = classPos.line();
+
+ if (memberLine == classLine)
+ return true;
+ else
+ return false;
+ }
+
+ private void process(ClassDoc enclosing, ClassDoc classDoc) {
+ // Make sure it isn't a @skip-ped topic.
+ //
+ if (classDoc.tags("@skip").length > 0) {
+ // This one is explicitly skipped right now.
+ //
+ return;
+ }
+
+ if (classDoc.isInterface())
+ beginln("interface");
+ else
+ beginln("class");
+
+ emitIdentity(getId(classDoc), classDoc.name());
+ emitLocation(classDoc);
+ emitDescription(enclosing, classDoc, classDoc.firstSentenceTags(),
+ classDoc.inlineTags());
+ emitOutOfLineTags(classDoc.tags());
+ emitModifiers(classDoc);
+
+ ClassDoc superclassDoc = classDoc.superclass();
+ if (superclassDoc != null) {
+ beginln("superclass", "ref", getId(superclassDoc));
+ text(superclassDoc.name());
+ endln();
+ }
+
+ ClassDoc[] superinterfacesDoc = classDoc.interfaces();
+ for (int i = 0; i < superinterfacesDoc.length; i++) {
+ ClassDoc superinterfaceDoc = superinterfacesDoc[i];
+ beginln("superinterface", "ref", getId(superinterfaceDoc));
+ text(superinterfaceDoc.name());
+ endln();
+ }
+
+ ClassDoc[] cda = classDoc.innerClasses();
+ for (int i = 0; i < cda.length; i++) {
+ process(classDoc, cda[i]);
+ }
+
+ FieldDoc[] fda = classDoc.fields();
+ for (int i = 0; i < fda.length; i++)
+ process(classDoc, fda[i]);
+
+ ConstructorDoc[] ctorDocs = classDoc.constructors();
+ for (int i = 0; i < ctorDocs.length; i++)
+ process(classDoc, ctorDocs[i]);
+
+ MethodDoc[] methods = classDoc.methods();
+ for (int i = 0; i < methods.length; i++)
+ process(classDoc, methods[i]);
+
+ endln();
+ }
+
+ private void process(ClassDoc enclosing, ExecutableMemberDoc memberDoc) {
+ if (looksSynthesized(memberDoc)) {
+ // Skip it.
+ //
+ return;
+ }
+
+ // Make sure it isn't a @skip-ped member.
+ //
+ if (memberDoc.tags("@skip").length > 0) {
+ // This one is explicitly skipped right now.
+ //
+ return;
+ }
+
+ if (memberDoc instanceof MethodDoc) {
+ beginln("method");
+ emitIdentity(getId(memberDoc), memberDoc.name());
+ emitLocation(memberDoc);
+
+ // If this method is not explicitly documented, use the best inherited
+ // one.
+ //
+ String rawComment = memberDoc.getRawCommentText();
+ if (rawComment.length() == 0) {
+ // Switch out the member doc being used.
+ //
+ MethodDoc methodDoc = (MethodDoc) memberDoc;
+ MethodDoc superMethodDoc = methodDoc.overriddenMethod();
+
+ if (superMethodDoc == null) {
+
+ ClassDoc classDocToTry = methodDoc.containingClass();
+ while (classDocToTry != null) {
+ // See if this is a method from an interface.
+ // If so, borrow its description.
+ //
+ superMethodDoc = findMatchingInterfaceMethodDoc(
+ classDocToTry.interfaces(), methodDoc);
+
+ if (superMethodDoc != null)
+ break;
+
+ classDocToTry = classDocToTry.superclass();
+ }
+ }
+
+ if (superMethodDoc != null) {
+ // Borrow the description from the superclass/superinterface.
+ //
+ memberDoc = superMethodDoc;
+ }
+ }
+ } else if (memberDoc instanceof ConstructorDoc) {
+ beginln("constructor");
+ emitIdentity(getId(memberDoc), memberDoc.containingClass().name());
+ emitLocation(memberDoc);
+ } else {
+ throw new IllegalStateException("What kind of executable member is this?");
+ }
+
+ emitDescription(enclosing, memberDoc, memberDoc.firstSentenceTags(),
+ memberDoc.inlineTags());
+ emitOutOfLineTags(memberDoc.tags());
+ emitModifiers(memberDoc);
+
+ begin("flatSignature");
+ text(memberDoc.flatSignature());
+ end();
+
+ // Return type if it's a method
+ //
+ if (memberDoc instanceof MethodDoc)
+ emitType(((MethodDoc) memberDoc).returnType());
+
+ // Parameters
+ //
+ beginln("params");
+ Parameter[] pda = memberDoc.parameters();
+ for (int i = 0; i < pda.length; i++) {
+ Parameter pd = pda[i];
+
+ begin("param");
+ emitType(pd.type());
+ begin("name");
+ text(pd.name());
+ end();
+ end();
+ }
+ endln();
+
+ // Exceptions thrown
+ //
+ ClassDoc[] tea = memberDoc.thrownExceptions();
+ if (tea.length > 0) {
+ beginln("throws");
+ for (int i = 0; i < tea.length; ++i) {
+ ClassDoc te = tea[i];
+ beginln("throw", "ref", getId(te));
+ text(te.name());
+ endln();
+ }
+ endln();
+ }
+
+ // Maybe show code
+ //
+ if (fShowCode) {
+ SourcePosition pos = memberDoc.position();
+ if (pos != null) {
+ beginln("code");
+ String source = slurpSource(pos);
+ begin("pre", "class", "code");
+ beginCDATA();
+ text(source);
+ endCDATA();
+ endln();
+ endln();
+ }
+ }
+
+ endln();
+ }
+
+ private void process(ClassDoc enclosing, FieldDoc fieldDoc) {
+ // Make sure it isn't @skip-ped.
+ //
+ if (fieldDoc.tags("@skip").length > 0) {
+ // This one is explicitly skipped right now.
+ //
+ return;
+ }
+
+ String commentText = fieldDoc.commentText();
+ if (fieldDoc.isPrivate()
+ && (commentText == null || commentText.length() == 0))
+ return;
+
+ beginln("field");
+ emitIdentity(fieldDoc.qualifiedName(), fieldDoc.name());
+ emitLocation(fieldDoc);
+ emitDescription(enclosing, fieldDoc, fieldDoc.firstSentenceTags(),
+ fieldDoc.inlineTags());
+ emitOutOfLineTags(fieldDoc.tags());
+ emitModifiers(fieldDoc);
+ emitType(fieldDoc.type());
+ endln();
+ }
+
+ private void process(PackageDoc packageDoc) {
+ beginln("package");
+
+ emitIdentity(packageDoc.name(), packageDoc.name());
+ emitLocation(packageDoc);
+ emitDescription(null, packageDoc, packageDoc.firstSentenceTags(),
+ packageDoc.inlineTags());
+ emitOutOfLineTags(packageDoc.tags());
+
+ // Top-level classes
+ //
+ ClassDoc[] cda = packageDoc.allClasses();
+ for (int i = 0; i < cda.length; i++) {
+ ClassDoc cd = cda[i];
+
+ // Make sure we have source.
+ //
+ SourcePosition p = cd.position();
+ if (p == null || p.line() == 0) {
+ // Skip this since it isn't ours (otherwise we would have source).
+ //
+ continue;
+ }
+
+ if (cd.containingClass() == null) {
+ process(cd, cda[i]);
+ } else {
+ // Not a top-level class.
+ //
+ cd = cda[i];
+ }
+ }
+
+ endln();
+ }
+
+ private void process(RootDoc rootDoc) {
+ try {
+ fRootDoc = rootDoc;
+ File outputFile = new File(fOutputPath);
+ outputFile.getParentFile().mkdirs();
+ FileWriter fw = new FileWriter(outputFile);
+ pw = new PrintWriter(fw, true);
+
+ beginln("booklet");
+
+ fRootDocId = "";
+ String title = "";
+ Tag[] idTags = rootDoc.tags("@id");
+ if (idTags.length > 0) {
+ fRootDocId = idTags[0].text();
+ } else {
+ fRootDoc.printWarning("Expecting @id in an overview html doc; see -overview");
+ }
+
+ Tag[] titleTags = rootDoc.tags("@title");
+ if (titleTags.length > 0) {
+ title = titleTags[0].text();
+ } else {
+ fRootDoc.printWarning("Expecting @title in an overview html doc; see -overview");
+ }
+
+ emitIdentity(fRootDocId, title);
+ emitLocation(rootDoc);
+ emitDescription(null, rootDoc, rootDoc.firstSentenceTags(),
+ rootDoc.inlineTags());
+ emitOutOfLineTags(rootDoc.tags());
+
+ // Create a list of the packages to iterate over.
+ //
+ HashSet packageNames = new HashSet();
+ ClassDoc[] cda = fRootDoc.classes();
+ for (int i = 0; i < cda.length; i++) {
+ ClassDoc cd = cda[i];
+ // Only top-level classes matter.
+ //
+ if (cd.containingClass() == null) {
+ packageNames.add(cd.containingPackage().name());
+ }
+ }
+
+ // Packages
+ //
+ for (Iterator iter = packageNames.iterator(); iter.hasNext();) {
+ String packageName = (String) iter.next();
+
+ // Only process this package if either no "docpkg" is set, or it is
+ // included.
+ //
+ if (fPackagesToGenerate == null
+ || fPackagesToGenerate.contains(packageName)) {
+ PackageDoc pd = fRootDoc.packageNamed(packageName);
+ process(pd);
+ }
+ }
+
+ endln();
+ } catch (Exception e) {
+ e.printStackTrace();
+ fRootDoc.printError("Caught exception: " + e.toString());
+ }
+ }
+
+ private void processSeeTag(SeeTag seeTag) {
+ String ref = null;
+ ClassDoc cd = null;
+ PackageDoc pd = null;
+ MemberDoc md = null;
+ String title = null;
+
+ // Ordered: most-specific to least-specific
+ if (null != (md = seeTag.referencedMember())) {
+ ref = getId(md);
+ } else if (null != (cd = seeTag.referencedClass())) {
+ ref = getId(cd);
+
+ // See if the target has a title.
+ //
+ Tag[] titleTag = cd.tags("@title");
+ if (titleTag.length > 0) {
+ title = titleTag[0].text().trim();
+ if (title.length() == 0)
+ title = null;
+ }
+ } else if (null != (pd = seeTag.referencedPackage())) {
+ ref = getId(pd);
+ }
+
+ String label = seeTag.label();
+
+ // If there is a label, use it.
+ if (label == null || (label != null && label.trim().length() == 0)) {
+
+ // If there isn't a label, see if the @see target has a @title.
+ //
+ if (title != null) {
+ label = title;
+ } else {
+ label = seeTag.text();
+
+ if (label.endsWith("."))
+ label = label.substring(0, label.length() - 1);
+
+ // Rip off all but the last interesting part to prevent fully-qualified
+ // names everywhere.
+ //
+ int last1 = label.lastIndexOf('.');
+ int last2 = label.lastIndexOf('#');
+
+ if (last2 > last1) {
+ // Use the class name plus the member name.
+ //
+ label = label.substring(last1 + 1).replace('#', '.');
+ } else if (last1 != -1)
+ label = label.substring(last1 + 1);
+
+ if (label.charAt(0) == '.') {
+ // Started with "#" so remove the dot.
+ //
+ label = label.substring(1);
+ }
+ }
+ }
+
+ if (ref != null) {
+ begin("link", "ref", ref);
+ text(label != null ? label.trim() : "");
+ end();
+ } else {
+ fRootDoc.printWarning(seeTag.position(),
+ "Unverifiable cross-reference to '" + seeTag.text() + "'");
+ // The link probably won't work, but emit it anyway.
+ begin("link");
+ text(label != null ? label.trim() : "");
+ end();
+ }
+ }
+
+ private void processTags(Tag[] tags) {
+ for (int i = 0; i < tags.length; i++) {
+ Tag tag = tags[i];
+ String tagKind = tag.kind();
+ if (tagKind.equals("Text")) {
+ text(tag.text());
+ } else if (tagKind.equals("@see")) {
+ processSeeTag((SeeTag) tag);
+ } else if (tagKind.equals("@param")) {
+ ParamTag paramTag = (ParamTag) tag;
+ beginln("param");
+ begin("name");
+ text(paramTag.parameterName());
+ end();
+ begin("description");
+ processTags(paramTag.inlineTags());
+ end();
+ endln();
+ } else if (tagKind.equals("@example")) {
+ ExtraClassResolver extraClassResolver = getExtraClassResolver(tag);
+ SourcePosition pos = LinkResolver.resolveLink(tag, extraClassResolver);
+ String source = slurpSource(pos);
+ begin("pre", "class", "code");
+ beginCDATA();
+ text(source);
+ endCDATA();
+ endln();
+ } else if (tagKind.equals("@gwt.include")) {
+ String contents = ResourceIncluder.getResourceFromClasspathScrubbedForHTML(tag);
+ begin("pre", "class", "code");
+ text(contents);
+ endln();
+ } else if (!fStandardTagKinds.contains(tag.name())) {
+ // Custom tag; pass it along other tag.
+ //
+ String tagName = tag.name().substring(1);
+ begin(tagName);
+ processTags(tag.inlineTags());
+ end();
+ }
+ }
+ }
+
+ private ExtraClassResolver getExtraClassResolver(Tag tag) {
+
+ if (tag.holder() instanceof PackageDoc) {
+ return new ExtraClassResolver() {
+ public ClassDoc findClass(String className) {
+ return fRootDoc.classNamed(className);
+ }};
+ }
+
+ return null;
+ }
+
+ private void text(String s) {
+ pw.print(s);
+ }
+
+ private String fOutputPath;
+ private HashSet fPackagesToGenerate;
+ private RootDoc fRootDoc;
+ private String fRootDocId;
+ private boolean fShowCode;
+ private HashSet fStandardTagKinds = new HashSet();
+ private Stack fTagStack = new Stack();
+ private PrintWriter pw;
+}
diff --git a/build-tools/doctool/src/com/google/doctool/DocTool.java b/build-tools/doctool/src/com/google/doctool/DocTool.java
new file mode 100644
index 0000000..44b58e6
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/DocTool.java
@@ -0,0 +1,693 @@
+package com.google.doctool;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+public class DocTool {
+
+ public static final class GenType {
+ private GenType() {
+ }
+ }
+
+ private class ImageCopier extends DefaultHandler {
+
+ private ImageCopier(File htmlDir) {
+ fHtmlDir = htmlDir;
+ }
+
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException {
+ if (qName.equalsIgnoreCase("img")) {
+ String imgSrc = attributes.getValue("src");
+ if (imgSrc != null) {
+ boolean found = false;
+ for (int i = 0, n = fImagePath.length; i < n; ++i) {
+ File dir = fImagePath[i];
+ File inFile = new File(dir, imgSrc);
+ if (inFile.exists()) {
+ // Copy it over.
+ //
+ found = true;
+ File outFile = new File(fHtmlDir, imgSrc);
+
+ if (outFile.exists()) {
+ if (outFile.lastModified() > inFile.lastModified()) {
+ // Already up to date.
+ break;
+ }
+ } else {
+ File outFileDir = outFile.getParentFile();
+ if (!outFileDir.exists() && !outFileDir.mkdirs()) {
+ fErr.println("Unable to create image output dir "
+ + outFileDir);
+ break;
+ }
+ }
+ if (!copyFile(inFile, outFile)) {
+ fErr.println("Unable to copy image file " + outFile);
+ }
+ }
+ }
+ if (!found) {
+ fErr.println("Unable to find image " + imgSrc);
+ }
+ }
+ }
+ }
+
+ private final File fHtmlDir;
+ }
+
+ private static final Pattern IN_XML_FILENAME = Pattern.compile(
+ "(.+)\\.([^\\.]+)\\.xml", Pattern.CASE_INSENSITIVE);
+
+ public static void main(String[] args) {
+ DocToolFactory factory = new DocToolFactory();
+ String arg;
+ String pathSep = System.getProperty("path.separator");
+ for (int i = 0, n = args.length; i < n; ++i) {
+ if (tryParseFlag(args, i, "-help")) {
+ printHelp();
+ return;
+ } else if (null != (arg = tryParseArg(args, i, "-out"))) {
+ ++i;
+ factory.setOutDir(arg);
+ } else if (null != (arg = tryParseArg(args, i, "-html"))) {
+ ++i;
+ factory.setGenerateHtml(true);
+ factory.setTitle(arg);
+
+ // Slurp every arg not prefixed with "-".
+ for (; i + 1 < n && !args[i + 1].startsWith("-"); ++i) {
+ factory.addHtmlFileBase(args[i + 1]);
+ }
+ } else if (null != (arg = tryParseArg(args, i, "-overview"))) {
+ ++i;
+ factory.setOverviewFile(arg);
+ } else if (null != (arg = tryParseArg(args, i, "-sourcepath"))) {
+ ++i;
+ String[] entries = arg.split("\\" + pathSep);
+ for (int entryIndex = 0; entryIndex < entries.length; entryIndex++) {
+ factory.addToSourcePath(entries[entryIndex]);
+ }
+ } else if (null != (arg = tryParseArg(args, i, "-classpath"))) {
+ ++i;
+ String[] entries = arg.split("\\" + pathSep);
+ for (int entryIndex = 0; entryIndex < entries.length; entryIndex++) {
+ factory.addToClassPath(entries[entryIndex]);
+ }
+ } else if (null != (arg = tryParseArg(args, i, "-packages"))) {
+ ++i;
+ String[] entries = arg.split("\\" + pathSep);
+ for (int entryIndex = 0; entryIndex < entries.length; entryIndex++) {
+ factory.addToPackages(entries[entryIndex]);
+ }
+ } else if (null != (arg = tryParseArg(args, i, "-imagepath"))) {
+ ++i;
+ String[] entries = arg.split("\\" + pathSep);
+ for (int entryIndex = 0; entryIndex < entries.length; entryIndex++) {
+ factory.addToImagePath(entries[entryIndex]);
+ }
+ } else {
+ if (factory.getFileType() == null)
+ factory.setFileType(args[i]);
+ else
+ factory.setFileBase(args[i]);
+ }
+ }
+
+ DocTool docTool = factory.create(System.out, System.err);
+ if (docTool != null)
+ docTool.process();
+ }
+
+ public static boolean recursiveDelete(File file) {
+ if (file.isDirectory()) {
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (int i = 0; i < children.length; i++) {
+ if (!recursiveDelete(children[i]))
+ return false;
+ }
+ }
+ }
+ if (!file.delete()) {
+ System.err.println("Unable to delete " + file.getAbsolutePath());
+ return false;
+ }
+ return true;
+ }
+
+ private static void printHelp() {
+ String s = "";
+ s += "DocTool (filetype filebase)? [docset-creation-options] [html-creation-options]\n";
+ s += " Creates structured javadoc xml output from Java source and/or\n";
+ s += " a table of contents and a set of cross-linked html files.\n";
+ s += " Specifying filebase/filetype produces output file \"filebase.filetype.xml\".\n";
+ s += " Specifying -html produces output files in ${out}/html.\n";
+ s += "\n";
+ s += "[docset-creation-options] are\n";
+ s += " -out\n";
+ s += " The output directory\n";
+ s += " -overview\n";
+ s += " The overview html file for this doc set\n";
+ s += " -sourcepath path\n";
+ s += " The path to find Java source for this doc set\n";
+ s += " -classpath path\n";
+ s += " The path to find imported classes for this doc set\n";
+ s += " -packages package-names\n";
+ s += " The command-separated list of package names to include in this doc set\n";
+ s += "\n";
+ s += "[html-creation-options] are\n";
+ s += " -html title filebase+\n";
+ s += " Causes topics in the named filebase(s) to be merged and converted into html\n";
+ s += " -imagepath\n";
+ s += " The semicolon-separated path to find images for html\n";
+ System.out.println(s);
+ }
+
+ /**
+ * Parse a flag with a argument.
+ */
+ private static String tryParseArg(String[] args, int i, String name) {
+ if (i < args.length) {
+ if (args[i].equals(name)) {
+ if (i + 1 < args.length) {
+ String arg = args[i + 1];
+ if (arg.startsWith("-")) {
+ System.out.println("Warning: arg to " + name
+ + " looks more like a flag: " + arg);
+ }
+ return arg;
+ } else {
+ throw new IllegalArgumentException("Expecting an argument after "
+ + name);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Parse just a flag with no subsequent argument.
+ */
+ private static boolean tryParseFlag(String[] args, int i, String name) {
+ if (i < args.length) {
+ if (args[i].equals(name))
+ return true;
+ }
+ return false;
+ }
+
+ DocTool(PrintStream out, PrintStream err, File outDir, boolean generateHtml,
+ String title, String[] htmlFileBases, String fileType, String fileBase,
+ File overviewFile, File[] sourcePath, File[] classPath,
+ String[] packages, File[] imagePath) {
+ fOut = out;
+ fErr = err;
+ fOutDir = outDir;
+ fGenerateHtml = generateHtml;
+ fFileBase = fileBase;
+ fFileType = fileType;
+ fOverviewFile = overviewFile;
+ fSourcePath = sourcePath;
+ fClassPath = classPath;
+ fPackages = packages;
+ fImagePath = imagePath;
+ fTitle = title;
+ fHtmlFileBases = (String[]) htmlFileBases.clone();
+ }
+
+ public boolean copyFile(File in, File out) {
+ FileInputStream fis = null;
+ FileOutputStream fos = null;
+ try {
+ fis = new FileInputStream(in);
+ fos = new FileOutputStream(out);
+ byte[] buf = new byte[4096];
+ int i = 0;
+ while ((i = fis.read(buf)) != -1) {
+ fos.write(buf, 0, i);
+ }
+ return true;
+ } catch (Exception e) {
+ return false;
+ } finally {
+ close(fis);
+ close(fos);
+ }
+ }
+
+ private void close(InputStream is) {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace(fErr);
+ }
+ }
+ }
+
+ private void close(OutputStream os) {
+ if (os != null) {
+ try {
+ os.close();
+ } catch (IOException e) {
+ e.printStackTrace(fErr);
+ }
+ }
+ }
+
+ private boolean copyImages(File htmlDir, File mergedTopicsFile) {
+ FileReader fileReader = null;
+ Throwable caught = null;
+ try {
+ fileReader = new FileReader(mergedTopicsFile);
+ SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
+ InputSource inputSource = new InputSource(fileReader);
+ XMLReader xmlReader = parser.getXMLReader();
+ xmlReader.setContentHandler(new ImageCopier(htmlDir));
+ xmlReader.parse(inputSource);
+ return true;
+ } catch (SAXException e) {
+ caught = e;
+ Exception inner = e.getException();
+ if (inner != null)
+ caught = inner;
+ } catch (ParserConfigurationException e) {
+ caught = e;
+ } catch (IOException e) {
+ caught = e;
+ } finally {
+ try {
+ if (fileReader != null)
+ fileReader.close();
+ } catch (IOException e) {
+ e.printStackTrace(fErr);
+ }
+ }
+ caught.printStackTrace(fErr);
+ return false;
+ }
+
+ private Set findSourcePackages() {
+ Set results = new HashSet();
+ for (int i = 0, n = fSourcePath.length; i < n; ++i) {
+ File srcDir = fSourcePath[i];
+ findSourcePackages(results, srcDir, "");
+ }
+ return results;
+ }
+
+ private void findSourcePackages(Set results, File dir, String parentPackage) {
+ File[] children = dir.listFiles();
+ if (children != null) {
+ for (int i = 0, n = children.length; i < n; ++i) {
+ File child = children[i];
+ String childName = parentPackage
+ + (parentPackage.length() > 0 ? "." : "") + child.getName();
+ if (child.isDirectory()) {
+ // Recurse
+ findSourcePackages(results, child, childName);
+ } else if (child.getName().endsWith(".java")) {
+ // Only include this dir as a result if there's at least one java file
+ results.add(parentPackage);
+ }
+ }
+ }
+ }
+
+ private String flattenPath(File[] entries) {
+ String pathSep = System.getProperty("path.separator");
+ String path = "";
+ for (int i = 0, n = entries.length; i < n; ++i) {
+ File entry = entries[i];
+ if (i > 0)
+ path += pathSep;
+ path += entry.getAbsolutePath();
+ }
+ return path;
+ }
+
+ private void freshenIf(File file) {
+ if (!file.isFile())
+ return;
+
+ String name = file.getName();
+ Matcher matcher = IN_XML_FILENAME.matcher(name);
+ if (matcher.matches()) {
+ String suffix = "." + matcher.group(2) + ".xml";
+ File topicFile = tryReplaceSuffix(file, suffix, ".topics.xml");
+ if (topicFile != null) {
+ if (file.lastModified() > topicFile.lastModified()) {
+ String xsltFileName = matcher.group(2) + "-" + "topics.xslt";
+ String xslt = getFileFromClassPath(xsltFileName); // yucky slow
+ fOut.println(file + " -> " + topicFile);
+ transform(xslt, file, topicFile, null);
+ }
+ }
+ }
+ }
+
+ private boolean genHtml() {
+ // Make sure the html directory exists.
+ //
+ File htmlDir = new File(fOutDir, "html");
+ if (!htmlDir.exists() && !htmlDir.mkdirs()) {
+ fErr.println("Cannot create html output directory "
+ + htmlDir.getAbsolutePath());
+ return false;
+ }
+
+ // Merge all *.topics.xml into one topics.xml file.
+ //
+ File mergedTopicsFile = new File(fOutDir, "topics.xml");
+ if (!mergeTopics(mergedTopicsFile))
+ return false;
+
+ // Parse it all to find the images and copy them over.
+ //
+ copyImages(htmlDir, mergedTopicsFile);
+
+ // Transform to merged topics into merged htmls.
+ //
+ File mergedHtmlsFile = new File(htmlDir, "topics.htmls");
+ long lastModifiedHtmls = mergedHtmlsFile.lastModified();
+ long lastModifiedTopics = mergedTopicsFile.lastModified();
+ if (!mergedHtmlsFile.exists() || lastModifiedHtmls < lastModifiedTopics) {
+ String xsltHtmls = getFileFromClassPath("topics-htmls.xslt");
+
+ Map params = new HashMap();
+ params.put("title", fTitle);
+
+ transform(xsltHtmls, mergedTopicsFile, mergedHtmlsFile, params);
+
+ // Split the merged htmls into many html files.
+ //
+ if (!splitHtmls(mergedHtmlsFile))
+ return false;
+
+ // Create a table of contents.
+ //
+ File tocFile = new File(htmlDir, "contents.html");
+ String xsltToc = getFileFromClassPath("topics-toc.xslt");
+ transform(xsltToc, mergedTopicsFile, tocFile, params);
+
+ // Copy the CSS file over.
+ //
+ String css = getFileFromClassPath("doc.css");
+ try {
+ FileWriter cssWriter = new FileWriter(new File(htmlDir, "doc.css"));
+ cssWriter.write(css);
+ cssWriter.close();
+ } catch (IOException e) {
+ e.printStackTrace(fErr);
+ }
+ } else {
+ fOut
+ .println("Skipping html creation since nothing seems to have changed since "
+ + mergedHtmlsFile.getAbsolutePath());
+ }
+
+ return true;
+ }
+
+ private String getFileFromClassPath(String filename) {
+ InputStream in = null;
+ try {
+ in = getClass().getClassLoader().getResourceAsStream(filename);
+ try {
+ if (in == null) {
+ fErr.println("Cannot find file: " + filename);
+ System.exit(-1); // yuck
+ }
+ StringWriter sw = new StringWriter();
+ int ch;
+ while ((ch = in.read()) != -1) {
+ sw.write(ch);
+ }
+ return sw.toString();
+ } finally {
+ if (in != null)
+ in.close();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean mergeTopics(File mergedTopicsFile) {
+ try {
+ List args = new ArrayList();
+ args.add("join"); // what to do
+ args.add("topics"); // the outer element is <topics>
+ args.add(mergedTopicsFile.getAbsolutePath());
+
+ // For each of the htmlFileBases, try to find a file having that name to
+ // merge into the big topics doc.
+ //
+ boolean foundAny = false;
+ for (int i = 0, n = fHtmlFileBases.length; i < n; ++i) {
+ String filebase = fHtmlFileBases[i];
+ File fileToMerge = new File(fOutDir, filebase + ".topics.xml");
+ if (fileToMerge.exists()) {
+ foundAny = true;
+ args.add(fileToMerge.getAbsolutePath());
+ } else {
+ fErr.println("Unable to find " + fileToMerge.getName());
+ }
+ }
+
+ if (foundAny) {
+ String[] argArray = (String[]) args.toArray(new String[0]);
+ traceCommand("SplitterJoiner", argArray);
+ SplitterJoiner.main(argArray);
+ } else {
+ fErr.println("No topics found");
+ return false;
+ }
+ } catch (IOException e) {
+ e.printStackTrace(fErr);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Runs the help process.
+ */
+ private boolean process() {
+ if (fFileType != null) {
+ // Produce XML from JavaDoc.
+ //
+ String fileName = fFileBase + "." + fFileType + ".xml";
+ if (!runBooklet(new File(fOutDir, fileName)))
+ return false;
+
+ }
+
+ // Process existing files to get them into topics format.
+ // Done afterwards for convenience when debugging your doc.
+ //
+ transformExistingIntoTopicXml();
+
+ if (fGenerateHtml) {
+ // Merge into HTML.
+ if (!genHtml())
+ return false;
+ }
+
+ return true;
+ }
+
+ private boolean runBooklet(File bkoutFile) {
+ // Write out the list of packages that can be found on the source path.
+ fOut.println("Creating " + bkoutFile.getAbsolutePath());
+ Set srcPackages = findSourcePackages();
+ if (srcPackages.isEmpty()) {
+ fErr.println("No input files found");
+ return false;
+ }
+
+ List args = new ArrayList();
+
+ // For now, harded-coded, but could be passed through
+ args.add("-source");
+ args.add("1.4");
+
+ // The doclet
+ args.add("-doclet");
+ args.add(Booklet.class.getName());
+
+ // Class path
+ args.add("-classpath");
+ args.add(flattenPath(fClassPath));
+
+ // Source path
+ args.add("-sourcepath");
+ args.add(flattenPath(fSourcePath));
+
+ // Overview file
+ if (fOverviewFile != null) {
+ args.add("-overview");
+ args.add(fOverviewFile.getAbsolutePath());
+ }
+
+ // Output file
+ args.add("-bkout");
+ args.add(bkoutFile.getAbsolutePath());
+
+ if (fPackages != null) {
+ // Specify the packages to actually emit doc for
+ StringBuffer bkdocpkg = new StringBuffer();
+ for (int i = 0; i < fPackages.length; i++) {
+ String pkg = fPackages[i];
+ bkdocpkg.append(pkg);
+ bkdocpkg.append(";");
+ }
+ args.add("-bkdocpkg");
+ args.add(bkdocpkg.toString());
+ }
+
+ args.add("-breakiterator");
+
+ // Specify the set of input packages (needed by JavaDoc)
+ args.addAll(srcPackages);
+
+ String[] argArray = (String[]) args.toArray(new String[0]);
+ traceCommand("Booklet", argArray);
+ Booklet.main(argArray);
+
+ return bkoutFile.exists();
+ }
+
+ private boolean splitHtmls(File mergedHtmlsFile) {
+ try {
+ List args = new ArrayList();
+ args.add("split"); // what to do
+ args.add(mergedHtmlsFile.getAbsolutePath());
+ String[] argArray = (String[]) args.toArray(new String[0]);
+ traceCommand("SplitterJoiner", argArray);
+ SplitterJoiner.main(argArray);
+ } catch (IOException e) {
+ e.printStackTrace(fErr);
+ return false;
+ }
+ return true;
+ }
+
+ private void traceCommand(String cmd, String[] args) {
+ fOut.print(cmd);
+ for (int i = 0, n = args.length; i < n; ++i) {
+ String arg = args[i];
+ fOut.print(" ");
+ fOut.print(arg);
+ }
+ fOut.println();
+ }
+
+ private void transform(String xslt, File inFile, File outFile, Map params) {
+ Throwable caught = null;
+ try {
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ StreamSource xsltSource = new StreamSource(new StringReader(xslt));
+ Transformer transformer = transformerFactory.newTransformer(xsltSource);
+ transformer.setOutputProperty(
+ javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT,
+ "yes");
+ transformer.setOutputProperty(
+ "{http://xml.apache.org/xslt}indent-amount", "4");
+
+ if (params != null) {
+ for (Iterator iter = params.entrySet().iterator(); iter.hasNext();) {
+ Map.Entry entry = (Map.Entry) iter.next();
+ transformer.setParameter((String) entry.getKey(), entry.getValue());
+ }
+ }
+
+ FileOutputStream fos = new FileOutputStream(outFile);
+ StreamResult result = new StreamResult(fos);
+ StreamSource xmlSource = new StreamSource(new FileReader(inFile));
+ transformer.transform(xmlSource, result);
+ fos.close();
+ return;
+ } catch (TransformerConfigurationException e) {
+ caught = e;
+ } catch (TransformerException e) {
+ caught = e;
+ } catch (IOException e) {
+ caught = e;
+ }
+ throw new RuntimeException("Unable to complete the xslt tranform", caught);
+ }
+
+ private void transformExistingIntoTopicXml() {
+ File[] children = fOutDir.listFiles();
+ if (children != null) {
+ for (int i = 0, n = children.length; i < n; ++i) {
+ File file = children[i];
+ freshenIf(file);
+ }
+ }
+ }
+
+ private File tryReplaceSuffix(File file, String oldSuffix, String newSuffix) {
+ String name = file.getName();
+ if (name.endsWith(oldSuffix)) {
+ String baseName = name.substring(0, name.length() - oldSuffix.length());
+ return new File(file.getParent(), baseName + newSuffix);
+ } else {
+ return null;
+ }
+ }
+
+ private final File[] fClassPath;
+ private final String[] fPackages;
+ private final PrintStream fErr;
+ private final String fFileBase;
+ private final String fFileType;
+ private final boolean fGenerateHtml;
+ private final String[] fHtmlFileBases;
+ private final File[] fImagePath;
+ private final PrintStream fOut;
+ private final File fOutDir;
+ private final File fOverviewFile;
+ private final File[] fSourcePath;
+ private final String fTitle;
+}
diff --git a/build-tools/doctool/src/com/google/doctool/DocToolFactory.java b/build-tools/doctool/src/com/google/doctool/DocToolFactory.java
new file mode 100644
index 0000000..7cc51f9
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/DocToolFactory.java
@@ -0,0 +1,135 @@
+package com.google.doctool;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class DocToolFactory {
+
+ public DocToolFactory() {
+ }
+
+ public void addHtmlFileBase(String filebase) {
+ htmlFileBases.add(filebase);
+ }
+
+ public void addToClassPath(String path) {
+ classPathEntries.add(new File(path));
+ }
+
+ public void addToImagePath(String path) {
+ imagePathEntries.add(new File(path));
+ }
+
+ public void addToPackages(String packageName) {
+ this.packageNameEntries.add(packageName);
+ }
+
+ public void addToSourcePath(String path) {
+ srcPathEntries.add(new File(path));
+ }
+
+ public DocTool create(PrintStream out, PrintStream err) {
+ File localOutDir = outDir;
+ if (localOutDir == null) {
+ localOutDir = new File(System.getProperty("user.dir"), "out");
+ out.println("Using default output directory: "
+ + localOutDir.getAbsolutePath());
+ }
+
+ File[] classPath = null;
+ File[] sourcePath = null;
+ String[] packageNames = null;
+ if (fileType != null) {
+ // Generating a doc set implies other settings.
+ //
+ if (fileBase == null) {
+ err.println("A file base must be specified when generating doc");
+ return null;
+ }
+// if (overviewFile == null) {
+// err
+// .println("An overview file must be specified when generating doc; if you don't have one, use this:");
+// err.println("<html><body>");
+// err.println(" " + fileBase + "documentation");
+// err.println(" @id " + fileBase + "-doc");
+// err.println(" @title Documentation for " + fileBase);
+// err.println("</body></html>");
+// return null;
+// }
+ classPath = (File[]) classPathEntries.toArray(new File[0]);
+ sourcePath = (File[]) srcPathEntries.toArray(new File[0]);
+ packageNames = (String[]) packageNameEntries.toArray(new String[0]);
+ }
+
+ if (generateHtml) {
+ if (title == null) {
+ out.println("A title must be specified when generating html");
+ return null;
+ }
+
+ if (htmlFileBases.isEmpty()) {
+ out.println("No html filebases were specified");
+ return null;
+ }
+ }
+
+ String[] htmlFileBaseArray = (String[]) htmlFileBases
+ .toArray(new String[0]);
+
+ // Handle -imagepath
+ //
+ List localImagePathEntries = new ArrayList(imagePathEntries);
+ if (localImagePathEntries.isEmpty()) {
+ out.println("No image path specified; using only the output dir");
+ }
+
+ localImagePathEntries.add(localOutDir);
+ File[] imagePath = (File[]) imagePathEntries.toArray(new File[0]);
+
+ return new DocTool(out, err, localOutDir, generateHtml, title,
+ htmlFileBaseArray, fileType, fileBase, overviewFile, sourcePath,
+ classPath, packageNames, imagePath);
+ }
+
+ public String getFileType() {
+ return fileType;
+ }
+
+ public void setFileBase(String fileBase) {
+ this.fileBase = fileBase;
+ }
+
+ public void setFileType(String fileType) {
+ this.fileType = fileType;
+ }
+
+ public void setGenerateHtml(boolean generateHtml) {
+ this.generateHtml = generateHtml;
+ }
+
+ public void setOutDir(String outDirPath) {
+ this.outDir = new File(outDirPath);
+ }
+
+ public void setOverviewFile(String overviewFile) {
+ this.overviewFile = new File(overviewFile);
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ private final List classPathEntries = new ArrayList();
+ private String fileBase;
+ private String fileType;
+ private boolean generateHtml;
+ private final List htmlFileBases = new ArrayList();
+ private final List imagePathEntries = new ArrayList();
+ private File outDir;
+ private File overviewFile;
+ private final List packageNameEntries = new ArrayList();
+ private final List srcPathEntries = new ArrayList();
+ private String title;
+}
diff --git a/build-tools/doctool/src/com/google/doctool/LinkResolver.java b/build-tools/doctool/src/com/google/doctool/LinkResolver.java
new file mode 100644
index 0000000..c36d2c0
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/LinkResolver.java
@@ -0,0 +1,123 @@
+package com.google.doctool;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.Doc;
+import com.sun.javadoc.MemberDoc;
+import com.sun.javadoc.MethodDoc;
+import com.sun.javadoc.PackageDoc;
+import com.sun.javadoc.Parameter;
+import com.sun.javadoc.SourcePosition;
+import com.sun.javadoc.Tag;
+
+public class LinkResolver {
+
+ public interface ExtraClassResolver {
+ ClassDoc findClass(String className);
+ }
+
+ public static SourcePosition resolveLink(Tag tag) {
+ return resolveLink(tag, null);
+ }
+
+ public static SourcePosition resolveLink(Tag tag,
+ ExtraClassResolver classResolver) {
+ String linkText = tag.text();
+ String className;
+ String methodSig;
+ int pos = linkText.indexOf('#');
+ if (pos >= 0) {
+ className = linkText.substring(0, pos);
+ methodSig = linkText.substring(pos + 1);
+ } else {
+ className = linkText;
+ methodSig = null;
+ }
+
+ ClassDoc containingClass = null;
+ Doc holder = tag.holder();
+ if (holder instanceof ClassDoc) {
+ containingClass = (ClassDoc) holder;
+ } else if (holder instanceof MemberDoc) {
+ containingClass = ((MemberDoc) holder).containingClass();
+ }
+
+ ClassDoc targetClass = null;
+ if (className.length() == 0) {
+ targetClass = containingClass;
+ } else if (holder instanceof PackageDoc) {
+ targetClass = ((PackageDoc) holder).findClass(className);
+ } else {
+ targetClass = containingClass.findClass(className);
+ }
+
+ if (targetClass == null) {
+ if (classResolver != null) {
+ targetClass = classResolver.findClass(className);
+ }
+ if (targetClass == null) {
+ System.err.println(tag.position().toString()
+ + ": unable to resolve class " + className + " for " + tag);
+ System.exit(1);
+ }
+ }
+
+ if (methodSig == null) {
+ return targetClass.position();
+ }
+
+ String paramSig = methodSig.substring(methodSig.indexOf('(') + 1,
+ methodSig.lastIndexOf(')'));
+ String[] resolvedParamTypes;
+ if (paramSig.length() > 0) {
+ String[] unresolvedParamTypes = paramSig.split("\\s*,\\s*");
+ resolvedParamTypes = new String[unresolvedParamTypes.length];
+ for (int i = 0; i < unresolvedParamTypes.length; ++i) {
+ ClassDoc paramType = containingClass.findClass(unresolvedParamTypes[i]);
+ if (paramType == null && classResolver != null) {
+ paramType = classResolver.findClass(unresolvedParamTypes[i]);
+ }
+ if (paramType == null) {
+ System.err.println(tag.position().toString()
+ + ": unable to resolve class " + unresolvedParamTypes[i] + " for "
+ + tag);
+ System.exit(1);
+ }
+ resolvedParamTypes[i] = paramType.qualifiedTypeName();
+ }
+ } else {
+ resolvedParamTypes = new String[0];
+ }
+
+ String possibleOverloads = "";
+
+ MethodDoc[] methods = targetClass.methods();
+ outer : for (int i = 0; i < methods.length; ++i) {
+ MethodDoc methodDoc = methods[i];
+ if (methodSig.startsWith(methodDoc.name())) {
+ possibleOverloads += "\n" + methodDoc.flatSignature();
+ Parameter[] tryParams = methodDoc.parameters();
+ if (resolvedParamTypes.length != tryParams.length) {
+ // param count mismatch
+ continue outer;
+ }
+ for (int j = 0; j < tryParams.length; ++j) {
+ if (!tryParams[j].type().qualifiedTypeName().equals(
+ resolvedParamTypes[j])) {
+ // param type mismatch
+ continue outer;
+ }
+ }
+ return methodDoc.position();
+ }
+ }
+
+ System.err.println(tag.position().toString()
+ + ": unable to resolve method for " + tag);
+ if (possibleOverloads.length() > 0) {
+ System.err.println("Possible overloads:" + possibleOverloads);
+ }
+ System.exit(1);
+ return null;
+ }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/ResourceIncluder.java b/build-tools/doctool/src/com/google/doctool/ResourceIncluder.java
new file mode 100644
index 0000000..d3afc89
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/ResourceIncluder.java
@@ -0,0 +1,90 @@
+package com.google.doctool;
+
+import com.sun.javadoc.Tag;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ResourceIncluder {
+
+ public static String getResourceFromClasspathScrubbedForHTML(Tag tag) {
+ String partialPath = tag.text();
+ try {
+ String contents;
+ contents = getFileFromClassPath(partialPath);
+ contents = scrubForHtml(contents);
+ return contents;
+ } catch (IOException e) {
+ System.err.println(tag.position().toString()
+ + ": unable to include resource " + partialPath + " for tag " + tag);
+ System.exit(1);
+ return null;
+ }
+ }
+
+ /**
+ * Copied from {@link com.google.gwt.util.tools.Utility#close(InputStream)}.
+ */
+ public static void close(InputStream is) {
+ try {
+ if (is != null)
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+
+ /**
+ * Copied from
+ * {@link com.google.gwt.util.tools.Utility#getFileFromClassPath(String)}.
+ */
+ private static String getFileFromClassPath(String partialPath)
+ throws IOException {
+ InputStream in = ResourceIncluder.class.getClassLoader().getResourceAsStream(
+ partialPath);
+ try {
+ if (in == null) {
+ throw new FileNotFoundException(partialPath);
+ }
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ int ch;
+ while ((ch = in.read()) != -1) {
+ os.write(ch);
+ }
+ return new String(os.toByteArray(), "UTF-8");
+ } finally {
+ close(in);
+ }
+ }
+
+ private static String scrubForHtml(String contents) {
+ char[] chars = contents.toCharArray();
+ int len = chars.length;
+ StringBuffer sb = new StringBuffer(len);
+ for (int i = 0; i < len; ++i) {
+ char c = chars[i];
+ switch (c) {
+ case '\r':
+ // collapse \r\n into \n
+ if (i == len - 1 || chars[i + 1] != '\n') {
+ sb.append('\n');
+ }
+ break;
+ case '&':
+ sb.append("&");
+ break;
+ case '<':
+ sb.append("<");
+ break;
+ case '>':
+ sb.append(">");
+ break;
+ default:
+ sb.append(c);
+ break;
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/build-tools/doctool/src/com/google/doctool/SplitterJoiner.java b/build-tools/doctool/src/com/google/doctool/SplitterJoiner.java
new file mode 100644
index 0000000..473f830
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/SplitterJoiner.java
@@ -0,0 +1,273 @@
+package com.google.doctool;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+/**
+ * Takes an input stream and splits it into multiple files.
+ * A new file begins when a line in the input stream begins with a specific <emph>prefix</emph>
+ * followed by whitespace then an absolute or relative file name to create.
+ */
+public class SplitterJoiner {
+
+ private static void split(String[] files) throws IOException {
+ BufferedReader reader = null;
+ String prefix = null;
+ File inputFile = null;
+
+ for (int i = 0; i < files.length; i++) {
+
+ // Close the current reader, if any.
+ //
+ if (reader != null)
+ reader.close();
+
+ // Open the next reader.
+ //
+ String file = files[i];
+ inputFile = new File(file);
+ if (!inputFile.exists()) {
+ System.err.println("Error: Cannot find input file "
+ + inputFile.getPath());
+ return;
+ }
+ reader = new BufferedReader(new FileReader(inputFile));
+
+ // Parse the input
+ //
+ File outFile = null;
+ PrintWriter writer = null;
+ String line = reader.readLine();
+ while (line != null) {
+ if (prefix == null) {
+ // Learn the prefix.
+ //
+ prefix = line.trim();
+ if (prefix.length() == 0) {
+ // The first line with anything on it counts as the prefix.
+ //
+ prefix = null;
+ }
+ } else if (line.startsWith(prefix)) {
+ // Close the current writer.
+ //
+ if (writer != null)
+ writer.close();
+
+ // Create the next writer.
+ //
+ String outPath = line.substring(prefix.length()).trim();
+ outFile = new File(outPath);
+ if (!outFile.isAbsolute() && inputFile != null) {
+ // Make the created file relative to the input file.
+ //
+ File absoluteParentDir = inputFile.getCanonicalFile()
+ .getParentFile();
+ outFile = new File(absoluteParentDir, outPath);
+ outFile.getParentFile().mkdirs();
+ }
+
+ writer = new PrintWriter(new FileWriter(outFile), true);
+
+ writer.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">");
+
+ } else if (writer != null) {
+ // Write this line to the current file.
+ //
+ writer.println(line);
+ } else {
+ // Ignored -- haven't yet seen a starting prefix.
+ //
+ }
+
+ line = reader.readLine();
+ }
+
+ if (writer != null)
+ writer.close();
+ }
+ }
+
+ private static void writeTopLevelChildren(Document doc, PrintWriter out)
+ throws TransformerException {
+ StreamResult result = new StreamResult(out);
+ TransformerFactory transformerFactory = TransformerFactory.newInstance();
+ Transformer transformer = transformerFactory.newTransformer();
+ transformer.setOutputProperty(javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION, "yes");
+ transformer.setOutputProperty(javax.xml.transform.OutputKeys.INDENT, "yes");
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount",
+ "4");
+
+ Node child = doc.getDocumentElement().getFirstChild();
+ while (child != null) {
+ DOMSource domSource = new DOMSource(child);
+ transformer.transform(domSource, result);
+ child = child.getNextSibling();
+ }
+ }
+
+ private static void emitFile(PrintWriter out, File outputFile, File inputFile)
+ throws IOException, ParserConfigurationException, SAXException,
+ TransformerException {
+ if (!inputFile.exists()) {
+ System.err
+ .println("Error: Cannot find input file " + inputFile.getPath());
+ return;
+ }
+
+ if (inputFile.getCanonicalFile().equals(outputFile)) {
+ // skip
+ return;
+ }
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(inputFile);
+ writeTopLevelChildren(doc, out);
+ }
+
+ private static void merge(String tag, String outputPath, String files[]) {
+ File outputFile = null;
+ try {
+ outputFile = new File(outputPath).getCanonicalFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ // Maybe we don't need to do anything.
+ //
+ boolean skipMerge = true;
+ if (!outputFile.exists()) {
+ skipMerge = false;
+ } else {
+ long outputFileLastModified = outputFile.lastModified();
+ for (int i = 0, n = files.length; i < n; ++i) {
+ if (isNewerThan(new File(files[i]), outputFileLastModified)) {
+ skipMerge = false;
+ break;
+ }
+ }
+ }
+
+ if (skipMerge) {
+ // Nothing to do.
+ //
+ return;
+ }
+
+ try {
+ PrintWriter out = new PrintWriter(new FileWriter(outputFile), true);
+
+ out.println("<?xml version='1.0'?>");
+ out.println("<" + tag + ">");
+
+ for (int i = 0; i < files.length; i++) {
+ File file = new File(files[i]);
+ if (file.isFile()) {
+ emitFile(out, outputFile, file);
+ } else {
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (int j = 0; j < children.length; ++j) {
+ if (children[j].isFile()
+ && children[j].getPath().endsWith(".xml"))
+ emitFile(out, outputFile, children[j]);
+ }
+ }
+ }
+ }
+ out.println("</" + tag + ">");
+ out.close();
+ } catch (IOException e) {
+ outputFile.deleteOnExit();
+ e.printStackTrace();
+ } catch (ParserConfigurationException e) {
+ outputFile.deleteOnExit();
+ e.printStackTrace();
+ } catch (SAXException e) {
+ outputFile.deleteOnExit();
+ e.printStackTrace();
+ } catch (TransformerException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static boolean isNewerThan(File file, long lastModified) {
+ if (file.isFile())
+ return file.lastModified() > lastModified;
+
+ File[] children = file.listFiles();
+ if (children != null) {
+ for (int i = 0, n = children.length; i < n; ++i) {
+ File child = children[i];
+ if (isNewerThan(child, lastModified)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static void help() {
+ System.out.println("Usage: SplitterJoiner split infile+");
+ System.out.println("Usage: SplitterJoiner join tag outfile (infile|dir)+");
+ System.out
+ .println("\tsplit indicates that inputs file should be split into multiple output files");
+ System.out
+ .println("\tjoin indicates xml files (or directories) should be merged into one big xml file (on stdout)");
+ System.out
+ .println("\ttag when joining, the outermost xml tag name");
+ System.out
+ .println("\toutfile when joining, the file to write the joined output into");
+ System.out.println("\tinfile if splitting, an input file to split");
+ System.out
+ .println("\t if joining, a file whose contents should be merged in");
+ System.out
+ .println("\tdir when joining, a directory whose xml files' contents should be merged in");
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length < 2) {
+ help();
+ return;
+ } else if (args[0].equals("split")) {
+ String[] files = new String[args.length - 1];
+ System.arraycopy(args, 1, files, 0, args.length - 1);
+ split(files);
+ } else if (args[0].equals("join")) {
+ if (args.length < 4) {
+ help();
+ return;
+ }
+ String[] files = new String[args.length - 3];
+ System.arraycopy(args, 3, files, 0, args.length - 3);
+ merge(args[1], args[2], files);
+ } else {
+ if (!args[0].equals("-h") && !args[0].equals("-?"))
+ System.err.println("Error: don't know '" + args[0] + "'");
+ help();
+ return;
+ }
+
+ }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/custom/ExampleTaglet.java b/build-tools/doctool/src/com/google/doctool/custom/ExampleTaglet.java
new file mode 100644
index 0000000..f2b99ba
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/custom/ExampleTaglet.java
@@ -0,0 +1,73 @@
+package com.google.doctool.custom;
+
+import com.google.doctool.Booklet;
+import com.google.doctool.LinkResolver;
+import com.google.doctool.LinkResolver.ExtraClassResolver;
+
+import com.sun.javadoc.ClassDoc;
+import com.sun.javadoc.SourcePosition;
+import com.sun.javadoc.Tag;
+import com.sun.tools.doclets.Taglet;
+
+import java.util.Map;
+
+public class ExampleTaglet implements Taglet {
+
+ public static void register(Map tagletMap) {
+ ExampleTaglet tag = new ExampleTaglet();
+ Taglet t = (Taglet) tagletMap.get(tag.getName());
+ if (t != null) {
+ tagletMap.remove(tag.getName());
+ }
+ tagletMap.put(tag.getName(), tag);
+ }
+
+ public String getName() {
+ return "example";
+ }
+
+ public boolean inConstructor() {
+ return true;
+ }
+
+ public boolean inField() {
+ return true;
+ }
+
+ public boolean inMethod() {
+ return true;
+ }
+
+ public boolean inOverview() {
+ return true;
+ }
+
+ public boolean inPackage() {
+ return true;
+ }
+
+ public boolean inType() {
+ return true;
+ }
+
+ public boolean isInlineTag() {
+ return true;
+ }
+
+ public String toString(Tag tag) {
+ SourcePosition position = LinkResolver.resolveLink(tag,
+ new ExtraClassResolver() {
+ public ClassDoc findClass(String className) {
+ return GWTJavaDoclet.root.classNamed(className);
+ }
+ });
+
+ String slurpSource = Booklet.slurpSource(position);
+ return "<blockquote><pre>" + slurpSource + "</pre></blockquote>";
+ }
+
+ public String toString(Tag[] tags) {
+ return null;
+ }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/custom/GWTJavaDoclet.java b/build-tools/doctool/src/com/google/doctool/custom/GWTJavaDoclet.java
new file mode 100644
index 0000000..1c883f8
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/custom/GWTJavaDoclet.java
@@ -0,0 +1,66 @@
+package com.google.doctool.custom;
+
+import com.sun.javadoc.Doclet;
+import com.sun.javadoc.RootDoc;
+import com.sun.tools.doclets.standard.Standard;
+import com.sun.tools.javadoc.Main;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class GWTJavaDoclet extends Doclet {
+
+ static RootDoc root = null;
+
+ private static final String[] TAGLET_ARGS = new String[]{
+ "-taglet", ExampleTaglet.class.getName(), "-taglet",
+ TipTaglet.class.getName(), "-taglet", IncludeTaglet.class.getName()};
+
+ public static void main(String[] args) {
+ List examplePackages = new ArrayList();
+ List filteredArgs = new ArrayList();
+
+ // filter out and save packages args
+ for (int i = 0; i < args.length; ++i) {
+ if (args[i].equalsIgnoreCase("-examplepackages")) {
+ String nextArg = args[++i];
+ String[] split = nextArg.split(":|;");
+ for (int j = 0; j < split.length; ++j) {
+ examplePackages.add(split[j]);
+ }
+ } else if (args[i].equalsIgnoreCase("-packages")) {
+ String nextArg = args[++i];
+ String[] split = nextArg.split(":|;");
+ for (int j = 0; j < split.length; ++j) {
+ filteredArgs.add(split[j]);
+ }
+ } else {
+ filteredArgs.add(args[i]);
+ }
+ }
+
+ // Build a javadoc structure that includes example packages for reference
+ String name = GWTJavaDoclet.class.getName();
+ List myArgs = new ArrayList();
+ myArgs.addAll(filteredArgs);
+ myArgs.addAll(examplePackages);
+ Main.execute(name, name, (String[]) myArgs.toArray(new String[]{}));
+
+ // Now delegate to the real javadoc without the example packages
+ filteredArgs.addAll(0, Arrays.asList(TAGLET_ARGS));
+ Main.execute((String[]) filteredArgs.toArray(new String[]{}));
+ }
+
+ public static int optionLength(String option) {
+ // delegate
+ return Standard.optionLength(option);
+ }
+
+ public static boolean start(RootDoc root) {
+ // cache the root; ExampleTag will use it for reference later.
+ GWTJavaDoclet.root = root;
+ return true;
+ }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/custom/IncludeTaglet.java b/build-tools/doctool/src/com/google/doctool/custom/IncludeTaglet.java
new file mode 100644
index 0000000..3c2e214
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/custom/IncludeTaglet.java
@@ -0,0 +1,62 @@
+package com.google.doctool.custom;
+
+import com.google.doctool.ResourceIncluder;
+
+import com.sun.javadoc.Tag;
+import com.sun.tools.doclets.Taglet;
+
+import java.util.Map;
+
+public class IncludeTaglet implements Taglet {
+
+ public static void register(Map tagletMap) {
+ IncludeTaglet tag = new IncludeTaglet();
+ Taglet t = (Taglet) tagletMap.get(tag.getName());
+ if (t != null) {
+ tagletMap.remove(tag.getName());
+ }
+ tagletMap.put(tag.getName(), tag);
+ }
+
+ public String getName() {
+ return "gwt.include";
+ }
+
+ public boolean inConstructor() {
+ return true;
+ }
+
+ public boolean inField() {
+ return true;
+ }
+
+ public boolean inMethod() {
+ return true;
+ }
+
+ public boolean inOverview() {
+ return true;
+ }
+
+ public boolean inPackage() {
+ return true;
+ }
+
+ public boolean inType() {
+ return true;
+ }
+
+ public boolean isInlineTag() {
+ return true;
+ }
+
+ public String toString(Tag tag) {
+ String contents = ResourceIncluder.getResourceFromClasspathScrubbedForHTML(tag);
+ return "<blockquote><pre>" + contents + "</pre></blockquote>";
+ }
+
+ public String toString(Tag[] tags) {
+ return null;
+ }
+
+}
diff --git a/build-tools/doctool/src/com/google/doctool/custom/TipTaglet.java b/build-tools/doctool/src/com/google/doctool/custom/TipTaglet.java
new file mode 100644
index 0000000..0cc7fbe
--- /dev/null
+++ b/build-tools/doctool/src/com/google/doctool/custom/TipTaglet.java
@@ -0,0 +1,23 @@
+package com.google.doctool.custom;
+
+import com.sun.tools.doclets.Taglet;
+import com.sun.tools.doclets.standard.tags.SimpleTaglet;
+
+import java.util.Map;
+
+public class TipTaglet extends SimpleTaglet {
+
+ public static void register(Map tagletMap) {
+ TipTaglet tag = new TipTaglet();
+ Taglet t = (Taglet) tagletMap.get(tag.getName());
+ if (t != null) {
+ tagletMap.remove(tag.getName());
+ }
+ tagletMap.put(tag.getName(), tag);
+ }
+
+ public TipTaglet() {
+ super("tip", "Tip:", "a");
+ }
+
+}
diff --git a/build-tools/doctool/src/doc-topics.xslt b/build-tools/doctool/src/doc-topics.xslt
new file mode 100644
index 0000000..ac0019f
--- /dev/null
+++ b/build-tools/doctool/src/doc-topics.xslt
@@ -0,0 +1,82 @@
+<?xml version="1.0"?>
+<!-- Transforms booklet expositive documentation into Topics -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="xml"/>
+
+ <xsl:template match="/">
+ <topics>
+ <xsl:apply-templates select="booklet"/>
+ </topics>
+ </xsl:template>
+
+ <xsl:template match="booklet">
+ <xsl:apply-templates select="package/*"/>
+ </xsl:template>
+
+ <xsl:template match="class | interface">
+ <topic>
+ <id><xsl:value-of select="id"/></id>
+
+ <xsl:if test="tags/order">
+ <order><xsl:value-of select="tags/order"/></order>
+ </xsl:if>
+
+ <xsl:variable name="title">
+ <xsl:value-of select="tags/title"/>
+ <xsl:if test="not(tags/title)">
+ <xsl:comment>[Missing title]</xsl:comment>
+ </xsl:if>
+ </xsl:variable>
+
+ <title>
+ <xsl:value-of select="$title"/>
+ </title>
+
+ <xsl:if test="location/link">
+ <location>
+ <xsl:for-each select="location/link">
+ <xsl:if test="position() > 2">
+ <span class="item"><xsl:copy-of select="."/></span> >
+ </xsl:if>
+ </xsl:for-each>
+ <span class="selected item">
+ <xsl:value-of select="$title"/>
+ </span>
+ </location>
+ </xsl:if>
+
+ <xsl:for-each select="tags/index">
+ <index><xsl:value-of select="."/></index>
+ </xsl:for-each>
+
+ <xsl:for-each select="tags/tip">
+ <xsl:copy-of select="."/>
+ </xsl:for-each>
+
+ <xsl:if test="tags/synopsis">
+ <synopsis><xsl:copy-of select="tags/synopsis/node()"/></synopsis>
+ </xsl:if>
+
+ <body><xsl:copy-of select="description/node()"/></body>
+
+ <!-- See also -->
+ <xsl:if test="tags/link">
+ <seeAlso>
+ <xsl:for-each select="tags/link">
+ <xsl:copy-of select="."/>
+ </xsl:for-each>
+ </seeAlso>
+ </xsl:if>
+
+ <xsl:if test="tags/childIntro">
+ <childIntro><xsl:copy-of select="tags/childIntro/node()"/></childIntro>
+ </xsl:if>
+
+ <xsl:apply-templates select="class | interface"/>
+ </topic>
+ </xsl:template>
+
+ <xsl:template match="*|node()">
+ <!-- quiet -->
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/doc.css b/build-tools/doctool/src/doc.css
new file mode 100644
index 0000000..1418ddf
--- /dev/null
+++ b/build-tools/doctool/src/doc.css
@@ -0,0 +1,194 @@
+TABLE {
+ font-family: Arial, sans-serif;
+ font-size: small;
+}
+
+PRE {
+ font-size: medium;
+}
+
+H2.api {
+ background-color: #f5f5f5;
+}
+
+H3.api {
+ margin-bottom: 0.5em;
+}
+
+H4.api {
+ margin-bottom: 0.5em;
+}
+
+LI {
+ margin-bottom: 0.5em;
+}
+
+#nextprev {
+ float: right;
+ padding-right: 6px;
+}
+
+#nextprev nobr {
+ margin: 0 0.2em 0 0.2em;
+}
+
+DD {
+ margin-bottom: 1em;
+}
+
+DIV.tipContainer {
+ position: relative;
+ background-color: #e5ecf9;
+ padding: 0.8em 1em 1em 0em;
+ margin: 1em 0 1em 0;
+}
+
+DIV.tipCallout {
+ position: absolute;
+ padding: 0;
+ top: 0.8em;
+ left: 0.8em;
+ font-weight: bold;
+}
+
+DIV.tipBody {
+ padding-left: 3.2em;
+}
+
+DIV.topicHeader {
+ border-bottom: 2px solid blue;
+ margin: 0 1em 1em 1em;
+}
+
+DIV.topicHeader A {
+ text-decoration: none;
+}
+
+DIV.topicHeader A:hover {
+ text-decoration: underline;
+}
+
+DIV.topicHeader NOBR A {
+ text-decoration: none;
+}
+
+DIV.topicHeader NOBR A:hover {
+ text-decoration: underline;
+}
+
+DIV.topicBody {
+ margin: 1em 1em 1em 1em;
+}
+
+DIV.topicChildren {
+ margin-left: 1em;
+}
+
+DIV.topicSeeAlso {
+ margin-left: 2em;
+}
+
+DIV.topicSeeAlso H2 {
+ margin-left: -1em;
+}
+
+DIV.memberOf {
+ font-size: small;
+ margin-bottom: 1em;
+}
+
+DIV.apiSection {
+ margin-left: 2em;
+}
+
+TABLE.members {
+ font-size: 100%;
+}
+
+TABLE.members TR {
+ vertical-align: top;
+}
+
+TABLE.members TR TH {
+ font-weight: bold;
+ background-color: #CCCCCC;
+ text-align: left;
+ padding: 4px 6px 4px 6px;
+}
+
+TABLE.members TR TD {
+ background-color: white;
+ text-align: left;
+ padding: 4px 6px 4px 6px;
+ margin: 1px;
+}
+
+DIV.memberSig {
+ font-family: "bogus font here" , monospace;
+ font-size: 100%;
+ margin-bottom: 1em;
+ overflow: auto;
+}
+
+DIV.classSig {
+ color: #007000;
+ font-family: "bogus font here" , monospace;
+ font-size: 100%;
+ background-color: #f5f5f5;
+ padding: 0.5em;
+ margin-bottom: 1em;
+ overflow: auto;
+}
+
+UL.css LI {
+ font-style: normal;
+ font-family: Courier New;
+ list-style-type: none;
+}
+
+DL.fixed DT {
+ font-style: normal;
+ font-family: Courier New;
+}
+
+DL.memberDetail {
+ margin-top: 0em;
+}
+
+DL.memberDetail DT {
+ font-family: "bogus font here" , monospace;
+}
+
+DL.memberDetail DD {
+ margin-bottom: 1em;
+}
+
+TD {
+ padding: 0.2em;
+ vertical-align: top;
+}
+
+TH {
+ text-align: left;
+}
+
+TR.gallery-link TD {
+ text-align: center;
+ vertical-align: bottom;
+ padding-top: 1em;
+}
+
+TR.gallery TD {
+ text-align: center;
+ vertical-align: top;
+ padding-top: 0.25em;
+ padding-bottom: 1em;
+ padding-left: 1em;
+ padding-right: 1em;
+}
+
+IMG.gallery {
+ border: 1px solid #95BBFC;
+ padding: 4px;
+}
+
diff --git a/build-tools/doctool/src/java-topics.xslt b/build-tools/doctool/src/java-topics.xslt
new file mode 100644
index 0000000..10fb8e6
--- /dev/null
+++ b/build-tools/doctool/src/java-topics.xslt
@@ -0,0 +1,459 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="xml" />
+ <xsl:strip-space elements="*"/>
+
+ <xsl:param name="id"/>
+ <xsl:param name="name"/>
+ <xsl:param name="synopsis"/>
+ <xsl:param name="childIntro"/>
+
+ <xsl:key name="extendsInterface" match="//interface/superinterface" use="@ref"/>
+ <xsl:key name="implementsInterface" match="//class/superinterface" use="@ref"/>
+ <xsl:key name="extendsClass" match="//class/superclass" use="@ref"/>
+
+ <xsl:template match="/">
+ <topics>
+ <xsl:apply-templates select="booklet"/>
+ </topics>
+ </xsl:template>
+
+ <xsl:template match="booklet">
+ <topic>
+ <xsl:copy-of select="id"/>
+ <title><xsl:value-of select="name"/></title>
+ <location>
+ <span class="selected item">
+ <xsl:value-of select="name"/>
+ </span>
+ </location>
+ <synopsis><xsl:apply-templates select="lead/node()"/></synopsis>
+ <header><h1><xsl:apply-templates select="name/node()"/></h1></header>
+ <body>
+ <!-- Lengthier description -->
+ <xsl:apply-templates select="description/node()"/>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="package"/>
+ <xsl:with-param name="title">Packages</xsl:with-param>
+ </xsl:call-template>
+ </body>
+ <xsl:apply-templates select="package">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+ </topic>
+ </xsl:template>
+
+ <xsl:template name="emit-location">
+ <xsl:if test="location">
+ <location>
+ <xsl:for-each select="location/link">
+ <span class="item"><xsl:copy-of select="."/></span> >
+ </xsl:for-each>
+ <span class="selected item">
+ <xsl:value-of select="name"/>
+ </span>
+ </location>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="package">
+ <topic>
+ <id><xsl:value-of select="id"/></id>
+ <title><xsl:value-of select="name"/></title>
+ <xsl:call-template name="emit-location"/>
+ <index><xsl:value-of select="name"/> package</index>
+ <xsl:if test="lead/node()">
+ <synopsis><xsl:apply-templates select="lead/node()"/></synopsis>
+ </xsl:if>
+ <header>
+ <xsl:call-template name="memberHeader">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+ </header>
+ <body>
+ <!-- Lengthier description -->
+ <xsl:apply-templates select="description/node()"/>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="class"/>
+ <xsl:with-param name="title">Classes</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="interface"/>
+ <xsl:with-param name="title">Interfaces</xsl:with-param>
+ </xsl:call-template>
+ </body>
+ <xsl:apply-templates select="class">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+ <xsl:apply-templates select="interface">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+ </topic>
+ </xsl:template>
+
+ <xsl:template match="class|interface">
+ <topic>
+ <xsl:copy-of select="id"/>
+ <title><xsl:value-of select="name"/></title>
+ <xsl:call-template name="emit-location"/>
+ <index>
+ <xsl:value-of select="name"/>
+ <xsl:if test="name()='class'"> class</xsl:if>
+ <xsl:if test="name()='interface'"> interface</xsl:if>
+ </index>
+ <synopsis><xsl:apply-templates select="lead/node()"/></synopsis>
+ <header>
+ <xsl:call-template name="memberHeader">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+ </header>
+ <body>
+ <!-- Lengthier description -->
+ <xsl:apply-templates select="description/node()"/>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="class"/>
+ <xsl:with-param name="title">Nested Classes</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="interface"/>
+ <xsl:with-param name="title">Nested Interfaces</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="field"/>
+ <xsl:with-param name="title">Fields</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="constructor"/>
+ <xsl:with-param name="title">Constructors</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="method"/>
+ <xsl:with-param name="title">Methods</xsl:with-param>
+ </xsl:call-template>
+
+ <!-- See also -->
+ <xsl:if test="tags/link">
+ <h2 class="api">See Also</h2>
+ <xsl:for-each select="tags/link">
+ <xsl:copy-of select="."/>
+ <xsl:if test="position()!=last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+
+ <xsl:for-each select="field[not(jre)]">
+ <xsl:sort select="name"/>
+ <xsl:if test="position() = 1">
+ <h2 class="api">Field Detail</h2>
+ </xsl:if>
+ <xsl:apply-templates select="."/>
+ <xsl:if test="position() != last()">
+ <hr/>
+ </xsl:if>
+ </xsl:for-each>
+
+ <xsl:for-each select="constructor[not(jre)]">
+ <xsl:sort select="name"/>
+ <xsl:if test="position() = 1">
+ <h2 class="api">Constructor Detail</h2>
+ </xsl:if>
+ <xsl:apply-templates select="."/>
+ <xsl:if test="position() != last()">
+ <hr/>
+ </xsl:if>
+ </xsl:for-each>
+
+ <xsl:for-each select="method[not(jre)]">
+ <xsl:sort select="name"/>
+ <xsl:if test="position() = 1">
+ <h2 class="api">Method Detail</h2>
+ </xsl:if>
+ <xsl:apply-templates select="."/>
+ <xsl:if test="position() != last()">
+ <hr/>
+ </xsl:if>
+ </xsl:for-each>
+
+ </body>
+
+ <xsl:apply-templates select="interface">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+
+ <xsl:apply-templates select="class">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+ </topic>
+ </xsl:template>
+
+ <xsl:template match="method|constructor">
+ <!-- Anchor -->
+ <xsl:if test="string-length(substring-after(id, "#")) != 0">
+ <a name="{substring-after(id, "#")}"/>
+ </xsl:if>
+
+ <!-- The simple name -->
+ <h3 class="api"><xsl:value-of select="name"/></h3>
+
+ <!-- The signature -->
+ <div class="memberSig">
+ <xsl:call-template name="memberSig">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+ </div>
+
+ <!-- Lengthier description -->
+ <xsl:apply-templates select="description/node()"/>
+
+ <!-- Parameter description -->
+ <xsl:if test="params/param">
+ <h4 class="api">Parameters</h4>
+ <dl class="memberDetail">
+ <xsl:for-each select="params/param">
+ <dt><xsl:value-of select="name"/></dt>
+ <dd>
+ <xsl:variable name="paramDesc" select="../../tags/param[name=current()/name]/description/node()"/>
+ <xsl:apply-templates select="$paramDesc"/>
+ <xsl:if test="not($paramDesc)"><xsl:comment>[Missing documentation]</xsl:comment></xsl:if>
+ </dd>
+ </xsl:for-each>
+ </dl>
+ </xsl:if>
+
+ <!-- Return value description -->
+ <xsl:if test="tags/return">
+ <h4 class="api">Return Value</h4>
+ <xsl:apply-templates select="tags/return/node()"/>
+ </xsl:if>
+
+ <!-- See also -->
+ <xsl:if test="tags/link">
+ <h4 class="api">See Also</h4>
+ <xsl:for-each select="tags/link">
+ <xsl:copy-of select="."/>
+ <xsl:if test="position()!=last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="field">
+ <!-- Anchor -->
+ <xsl:if test="string-length(substring-after(id, "#")) != 0">
+ <a name="#{substring-after(id, "#")}"/>
+ </xsl:if>
+
+ <!-- The simple name -->
+ <h3 class="api"><xsl:value-of select="name"/></h3>
+
+ <!-- The signature -->
+ <div class="memberSig">
+ <xsl:call-template name="memberSig">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+ </div>
+
+ <!-- Lengthier description -->
+ <xsl:apply-templates select="description/node()"/>
+ </xsl:template>
+
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template name="memberTable">
+ <xsl:param name="members"/>
+ <xsl:param name="title"/>
+
+ <xsl:if test="$members">
+ <h2 class="api"><xsl:value-of select="$title"/></h2>
+ <table cellspacing="1" cellpadding="1" class="members">
+ <xsl:apply-templates select="$members" mode="memberTableImpl">
+ <xsl:sort case-order="lower-first" data-type="text" order="ascending" select="name"/>
+ </xsl:apply-templates>
+ </table>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="*" mode="memberTableImpl">
+ <tr>
+ <td>
+ <xsl:choose>
+ <xsl:when test="jre and (name()='method' or name()='constructor' or name()='field')">
+ <a href="{jre}"><xsl:value-of select="name"/><xsl:value-of select="flatSignature"/></a>
+ </xsl:when>
+ <xsl:otherwise>
+ <link ref="{id}"><xsl:value-of select="name"/><xsl:value-of select="flatSignature"/></link>
+ </xsl:otherwise>
+ </xsl:choose>
+ </td>
+ <td>
+ <xsl:apply-templates select="lead/node()"/><xsl:text> </xsl:text>
+ </td>
+ </tr>
+ </xsl:template>
+
+ <xsl:template name="memberHeader">
+ <xsl:param name="member"/>
+ <xsl:variable name="parent" select="$member/.."/>
+
+ <!-- The header announces the simple name of the member -->
+ <h1>
+ <xsl:if test="name()='package'">Package </xsl:if>
+ <xsl:if test="name()='class'">Class </xsl:if>
+ <xsl:if test="name()='interface'">Interface </xsl:if>
+ <xsl:if test="name()='method'">Method </xsl:if>
+ <xsl:if test="name()='constructor'">Constructor </xsl:if>
+ <xsl:if test="name()='field'">Field </xsl:if>
+ <xsl:value-of select="name"/>
+ </h1>
+
+ <xsl:if test="name()!='package'">
+ <!-- A signature for the member -->
+ <div class="classSig">
+ <xsl:call-template name="memberSig">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+
+ <!-- Show the implementing classes and extending interfaces -->
+ <xsl:if test="name()='interface'">
+ <xsl:variable name="id" select="id"/>
+ <xsl:for-each select="key('extendsInterface', $id)/..">
+ <xsl:if test="position() = 1">
+ <br/><br/>
+ <xsl:text>// Extended by </xsl:text>
+ </xsl:if>
+ <code><link ref="{id}"><xsl:value-of select="name"/></link></code>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+
+ <xsl:for-each select="key('implementsInterface', $id)/..">
+ <xsl:if test="position() = 1">
+ <xsl:if test="not(key('extendsInterface', $id)/..)">
+ <!-- Extra break needed -->
+ <br/>
+ </xsl:if>
+ <br/>
+ <xsl:text>// Implemented by </xsl:text>
+ </xsl:if>
+ <code><link ref="{id}"><xsl:value-of select="name"/></link></code>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+
+ <!-- Show the derived classes -->
+ <xsl:if test="name()='class'">
+ <xsl:variable name="id" select="id"/>
+ <xsl:if test="name!='Object' and key('extendsClass', $id)">
+ <br/><br/>
+ <xsl:text>// Superclass of </xsl:text>
+ <xsl:for-each select="key('extendsClass', $id)/..">
+ <code><link ref="{id}"><xsl:value-of select="name"/></link></code>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:if>
+ </div>
+ </xsl:if>
+
+ </xsl:template>
+
+ <xsl:template name="memberSig">
+ <xsl:param name="member"/>
+
+ <xsl:if test="isPublic">public </xsl:if>
+ <xsl:if test="isProtected">protected </xsl:if>
+ <xsl:if test="isPrivate">private </xsl:if>
+ <xsl:if test="isPackagePrivate">/*package*/ </xsl:if>
+ <xsl:if test="isStatic">static </xsl:if>
+ <xsl:if test="isFinal">final </xsl:if>
+ <xsl:if test="isAbstract">abstract </xsl:if>
+ <xsl:if test="isSynchronized">synchronized </xsl:if>
+
+ <xsl:if test="name()='class'">
+ class
+ <xsl:value-of select="name"/>
+ <xsl:if test="superclass">
+ <br/>extends
+ <link ref="{superclass/@ref}"><xsl:value-of select="superclass"/></link>
+ </xsl:if>
+ <xsl:if test="superinterface">
+ <br/>implements
+ <xsl:for-each select="superinterface">
+ <link ref="{@ref}"><xsl:value-of select="."/></link>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:if>
+
+ <xsl:if test="name()='interface'">
+ interface
+ <b><xsl:value-of select="name"/></b>
+ <xsl:if test="superinterface">
+ <br/>extends
+ <xsl:for-each select="superinterface">
+ <link ref="{@ref}"><xsl:value-of select="."/></link>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:if>
+
+ <xsl:if test="name()='field'">
+ <xsl:if test="@ref">
+ <link ref="{@ref}"><xsl:value-of select="type"/></link>
+ </xsl:if>
+ <xsl:if test="not(@ref)">
+ <xsl:value-of select="type"/>
+ </xsl:if>
+ <xsl:text> </xsl:text>
+ <b><xsl:value-of select="name"/></b>
+ </xsl:if>
+
+ <xsl:if test="name()='method' or name()='constructor'">
+ <xsl:if test="type/@ref">
+ <link ref="{type/@ref}"><xsl:value-of select="type"/></link>
+ </xsl:if>
+ <xsl:if test="not(type/@ref)">
+ <xsl:value-of select="type"/>
+ </xsl:if>
+ <b><xsl:text> </xsl:text><xsl:value-of select="name"/></b><xsl:text>(</xsl:text>
+ <xsl:if test="params/param">
+ <xsl:for-each select="params/param">
+ <nobr>
+ <xsl:if test="type/@ref">
+ <link ref="{type/@ref}"><xsl:value-of select="type"/></link>
+ </xsl:if>
+ <xsl:if test="not(type/@ref)">
+ <xsl:value-of select="type"/>
+ </xsl:if>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="name"/>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </nobr>
+ </xsl:for-each>
+ </xsl:if><xsl:text>)</xsl:text>
+ <xsl:if test="throws/throw">
+ <br/>    
+ throws
+ <xsl:for-each select="throws/throw">
+ <xsl:text> </xsl:text>
+ <xsl:if test="@ref">
+ <link ref="{@ref}"><xsl:value-of select="."/></link>
+ </xsl:if>
+ <xsl:if test="not(@ref)">
+ <xsl:value-of select="."/>
+ </xsl:if>
+ <xsl:if test="position() != last()">,</xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:if>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/javascript-topics.xslt b/build-tools/doctool/src/javascript-topics.xslt
new file mode 100644
index 0000000..383ae50
--- /dev/null
+++ b/build-tools/doctool/src/javascript-topics.xslt
@@ -0,0 +1,420 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="xml" />
+ <xsl:strip-space elements="*"/>
+
+ <xsl:param name="id"/>
+ <xsl:param name="name"/>
+ <xsl:param name="synopsis"/>
+ <xsl:param name="childIntro"/>
+
+ <xsl:template match="*|node()">
+ <!-- no echo -->
+ </xsl:template>
+
+ <xsl:template match="/">
+ <xsl:apply-templates select="booklet"/>
+ </xsl:template>
+
+ <xsl:template match="booklet">
+ <topic>
+ <xsl:copy-of select="id"/>
+ <title><xsl:value-of select="name"/></title>
+ <synopsis><xsl:copy-of select="lead/node()"/></synopsis>
+ <header><h1><xsl:copy-of select="name/node()"/></h1></header>
+ <body>
+ <!-- Lengthier description -->
+ <xsl:copy-of select="description/node()"/>
+ <xsl:apply-templates select="package">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+ </body>
+ </topic>
+ </xsl:template>
+
+ <xsl:template match="package">
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="class"/>
+ <xsl:with-param name="title">Classes</xsl:with-param>
+ </xsl:call-template>
+<!--
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="interface"/>
+ <xsl:with-param name="title">Interfaces</xsl:with-param>
+ </xsl:call-template>
+-->
+ <xsl:apply-templates select="class">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+ </xsl:template>
+
+ <xsl:template match="class|interface">
+ <topic>
+ <xsl:copy-of select="id"/>
+ <title><xsl:value-of select="name"/></title>
+ <index>
+ <xsl:value-of select="name"/>
+ <xsl:if test="name()='class'"> class</xsl:if>
+ <xsl:if test="name()='interface'"> interface</xsl:if>
+ </index>
+ <synopsis><xsl:copy-of select="lead/node()"/></synopsis>
+ <header>
+ <xsl:call-template name="memberHeader">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+ </header>
+ <body>
+ <!-- Lengthier description -->
+ <xsl:copy-of select="description/node()"/>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="class"/>
+ <xsl:with-param name="title">Nested classes</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="interface"/>
+ <xsl:with-param name="title">Nested interfaces</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="field"/>
+ <xsl:with-param name="title">Fields</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="constructor"/>
+ <xsl:with-param name="title">Constructors</xsl:with-param>
+ </xsl:call-template>
+
+ <xsl:call-template name="memberTable">
+ <xsl:with-param name="members" select="method"/>
+ <xsl:with-param name="title">Methods</xsl:with-param>
+ </xsl:call-template>
+
+ <!-- See also -->
+ <xsl:if test="tags/link">
+ <h2>See Also</h2>
+ <div class="apiSection">
+ <xsl:for-each select="tags/link">
+ <xsl:copy-of select="."/>
+ <xsl:if test="position()!=last()">, </xsl:if>
+ </xsl:for-each>
+ </div>
+ </xsl:if>
+ </body>
+
+ <xsl:apply-templates select="interface">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+
+ <xsl:apply-templates select="class">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+
+ <xsl:apply-templates select="field">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+
+ <xsl:apply-templates select="constructor">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+
+ <xsl:apply-templates select="method">
+ <xsl:sort select="name"/>
+ </xsl:apply-templates>
+ </topic>
+ </xsl:template>
+
+ <xsl:template match="method|constructor">
+ <topic>
+ <id><xsl:value-of select="id"/></id>
+ <title>
+ <xsl:call-template name="emitFlatSig">
+ <xsl:with-param name="methodOrCtor" select="current()"/>
+ </xsl:call-template>
+ </title>
+ <index>
+ <xsl:value-of select="name"/>
+ <xsl:if test="name()='method'"> method</xsl:if>
+ <xsl:if test="name()='constructor'"> constructor</xsl:if>
+ </index>
+ <synopsis><xsl:copy-of select="lead/node()"/></synopsis>
+ <header>
+ <xsl:call-template name="memberHeader">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+ </header>
+ <body>
+ <!-- Lengthier description -->
+ <xsl:copy-of select="description/node()"/>
+
+ <!-- Parameter description -->
+ <xsl:if test="params/param">
+ <h2>Parameters</h2>
+ <div class="apiSection">
+ <dl>
+ <xsl:for-each select="params/param">
+ <dt>
+ <xsl:if test="starts-with(name, '_')">
+ <xsl:value-of select="substring(name,2)"/><xsl:text>?</xsl:text>
+ </xsl:if>
+ <xsl:if test="not(starts-with(name, '_'))">
+ <xsl:value-of select="name"/>
+ </xsl:if>
+ </dt>
+ <dd>
+ <xsl:if test="starts-with(name, '_')">
+ <em>[Optional] </em>
+ </xsl:if>
+ <xsl:variable name="paramDesc" select="../../tags/param[name=current()/name]/description/node()"/>
+ <xsl:copy-of select="$paramDesc"/>
+ <xsl:if test="not($paramDesc)"><xsl:comment>[Missing documentation]</xsl:comment></xsl:if>
+ </dd>
+ </xsl:for-each>
+ </dl>
+ </div>
+ </xsl:if>
+
+ <!-- Return value description -->
+ <xsl:if test="tags/return">
+ <h2>Return Value</h2>
+ <div class="apiSection">
+ <xsl:copy-of select="tags/return/node()"/>
+ </div>
+ </xsl:if>
+
+ <!-- See also -->
+ <xsl:if test="tags/link">
+ <h2>See Also</h2>
+ <div class="apiSection">
+ <xsl:for-each select="tags/link">
+ <xsl:copy-of select="."/>
+ <xsl:if test="position()!=last()">, </xsl:if>
+ </xsl:for-each>
+ </div>
+ </xsl:if>
+ </body>
+ </topic>
+ </xsl:template>
+
+ <xsl:template match="field">
+ <topic>
+ <id><xsl:value-of select="id"/></id>
+ <title><xsl:value-of select="name"/></title>
+ <index><xsl:value-of select="name"/> field</index>
+ <synopsis><xsl:copy-of select="lead/node()"/></synopsis>
+ <header>
+ <xsl:call-template name="memberHeader">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+ </header>
+ <body>
+ <!-- Lengthier description -->
+ <xsl:copy-of select="description/node()"/>
+ </body>
+ </topic>
+ </xsl:template>
+
+ <xsl:template match="*|node()">
+ <!-- quiet -->
+ </xsl:template>
+
+ <xsl:template name="memberTable">
+ <xsl:param name="members"/>
+ <xsl:param name="title"/>
+
+ <xsl:if test="$members">
+ <h2><xsl:value-of select="$title"/></h2>
+ <table class="members" cellspacing="1" cellpadding="0">
+ <xsl:apply-templates select="$members" mode="memberTableImpl">
+ <xsl:sort case-order="lower-first" data-type="text" order="ascending" select="."/>
+ </xsl:apply-templates>
+ </table>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template match="*" mode="memberTableImpl">
+ <tr>
+ <td width="1%">
+ <nobr>
+ <link ref="{id}">
+ <xsl:call-template name="emitFlatSig">
+ <xsl:with-param name="methodOrCtor" select="current()"/>
+ </xsl:call-template>
+ </link>
+ </nobr>
+ </td>
+ <td><xsl:copy-of select="lead/node()"/><xsl:text> </xsl:text></td>
+ </tr>
+ </xsl:template>
+
+ <xsl:template name="memberHeader">
+ <xsl:param name="member"/>
+ <xsl:variable name="parent" select="$member/.."/>
+
+ <div class="memberHeader">
+
+ <!-- The header announces the simple name of the member -->
+ <h1>
+ <xsl:if test="name()='package'">Package </xsl:if>
+ <xsl:if test="name()='class'">Class </xsl:if>
+ <xsl:if test="name()='interface'">Interface </xsl:if>
+ <xsl:if test="name()='method'">Method </xsl:if>
+ <xsl:if test="name()='constructor'">Constructor </xsl:if>
+ <xsl:if test="name()='field'">Field </xsl:if>
+ <xsl:value-of select="name"/>
+ </h1>
+
+ <xsl:if test="name()!='package'">
+ <!-- Tell where this member lives -->
+<!--
+ <div class="memberOf">
+ Member of <link ref="{$parent/id}"><xsl:value-of select="$parent/name"/></link>
+ </div>
+-->
+ <!-- A signature for the member -->
+ <code class="signature">
+ <xsl:call-template name="memberSig">
+ <xsl:with-param name="member" select="."/>
+ </xsl:call-template>
+ </code>
+ </xsl:if>
+ </div>
+
+ </xsl:template>
+
+ <xsl:template name="memberSig">
+ <xsl:param name="member"/>
+
+<!--
+ <xsl:if test="isPublic">public </xsl:if>
+ <xsl:if test="isProtected">protected </xsl:if>
+ <xsl:if test="isPrivate">private </xsl:if>
+ <xsl:if test="isPackagePrivate">/*package*/ </xsl:if>
+ <xsl:if test="isStatic">static </xsl:if>
+ <xsl:if test="isFinal">final </xsl:if>
+ <xsl:if test="isAbstract">abstract </xsl:if>
+ <xsl:if test="isSynchronized">synchronized </xsl:if>
+-->
+ <xsl:if test="name()='class'">
+ class
+ <b><xsl:value-of select="name"/></b>
+<!--
+ <xsl:if test="superclass[@ref != 'java.lang.Object']">
+ <br/>derives from
+ <link ref="{superclass/@ref}"><xsl:value-of select="superclass"/></link>
+ </xsl:if>
+-->
+<!--
+ <xsl:if test="superinterface">
+ <br/>can be used as
+ <xsl:for-each select="superinterface">
+ <link ref="{@ref}"><xsl:value-of select="."/></link>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+-->
+ </xsl:if>
+<!--
+ <xsl:if test="name()='interface'">
+ interface
+ <b><xsl:value-of select="name"/></b>
+ <xsl:if test="superinterface">
+ <br/>extends
+ <xsl:for-each select="superinterface">
+ <link ref="{@ref}"><xsl:value-of select="."/></link>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:if>
+-->
+ <xsl:if test="name()='field'">
+ <xsl:if test="@ref">
+ <link ref="{@ref}"><xsl:value-of select="type"/></link>
+ </xsl:if>
+ <xsl:if test="not(@ref)">
+ <xsl:value-of select="type"/>
+ </xsl:if>
+ <xsl:text> </xsl:text>
+ <b><xsl:value-of select="name"/></b>
+ </xsl:if>
+
+ <xsl:if test="name()='method' or name()='constructor'">
+<!--
+ <xsl:if test="type/@ref">
+ <link ref="{type/@ref}"><xsl:value-of select="type"/></link>
+ </xsl:if>
+ <xsl:if test="not(type/@ref)">
+ <xsl:value-of select="type"/>
+ </xsl:if>
+-->
+ <b><xsl:value-of select="name"/></b><xsl:text>(</xsl:text>
+ <xsl:if test="params/param">
+ <xsl:for-each select="params/param">
+<!--
+ <div style="margin-left: 2em">
+ <nobr>
+ <xsl:if test="type/@ref">
+ <link ref="{type/@ref}"><xsl:value-of select="type"/></link>
+ </xsl:if>
+ <xsl:if test="not(type/@ref)">
+ <xsl:value-of select="type"/>
+ </xsl:if>
+ <xsl:text> </xsl:text>
+-->
+ <xsl:if test="starts-with(name, '_')">
+ <xsl:value-of select="substring(name,2)"/><xsl:text>?</xsl:text>
+ </xsl:if>
+ <xsl:if test="not(starts-with(name, '_'))">
+ <xsl:value-of select="name"/>
+ </xsl:if>
+ <xsl:if test="position() != last()">, </xsl:if>
+<!--
+ </nobr>
+ </div>
+-->
+ </xsl:for-each>
+ </xsl:if>
+ <xsl:text>)</xsl:text>
+ <xsl:if test="throws/throw">
+ <br/>
+ throws
+ <xsl:for-each select="throws/throw">
+ <xsl:text> </xsl:text>
+ <xsl:if test="@ref">
+ <link ref="{@ref}"><xsl:value-of select="."/></link>
+ </xsl:if>
+ <xsl:if test="not(@ref)">
+ <xsl:value-of select="."/>
+ </xsl:if>
+ <xsl:if test="position() != last()">,</xsl:if>
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:if>
+ </xsl:template>
+
+ <xsl:template name="emitFlatSig">
+ <xsl:param name="methodOrCtor"/>
+ <xsl:value-of select="$methodOrCtor/name"/>
+ <xsl:text>(</xsl:text>
+ <xsl:for-each select="$methodOrCtor/params/param">
+ <xsl:call-template name="emitParamName">
+ <xsl:with-param name="param" select="."/>
+ </xsl:call-template>
+ <xsl:if test="position() != last()">, </xsl:if>
+ </xsl:for-each>
+ <xsl:text>)</xsl:text>
+ </xsl:template>
+
+ <xsl:template name="emitParamName">
+ <xsl:param name="param"/>
+ <xsl:if test="starts-with($param/name, '_')">
+ <xsl:value-of select="substring($param/name,2)"/><xsl:text>?</xsl:text>
+ </xsl:if>
+ <xsl:if test="not(starts-with($param/name, '_'))">
+ <xsl:value-of select="$param/name"/>
+ </xsl:if>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-hhc.xslt b/build-tools/doctool/src/topics-hhc.xslt
new file mode 100644
index 0000000..8ce3bd3
--- /dev/null
+++ b/build-tools/doctool/src/topics-hhc.xslt
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html"/>
+ <xsl:template match="/">
+ <html>
+ <body>
+ <ul>
+ <xsl:apply-templates select="topic">
+ <xsl:sort select="order" data-type="number" order="descending"/>
+ </xsl:apply-templates>
+ </ul>
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template match="topic">
+ <li>
+ <object type="text/sitemap">
+ <xsl:element name="param">
+ <xsl:attribute name="name">Name</xsl:attribute>
+ <xsl:attribute name="value"><xsl:value-of select="title" /></xsl:attribute>
+ </xsl:element>
+ <xsl:element name="param">
+ <xsl:attribute name="name">Local</xsl:attribute>
+ <xsl:attribute name="value"><xsl:value-of select="id" />.html</xsl:attribute>
+ </xsl:element>
+ </object>
+ </li>
+ <xsl:if test="topic">
+ <ul>
+ <xsl:apply-templates select="topic" />
+ </ul>
+ </xsl:if>
+ </xsl:template>
+
+ <!-- If a topic doesn't have a title, we pretend it isn't there and hoist its children up in its place -->
+ <xsl:template match="topic[not(title)]">
+ <xsl:apply-templates select="topic">
+ <xsl:sort select="order" data-type="number" order="descending"/>
+ </xsl:apply-templates>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-hhk.xslt b/build-tools/doctool/src/topics-hhk.xslt
new file mode 100644
index 0000000..ada0d3b
--- /dev/null
+++ b/build-tools/doctool/src/topics-hhk.xslt
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html"/>
+ <xsl:template match="/">
+ <html>
+ <body>
+ <ul>
+ <xsl:for-each select="//topic/index">
+ <xsl:sort select="."/>
+ <li>
+ <object type="text/sitemap">
+ <xsl:element name="param">
+ <xsl:attribute name="name">Name</xsl:attribute>
+ <xsl:attribute name="value">
+ <xsl:value-of select="." />
+ </xsl:attribute>
+ </xsl:element>
+ <xsl:element name="param">
+ <xsl:attribute name="name">Local</xsl:attribute>
+ <xsl:attribute name="value"><xsl:value-of select="../id" />.html</xsl:attribute>
+ </xsl:element>
+ </object>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </body>
+ </html>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-hhp.xslt b/build-tools/doctool/src/topics-hhp.xslt
new file mode 100644
index 0000000..980a1eb
--- /dev/null
+++ b/build-tools/doctool/src/topics-hhp.xslt
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="text"/>
+
+ <xsl:param name="title"/>
+ <xsl:param name="filebase"/>
+ <xsl:param name="chm"><xsl:value-of select="$filebase"/>.chm</xsl:param>
+ <xsl:param name="hhc"><xsl:value-of select="$filebase"/>.hhc</xsl:param>
+ <xsl:param name="hhk"><xsl:value-of select="$filebase"/>.hhk</xsl:param>
+ <xsl:param name="css"><xsl:value-of select="$filebase"/>.css</xsl:param>
+ <xsl:template match="/">
+[Options]
+Compatibility = 1.1 Or later
+Compiled file = <xsl:value-of select="$chm"/>
+Contents file = <xsl:value-of select="$hhc"/>
+Default Font = Arial, 9, 0
+Default Window=Main
+Display compile notes=No
+Display compile progress=No
+Error log file=error.log
+Full-text search=Yes
+Index file = <xsl:value-of select="$hhk"/>
+Language=0x409 English (United States)
+Title=<xsl:value-of select="$title"/>
+
+[Windows]
+Main="<xsl:value-of select="$title"/>","<xsl:value-of select="$hhc"/>","<xsl:value-of select="$hhk"/>",,,,,,,0x62520,,0x304c,[10,10,610,510],,,,,,,0
+
+[Files]
+<xsl:for-each select="//topic[title]">
+<xsl:sort select="order" data-type="number" order="descending"/>
+<xsl:value-of select="id"/>.html
+</xsl:for-each>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-htmls.xslt b/build-tools/doctool/src/topics-htmls.xslt
new file mode 100644
index 0000000..70b8584
--- /dev/null
+++ b/build-tools/doctool/src/topics-htmls.xslt
@@ -0,0 +1,331 @@
+<?xml version="1.0"?>
+<!-- edited with XMLSpy v2005 rel. 3 U (http://www.altova.com) by Bruce Johnson (private) -->
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html"/>
+ <xsl:strip-space elements="*"/>
+ <xsl:key name="topicSearch" match="//topic" use="id"/>
+ <xsl:param name="css"/>
+ <xsl:param name="title"/>
+ <xsl:template match="/">
+@FILE
+ <xsl:apply-templates select="//topic"/>
+ </xsl:template>
+ <xsl:template name="gen-side">
+ <div id="side">
+ <div id="menu">
+ <div class="header item">
+ <a href="http://code.google.com/webtoolkit/">Google Web Toolkit</a>
+ </div>
+ <div class="group">
+ <div class="item">
+ <a href="http://code.google.com/webtoolkit/overview.html">Product Overview</a>
+ </div>
+ <div class="item">
+ <a href="http://code.google.com/webtoolkit/gettingstarted.html">Getting Started Guide</a>
+ </div>
+ <div class="item">
+ <a href="http://code.google.com/webtoolkit/download.html">Download SDK</a>
+ </div>
+ </div>
+ <div class="group">
+ <xsl:element name="div">
+ <xsl:attribute name="class"><xsl:text>item</xsl:text><xsl:if test="contains(id, 'com.google.gwt.doc')"><xsl:text> </xsl:text><xsl:text> selected</xsl:text></xsl:if></xsl:attribute>
+ <a href="./com.google.gwt.doc.DeveloperGuide.html">Developer Guide</a>
+ </xsl:element>
+ <div class="group">
+ <div class="item">
+ <a href="http://code.google.com/webtoolkit/documentation/examples/">Example Projects</a>
+ </div>
+ <xsl:element name="div">
+ <xsl:attribute name="class"><xsl:text>item</xsl:text><xsl:if test="(contains(id, 'com.google.gwt.') and not(contains(id, 'com.google.gwt.doc.'))) or id = 'gwt'"><xsl:text> </xsl:text><xsl:text> selected</xsl:text></xsl:if></xsl:attribute>
+ <a href="./gwt.html">GWT Class Reference</a>
+ </xsl:element>
+ <xsl:element name="div">
+ <xsl:attribute name="class"><xsl:text>item</xsl:text><xsl:if test="contains(id, 'java.') or id='jre'"><xsl:text> </xsl:text><xsl:text> selected</xsl:text></xsl:if></xsl:attribute>
+ <a href="./jre.html">JRE Emulation Library</a>
+ </xsl:element>
+ </div>
+ </div>
+ <div class="group">
+ <div class="item">
+ <a href="http://code.google.com/webtoolkit/faq.html">Web Toolkit FAQ</a>
+ </div>
+ <div class="item">
+ <a href="http://googlewebtoolkit.blogspot.com/">Web Toolkit Blog</a>
+ </div>
+ <div class="item">
+ <a href="http://code.google.com/webtoolkit/thirdparty.html">Third Party Tools</a>
+ </div>
+ <div class="item">
+ <a href="http://code.google.com/webtoolkit/issues/">Issue Tracking</a>
+ </div>
+ <div class="item">
+ <a href="http://groups.google.com/group/Google-Web-Toolkit">Developer Forum</a>
+ </div>
+ </div>
+ </div>
+ <div id="search">
+ <form action="http://www.google.com/search" method="get">
+ <div>
+ <input name="domains" value="code.google.com" type="hidden"/>
+ <input name="sitesearch" value="code.google.com" type="hidden"/>
+ <div class="header">Search this site:</div>
+ <div class="input">
+ <input name="q" size="10"/>
+ </div>
+ <div class="button">
+ <input value="Search" type="submit"/>
+ </div>
+ </div>
+ </form>
+ </div>
+ </div>
+ </xsl:template>
+ <xsl:template name="gen-header">
+ <div id="gaia"> </div>
+ <div id="header">
+ <div id="logo">
+ <a href="http://code.google.com/">
+ <img src="http://code.google.com/images/code_sm.png" alt="Google"/>
+ </a>
+ </div>
+ <!-- The title for the entire docset -->
+ <div id="title">
+ <xsl:value-of select="$title"/>
+ </div>
+ <div id="breadcrumbs">
+ <div id="nextprev">
+ <xsl:call-template name="emitPrevTopic">
+ <xsl:with-param name="start" select="."/>
+ </xsl:call-template>
+ <xsl:text> </xsl:text>
+ <xsl:call-template name="emitNextTopic">
+ <xsl:with-param name="start" select="."/>
+ </xsl:call-template>
+ </div>
+ <span class="item">
+ <a href="http://code.google.com/">Google Code Home</a>
+ </span> >
+ <span class="item">
+ <a href="http://code.google.com/webtoolkit/">Google Web Toolkit</a>
+ </span> >
+ <!-- The topic location -->
+ <xsl:if test="location">
+ <xsl:apply-templates select="location/@*|location/node()"/>
+ </xsl:if>
+ </div>
+ </div>
+ </xsl:template>
+ <xsl:template name="emitPrevTopic">
+ <xsl:param name="start"/>
+ <xsl:variable name="prev" select="$start/preceding-sibling::topic[position()=1]"/>
+ <xsl:variable name="parentLastChild" select="$prev"/>
+ <xsl:variable name="parent" select="$start/parent::topic"/>
+ <xsl:choose>
+ <!-- if there's a previous sibling, use "last deepest child" algorithm -->
+ <xsl:when test="$prev/topic">
+ <xsl:call-template name="emitDeepestPriorChild">
+ <xsl:with-param name="start" select="$prev"/>
+ </xsl:call-template>
+ </xsl:when>
+ <!-- if there's a previous sibling without a child, use it -->
+ <xsl:when test="$prev">
+ <nobr>
+ <a href="{$prev/id}.html">« prev</a>
+ </nobr>
+ </xsl:when>
+ <!-- if there's a parent, use it -->
+ <xsl:when test="$parent">
+ <nobr>
+ <a href="{$parent/id}.html">« prev</a>
+ </nobr>
+ </xsl:when>
+ <xsl:otherwise>
+ <nobr style="visibility:hidden">« prev</nobr>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+ <xsl:template name="emitDeepestPriorChild">
+ <xsl:param name="start"/>
+ <xsl:choose>
+ <xsl:when test="$start/topic">
+ <xsl:call-template name="emitDeepestPriorChild">
+ <xsl:with-param name="start" select="$start/topic[position()=last()]"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <nobr>
+ <a href="{$start/id}.html">« prev</a>
+ </nobr>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+ <xsl:template name="emitNextTopic">
+ <xsl:param name="start"/>
+ <xsl:variable name="child" select="$start/topic[position()=1]"/>
+ <xsl:variable name="next" select="$start/following-sibling::topic[position()=1]"/>
+ <xsl:variable name="parentNext" select="$start/ancestor::topic[following-sibling::topic]/following-sibling::topic"/>
+ <xsl:choose>
+ <!-- if there's a first child, use it -->
+ <xsl:when test="$child">
+ <nobr>
+ <a href="{$child/id}.html">next »</a>
+ </nobr>
+ </xsl:when>
+ <!-- if there's a next sibling, use it -->
+ <xsl:when test="$next">
+ <nobr>
+ <a href="{$next/id}.html">next »</a>
+ </nobr>
+ </xsl:when>
+ <!-- find the first parent that has a next sibling -->
+ <xsl:when test="$parentNext">
+ <nobr>
+ <a href="{$parentNext/id}.html">next »</a>
+ </nobr>
+ </xsl:when>
+ <xsl:otherwise>
+ <nobr style="visibility:hidden">next »</nobr>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+ <!-- Only topics that have titles get pulled in -->
+ <xsl:template match="topic[title]">
+@FILE <xsl:value-of select="id"/>.html
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>
+ Google Web Toolkit
+ <xsl:if test="title">
+ - <xsl:value-of select="title"/>
+ </xsl:if>
+ </title>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
+ <link href="../css/base.css" rel="stylesheet" type="text/css"/>
+ <link href="./doc.css" rel="stylesheet" type="text/css"/>
+ <link href="../css/print.css" rel="stylesheet" media="print" type="text/css"/>
+ </head>
+ <body>
+ <xsl:call-template name="gen-header"/>
+ <xsl:call-template name="gen-side"/>
+ <div id="body">
+ <!-- If there is a header, prefer it to the title -->
+ <xsl:choose>
+ <xsl:when test="header">
+ <xsl:apply-templates select="header/@*|header/node()"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- The title for this topic -->
+ <h1>
+ <xsl:value-of select="title"/>
+ </h1>
+ </xsl:otherwise>
+ </xsl:choose>
+ <!-- The topic body -->
+ <xsl:if test="body">
+ <xsl:apply-templates select="body/@*|body/node()"/>
+ </xsl:if>
+ <!-- Tips -->
+ <xsl:for-each select="tip">
+ <div class="tipContainer">
+ <div class="tipCallout">
+ Tip
+ </div>
+ <div class="tipBody">
+ <xsl:apply-templates select="node()"/>
+ </div>
+ </div>
+ </xsl:for-each>
+ <!-- Links to child topics -->
+ <xsl:if test="childIntro">
+ <xsl:if test="childIntro/text()">
+ <h2>
+ <xsl:value-of select="childIntro/text()"/>
+ </h2>
+ </xsl:if>
+ <ul>
+ <xsl:for-each select="topic">
+ <li>
+ <div class="heading">
+ <xsl:call-template name="makeLink">
+ <xsl:with-param name="linkText" select="title/node()"/>
+ <xsl:with-param name="linkTarget" select="."/>
+ </xsl:call-template>
+ </div>
+ <xsl:if test="synopsis">
+ <div>
+ <xsl:apply-templates select="synopsis"/>
+ </div>
+ </xsl:if>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </xsl:if>
+ <!-- See also links -->
+ <xsl:if test="seeAlso/link">
+ <div class="topicSeeAlso">
+ <h2>Related topics</h2>
+ <xsl:for-each select="seeAlso/link">
+ <xsl:apply-templates select="."/>
+ <xsl:if test="position()!=last()">, </xsl:if>
+ </xsl:for-each>
+ </div>
+ </xsl:if>
+ </div>
+ <div id="footer">
+ ©2006 Google
+ <span class="noprint"> -
+ <a href="http://www.google.com/">Google Home</a> -
+ <a href="http://www.google.com/jobs/">We're Hiring</a> -
+ <a href="http://www.google.com/privacy.html">Privacy Policy</a> -
+ <a href="http://www.google.com/terms_of_service.html">Terms of Service</a> -
+ <a href="mailto:code@google.com">Contact Us</a>
+ </span>
+ </div>
+ <!-- analytics -->
+ <script src="https://ssl.google-analytics.com/urchin.js" type="text/javascript"/>
+ <script type="text/javascript">_uacct="UA-18071-1"; _uanchor=1; urchinTracker();</script>
+ </body>
+ </html>
+ </xsl:template>
+ <xsl:template match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+ <xsl:template match="link">
+ <xsl:variable name="testLinkBase" select="substring-before(@ref, "#")"/>
+ <xsl:variable name="linkBase">
+ <xsl:if test="string-length($testLinkBase) != 0">
+ <xsl:value-of select="$testLinkBase"/>
+ </xsl:if>
+ <xsl:if test="string-length($testLinkBase) = 0">
+ <xsl:value-of select="@ref"/>
+ </xsl:if>
+ </xsl:variable>
+ <xsl:call-template name="makeLink">
+ <xsl:with-param name="linkRef" select="@ref"/>
+ <xsl:with-param name="linkText" select="node()"/>
+ <xsl:with-param name="linkTarget" select="key('topicSearch', $linkBase)"/>
+ </xsl:call-template>
+ </xsl:template>
+ <xsl:template name="makeLink">
+ <!-- This only becomes a hyperlink if the cross-ref can be resolved -->
+ <xsl:param name="linkRef"/>
+ <xsl:param name="linkText"/>
+ <xsl:param name="linkTarget"/>
+ <xsl:choose>
+ <xsl:when test="$linkTarget">
+ <xsl:variable name="extra" select="substring-after($linkRef, "#")"/>
+ <xsl:element name="a">
+ <xsl:attribute name="href"><xsl:value-of select="$linkTarget/id"/><xsl:text>.html</xsl:text><xsl:if test="string-length($extra) != 0"><xsl:text>#</xsl:text><xsl:value-of select="$extra"/></xsl:if></xsl:attribute>
+ <xsl:apply-templates select="$linkText"/>
+ </xsl:element>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- plain text -->
+ <xsl:apply-templates select="$linkText"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-index.xslt b/build-tools/doctool/src/topics-index.xslt
new file mode 100644
index 0000000..0620bc8
--- /dev/null
+++ b/build-tools/doctool/src/topics-index.xslt
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html"/>
+ <xsl:param name="title"/>
+ <xsl:template match="/">
+ <html>
+ <head>
+ <link rel="stylesheet" href="help.css" type="text/css"/>
+ </head>
+ <body style="margin: 0.5em">
+ <h1>
+ <xsl:value-of select="$title"/>
+ </h1>
+ <xsl:apply-templates select="//topic[index]">
+ <xsl:sort select="index" order="ascending"/>
+ </xsl:apply-templates>
+ </body>
+ </html>
+ </xsl:template>
+ <xsl:template match="topic[index]">
+ <div>
+ <xsl:value-of select="index"/>
+ <xsl:text> - </xsl:text>
+ <xsl:element name="a">
+ <xsl:attribute name="href"><xsl:value-of select="id"/>.html</xsl:attribute>
+ <xsl:attribute name="class">tocLink</xsl:attribute>
+ <xsl:value-of select="title"/>
+ </xsl:element>
+ </div>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-singlehtml.xslt b/build-tools/doctool/src/topics-singlehtml.xslt
new file mode 100644
index 0000000..08e64ba
--- /dev/null
+++ b/build-tools/doctool/src/topics-singlehtml.xslt
@@ -0,0 +1,111 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html"/>
+ <xsl:strip-space elements="*"/>
+
+ <xsl:key name="topicSearch" match="//topic" use="id"/>
+
+ <xsl:param name="css"/>
+
+ <xsl:template match="/">
+ <html>
+ <head>
+ <title>Innuvo Dynamic Client System 1.1 Documentation</title>
+ <link rel="stylesheet" href="help.css" type="text/css" />
+ </head>
+ <body>
+ <xsl:apply-templates select="//topic"/>
+ </body>
+ </html>
+ </xsl:template>
+
+ <!-- Only topics that have titles get pulled in -->
+ <xsl:template match="topic[title]">
+ <xsl:element name="a">
+ <xsl:attribute name="name"><xsl:value-of select="id"/></xsl:attribute>
+ </xsl:element>
+
+<!--
+ <xsl:if test="header">
+ <div class="topicHeader">
+ <xsl:apply-templates select="header/@*|header/node()" />
+ </div>
+ </xsl:if>
+-->
+
+ <xsl:if test="body">
+ <div class="topicBody">
+ <xsl:apply-templates select="body/@*|body/node()" />
+ </div>
+ </xsl:if>
+
+ <!-- Links to child topics -->
+<!--
+ <xsl:if test="childIntro">
+ <div class="topicChildren">
+ <xsl:apply-templates select="childIntro"/>
+ <ul>
+ <xsl:for-each select="topic">
+ <li>
+ <xsl:call-template name="makeLink">
+ <xsl:with-param name="linkText" select="title/node()"/>
+ <xsl:with-param name="linkTarget" select="."/>
+ </xsl:call-template>
+ <xsl:if test="synopsis">
+ - <xsl:apply-templates select="synopsis"/>
+ </xsl:if>
+ </li>
+ </xsl:for-each>
+ </ul>
+ </div>
+ </xsl:if>
+-->
+
+ <!-- See also links -->
+<!--
+ <xsl:if test="seeAlso">
+ <div class="topicSeeAlso">
+ <h2>Related topics</h2>
+ <xsl:for-each select="seeAlso">
+ <xsl:apply-templates select="link"/>
+ <xsl:if test="position()!=last()">, </xsl:if>
+ </xsl:for-each>
+ </div>
+ </xsl:if>
+-->
+ </xsl:template>
+
+ <xsl:template match="@*|node()">
+ <xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
+ </xsl:template>
+
+ <xsl:template match="pre">
+ <!-- Do not copy, wastes time during spelling/grammar checks -->
+ <div>CODE SAMPLE REMOVED</div>
+ </xsl:template>
+
+ <xsl:template match="table[@class='members']">
+ <!-- Do not copy, wastes time during spelling/grammar checks -->
+ <div>TABLE OF MEMBERS REMOVED</div>
+ </xsl:template>
+
+ <xsl:template match="img">
+ <!-- Do not copy, wastes time during spelling/grammar checks -->
+ <div>IMAGE REMOVED</div>
+ </xsl:template>
+
+ <xsl:template match="link">
+ <xsl:call-template name="makeLink">
+ <xsl:with-param name="linkText" select="node()"/>
+ <xsl:with-param name="linkTarget" select="key('topicSearch', @ref)"/>
+ </xsl:call-template>
+ </xsl:template>
+
+ <xsl:template name="makeLink">
+ <xsl:param name="linkText"/>
+ <xsl:param name="linkTarget"/>
+ <!-- Always emit plain text -->
+ <xsl:apply-templates select="$linkText"/>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics-toc.xslt b/build-tools/doctool/src/topics-toc.xslt
new file mode 100644
index 0000000..70d285e
--- /dev/null
+++ b/build-tools/doctool/src/topics-toc.xslt
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+ <xsl:output method="html"/>
+ <xsl:param name="title"/>
+ <xsl:template match="/">
+ <html>
+ <head>
+ <link rel="stylesheet" href="help.css" type="text/css"/>
+ </head>
+ <body style="margin: 0.5em">
+ <h1>
+ <xsl:value-of select="$title"/>
+ </h1>
+ <xsl:apply-templates select="/topics/topic">
+ <xsl:sort select="order" data-type="number" order="descending"/>
+ </xsl:apply-templates>
+ </body>
+ </html>
+ </xsl:template>
+
+ <xsl:template match="topic">
+ <div style="margin-left: 2%">
+ <xsl:element name="a">
+ <xsl:attribute name="href"><xsl:value-of select="id"/>.html</xsl:attribute>
+ <xsl:attribute name="class">tocLink</xsl:attribute>
+ <xsl:value-of select="title"/>
+ </xsl:element>
+ <xsl:apply-templates select="topic"/>
+ </div>
+ </xsl:template>
+</xsl:stylesheet>
diff --git a/build-tools/doctool/src/topics.xsd b/build-tools/doctool/src/topics.xsd
new file mode 100644
index 0000000..7f8342f
--- /dev/null
+++ b/build-tools/doctool/src/topics.xsd
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- edited with XMLSpy v2005 rel. 3 U (http://www.altova.com) by Bruce Johnson (private) -->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" attributeFormDefault="unqualified">
+ <xs:element name="topics">
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element ref="topic" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="topic">
+ <xs:annotation>
+ <xs:documentation>Topic element; can be freely nested</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:sequence>
+ <xs:sequence minOccurs="0">
+ <xs:element name="id" type="xs:string">
+ <xs:annotation>
+ <xs:documentation>The id for this topic, which should be unique for the entire library</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="order" type="xs:int" minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>An arbitrary number used to indicate sorting order</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="title" type="xs:string">
+ <xs:annotation>
+ <xs:documentation>A simple title for the topic</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="index" type="xs:string" minOccurs="0" maxOccurs="unbounded">
+ <xs:annotation>
+ <xs:documentation>A word or phrase to include in the documentation index that points to this topic</xs:documentation>
+ </xs:annotation>
+ </xs:element>
+ <xs:element name="synopsis" minOccurs="0">
+ <xs:annotation>
+ <xs:documentation>A short (one phrase or sentence) that summaries the point of the topic</xs:documentation>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence>
+ <xs:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="header">
+ <xs:annotation>
+ <xs:documentation>HTML header that may get special treatment in the published documentation</xs:documentation>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence>
+ <xs:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="body">
+ <xs:annotation>
+ <xs:documentation>HTML body that constitutes the primary content of the topic</xs:documentation>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence>
+ <xs:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ <xs:element name="seeAlso" minOccurs="0" maxOccurs="unbounded">
+ <xs:annotation>
+ <xs:documentation>A list of related cross-ref links</xs:documentation>
+ </xs:annotation>
+ <xs:complexType>
+ <xs:sequence>
+ <xs:element name="link" minOccurs="0" maxOccurs="unbounded">
+ <xs:annotation>
+ <xs:documentation>Links to topics that are not necessarily child topics</xs:documentation>
+ </xs:annotation>
+ <xs:complexType mixed="true">
+ <xs:sequence minOccurs="0" maxOccurs="unbounded">
+ <xs:any namespace="##any" processContents="skip">
+ <xs:annotation>
+ <xs:documentation>HTML representing the link caption</xs:documentation>
+ </xs:annotation>
+ </xs:any>
+ </xs:sequence>
+ <xs:attribute name="ref">
+ <xs:annotation>
+ <xs:documentation>The target id of a topic anywhere in the library; if absent, it a link that could not be resolved</xs:documentation>
+ </xs:annotation>
+ </xs:attribute>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+ </xs:sequence>
+ <xs:sequence>
+ <xs:element name="childIntro" minOccurs="0"/>
+ <xs:element ref="topic" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:sequence>
+ </xs:sequence>
+ </xs:complexType>
+ </xs:element>
+</xs:schema>