<?xml version ="1.0"?>
<!DOCTYPE xsl:stylesheet>

<!--
Copyright
=========
Copyright (C) 2001-2006 Motive, Inc. 

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the ``Software''), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

Except as contained in this notice, the names of individuals
and companies credited with contribution to this software
shall not be used in advertising or otherwise to promote the
sale, use or other dealings in this Software without prior
written authorization from the individuals in question.

Any stylesheet derived from this Software that is publically
distributed will be identified with a different name and the
version strings in any derived Software will be changed so that
no possibility of confusion between the derived package and this
Software will exist.

Warranty
========

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT.  IN NO EVENT SHALL MOTIVE ANY OTHER
CONTRIBUTOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

Overview
========

This stylesheet pulls in all <glossentry>s which are referenced, directly or
indirectly, from <glossterm>s outside of their ancestor <glossary>.
(Indirectly referenced <glossentry>s are those which are referenced by a chain
of other <glossentry>s (via their <glossterm>, <glosssee>, or <glossseealso>
children), the head of which is directly referenced.)  Those <glossentry>s
which are not referenced from without the <glossary> are not written to the
output.

This transformation was previously accomplished with three separate
transformations, called "weed," "sort" and "unique."  This one reduces the
aggregate time needed to run those three by about 36%.

-->

  <xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

	<!-- "quiet" != 0 => Quell non-fatal <xsl:message>s. -->
	<xsl:param name="quiet" select="0"/>
	
	<!-- If recurse.on.glossary != 0 => put glossterms that
	occur in glossdefs in glossary -->
	<xsl:param name="recurse.on.glossary" select="'1'"/>

    <!--xsl:output
      method="xml"
      doctype-public="-//OASIS//DTD DocBook XML V4.1.2//EN"
      doctype-system="http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd"/-->

    <xsl:key
      name="outsideGlossaryGlossterms"
      match="//glossterm[not(ancestor::glossary)]"
      use="@linkend"/>

    <!--
    This is a list of all <glossentry>s directly referenced from without the
    glossary, with all duplicates eliminated.  It has a trailing space but no
    leading space, which is useful for recursive list-walking.  The space
    character is a good ``list'' delimiter because we're guaranteed that it
    will not appear in an attribute value.
    -->
    <xsl:variable name="allDirectlyReferencedGlossentries">
      <!--
      Apply the "Muench Method" (after Oracle XML guy Steve Muench; see pp
      144-145 of Doug Tidwell's _XSLT_ ((c) 2001 O'Reilly).

      Okay, let's restate what is said there about this arcanum.  We are
      selecting the set of <glossterm>s which do not descend from a <glossary>
      and for which the *unique id* of each <glossterm> is the same as the
      *unique id* of the first (in input document order) <glossterm> of that
      other set (returned by key()) of <glossterm>s that all have the same
      value for their linkend attributes.

      Notice that when given a node set, the generate-id() function returns
      the unique id for the first node in that set, making the ``[1]''
      predicate applied to the result of the key() function on p 144 of
      Tidwell redundant.
      -->
      <xsl:for-each select="//glossterm[generate-id(.) =
        generate-id(key('outsideGlossaryGlossterms', @linkend))]">
        <xsl:value-of select="@linkend"/><xsl:text> </xsl:text>
      </xsl:for-each>
    </xsl:variable>

    <!--
    This is a list of all <glossentry>s which are only indirectly referenced
    from without the glossary.  It has *both* a leading and a trailing space,
    making it useful for comparisons using the contains() function.
    Generation of this list probably eats most of the time required for this
    transformation.
    -->
    <xsl:variable name="allIndirectlyReferencedGlossentries">
      <xsl:call-template name="genAllIndirectlyReferencedGlossentries">
        <xsl:with-param name="sources"
            select="$allDirectlyReferencedGlossentries"/>
        <xsl:with-param name="targets" select="''"/>
      </xsl:call-template>
    </xsl:variable>


    <xsl:template match="book">
      <!-- Ensure that there's only one glossary in the document. -->
      <xsl:if test="count(//glossary) > 1">
        <xsl:message terminate="yes">
          This XSLT stylesheet supports only one glossary element.
        </xsl:message>
      </xsl:if>

      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:template>


    <xsl:template match="glossary">
      <xsl:copy>
        <xsl:apply-templates select="@*"/>
        <xsl:for-each select=".//glossentry">
          <!--
          Do not be confused by the fact that each <glossentry> begins with a
          <glossterm> which does not have a linkend attribute, and that we're
          sorting on this <glossterm>.  Any <glossterm> in a <glossentry> that
          links the latter to a another <glossentry> appears after the
          initial, linkend-less <glossterm>.

          We could eliminate "bad" sort orders caused by spurious whitespace
          by wrapping glossterm in a normalize-space() in the select=, below.
          -->
          <xsl:sort lang="{translate(ancestor::*/@lang,'_','')}" select="normalize-space(concat(@sortas, self::*[not(@sortas)]/glossterm))"/>
          <!--
          We wrap the @id attribute value in whitespaces to prevent false
          matches (e.g. "bar.glossary" incorrectly matching
          "foobar.glossary").  This is why the
          allIndirectlyReferencedGlossentries has to have a leading and a
          trailing space char.
          -->
          <xsl:if test="key('outsideGlossaryGlossterms', @id) or
                        contains($allIndirectlyReferencedGlossentries,
                                 concat(' ', @id, ' '))">
            <!--
            Whine if the glossentry we're about to write out has
            role="deprecated" and if we're not quelling such whining.
            -->
            <xsl:if test="($quiet = 0) and @role = 'deprecated'">
              <xsl:message>
                <xsl:text>GLOSSIFY WARNING: glossary entry </xsl:text>
                <xsl:text disable-output-escaping="yes">&lt;</xsl:text>
                <xsl:text>glossentry id="</xsl:text>
                <xsl:value-of select="@id"/>
                <xsl:text>" role="deprecated"</xsl:text>
                <xsl:text disable-output-escaping="yes">&gt; </xsl:text>
                <xsl:text>is deprecated yet referenced by this </xsl:text>
                <xsl:text>document.</xsl:text>
              </xsl:message>
            </xsl:if>
            <xsl:copy>
              <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
          </xsl:if>
        </xsl:for-each>
      </xsl:copy>
    </xsl:template>


    <xsl:template match="@*|node()">
      <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
      </xsl:copy>
    </xsl:template>


    <xsl:template name="genAllIndirectlyReferencedGlossentries">
      <xsl:param name="sources"/>
      <xsl:param name="targets"/>

      <xsl:choose>
        <xsl:when test="$sources=''">
          <xsl:value-of select="$targets"/>
        </xsl:when>
        <xsl:otherwise>
          <!--
          I don't know why we're getting a leading space out of this, but the
          code below (viz., the recursive call) now depends on it.  Exercise
          caution wrt to these leading and trailing space chars should you
          mess with this code.

          UPDATE (Thu Apr 18 18:57:58  2002)
          Hmm, now it seems that we're intermittently getting the leading
          whitespace.  Looks like it's time to just hammer it with a
          conditional and normalize_space() (i.e., "cleanSourceReferents").
          -->
          <xsl:variable name="sourceReferents">
            <xsl:for-each
              select="//glossentry[@id=substring-before($sources, ' ')]">
              <xsl:for-each
                select=".//glossterm | .//glosssee | .//glossseealso">
                <xsl:choose>
                  <xsl:when test="name(.)='glossterm'">
                    <xsl:if
                      test="
					  $recurse.on.glossary != '0'
					  and
					  not(key('outsideGlossaryGlossterms', @linkend))
                      and
                      not(contains($targets, concat(' ', @linkend, ' ')))">
                      <xsl:value-of select="@linkend"/><xsl:text> </xsl:text>
                    </xsl:if>
                  </xsl:when>
                  <xsl:otherwise>
                    <xsl:if
                      test="not(key('outsideGlossaryGlossterms', @otherterm))
                      and
                      not(contains($targets, concat(' ', @otherterm, ' ')))">
                      <xsl:value-of
                        select="@otherterm"/><xsl:text> </xsl:text>
                    </xsl:if>
                  </xsl:otherwise>
                </xsl:choose>
              </xsl:for-each>
            </xsl:for-each>
          </xsl:variable>
          <xsl:variable name="cleanSourceReferents">
            <xsl:if test="normalize-space($sourceReferents)">
              <xsl:value-of
                select="concat(' ', normalize-space($sourceReferents), ' ')"/>
            </xsl:if>
          </xsl:variable>
          <!-- DEBUG -->
<!--        <xsl:message>genAllIndirectlyReferencedGlossentries: $sources="<xsl:value-of select="$sources"/>", $targets="<xsl:value-of select="$targets"/>".</xsl:message> -->
<!--            <xsl:message>                                        $sourceReferents="<xsl:value-of select="$sourceReferents"/>".</xsl:message> -->
<!--            <xsl:message>                                        $cleanSourceReferents="<xsl:value-of select="$cleanSourceReferents"/>".</xsl:message> -->
          <!-- GUBED -->
          <xsl:call-template name="genAllIndirectlyReferencedGlossentries">
            <xsl:with-param name="sources"
              select="concat(substring-after($cleanSourceReferents, ' '),
                             substring-after($sources, ' '))"/>
            <xsl:with-param name="targets"
              select="concat(' ', normalize-space(concat($sourceReferents,
                                                         $targets)), ' ')"/>
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:template>

  </xsl:stylesheet>

