<?xml version="1.0" encoding="UTF-8"?>
<!--
  doxy2md.xsl
  Converts a single Doxygen compound XML file (kind="group") to Markdown
  for the Zensical / MkDocs documentation site.

  Usage: xsltproc [options] doxy2md.xsl group__X.xml

  The output goes to stdout; run-xslt.sh redirects it to the correct .md file.

  Cross-reference links use the member name as the anchor (e.g. #fluid_synth_noteon)
  rather than the opaque Doxygen refid.  run-xslt.sh passes a pre-built mapping
  via the 'refmap-text' string parameter.

  If refmap-text is empty the stylesheet just code-formats the cross-reference
  name without generating a hyperlink.
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="text" encoding="UTF-8" omit-xml-declaration="yes"/>

  <!-- Strip whitespace-only text nodes from structural elements -->
  <xsl:strip-space elements="doxygen compounddef sectiondef memberdef
                              briefdescription detaileddescription inbodydescription
                              parameterlist parameteritem parameternamelist
                              parameterdescription enumvalue
                              itemizedlist orderedlist listitem
                              programlisting codeline
                              simplesect"/>

  <!-- Preserve whitespace in code elements -->
  <xsl:preserve-space elements="verbatim"/>

  <!--
    refmap-text: newline-separated list of "refid TAB filename#anchor" pairs.
    Generated by run-xslt.sh in a first pass over all group XML files.
  -->
  <xsl:param name="refmap-text" select="''"/>

  <!-- ======================================================================
       Root template
       ====================================================================== -->
  <xsl:template match="/doxygen">
    <xsl:apply-templates select="compounddef[@kind='group' or @kind='page']"/>
  </xsl:template>

  <!-- ======================================================================
       Page (compounddef kind="page") – usage guides and similar narrative docs
       ====================================================================== -->
  <xsl:template match="compounddef[@kind='page']">
    <xsl:text># </xsl:text>
    <xsl:value-of select="title"/>
    <xsl:text>&#xa;&#xa;</xsl:text>

    <xsl:apply-templates select="detaileddescription"/>
  </xsl:template>

  <!-- sect1 → ## heading (used in \page narrative docs) -->
  <xsl:template match="sect1">
    <xsl:text>## </xsl:text>
    <xsl:apply-templates select="title" mode="inline"/>
    <xsl:text>&#xa;&#xa;</xsl:text>
    <xsl:apply-templates select="*[not(self::title)]"/>
  </xsl:template>

  <!-- sect2 → ### heading -->
  <xsl:template match="sect2">
    <xsl:text>### </xsl:text>
    <xsl:apply-templates select="title" mode="inline"/>
    <xsl:text>&#xa;&#xa;</xsl:text>
    <xsl:apply-templates select="*[not(self::title)]"/>
  </xsl:template>

  <!-- sect3 → #### heading -->
  <xsl:template match="sect3">
    <xsl:text>#### </xsl:text>
    <xsl:apply-templates select="title" mode="inline"/>
    <xsl:text>&#xa;&#xa;</xsl:text>
    <xsl:apply-templates select="*[not(self::title)]"/>
  </xsl:template>

  <!-- sect4 → ##### heading -->
  <xsl:template match="sect4">
    <xsl:text>##### </xsl:text>
    <xsl:apply-templates select="title" mode="inline"/>
    <xsl:text>&#xa;&#xa;</xsl:text>
    <xsl:apply-templates select="*[not(self::title)]"/>
  </xsl:template>

  <!-- title element in inline mode: render text content (may contain refs) -->
  <xsl:template match="title" mode="inline">
    <xsl:apply-templates mode="inline"/>
  </xsl:template>

  <!-- Suppress title as a direct child of compounddef (already output by the heading
       at the top of the page/group template via xsl:value-of).  Only matches in
       default (block) mode to avoid hiding refs inside titles that appear elsewhere. -->
  <xsl:template match="compounddef/title"/>

  <!-- ======================================================================
       Group (compounddef)
       ====================================================================== -->
  <xsl:template match="compounddef[@kind='group']">
    <xsl:variable name="title" select="title"/>

    <!-- H1 heading -->
    <xsl:text># </xsl:text>
    <xsl:value-of select="$title"/>
    <xsl:text>&#xa;&#xa;</xsl:text>

    <!-- Brief / detailed description of the group itself -->
    <xsl:apply-templates select="briefdescription"/>
    <xsl:apply-templates select="detaileddescription"/>

    <!-- Subgroups -->
    <xsl:if test="innergroup">
      <xsl:text>## Subgroups&#xa;&#xa;</xsl:text>
      <xsl:for-each select="innergroup">
        <xsl:variable name="sub-refid" select="@refid"/>
        <xsl:variable name="sub-file">
          <xsl:call-template name="refid-to-filename">
            <xsl:with-param name="refid" select="$sub-refid"/>
          </xsl:call-template>
        </xsl:variable>
        <xsl:text>- [</xsl:text>
        <xsl:value-of select="."/>
        <xsl:text>](</xsl:text>
        <xsl:value-of select="$sub-file"/>
        <xsl:text>)&#xa;</xsl:text>
      </xsl:for-each>
      <xsl:text>&#xa;</xsl:text>
    </xsl:if>

    <!-- Sections -->
    <xsl:apply-templates select="sectiondef"/>
  </xsl:template>

  <!-- ======================================================================
       Section definitions
       ====================================================================== -->
  <xsl:template match="sectiondef">
    <xsl:variable name="kind" select="@kind"/>
    <xsl:variable name="heading">
      <xsl:choose>
        <xsl:when test="$kind='func'">Functions</xsl:when>
        <xsl:when test="$kind='typedef'">Types</xsl:when>
        <xsl:when test="$kind='enum'">Enumerations</xsl:when>
        <xsl:when test="$kind='define'">Macros</xsl:when>
        <xsl:when test="$kind='var'">Variables</xsl:when>
        <xsl:when test="$kind='user-defined'">
          <!-- Use the <header> text, strip the Doxygen "_linebr@{" suffix -->
          <xsl:variable name="raw-header" select="normalize-space(header)"/>
          <xsl:choose>
            <xsl:when test="contains($raw-header, '_linebr')">
              <xsl:value-of select="normalize-space(substring-before($raw-header, '_linebr'))"/>
            </xsl:when>
            <xsl:otherwise><xsl:value-of select="$raw-header"/></xsl:otherwise>
          </xsl:choose>
        </xsl:when>
        <xsl:otherwise><xsl:value-of select="$kind"/></xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:if test="memberdef">
      <xsl:text>## </xsl:text>
      <xsl:value-of select="$heading"/>
      <xsl:text>&#xa;&#xa;</xsl:text>
      <xsl:apply-templates select="memberdef"/>
    </xsl:if>
  </xsl:template>

  <!-- ======================================================================
       Function member
       ====================================================================== -->
  <xsl:template match="memberdef[@kind='function']">
    <xsl:variable name="name" select="name"/>
    <!-- Return type: strip FLUIDSYNTH_API / FLUID_DEPRECATED macros -->
    <xsl:variable name="ret-raw">
      <xsl:apply-templates select="type" mode="type-text"/>
    </xsl:variable>

    <xsl:text>### `</xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:text>()` {#</xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:text>}&#xa;&#xa;</xsl:text>

    <!-- C signature -->
    <xsl:text>```c&#xa;</xsl:text>
    <xsl:call-template name="strip-api-macros">
      <xsl:with-param name="text" select="$ret-raw"/>
    </xsl:call-template>
    <xsl:text> </xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:value-of select="argsstring"/>
    <xsl:text>&#xa;```&#xa;&#xa;</xsl:text>

    <!-- Brief description -->
    <xsl:apply-templates select="briefdescription"/>

    <!-- Detailed description -->
    <xsl:apply-templates select="detaileddescription"/>
  </xsl:template>

  <!-- ======================================================================
       Typedef member
       ====================================================================== -->
  <xsl:template match="memberdef[@kind='typedef']">
    <xsl:variable name="name" select="name"/>

    <xsl:text>### `</xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:text>` {#</xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:text>}&#xa;&#xa;</xsl:text>

    <xsl:text>```c&#xa;</xsl:text>
    <xsl:apply-templates select="definition" mode="type-text"/>
    <xsl:text>;&#xa;```&#xa;&#xa;</xsl:text>

    <xsl:apply-templates select="briefdescription"/>
    <xsl:apply-templates select="detaileddescription"/>
  </xsl:template>

  <!-- ======================================================================
       Enum member
       ====================================================================== -->
  <xsl:template match="memberdef[@kind='enum']">
    <xsl:variable name="name" select="name"/>

    <xsl:text>### `</xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:text>` {#</xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:text>}&#xa;&#xa;</xsl:text>

    <xsl:apply-templates select="briefdescription"/>

    <!-- Enum value table -->
    <xsl:if test="enumvalue">
      <xsl:text>| Value | Description |&#xa;</xsl:text>
      <xsl:text>|-------|-------------|&#xa;</xsl:text>
      <xsl:for-each select="enumvalue">
        <xsl:text>| `</xsl:text>
        <xsl:value-of select="name"/>
        <xsl:text>` | </xsl:text>
        <xsl:apply-templates select="briefdescription" mode="inline"/>
        <xsl:apply-templates select="detaileddescription" mode="inline"/>
        <xsl:text> |&#xa;</xsl:text>
      </xsl:for-each>
      <xsl:text>&#xa;</xsl:text>
    </xsl:if>

    <xsl:apply-templates select="detaileddescription"/>
  </xsl:template>

  <!-- ======================================================================
       Define (macro) member
       ====================================================================== -->
  <xsl:template match="memberdef[@kind='define']">
    <xsl:variable name="name" select="name"/>

    <xsl:text>### `</xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:text>` {#</xsl:text>
    <xsl:value-of select="$name"/>
    <xsl:text>}&#xa;&#xa;</xsl:text>

    <xsl:apply-templates select="briefdescription"/>
    <xsl:apply-templates select="detaileddescription"/>
  </xsl:template>

  <!-- ======================================================================
       Description containers (briefdescription / detaileddescription)
       ====================================================================== -->
  <xsl:template match="briefdescription|detaileddescription|inbodydescription">
    <xsl:apply-templates/>
  </xsl:template>

  <!-- Inline mode: apply child templates rather than collapsing to a string.
       This ensures <ref> elements inside brief/detailed descriptions are
       resolved to proper Markdown links when rendered in table cells etc. -->
  <xsl:template match="briefdescription|detaileddescription" mode="inline">
    <xsl:apply-templates mode="inline"/>
  </xsl:template>

  <!-- ======================================================================
       Paragraph
       ====================================================================== -->
  <xsl:template match="para">
    <!-- Check whether this para contains only block-level children that
         should suppress the extra blank line wrapping -->
    <xsl:variable name="has-block"
      select="parameterlist or simplesect or programlisting
              or itemizedlist or orderedlist or verbatim or parblock or xrefsect"/>
    <xsl:choose>
      <xsl:when test="$has-block">
        <xsl:apply-templates/>
        <xsl:text>&#xa;</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>&#xa;</xsl:text>
        <xsl:apply-templates/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- ======================================================================
       Inline markup
       ====================================================================== -->
  <xsl:template match="computeroutput|literal|code">
    <xsl:text>`</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>`</xsl:text>
  </xsl:template>

  <xsl:template match="emphasis">
    <xsl:text>*</xsl:text>
    <xsl:apply-templates/>
    <xsl:text>*</xsl:text>
  </xsl:template>

  <xsl:template match="bold">
    <xsl:text>**</xsl:text>
    <xsl:apply-templates/>
    <xsl:text>**</xsl:text>
  </xsl:template>

  <xsl:template match="ulink">
    <xsl:text>[</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>](</xsl:text>
    <xsl:value-of select="@url"/>
    <xsl:text>)</xsl:text>
  </xsl:template>

  <xsl:template match="sp">
    <xsl:text> </xsl:text>
  </xsl:template>

  <xsl:template match="linebreak">
    <xsl:text>  &#xa;</xsl:text>
  </xsl:template>

  <xsl:template match="ndash">
    <xsl:text>–</xsl:text>
  </xsl:template>

  <xsl:template match="mdash">
    <xsl:text>—</xsl:text>
  </xsl:template>

  <!-- ref in default mode (block/paragraph context) -->
  <xsl:template match="ref">
    <xsl:variable name="refid"  select="@refid"/>
    <xsl:variable name="name"   select="."/>
    <xsl:variable name="refmap-line">
      <xsl:call-template name="lookup-refmap">
        <xsl:with-param name="refid"   select="$refid"/>
        <xsl:with-param name="refmap"  select="$refmap-text"/>
      </xsl:call-template>
    </xsl:variable>
    <!-- Use plain link text for page/compound refs; backtick style for code symbols -->
    <xsl:variable name="is-page" select="@kindref = 'compound' and not(starts-with($refid, 'group__'))"/>
    <xsl:choose>
      <xsl:when test="$refmap-line != ''">
        <xsl:text>[</xsl:text>
        <xsl:if test="not($is-page)"><xsl:text>`</xsl:text></xsl:if>
        <xsl:value-of select="$name"/>
        <xsl:if test="not($is-page)"><xsl:text>`</xsl:text></xsl:if>
        <xsl:text>](</xsl:text>
        <xsl:value-of select="$refmap-line"/>
        <xsl:text>)</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>`</xsl:text>
        <xsl:value-of select="$name"/>
        <xsl:text>`</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- ref in inline mode (e.g. inside simplesect, parameterdescription) -->
  <xsl:template match="ref" mode="inline">
    <xsl:variable name="refid"  select="@refid"/>
    <xsl:variable name="name"   select="."/>
    <xsl:variable name="refmap-line">
      <xsl:call-template name="lookup-refmap">
        <xsl:with-param name="refid"   select="$refid"/>
        <xsl:with-param name="refmap"  select="$refmap-text"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="is-page" select="@kindref = 'compound' and not(starts-with($refid, 'group__'))"/>
    <xsl:choose>
      <xsl:when test="$refmap-line != ''">
        <xsl:text>[</xsl:text>
        <xsl:if test="not($is-page)"><xsl:text>`</xsl:text></xsl:if>
        <xsl:value-of select="$name"/>
        <xsl:if test="not($is-page)"><xsl:text>`</xsl:text></xsl:if>
        <xsl:text>](</xsl:text>
        <xsl:value-of select="$refmap-line"/>
        <xsl:text>)</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>`</xsl:text>
        <xsl:value-of select="$name"/>
        <xsl:text>`</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- ======================================================================
       Parameter list
       ====================================================================== -->
  <xsl:template match="parameterlist[@kind='param']">
    <xsl:text>&#xa;**Parameters:**&#xa;&#xa;</xsl:text>
    <xsl:text>| Name | Description |&#xa;</xsl:text>
    <xsl:text>|------|-------------|&#xa;</xsl:text>
    <xsl:for-each select="parameteritem">
      <xsl:text>| </xsl:text>
      <xsl:for-each select="parameternamelist/parametername">
        <xsl:if test="position() &gt; 1">, </xsl:if>
        <xsl:text>`</xsl:text>
        <xsl:value-of select="."/>
        <xsl:text>`</xsl:text>
      </xsl:for-each>
      <xsl:text> | </xsl:text>
      <xsl:apply-templates select="parameterdescription" mode="inline"/>
      <xsl:text> |&#xa;</xsl:text>
    </xsl:for-each>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>

  <xsl:template match="parameterlist[@kind='retval']">
    <xsl:text>&#xa;**Return values:**&#xa;&#xa;</xsl:text>
    <xsl:text>| Value | Description |&#xa;</xsl:text>
    <xsl:text>|-------|-------------|&#xa;</xsl:text>
    <xsl:for-each select="parameteritem">
      <xsl:text>| </xsl:text>
      <xsl:for-each select="parameternamelist/parametername">
        <xsl:if test="position() &gt; 1">, </xsl:if>
        <xsl:text>`</xsl:text>
        <xsl:value-of select="."/>
        <xsl:text>`</xsl:text>
      </xsl:for-each>
      <xsl:text> | </xsl:text>
      <xsl:apply-templates select="parameterdescription" mode="inline"/>
      <xsl:text> |&#xa;</xsl:text>
    </xsl:for-each>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>

  <!-- Inline rendering of parameterdescription -->
  <xsl:template match="parameterdescription" mode="inline">
    <xsl:apply-templates mode="inline"/>
  </xsl:template>

  <!-- ======================================================================
       simplesect (return, see, note, warning, etc.)
       ====================================================================== -->
  <xsl:template match="simplesect">
    <xsl:variable name="kind" select="@kind"/>
    <xsl:choose>
      <xsl:when test="$kind='return'">
        <xsl:text>&#xa;**Returns:** </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:when test="$kind='see'">
        <xsl:text>&#xa;**See also:** </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:when test="$kind='note'">
        <xsl:text>&#xa;!!! note&#xa;    </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:when test="$kind='warning'">
        <xsl:text>&#xa;!!! warning&#xa;    </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:when test="$kind='important'">
        <xsl:text>&#xa;!!! danger&#xa;    </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:when test="$kind='attention'">
        <xsl:text>&#xa;!!! attention&#xa;    </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:when test="$kind='since'">
        <xsl:text>&#xa;!!! tip "Since"&#xa;    </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:when test="$kind='deprecated'">
        <xsl:text>&#xa;!!! warning "Deprecated"&#xa;    </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:when test="$kind='bug'">
        <xsl:text>&#xa;!!! bug&#xa;    </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>&#xa;**</xsl:text>
        <xsl:value-of select="$kind"/>
        <xsl:text>:** </xsl:text>
        <xsl:apply-templates mode="inline"/>
        <xsl:text>&#xa;&#xa;</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- Inline simplesect (used inside table cells) -->
  <xsl:template match="simplesect" mode="inline">
    <xsl:apply-templates mode="inline"/>
  </xsl:template>

  <!-- Inline para -->
  <xsl:template match="para" mode="inline">
    <xsl:apply-templates mode="inline"/>
    <xsl:text> </xsl:text>
  </xsl:template>

  <!-- Inline computeroutput/code -->
  <xsl:template match="computeroutput|literal|code" mode="inline">
    <xsl:text>`</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>`</xsl:text>
  </xsl:template>

  <!-- Inline ulink -->
  <xsl:template match="ulink" mode="inline">
    <xsl:text>[</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>](</xsl:text>
    <xsl:value-of select="@url"/>
    <xsl:text>)</xsl:text>
  </xsl:template>

  <!-- Inline text nodes: escape [ and ] to prevent spurious Markdown link references -->
  <xsl:template match="text()" mode="inline">
    <xsl:variable name="t1">
      <xsl:call-template name="str-replace">
        <xsl:with-param name="str"     select="."/>
        <xsl:with-param name="search"  select="'['"/>
        <xsl:with-param name="replace" select="'\['"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:call-template name="str-replace">
      <xsl:with-param name="str"     select="$t1"/>
      <xsl:with-param name="search"  select="']'"/>
      <xsl:with-param name="replace" select="'\]'"/>
    </xsl:call-template>
  </xsl:template>

   <!-- programlisting in inline mode (e.g. inside simplesect note/warning):
        handled by the template in the Code blocks section below. -->

  <!-- itemizedlist in inline mode (nested inside a listitem or para) -->
  <xsl:template match="itemizedlist" mode="inline">
    <xsl:text>&#xa;</xsl:text>
    <xsl:for-each select="listitem">
      <xsl:text>    - </xsl:text>
      <xsl:apply-templates mode="inline"/>
      <xsl:if test="position() != last()">
        <xsl:text>&#xa;</xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

  <!-- orderedlist in inline mode (nested inside a listitem or para) -->
  <xsl:template match="orderedlist" mode="inline">
    <xsl:text>&#xa;</xsl:text>
    <xsl:for-each select="listitem">
      <xsl:number/>
      <xsl:text>. </xsl:text>
      <xsl:apply-templates mode="inline"/>
      <xsl:if test="position() != last()">
        <xsl:text>&#xa;</xsl:text>
      </xsl:if>
    </xsl:for-each>
  </xsl:template>

  <!-- parblock: group of paragraphs used by Doxygen for multi-para notes etc.
       In block mode, just process children.  In inline mode, do the same. -->
  <xsl:template match="parblock">
    <xsl:apply-templates/>
  </xsl:template>

  <xsl:template match="parblock" mode="inline">
    <xsl:apply-templates mode="inline"/>
  </xsl:template>

  <!-- ======================================================================
       Lists
       ====================================================================== -->
  <xsl:template match="itemizedlist">
    <xsl:text>&#xa;</xsl:text>
    <xsl:apply-templates/>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>

  <xsl:template match="orderedlist">
    <xsl:text>&#xa;</xsl:text>
    <xsl:apply-templates/>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>

  <xsl:template match="itemizedlist/listitem">
    <xsl:text>- </xsl:text>
    <xsl:apply-templates mode="inline"/>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>

  <xsl:template match="orderedlist/listitem">
    <xsl:number/>
    <xsl:text>. </xsl:text>
    <xsl:apply-templates mode="inline"/>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>

  <!-- ======================================================================
       Code blocks
       ====================================================================== -->

  <!-- Named template: render a single codeline's text (handles <sp/> elements) -->
  <xsl:template name="render-codeline">
    <xsl:for-each select="highlight">
      <xsl:for-each select="node()">
        <xsl:choose>
          <xsl:when test="self::sp">
            <xsl:text> </xsl:text>
          </xsl:when>
          <xsl:when test="self::ref">
            <xsl:value-of select="."/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="."/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>
    </xsl:for-each>
  </xsl:template>

  <!-- Named template: output the fenced-code language tag for a programlisting.
       Outputs "c" for C/C++/header files; empty string otherwise. -->
  <xsl:template name="code-lang">
    <xsl:param name="filename" select="@filename"/>
    <xsl:if test="contains($filename,'.c') or contains($filename,'.cpp') or contains($filename,'.h')">c</xsl:if>
  </xsl:template>

  <xsl:template match="programlisting">
    <xsl:text>&#xa;```</xsl:text>
    <xsl:call-template name="code-lang"/>
    <xsl:text>&#xa;</xsl:text>
    <xsl:for-each select="codeline">
      <xsl:call-template name="render-codeline"/>
      <xsl:text>&#xa;</xsl:text>
    </xsl:for-each>
    <xsl:text>```&#xa;&#xa;</xsl:text>
  </xsl:template>

  <!-- programlisting in inline mode (e.g. inside simplesect note/warning):
       break out of the inline context and render a proper fenced code block. -->
  <xsl:template match="programlisting" mode="inline">
    <xsl:text>&#xa;```</xsl:text>
    <xsl:call-template name="code-lang"/>
    <xsl:text>&#xa;</xsl:text>
    <xsl:for-each select="codeline">
      <xsl:call-template name="render-codeline"/>
      <xsl:text>&#xa;</xsl:text>
    </xsl:for-each>
    <xsl:text>```&#xa;</xsl:text>
  </xsl:template>

  <xsl:template match="verbatim">
    <xsl:text>&#xa;```&#xa;</xsl:text>
    <xsl:value-of select="."/>
    <xsl:text>&#xa;```&#xa;&#xa;</xsl:text>
  </xsl:template>

  <!-- ======================================================================
       type-text mode: extract text content from a <type> element,
       following <ref> children to get their text
       ====================================================================== -->
  <xsl:template match="type|definition" mode="type-text">
    <xsl:for-each select="node()">
      <xsl:choose>
        <xsl:when test="self::ref">
          <xsl:value-of select="."/>
        </xsl:when>
        <xsl:when test="self::text()">
          <xsl:value-of select="."/>
        </xsl:when>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

  <!-- ======================================================================
       strip-api-macros: remove FLUIDSYNTH_API / FLUID_DEPRECATED from text
       ====================================================================== -->
  <xsl:template name="strip-api-macros">
    <xsl:param name="text"/>
    <xsl:variable name="t1">
      <xsl:call-template name="str-replace">
        <xsl:with-param name="str"     select="$text"/>
        <xsl:with-param name="search"  select="'FLUIDSYNTH_API '"/>
        <xsl:with-param name="replace" select="''"/>
      </xsl:call-template>
    </xsl:variable>
    <xsl:call-template name="str-replace">
      <xsl:with-param name="str"     select="$t1"/>
      <xsl:with-param name="search"  select="'FLUID_DEPRECATED '"/>
      <xsl:with-param name="replace" select="''"/>
    </xsl:call-template>
  </xsl:template>

  <!-- ======================================================================
       refid-to-filename: convert a Doxygen group refid to a .md filename
       e.g. "group__midi__messages" → "midi-messages.md"
       ====================================================================== -->
  <xsl:template name="refid-to-filename">
    <xsl:param name="refid"/>
    <!-- Strip leading "group__" -->
    <xsl:variable name="no-prefix">
      <xsl:choose>
        <xsl:when test="starts-with($refid, 'group__')">
          <xsl:value-of select="substring($refid, 8)"/>
        </xsl:when>
        <xsl:otherwise><xsl:value-of select="$refid"/></xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <!-- Replace "__" with "-" -->
    <xsl:call-template name="str-replace">
      <xsl:with-param name="str"    select="$no-prefix"/>
      <xsl:with-param name="search" select="'__'"/>
      <xsl:with-param name="replace" select="'-'"/>
    </xsl:call-template>
    <xsl:text>.md</xsl:text>
  </xsl:template>

  <!-- lookup-refmap: find the link target for a given refid in refmap-text
       Format of refmap-text: "refid|filename#anchor" newline-separated.
       The pipe separator is used instead of TAB because xsltproc strips
       whitespace from stringparam values.
       ====================================================================== -->
  <xsl:template name="lookup-refmap">
    <xsl:param name="refid"/>
    <xsl:param name="refmap"/>
    <xsl:if test="$refmap != ''">
      <xsl:variable name="line">
        <xsl:choose>
          <xsl:when test="contains($refmap, '&#xa;')">
            <xsl:value-of select="substring-before($refmap, '&#xa;')"/>
          </xsl:when>
          <xsl:otherwise><xsl:value-of select="$refmap"/></xsl:otherwise>
        </xsl:choose>
      </xsl:variable>
      <xsl:variable name="line-key"   select="substring-before($line, '|')"/>
      <xsl:variable name="line-value" select="substring-after($line, '|')"/>
      <xsl:choose>
        <xsl:when test="$line-key = $refid">
          <xsl:value-of select="$line-value"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="lookup-refmap">
            <xsl:with-param name="refid"  select="$refid"/>
            <xsl:with-param name="refmap" select="substring-after($refmap, '&#xa;')"/>
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:if>
  </xsl:template>

  <!-- ======================================================================
       str-replace: replace all occurrences of 'search' with 'replace' in 'str'
       ====================================================================== -->
  <xsl:template name="str-replace">
    <xsl:param name="str"/>
    <xsl:param name="search"/>
    <xsl:param name="replace"/>
    <xsl:choose>
      <xsl:when test="contains($str, $search)">
        <xsl:value-of select="substring-before($str, $search)"/>
        <xsl:value-of select="$replace"/>
        <xsl:call-template name="str-replace">
          <xsl:with-param name="str"     select="substring-after($str, $search)"/>
          <xsl:with-param name="search"  select="$search"/>
          <xsl:with-param name="replace" select="$replace"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$str"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <!-- ======================================================================
       Suppress elements that should not appear in the output
       ====================================================================== -->
  <xsl:template match="location|inbodydescription|inheritancegraph|collaborationgraph"/>

  <!-- ======================================================================
       xrefsect: Doxygen @deprecated / @bug / @todo cross-reference sections.
       Rendered as a named admonition.  The xreftitle (e.g. "Deprecated")
       maps to the admonition type used by Zensical.
       ====================================================================== -->
  <xsl:template match="xrefsect">
    <xsl:variable name="title" select="normalize-space(xreftitle)"/>
    <xsl:variable name="admonition-type">
      <xsl:choose>
        <xsl:when test="$title = 'Deprecated'">warning</xsl:when>
        <xsl:when test="$title = 'Bug'">danger</xsl:when>
        <xsl:when test="$title = 'Todo'">note</xsl:when>
        <xsl:otherwise>note</xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <xsl:text>&#xa;!!! </xsl:text>
    <xsl:value-of select="$admonition-type"/>
    <xsl:text> "</xsl:text>
    <xsl:value-of select="$title"/>
    <xsl:text>"&#xa;</xsl:text>
    <!-- Render each paragraph of xrefdescription with 4-space indent -->
    <xsl:for-each select="xrefdescription/para">
      <xsl:text>    </xsl:text>
      <xsl:apply-templates mode="inline"/>
      <xsl:text>&#xa;</xsl:text>
    </xsl:for-each>
    <xsl:text>&#xa;</xsl:text>
  </xsl:template>

  <!-- Suppress the xreftitle (already consumed by xrefsect template above) -->
  <xsl:template match="xreftitle"/>
  <xsl:template match="xreftitle" mode="inline"/>

  <!-- xrefdescription: block container inside xrefsect -->
  <xsl:template match="xrefdescription">
    <xsl:apply-templates/>
  </xsl:template>
  <xsl:template match="xrefdescription" mode="inline">
    <xsl:apply-templates mode="inline"/>
  </xsl:template>

  <!-- Fall-through for unrecognised desc elements: emit their text inline -->
  <xsl:template match="*" mode="inline">
    <xsl:apply-templates mode="inline"/>
  </xsl:template>

</xsl:stylesheet>
