Project

Profile

Help

Bug #6541 » chunk-xmljavadoc.xsl

Norm Tovey-Walsh, 2024-09-19 17:34

 
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:f="urn:local.function" xmlns:h="http://www.w3.org/1999/xhtml"
xmlns:d='https://saxonica.com/ns/doclet'
exclude-result-prefixes="f xs h d">

<!--
Stylesheet to process JavaDoc XML output produced by xmldoclet, to produce XML output for the
SaxonJS user documentation application.

* input: doclet.xml,
the input to this stylesheet is the XML generated by the xmldoclet
(https://github.com/Saxonica/xmldoclet)

Outputs:

* output 1. A set of files describing the Java source packages, with one
file for each Java package. Each file contains descriptions of all the
classes for the represented package.
* output 2. The file 'javadoc-packages' containing the comments for
each package.
* output 3. The file 'javadoc-types' lists all classes and members - used
in javadoc search.
* output 4. The file 'javadoc-tree.xml' used to generate the outline
view of all packages/classes/sub-classes

About JavaDoc formatting

The format of JavaDoc comments conforms to a set of rules described here:
http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html

A comment comprises a first sentence, followed optionally by further comments.
HTML markup is used within the comments - of note: sometimes self-closed
<p/> tags are sometimes used as separators rather than wrappers. Following the
comments there is a blank line, and after this the parameters are described
with each parameter name prefixed with a '@' character

History

06 Dec 2023: First implementation by Debbie Lockett
- based on chunk-javadoc.xml from previous javadoc pipeline (used for Saxon 12 and earlier)

-->
<xsl:param name="base-output" select="'.'"/>

<xsl:output method="xml" indent="no"/>

<xsl:template match="@*">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="@*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template name="javadoc-package">
<article id="javadoc-packages">
<xsl:apply-templates mode="pack" select="//d:package" />
</article>
</xsl:template>
<xsl:template match="d:package" mode="pack">
<section id="{@name}">
<xsl:call-template name="full-comment"/>
</section>
</xsl:template>

<xsl:template match="/">

<!-- chunk xml at package level (default) -->

<xsl:for-each-group select="d:doclet/(d:class|d:interface|d:enum)[@nesting eq 'top_level']" group-by="@package">
<xsl:sort select="@package"/>
<xsl:variable name="package" select="current-grouping-key()"/>
<!--<xsl:message>package document: <xsl:value-of select="$package"/></xsl:message>-->
<xsl:result-document href="{$base-output}/{concat('packages/', $package, '.xml')}">
<package id="{$package}">
<xsl:for-each select="current-group()">
<xsl:sort select="upper-case(@type)"/>
<xsl:apply-templates select="."/>
</xsl:for-each>
</package>
</xsl:result-document>
</xsl:for-each-group>


<xsl:result-document href="{$base-output}/javadoc-packages.xml">
<xsl:call-template name="javadoc-package"/>
</xsl:result-document>

<!-- Produce index of classes and members - used in javadoc search.
Group into hierarchy of packages, classes and members.
-->
<xsl:result-document href="{$base-output}/javadoc-types.xml">
<packages title="Search Data">

<xsl:for-each-group select="d:doclet/(d:class|d:interface|d:enum)[@nesting eq 'top_level']" group-by="@package">
<xsl:sort select="@package"/>
<j id="{@package}" in="p">

<xsl:apply-templates select="current-group()" mode="deeptree">
<xsl:sort select="upper-case(@type)"/>
</xsl:apply-templates>

</j>
</xsl:for-each-group>

</packages>
</xsl:result-document>

<!-- Group into hierarchy of packages, classes and nestedclasses (those with a '.' in their name).
javadoc-tree.xml used for index in nav list. -->
<xsl:result-document href="{$base-output}/javadoc-tree.xml">
<packages id="javadoc" title="Java API">

<xsl:for-each-group select="d:doclet/(d:class|d:interface|d:enum)[@nesting eq 'top_level']" group-by="@package">
<xsl:sort select="@package"/>
<j id="{@package}" in="p">

<xsl:apply-templates select="current-group()" mode="tree">
<xsl:sort select="upper-case(@type)"/>
</xsl:apply-templates>

</j>
</xsl:for-each-group>

</packages>
</xsl:result-document>

</xsl:template>

<xsl:template match="d:class|d:interface|d:enum" mode="tree">
<xsl:variable name="type" select="@type"/>
<xsl:variable name="has-nonPP-implementing-classes" as="xs:boolean"
select="self::d:interface and exists(key('implementing-classes',
@fullname)[exists(@access) and @access ne 'private'])" />
<xsl:variable name="has-nonPP-subclasses" as="xs:boolean"
select="exists(key('subclasses-key', @fullname)[exists(@access) and @access ne 'private'])"/>
<xsl:variable name="has-PP-members-only" as="xs:boolean"
select="empty((d:method|d:constructor|d:field|d:constant)[exists(@access) and @access ne 'private'])"/>
<xsl:if test="@access ne 'private' or (empty(@access) and not($has-PP-members-only) and
($has-nonPP-implementing-classes or $has-nonPP-subclasses))">
<j id="{$type}" in="{f:get-att-name(.)}">
<xsl:apply-templates select="d:class|d:interface|d:enum" mode="tree">
<xsl:sort select="upper-case(@type)"/>
</xsl:apply-templates>
</j>
</xsl:if>
</xsl:template>
<!-- Used in mode="tree" for javadoc-tree.xml output -->
<xsl:function name="f:get-att-name" as="xs:string">
<xsl:param name="class" as="element()"/>
<xsl:for-each select="$class">
<xsl:choose>
<xsl:when test="exists(@abstract) and xs:boolean(@abstract) eq true()">a</xsl:when>
<xsl:when test="$class/self::d:interface">i</xsl:when>
<!-- But note that all interfaces are abstract, so never get 'i' -->
<xsl:otherwise>c</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:function>
<xsl:template match="d:field|d:method|d:constant" mode="deeptree">
<xsl:variable name="initial" select="if (self::d:constant) then 'e' else substring(name(.), 1, 1)"/>
<j id="{@name}" in="{$initial}" qy="{lower-case(@name)}"/>
</xsl:template>
<xsl:template match="d:method[exists(preceding-sibling::d:method[(@name eq current()/@name) and
(@access ne 'private')])]" mode="deeptree">
<!--<xsl:message>Duplicate method name <xsl:value-of select="@name"/></xsl:message>-->
</xsl:template>
<xsl:template match="d:class|d:interface|d:enum" mode="deeptree">
<xsl:variable name="type" select="@type"/>
<xsl:variable name="has-nonPP-implementing-classes" as="xs:boolean"
select="self::d:interface and exists(key('implementing-classes',
@fullname)[exists(@access) and @access ne 'private'])" />
<xsl:variable name="has-nonPP-subclasses" as="xs:boolean"
select="exists(key('subclasses-key', @fullname)[exists(@access) and @access ne 'private'])"/>
<xsl:variable name="has-PP-members-only" as="xs:boolean"
select="empty((d:method|d:constructor|d:field|d:constant)[exists(@access) and @access ne 'private'])"/>
<xsl:if test="@access ne 'private' or (empty(@access) and not($has-PP-members-only) and
($has-nonPP-implementing-classes or $has-nonPP-subclasses))">
<j id="{$type}" in="c" qy="{lower-case($type)}">
<xsl:apply-templates select="d:class|d:interface|d:enum|d:field[@access ne
'private']|d:constant[@access ne 'private']" mode="deeptree">
<xsl:sort select="if (self::d:class or self::d:interface or self::d:enum) then
upper-case(@type) else upper-case(@name)"/>
</xsl:apply-templates>
<xsl:apply-templates select="d:method[@access ne 'private']" mode="deeptree">
<xsl:sort select="upper-case(@name)"/>
</xsl:apply-templates>
</j>
</xsl:if>
</xsl:template>
<xsl:template name="full-comment">
<div>
<xsl:if test="d:purpose|d:description">
<p>
<xsl:call-template name="comment-first-para"/>
</p>
<xsl:apply-templates select="d:description/h:body/(h:p|h:ul|h:ol)"
mode="comment"/>
</xsl:if>
</div>
</xsl:template>
<xsl:template name="comment-first-para">
<!-- First paragraph is made up of the content of d:purpose, and the content of
d:description up to the first <p> -->
<xsl:apply-templates select="d:purpose/h:body/node()" mode="comment"/>
<xsl:if test="exists(d:description/h:body/text()) and
normalize-space(string-join(d:description/h:body/text()/string(),
'')) ne ''">
<xsl:value-of select="' '"/>
<xsl:apply-templates select="d:description/h:body/node()[empty(preceding-sibling::h:p)]" mode="purpose"/>
</xsl:if>
</xsl:template>
<xsl:template match="*" mode="comment">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="node()|@*" mode="comment"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*" mode="comment">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="d:purpose/h:body/h:p" mode="comment">
<xsl:apply-templates select="node()" mode="comment"/>
</xsl:template>

<xsl:template match="h:p[not(text())]" mode="comment"/>
<xsl:template match="d:package//h:a[@class eq 'ref']" mode="comment purpose #unnamed"
priority="5">
<span>
<xsl:attribute name="class" select="'javalink'"/>
<xsl:attribute name="data-href" select="@href"/>
<xsl:value-of select="tokenize(., '\.')[last()]"/>
</span>
</xsl:template>
<xsl:template match="(h:a|h:span)[@class eq 'ref']" mode="comment purpose #unnamed">
<span>
<xsl:attribute name="class" select="'javalink'"/>
<xsl:attribute name="data-href" select="@href"/>
<xsl:apply-templates select="node()" mode="comment"/>
</span>
</xsl:template>
<xsl:template match="h:a|h:span" mode="comment purpose #unnamed">
<a>
<xsl:copy-of select="@href"/>
<xsl:apply-templates select="node()" mode="comment"/>
</a>
</xsl:template>
<xsl:template match="h:span[@class eq 'ref-param']" mode="comment purpose #unnamed">
<xsl:apply-templates select="node()" mode="comment"/>
</xsl:template>
<xsl:template match="h:hr" mode="purpose"/>
<xsl:template match="*" mode="purpose">
<xsl:element name="{local-name(.)}">
<xsl:apply-templates select="node()|@*" mode="purpose"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*" mode="purpose">
<xsl:copy-of select="." />
</xsl:template>
<xsl:template match="h:p" mode="purpose"/>
<xsl:template match="h:ul" mode="purpose"/>



<xsl:template match="d:class|d:interface|d:enum">
<!-- Don't include class if @access='private' and no non-package-private classes-implemented-by-interface or
subclasses, or no non-package-private members -->
<xsl:variable name="implementing-classes" as="element(implemented-by)?">
<xsl:call-template name="classes-which-implement-interface"/>
</xsl:variable>
<xsl:variable name="subclasses" as="element(subclasses)?">
<xsl:call-template name="subclasses"/>
</xsl:variable>
<xsl:variable name="has-nonPP-implementing-classes" as="xs:boolean"
select="exists($implementing-classes)" />
<xsl:variable name="has-nonPP-subclasses" as="xs:boolean"
select="exists($subclasses)" />
<xsl:variable name="has-PP-members-only" as="xs:boolean"
select="empty((d:method|d:constructor|d:field|d:constant)[exists(@access) and @access ne 'private'])"/>
<!--<xsl:if test="empty(@access) and $has-PP-members-only">
<xsl:message>isPP with PP-members only: <xsl:value-of select="@fullname"/></xsl:message>
</xsl:if>
<xsl:if test="empty(@access) and empty($implementing-classes) and empty($subclasses)">
<xsl:message>isPP with no nonPP implementing classes or subclasses: <xsl:value-of select="@fullname"/></xsl:message>
</xsl:if>-->
<xsl:if test="@access ne 'private' or (empty(@access) and not($has-PP-members-only) and ($has-nonPP-implementing-classes
or $has-nonPP-subclasses))">
<!--<xsl:if test="empty(@access)">
<xsl:message>include isPP: <xsl:value-of select="@fullname"/></xsl:message>
</xsl:if>-->
<class>
<xsl:apply-templates select="@* except (@package|@nesting)"/>
<xsl:if test="d:superclass">
<xsl:attribute name="superclass" select="d:superclass/d:type/@name"/>
<xsl:attribute name="superclassfulltype" select="d:superclass/d:type/@fullname"/>
</xsl:if>
<xsl:if test="self::d:interface">
<xsl:attribute name="interface" select="'true'"/>
</xsl:if>
<xsl:apply-templates select="d:interfaces"/>
<xsl:apply-templates select="d:typeparams"/>
<xsl:apply-templates select="d:superclass"/>
<xsl:if test="d:purpose|d:description|d:since|d:see|d:deprecated">
<comment>
<xsl:if test="d:description">
<body>
<xsl:call-template name="full-comment"/>
</body>
</xsl:if>
<xsl:apply-templates select="d:purpose"/>
<xsl:apply-templates select="d:since"/>
<xsl:apply-templates select="d:see"/>
<xsl:apply-templates select="d:deprecated"/>
</comment>
</xsl:if>
<xsl:if test="d:field[@access ne 'private']">
<fields>
<xsl:apply-templates select="d:field[@access ne 'private']">
<xsl:sort select="upper-case(@name)"/>
</xsl:apply-templates>
</fields>
</xsl:if>
<xsl:if test="d:constructor[@access ne 'private']|d:method[@access ne 'private']">
<methods>
<xsl:apply-templates select="d:constructor[@access ne 'private']">
<xsl:sort select="count(d:parameter)"/>
</xsl:apply-templates>
<xsl:apply-templates select="d:method[@access ne 'private']">
<xsl:sort select="upper-case(@name)"/>
<xsl:sort select="count(d:parameter)"/>
</xsl:apply-templates>
</methods>
</xsl:if>
<xsl:if test="d:constant[@access ne 'private']">
<enumConstants>
<xsl:apply-templates select="d:constant[@access ne 'private']">
<xsl:sort select="upper-case(@name)"/>
</xsl:apply-templates>
</enumConstants>
</xsl:if>
<xsl:apply-templates select="d:class|d:enum">
<xsl:sort select="upper-case(@type)"/>
</xsl:apply-templates>
<xsl:sequence select="$implementing-classes"/>
<xsl:sequence select="$subclasses"/>
</class>
</xsl:if>
</xsl:template>
<xsl:template match="@type">
<xsl:attribute name="id" select="."/>
</xsl:template>
<xsl:template match="@type[parent::element()/@nesting eq 'member']">
<xsl:attribute name="id" select="f:getNestedName(parent::element())"/>
</xsl:template>
<xsl:function name="f:getNestedName" as="xs:string">
<xsl:param name="class" as="element()"/>
<xsl:choose>
<xsl:when test="$class/@nesting eq 'member'">
<xsl:sequence select="concat(f:getNestedName($class/parent::element()), '.', $class/@type)"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$class/@type"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="@name">
<xsl:attribute name="id" select="."/>
</xsl:template>
<xsl:template match="@fullname">
<xsl:attribute name="fulltype" select="."/>
</xsl:template>
<xsl:template match="@access">
<xsl:attribute name="visibility" select="."/>
</xsl:template>
<xsl:template match="d:interfaces">
<implements>
<xsl:apply-templates select="d:interfaceref"/>
</implements>
</xsl:template>
<xsl:template match="d:interfaceref">
<interface fulltype="{@fullname}" type="{@name}">
<!-- TODO include inherited methods once included in xmldoclet output -->
</interface>
</xsl:template>
<xsl:template match="d:typeparams">
<paramtypes>
<xsl:apply-templates select="d:typeparam"/>
</paramtypes>
</xsl:template>
<xsl:template match="d:typeparam">
<type name="{@name}">
<xsl:apply-templates select="d:type" mode="typeparam"/>
</type>
</xsl:template>
<xsl:template match="d:type" mode="typeparam">
<bounds>
<limit fulltype="{@fullname}" type="{@name}"/>
</bounds>
</xsl:template>
<xsl:template match="d:purpose">
<sentence>
<xsl:apply-templates select="h:body/node()"/>
</sentence>
</xsl:template>
<xsl:template match="d:since|d:see|d:deprecated">
<attribute name="{concat('@', local-name(.))}">
<body>
<div>
<xsl:apply-templates select="h:body/node()"/>
</div>
</body>
</attribute>
</xsl:template>
<xsl:template match="d:field">
<field>
<xsl:apply-templates select="@*"/>
<xsl:call-template name="type-attributes"/>
</field>
</xsl:template>
<xsl:template match="d:field/@value">
<xsl:attribute name="const" select="."/>
</xsl:template>
<xsl:template match="d:superclass">
<extends>
<xsl:if test="d:interface">
<inherits>
<xsl:apply-templates select="d:interface">
<xsl:sort select="@fullname"/>
</xsl:apply-templates>
</inherits>
</xsl:if>
<!--<xsl:variable name="superclass-fullname" select="d:type/@fullname" as="xs:string"/>
<xsl:variable name="superclass-element" select="//d:class[@fullname eq $superclass-fullname]"/>
<xsl:if test="$superclass-element[@access eq 'private']">
<xsl:message>Private superclass: <xsl:value-of select="$superclass-fullname"/></xsl:message>
</xsl:if>-->
<interface fulltype="{d:type/@fullname}" type="{d:type/@name}">
<xsl:apply-templates select="d:type/d:param"/>
<xsl:if test="d:inherited/d:method">
<methods>
<xsl:apply-templates select="d:inherited/d:method">
<xsl:sort select="upper-case(@name)"/>
</xsl:apply-templates>
</methods>
</xsl:if>
<xsl:if test="d:inherited/d:field">
<fields>
<xsl:apply-templates select="d:inherited/d:field">
<xsl:sort select="upper-case(@name)"/>
</xsl:apply-templates>
</fields>
</xsl:if>
<xsl:apply-templates select="d:superclass"/>
</interface>
</extends>
</xsl:template>
<xsl:template match="d:superclass/d:interface">
<interface fulltype="{@fullname}" type="{@name}" />
</xsl:template>
<xsl:template match="d:inherited/d:method">
<method><xsl:value-of select="@name"/></method>
</xsl:template>
<xsl:template match="d:inherited/d:field">
<field><xsl:value-of select="@name"/></field>
</xsl:template>
<xsl:template match="d:constructor">
<constructor>
<xsl:apply-templates select="@* except @fullsig"/>
<xsl:attribute name="id" select="parent::d:class/@type"/>
<xsl:if test="d:purpose|d:description|d:since|d:see|d:deprecated">
<comment>
<xsl:apply-templates select="d:purpose"/>
<xsl:if test="d:description">
<body>
<xsl:call-template name="full-comment"/>
</body>
</xsl:if>
<xsl:apply-templates select="d:since|d:see|d:deprecated"/>
</comment>
</xsl:if>
<xsl:if test="d:parameter">
<params>
<xsl:apply-templates select="d:parameter"/>
</params>
</xsl:if>
<xsl:if test="d:throws">
<exceptions>
<xsl:apply-templates select="d:throws"/>
</exceptions>
</xsl:if>
</constructor>
</xsl:template>
<xsl:template match="d:method">
<method>
<xsl:apply-templates select="@* except @fullsig"/>
<xsl:call-template name="type-attributes">
<xsl:with-param name="this" select="d:return"/>
</xsl:call-template>
<xsl:apply-templates select="d:return/d:type/d:param|d:return/d:type/d:wildcard"/>
<xsl:if test="d:purpose|d:description|d:since|d:see|d:deprecated">
<comment>
<xsl:if test="d:description">
<body>
<xsl:call-template name="full-comment"/>
</body>
</xsl:if>
<xsl:apply-templates select="d:purpose"/>
<xsl:if test="d:return/d:purpose">
<return>
<body>
<div>
<xsl:apply-templates select="d:return/d:purpose/h:body/node()"/>
</div>
</body>
</return>
</xsl:if>
<xsl:apply-templates select="d:since|d:see|d:deprecated"/>
</comment>
</xsl:if>
<xsl:apply-templates select="d:overrides"/>
<xsl:if test="d:parameter">
<params>
<xsl:apply-templates select="d:parameter"/>
</params>
</xsl:if>
<xsl:if test="d:throws">
<exceptions>
<xsl:apply-templates select="d:throws"/>
</exceptions>
</xsl:if>
</method>
</xsl:template>
<xsl:template match="d:overrides">
<xsl:variable name="override-parts" select="tokenize(@method, '#')"/>
<override-of fulltype="{$override-parts[1]}" method="{$override-parts[2]}"
type="{tokenize($override-parts[1], '\.')[last()]}" />
</xsl:template>
<xsl:template match="d:parameter">
<param>
<xsl:apply-templates select="@*"/>
<xsl:call-template name="type-attributes"/>
<xsl:apply-templates select="d:type/d:param|d:type/d:wildcard"/>
<xsl:if test="d:purpose">
<body>
<div>
<xsl:apply-templates select="d:purpose/h:body/node()"/>
</div>
</body>
</xsl:if>
</param>
</xsl:template>
<xsl:template match="d:parameter/@name"><!-- Usually @name becomes @id, but not for param -->
<xsl:attribute name="name" select="."/>
</xsl:template>
<xsl:template match="d:param">
<type fulltype="{if (@kind eq 'typevar') then @name else @fullname}" type="{@name}"/>
</xsl:template>
<xsl:template match="d:wildcard">
<xsl:choose>
<xsl:when test="d:extends">
<xsl:apply-templates select="d:extends"/>
</xsl:when>
<xsl:when test="d:super">
<xsl:apply-templates select="d:super"/>
</xsl:when>
<xsl:otherwise>
<type fulltype="?" type="?"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="(d:wildcard/d:extends)|(d:wildcard/d:super)">
<type fulltype="{if (@kind eq 'typevar') then @name else @fullname}" type="{@name}">
<xsl:attribute name="{if (self::d:extends) then 'extendby' else 'super'}"
select="'?'"/>
<xsl:apply-templates select="d:wildcard|d:param"/>
</type>
</xsl:template>
<!--<xsl:template match="d:wildcard/d:super">
<type fulltype="{if (@kind eq 'typevar') then @name else @fullname}" super="?" type="{@name}"/>
</xsl:template>-->
<xsl:template match="d:throws">
<exception>
<xsl:variable name="exception-type" select="if (contains(@exception, '.')) then
(tokenize(@exception, '\.')[last()]) else @exception"/>
<xsl:variable name="exception-fulltype" select="if (contains(@exception, '.')) then
@exception else key('classref', @exception)/@fullname"/>
<xsl:attribute name="id" select="$exception-type"/>
<xsl:attribute name="fulltype" select="$exception-fulltype"/>
<xsl:if test="h:body">
<sentence><xsl:apply-templates select="h:body/node()"/></sentence>
</xsl:if>
</exception>
</xsl:template>
<xsl:template match="d:constant">
<enumConstant>
<xsl:apply-templates select="@*"/>
<xsl:call-template name="type-attributes"/>
</enumConstant>
</xsl:template>
<xsl:template name="type-attributes">
<xsl:param name="this" select="."/>
<xsl:choose>
<xsl:when test="$this/d:array">
<xsl:attribute name="type" select="concat($this/d:array/d:component/@name, '[]')"/>
<xsl:attribute name="fulltype" select="concat(if ($this/d:array/d:component/@kind eq
'typevar') then $this/d:array/d:component/@name else
$this/d:array/d:component/(@fullname|@kind), '[]')"/>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="type" select="$this/d:type/@name"/>
<xsl:attribute name="fulltype" select="if ($this/d:type/@kind eq 'primitive') then
$this/d:type/@name else $this/d:type/(@fullname|@kind)"/>
<!-- TODO or allow 'primitive' and handle this in app -->
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:key name="classref" match="d:package/d:classref" use="@name"/>
<xsl:template name="classes-which-implement-interface">
<xsl:if test="self::d:interface and exists(key('implementing-classes',
@fullname)[exists(@access) and @access ne 'private'])">
<implemented-by>
<xsl:for-each select="key('implementing-classes', @fullname)[exists(@access) and @access ne 'private']">
<xsl:sort select="upper-case(@type)"/>
<class fulltype="{@fullname}" type="{@type}"/>
</xsl:for-each>
</implemented-by>
</xsl:if>
</xsl:template>
<xsl:key name="implementing-classes" match="d:class"
use="(d:superclass/descendant::d:interface/@fullname, d:interfaces/d:interfaceref/@fullname)"/>
<xsl:template name="subclasses">
<xsl:if test="exists(key('subclasses-key', @fullname)[exists(@access) and @access ne 'private'])">
<subclasses>
<xsl:for-each select="key('subclasses-key', @fullname)[exists(@access) and @access ne 'private']">
<xsl:sort select="upper-case(@type)"/>
<class fulltype="{@fullname}" type="{@type}"/>
</xsl:for-each>
</subclasses>
</xsl:if>
</xsl:template>
<xsl:key name="subclasses-key" match="d:class" use="d:superclass/d:type/@fullname"/>

</xsl:stylesheet>
(1-1/2)