Subject: Moving documentation to XML

From: Luzanov Pavel <[email-masked]>
Date: Thu, 2 Apr 2015 21:22:59 +0000

Peter,

I found this message in archives:

http://www.postgresql.org/message-id/flat/[email-masked][email-masked]

and, as you recommend, tested a speed of building docs on a fresh Ubuntu installation.

50sec for make html
and 14min 50sec for make xslthtml
17 times slower!

Is it still a main stopper for moving to XML?

-----

Pavel Luzanov
[email-masked]

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 06 Apr 2015 16:02:30 -0400

> Is it still a main stopper for moving to XML?

Yes :)

-- 
Sent via pgsql-docs mailing list ([email-masked])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-docs

---

From: Alexander Lakhin <[email-masked]>
Date: Mon, 26 Oct 2015 13:53:15 +0300

Hello, Peter.

I've managed to speed up html generation from xml (make xslthtml) from 
32 min. (in my environment) to 4 min. by modifying slowest XSL templates.
All my modifications incorporated in a single file 
stylesheet-xhtml-speedup.xsl, which is included in stylesheet.xsl.
I performed optimization by analyzing output of:
xsltproc --profile --stringparam pg.version '9.6devel' stylesheet.xsl 
postgres.xml
Initial statistics:
number               match                name      mode Calls Tot 100us Avg

     0             appendix label.markup
23090 90677526   3927
     1              chapter label.markup
28870 39740757   1376
     2 chunk-all-sections             1289 23845066  18498
     3                     make.legalnotice.head.links
2578 9630258   3735
     4            indexterm reference   2579 4126513   1600
     5 html.head             1289 3112534   2414
...
index % time    self  children    called     name
                 0.479 1326.034     22/23090     toc.line [61]
                 5.128 1308.245  21944/23090 sect1[label.markup] [13]
                 3.772 1318.264    850/23090 substitute-markup [15]
                 1.355 1304.631    274/23090 
figure|table|example[label.markup] [32]
[0]    47.95  906.775    1.613  23090 appendix[label.markup] [0]
                 1.613    0.000  23090/23090 autolabel.format [29]
-----------------------------------------------
                 5.128 1308.245  24708/28870 sect1[label.markup] [13]
                 0.479 1326.034    130/28870     toc.line [61]
                 3.772 1318.264   2112/28870 substitute-markup [15]
                 1.355 1304.631   1920/28870 
figure|table|example[label.markup] [32]
[1]    21.01  397.408    1.613  28870 chapter[label.markup] [1]
                 1.613    0.000  28870/28870 autolabel.format [29]
-----------------------------------------------
                 0.164  238.606   1289/1289 process-chunk-element [98]
[2]    12.61  238.451    0.225   1289 chunk-all-sections [2]
                 0.225    7.117   1289/1289 process-chunk [86]
-----------------------------------------------
                31.125  112.261   1289/2578      html.head [5]
                96.303   96.726   1289/2578 make.legalnotice.head.links [3]
[3]     5.09   96.303   96.726   2578 make.legalnotice.head.links [3]
                96.303   96.726   1289/3867 make.legalnotice.head.links [3]
                 0.339    0.494   1289/3867 
*[object.title.markup.textonly] [69]
                 0.085    0.781   1289/3867 ln.or.rh.filename [116]

-----------------------------------------------

Currrent statistics:
number               match                name      mode Calls Tot 100us Avg

     0 chunk-all-sections             1289 5405958   4193
     1                     make.legalnotice.head.links
                                                            1289 
3159538   2451
     2 html.head             1289 3068417   2380
     3                         gentext.template 689835 2327761      3
     4                            l10n.language 564453 1455253      2
     5 href.target            29881 1344063     44
---
index % time    self  children    called     name
                 0.136   54.207   1289/1289 process-chunk-element [95]
[0]    20.40   54.060    0.312   1289 chunk-all-sections [0]
                 0.312    6.468   1289/1289 process-chunk [67]
-----------------------------------------------
                30.684   45.458   1289/1289      html.head [2]
[1]    11.92   31.595    0.448   1289 make.legalnotice.head.links [1]
                 0.290    0.403   1289/2578 
*[object.title.markup.textonly] [71]
                 0.159    0.828   1289/2578 ln.or.rh.filename [91]
-----------------------------------------------
                 0.330   31.617   1289/1289 chunk-element-content [65]
[2]    11.58   30.684   45.458   1289     html.head [2]
                31.595    0.448   1289/15462 make.legalnotice.head.links [1]
                13.441    4.726   5153/15462 href.target [5]
                 0.290    0.403   5153/15462 
*[object.title.markup.textonly] [71]
                 0.115    1.576   1289/15462 head.content [99]
                 0.012    0.000   1289/15462 system.head.content [186]
                 0.006    0.000   1289/15462 user.head.content [228]

To make sure that result of the transformation is the same, I've 
compared original .html's with .html's generated with modified templates.
Unfortunately xslt generates random id's, so it's needed to exclude them 
before comparing. I do that with:
for f in */*.html; do sed -e 
's/id=\"\(ftn\.\)\?id[a-z][0-9]\+\"/id=\"id\"/g' -i $f ; sed -e 
's/href=\"[^#]*#\(ftn\.\)\?id[a-z][0-9]\+\"/href=\"#\"/g' -i $f; done

So if it's acceptable way to speed up generation of HTML (and maybe some 
other formats), what other steps should we take to move away from SGML?
If the performance is still not satisfying, please let me know, I'll 
continue to optimize xslt.
Beside performance issues, I can see some difference in results of 'make 
html' and 'make xslthtml'. For example, see doc/src/sgml/html/spi.html 
(xslt-generated version doesn't contain the lists of functions).

Best regards,
Alexander

>> Is it still a main stopper for moving to XML?

diff --git a/doc/src/sgml/stylesheet-xhtml-speedup.xsl b/doc/src/sgml/stylesheet-xhtml-speedup.xsl
new file mode 100644
index 0000000..d52b48e
--- /dev/null
+++ b/doc/src/sgml/stylesheet-xhtml-speedup.xsl
@@ -0,0 +1,327 @@
+<?xml version="1.0" encoding="utf-8"?>
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                version="1.0"
+                xmlns="http://www.w3.org/1999/xhtml"
+                exclude-result-prefixes="#default">
+
+<!-- common/labels.xsl -->
+<xsl:template match="appendix" mode="label.markup">
+  <xsl:choose>
+    <xsl:when test="@label">
+      <xsl:value-of select="@label"/>
+    </xsl:when>
+    <xsl:when test="string($appendix.autolabel) != 0">
+      <xsl:if test="$component.label.includes.part.label != 0 and
+                      ancestor::part">
+        <xsl:variable name="part.label">
+          <xsl:apply-templates select="ancestor::part" 
+                               mode="label.markup"/>
+        </xsl:variable>
+        <xsl:if test="$part.label != ''">
+          <xsl:value-of select="$part.label"/>
+          <xsl:apply-templates select="ancestor::part" 
+                               mode="intralabel.punctuation"/>
+        </xsl:if>
+      </xsl:if>
+      <xsl:variable name="format">
+        <xsl:call-template name="autolabel.format">
+          <xsl:with-param name="format" select="$appendix.autolabel"/>
+        </xsl:call-template>
+      </xsl:variable>
+        <xsl:choose>
+        <xsl:when test="$label.from.part != 0 and ancestor::part">
+          <xsl:number from="part" count="appendix" format="{$format}" level="any"/>
+        </xsl:when>
+        <xsl:otherwise>
+<!-- speed optimizations for pgsql-docs -->
+          <xsl:number value="count(../preceding-sibling::part/appendix) + count(preceding-sibling::appendix) + 1" format="{$format}" />
+<!--       <xsl:number from="book|article"
+                      count="appendix" format="{$format}" level="any"/> -->
+        </xsl:otherwise>
+      </xsl:choose>
+    </xsl:when>
+  </xsl:choose>
+</xsl:template>
+
+<!-- common/labels.xsl -->
+<xsl:template match="chapter" mode="label.markup">
+  <xsl:choose>
+    <xsl:when test="@label">
+      <xsl:value-of select="@label"/>
+    </xsl:when>
+    <xsl:when test="string($chapter.autolabel) != 0">
+      <xsl:if test="$component.label.includes.part.label != 0 and
+                      ancestor::part">
+        <xsl:variable name="part.label">
+          <xsl:apply-templates select="ancestor::part" 
+                               mode="label.markup"/>
+        </xsl:variable>
+        <xsl:if test="$part.label != ''">
+          <xsl:value-of select="$part.label"/>
+          <xsl:apply-templates select="ancestor::part" 
+                               mode="intralabel.punctuation"/>
+        </xsl:if>
+      </xsl:if>
+      <xsl:variable name="format">
+        <xsl:call-template name="autolabel.format">
+          <xsl:with-param name="format" select="$chapter.autolabel"/>
+        </xsl:call-template>
+      </xsl:variable>
+      <xsl:choose>
+        <xsl:when test="$label.from.part != 0 and ancestor::part">
+          <xsl:number from="part" count="chapter" format="{$format}" level="any"/>
+        </xsl:when>
+        <xsl:otherwise>
+<!-- speed optimizations for pgsql-docs -->
+          <xsl:number value="count(../preceding-sibling::part/chapter) + count(preceding-sibling::chapter) + 1" format="{$format}" />
+<!--          <xsl:number from="book" count="chapter" format="{$format}" level="any"/> -->
+        </xsl:otherwise>
+      </xsl:choose>
+    </xsl:when>
+  </xsl:choose>
+</xsl:template>
+
+<!-- xhtml/autoidx.xsl -->
+<xsl:template match="indexterm" mode="reference">
+  <xsl:param name="scope" select="."/>
+  <xsl:param name="role" select="''"/>
+  <xsl:param name="type" select="''"/>
+  <xsl:param name="position"/>
+  <xsl:param name="separator" select="''"/>
+  
+  <xsl:variable name="term.separator">
+    <xsl:call-template name="index.separator">
+      <xsl:with-param name="key" select="'index.term.separator'"/>
+    </xsl:call-template>
+  </xsl:variable>
+
+  <xsl:variable name="number.separator">
+    <xsl:call-template name="index.separator">
+      <xsl:with-param name="key" select="'index.number.separator'"/>
+    </xsl:call-template>
+  </xsl:variable>
+
+  <xsl:variable name="range.separator">
+    <xsl:call-template name="index.separator">
+      <xsl:with-param name="key" select="'index.range.separator'"/>
+    </xsl:call-template>
+  </xsl:variable>
+
+  <xsl:choose>
+    <xsl:when test="$separator != ''">
+      <xsl:value-of select="$separator"/>
+    </xsl:when>
+    <xsl:when test="$position = 1">
+      <xsl:value-of select="$term.separator"/>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:value-of select="$number.separator"/>
+    </xsl:otherwise>
+  </xsl:choose>
+
+  <xsl:choose>
+    <xsl:when test="@zone and string(@zone)">
+      <xsl:call-template name="reference">
+        <xsl:with-param name="zones" select="normalize-space(@zone)"/>
+        <xsl:with-param name="position" select="position()"/>
+        <xsl:with-param name="scope" select="$scope"/>
+        <xsl:with-param name="role" select="$role"/>
+        <xsl:with-param name="type" select="$type"/>
+      </xsl:call-template>
+    </xsl:when>
+    <xsl:otherwise>
+      <a>
+        <xsl:apply-templates select="." mode="class.attribute"/>
+        <xsl:variable name="title">
+          <xsl:choose>
+            <xsl:when test="$index.prefer.titleabbrev != 0">
+              <xsl:apply-templates select="(ancestor-or-self::set|ancestor-or-self::book|ancestor-or-self::part|ancestor-or-self::reference|ancestor-or-self::partintro|ancestor-or-self::chapter|ancestor-or-self::appendix|ancestor-or-self::preface|ancestor-or-self::article|ancestor-or-self::section|ancestor-or-self::sect1|ancestor-or-self::sect2|ancestor-or-self::sect3|ancestor-or-self::sect4|ancestor-or-self::sect5|ancestor-or-self::refentry|ancestor-or-self::refsect1|ancestor-or-self::refsect2|ancestor-or-self::refsect3|ancestor-or-self::simplesect|ancestor-or-self::bibliography|ancestor-or-self::glossary|ancestor-or-self::index|ancestor-or-self::webpage|ancestor-or-self::topic)[last()]" mode="titleabbrev.markup"/>
+            </xsl:when>
+            <xsl:otherwise>
+              <xsl:apply-templates select="(ancestor-or-self::set|ancestor-or-self::book|ancestor-or-self::part|ancestor-or-self::reference|ancestor-or-self::partintro|ancestor-or-self::chapter|ancestor-or-self::appendix|ancestor-or-self::preface|ancestor-or-self::article|ancestor-or-self::section|ancestor-or-self::sect1|ancestor-or-self::sect2|ancestor-or-self::sect3|ancestor-or-self::sect4|ancestor-or-self::sect5|ancestor-or-self::refentry|ancestor-or-self::refsect1|ancestor-or-self::refsect2|ancestor-or-self::refsect3|ancestor-or-self::simplesect|ancestor-or-self::bibliography|ancestor-or-self::glossary|ancestor-or-self::index|ancestor-or-self::webpage|ancestor-or-self::topic)[last()]" mode="title.markup"/>
+            </xsl:otherwise>
+          </xsl:choose>
+        </xsl:variable>
+
+        <xsl:attribute name="href">
+          <xsl:choose>
+            <xsl:when test="$index.links.to.section = 1">
+              <xsl:call-template name="href.target">
+                <xsl:with-param name="object" select="(ancestor-or-self::set|ancestor-or-self::book|ancestor-or-self::part|ancestor-or-self::reference|ancestor-or-self::partintro|ancestor-or-self::chapter|ancestor-or-self::appendix|ancestor-or-self::preface|ancestor-or-self::article|ancestor-or-self::section|ancestor-or-self::sect1|ancestor-or-self::sect2|ancestor-or-self::sect3|ancestor-or-self::sect4|ancestor-or-self::sect5|ancestor-or-self::refentry|ancestor-or-self::refsect1|ancestor-or-self::refsect2|ancestor-or-self::refsect3|ancestor-or-self::simplesect|ancestor-or-self::bibliography|ancestor-or-self::glossary|ancestor-or-self::index|ancestor-or-self::webpage|ancestor-or-self::topic)[last()]"/>
+<!-- speed optimizations for pgsql-docs -->
+                <xsl:with-param name="context" select="(/book/index[count(ancestor::node()|$scope) = count(ancestor::node()) and ($role = @role or $type = @type or (string-length($role) = 0 and string-length($type) = 0))])[1]"/>
+              </xsl:call-template>
+            </xsl:when>
+            <xsl:otherwise>
+              <xsl:call-template name="href.target">
+                <xsl:with-param name="object" select="."/>
+                <xsl:with-param name="context" select="(//index[count(ancestor::node()|$scope) = count(ancestor::node()) and ($role = @role or $type = @type or (string-length($role) = 0 and string-length($type) = 0))] | //setindex[count(ancestor::node()|$scope) = count(ancestor::node()) and ($role = @role or $type = @type or (string-length($role) = 0 and string-length($type) = 0))])[1]"/>
+              </xsl:call-template>
+            </xsl:otherwise>
+          </xsl:choose>
+
+        </xsl:attribute>
+
+        <xsl:value-of select="$title"/> <!-- text only -->
+      </a>
+
+      <xsl:variable name="id" select="(@id|@xml:id)[1]"/>
+      <xsl:if test="key('endofrange', $id)[count(ancestor::node()|$scope) = count(ancestor::node()) and ($role = @role or $type = @type or (string-length($role) = 0 and string-length($type) = 0))]">
+        <xsl:apply-templates select="key('endofrange', $id)[count(ancestor::node()|$scope) = count(ancestor::node()) and ($role = @role or $type = @type or (string-length($role) = 0 and string-length($type) = 0))][last()]" mode="reference">
+          <xsl:with-param name="position" select="position()"/>
+          <xsl:with-param name="scope" select="$scope"/>
+          <xsl:with-param name="role" select="$role"/>
+          <xsl:with-param name="type" select="$type"/>
+          <xsl:with-param name="separator" select="$range.separator"/>
+        </xsl:apply-templates>
+      </xsl:if>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+<!-- xhtml/chunk-common.xsl -->
+<xsl:template name="chunk-all-sections">
+  <xsl:param name="content">
+    <xsl:apply-imports/>
+  </xsl:param>
+
+<!-- speed optimizations for pgsql-docs -->
+  <xsl:variable name="prev-v1" select="preceding::*[(self::sect1 and $chunk.section.depth &gt; 0) or (self::sect2 and $chunk.section.depth &gt; 1) or (self::sect3 and $chunk.section.depth &gt; 2) or (self::sect4 and $chunk.section.depth &gt; 3) or (self::sect5 and $chunk.section.depth &gt; 4)][1]"/>
+
+  <xsl:variable name="prev-v2" select="(ancestor::sect1[$chunk.section.depth &gt; 0 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |ancestor::sect2[$chunk.section.depth &gt; 1 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |ancestor::sect3[$chunk.section.depth &gt; 2 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |ancestor::sect4[$chunk.section.depth &gt; 3 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |ancestor::sect5[$chunk.section.depth &gt; 4 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |ancestor::section[$chunk.section.depth &gt; count(ancestor::section) and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1])[last()]"/>
+
+  <xsl:variable name="prev" select="(preceding::book[1]              |preceding::preface[1]              |preceding::chapter[1]              |preceding::appendix[1]              |preceding::part[1]              |preceding::reference[1]              |preceding::refentry[1]              |preceding::colophon[1]              |preceding::article[1]              |preceding::topic[1]              |preceding::bibliography[parent::article or parent::book or parent::part][1]              |preceding::glossary[parent::article or parent::book or parent::part][1]              |preceding::index[$generate.index != 0]                                [parent::article or parent::book or parent::part][1]              |preceding::setindex[$generate.index != 0][1]              |ancestor::set              |ancestor::book[1]              |ancestor::preface[1]              |ancestor::chapter[1]              |ancestor::appendix[1]              |ancestor::part[1]              |ancestor::reference[1]              |ancestor::article[1]   |ancestor::topic[1]              |$prev-v1              |$prev-v2)[last()]"/>
+
+<!-- speed optimizations for pgsql-docs -->
+  <xsl:variable name="next-v1" select="following::*[self::sect1 or self::sect2 or self::sect3 or self::sect4 or self::sect5][1]"/>
+
+  <xsl:variable name="next-v2" select="(descendant::sect1[$chunk.section.depth &gt; 0 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |descendant::sect2[$chunk.section.depth &gt; 1 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |descendant::sect3[$chunk.section.depth &gt; 2 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |descendant::sect4[$chunk.section.depth &gt; 3 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |descendant::sect5[$chunk.section.depth &gt; 4 and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1]              |descendant::section[$chunk.section.depth                                    &gt; count(ancestor::section) and not(ancestor::*/processing-instruction('dbhtml')[normalize-space(.) ='stop-chunking'])][1])[1]"/>
+
+  <xsl:variable name="next" select="(following::book[1]              |following::preface[1]              |following::chapter[1]              |following::appendix[1]              |following::part[1]              |following::reference[1]              |following::refentry[1]              |following::colophon[1]              |following::bibliography[parent::article or parent::book or parent::part][1]              |following::glossary[parent::article or parent::book or parent::part][1]              |following::index[$generate.index != 0]                                [parent::article or parent::book][1]              |following::article[1]              |following::topic[1]              |following::setindex[$generate.index != 0][1]              |descendant::book[1]              |descendant::preface[1]              |descendant::chapter[1]              |descendant::appendix[1]              |descendant::article[1]              |descendant::topic[1]              |descendant::bibliography[parent::article or parent::book][1]              |descendant::glossary[parent::article or parent::book or parent::part][1]              |descendant::index[$generate.index != 0]                                [parent::article or parent::book][1]              |descendant::colophon[1]              |descendant::setindex[$generate.index != 0][1]              |descendant::part[1]              |descendant::reference[1]              |descendant::refentry[1]              |$next-v1              |$next-v2)[1]"/>
+
+  
+  <xsl:call-template name="process-chunk">
+    <xsl:with-param name="prev" select="$prev"/>
+    <xsl:with-param name="next" select="$next"/>
+    <xsl:with-param name="content" select="$content"/>
+  </xsl:call-template>
+
+</xsl:template>
+
+
+<xsl:template name="make.legalnotice.head.links">
+  <!-- * the following ID is used as part of the legalnotice filename; -->
+  <!-- * we need it in order to construct the filename for use in the -->
+  <!-- * value of the href attribute on the link -->
+
+  <xsl:param name="ln-node" select="(//legalnotice)[1]"/>
+  
+  <xsl:param name="linktype">
+    <xsl:choose>
+      <xsl:when test="contains($html.head.legalnotice.link.types, ' ')">
+        <xsl:value-of select="normalize-space(                     substring-before($html.head.legalnotice.link.types, ' '))"/>
+      </xsl:when>
+      <xsl:otherwise>
+        <xsl:value-of select="$html.head.legalnotice.link.types"/>
+      </xsl:otherwise>
+    </xsl:choose>
+  </xsl:param>
+  <xsl:param name="remaining.linktypes" select="concat(               normalize-space(               substring-after($html.head.legalnotice.link.types, ' ')),' ')"/>
+  <xsl:if test="not($linktype = '')">
+    <!-- Compute name of legalnotice file (see titlepage.xsl) -->
+    <xsl:variable name="file">
+      <xsl:call-template name="ln.or.rh.filename">
+    <xsl:with-param name="node" select="$ln-node"/>
+      </xsl:call-template>
+    </xsl:variable>
+   
+    <link rel="{$linktype}">
+      <xsl:attribute name="href">
+        <xsl:value-of select="$file"/>
+      </xsl:attribute>
+      <xsl:attribute name="title">
+<!-- speed optimizations for pgsql-docs -->
+        <xsl:apply-templates select="/book/bookinfo/legalnotice" mode="object.title.markup.textonly"/>
+      </xsl:attribute>
+    </link>
+<!-- speed optimizations for pgsql-docs -->
+<!--    <xsl:call-template name="make.legalnotice.head.links">
+      < ! - - * pop the next value off the list of link types - - >
+      <xsl:with-param name="linktype" select="substring-before($remaining.linktypes, ' ')"/>
+      < ! - - * remove the link type from the list of remaining link types - - >
+      <xsl:with-param name="remaining.linktypes" select="substring-after($remaining.linktypes, ' ')"/>
+    </xsl:call-template> -->
+  </xsl:if>
+</xsl:template>
+
+<!-- xhtml/html.xsl (needed for the following "reference" template) -->
+<xsl:template name="id.attribute">
+  <xsl:param name="node" select="."/>
+  <xsl:param name="conditional" select="1"/>
+  <xsl:choose>
+    <xsl:when test="$generate.id.attributes = 0">
+      <!-- No id attributes when this param is zero -->
+    </xsl:when>
+    <xsl:when test="$conditional = 0 or $node/@id or $node/@xml:id">
+      <xsl:attribute name="id">
+        <xsl:call-template name="object.id">
+          <xsl:with-param name="object" select="$node"/>
+        </xsl:call-template>
+      </xsl:attribute>
+    </xsl:when>
+  </xsl:choose>
+</xsl:template>
+
+<!-- xhtml/autoidx.xsl -->
+<xsl:template name="reference">
+  <xsl:param name="scope" select="."/>
+  <xsl:param name="role" select="''"/>
+  <xsl:param name="type" select="''"/>
+  <xsl:param name="zones"/>
+
+  <xsl:choose>
+    <xsl:when test="contains($zones, ' ')">
+      <xsl:variable name="zone" select="substring-before($zones, ' ')"/>
+      <xsl:variable name="target" select="key('sections', $zone)"/>
+
+      <a>
+        <xsl:apply-templates select="." mode="class.attribute"/>
+        <xsl:call-template name="id.attribute"/>
+        <xsl:attribute name="href">
+          <xsl:call-template name="href.target">
+            <xsl:with-param name="object" select="$target[1]"/>
+            <xsl:with-param name="context" select="//index[count(ancestor::node()|$scope) = count(ancestor::node()) and ($role = @role or $type = @type or (string-length($role) = 0 and string-length($type) = 0))][1]"/>
+          </xsl:call-template>
+        </xsl:attribute>
+        <xsl:apply-templates select="$target[1]" mode="index-title-content"/>
+      </a>
+      <xsl:text>, </xsl:text>
+      <xsl:call-template name="reference">
+        <xsl:with-param name="zones" select="substring-after($zones, ' ')"/>
+        <xsl:with-param name="position" select="position()"/>
+        <xsl:with-param name="scope" select="$scope"/>
+        <xsl:with-param name="role" select="$role"/>
+        <xsl:with-param name="type" select="$type"/>
+      </xsl:call-template>
+    </xsl:when>
+    <xsl:otherwise>
+      <xsl:variable name="zone" select="$zones"/>
+      <xsl:variable name="target" select="key('sections', $zone)"/>
+
+      <a>
+        <xsl:apply-templates select="." mode="class.attribute"/>
+        <xsl:call-template name="id.attribute"/>
+        <xsl:attribute name="href">
+          <xsl:call-template name="href.target">
+            <xsl:with-param name="object" select="$target[1]"/>
+<!-- speed optimizations for pgsql-docs -->
+            <xsl:with-param name="context" select="/book/index[count(ancestor::node()|$scope) = count(ancestor::node()) and ($role = @role or $type = @type or (string-length($role) = 0 and string-length($type) = 0))][1]"/>
+          </xsl:call-template>
+        </xsl:attribute>
+        <xsl:apply-templates select="$target[1]" mode="index-title-content"/>
+      </a>
+    </xsl:otherwise>
+  </xsl:choose>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/doc/src/sgml/stylesheet.xsl b/doc/src/sgml/stylesheet.xsl
index 7967b36..8b01f41 100644
--- a/doc/src/sgml/stylesheet.xsl
+++ b/doc/src/sgml/stylesheet.xsl
@@ -6,6 +6,7 @@
 
 <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/xhtml/chunk.xsl"/>
 <xsl:include href="stylesheet-common.xsl" />
+<xsl:include href="stylesheet-xhtml-speedup.xsl" />

 <!-- Parameters -->

---

From: Guillaume Lelarge <[email-masked]>
Date: Wed, 28 Oct 2015 12:46:28 +0100

Le 26 oct. 2015 6:40 PM, "Alexander Lakhin" <[email-masked]> a
écrit :
>
> Hello, Peter.
>
> I've managed to speed up html generation from xml (make xslthtml) from 32
min. (in my environment) to 4 min. by modifying slowest XSL templates.
postgres.xml
>                                                            1289 3159538
 2451
> To make sure that result of the transformation is the same, I've compared
original .html's with .html's generated with modified templates.

What you've done is awesome. I can't wait to test it on the french
translation.

Nice work!

---

From: Oleg Bartunov <[email-masked]>
Date: Tue, 3 Nov 2015 18:46:25 +0100

On Mon, Oct 26, 2015 at 11:53 AM, Alexander Lakhin <[email-masked]>
wrote:

>                 0.339    0.494   1289/3867 *[object.title.markup.textonly]
> [69]
>                 0.290    0.403   1289/2578 *[object.title.markup.textonly]
> [71]

I think this is great result  and it's worth to start moving to xml. I want
to note, that it's 21-th century  and we should think about including
pictures into our documentation, which will greatly improve it. XML makes
this easier.

---

From: Dmitry Igrishin <[email-masked]>
Date: Tue, 3 Nov 2015 23:10:59 +0300

2015-11-03 20:46 GMT+03:00 Oleg Bartunov <[email-masked]>:

>
>
> On Mon, Oct 26, 2015 at 11:53 AM, Alexander Lakhin <
> [email-masked]> wrote:
>
>> Hello, Peter.
>>
> I think this is great result  and it's worth to start moving to xml.
>
I think that moving to XML is step backward, because XML is ugly.

> I want to note, that it's 21-th century  and we should think about
> including pictures into our documentation, which will greatly improve it.
>
Yeah, +1.

> XML makes this easier.
And I think that Lisp is much better for this  puprose.

-- 
// Dmitry.

---

From: Tair Sabirgaliev <[email-masked]>
Date: Wed, 04 Nov 2015 03:15:23 +0000

Then there is asciidoc..