]> Lady’s Gitweb - LesML/blobdiff - xslt/lesml.xslt
Support definition lists (finally!)
[LesML] / xslt / lesml.xslt
index 9a603834c4bbef7c765730fdbce743b28a721df24c006aea7d1ef3110a405e68..61539dda4b77146b7074cf5e04f10cfc247e2be9db5625ab3d703fdf769e11ed 100644 (file)
@@ -1,6 +1,6 @@
 <?xml version="1.0"?>
 <!--
-@(#)💄📝 Les·M·L xslt/lesml.xslt 2026-03-31T01:28:11Z
+@(#)💄📝 Les·M·L xslt/lesml.xslt 2026-03-31T01:31:16Z
 SPDX-FileCopyrightText: 2024, 2025, 2026 Lady <https://www.ladys.computer/about/#lady>
 SPDX-License-Identifier: MPL-2.0
 -->
@@ -21,6 +21,31 @@ This file implements a transformation, via X·S·L·T, from an H·T·M·L
 This forms the “canonical” definition of the Les·M·L syntax;
   no features will be added to the language which are not feasible to
   be implemented here.
+
+§ Implementation
+
+❦ On `LesML-´ processing instructions
+
+Processing instructions which begin with `LesML-´ are used internally
+  for bookkeeping as, unlike attributes, the Les·M·L format provides
+  no means by which authors could generate them.
+All of these instructions should be removed by the end of the
+  transformation, but understanding them is important to understanding
+  how this code operates.
+The processing instructions are as follows :⁠—
+
+• `<?LesML-Footnote?>´ identifies list items which are also footnotes.
+
+• `<?LesML-Level?>´ gives the level of the containing block or
+  paragraph.
+
+❦ Doctype, entity, and namespace definitions
+
+The `&block-types;´ and `&paragraph-types;´ entities provide X·Path
+  tests for determining if the current node is a block or paragraph
+  element.
+The `&level-pi;´ entity provides an X·Path selector for
+  `<?LesML-Level?>´ processing instructions.
 </!-->
 
 <!DOCTYPE transform [
@@ -45,7 +70,28 @@ This forms the “canonical” definition of the Les·M·L syntax;
        version="1.0"
 >
        <书社:id>urn:fdc:ladys.computer:20240512:LesML:parser.xslt</书社:id>
+
+<!-->
+❦ Template parameters
+
+The `LESML_SECTION_BREAK_CHARS´ parameter is provided to allow users to
+  configure which characters are treated as section break characters.
+Changing this value isn¦t recommended, but the option is provided for
+  now.
+</!-->
+
        <param name="LESML_SECTION_BREAK_CHARS" select="'*-.=_~ ·․‥…⁂⁠⋯─━┄┅┈┉╌╍═╴╶╸╺☙❧ ・*-.=_~'"/>
+
+<!-->
+❦ Functions
+
+The `LesML:split´ function returns a nodeset of H·T·M·L `<span>´
+  elements, with each one giving the next item whivh results from
+  splitting the provided source text on the provided separator.
+This is just a convenience wrapper for the `LesML:do-split´ named
+  template.
+</!-->
+
        <exslfunc:function name="LesML:split">
                <param name="source" select="string()"/>
                <param name="separator" select="'&#xA;'"/>
@@ -57,6 +103,22 @@ This forms the “canonical” definition of the Les·M·L syntax;
                </variable>
                <exslfunc:result select="exsl:node-set($result-fragment)/node()"/>
        </exslfunc:function>
+
+<!-->
+❦ Named templates
+
+In contrast to matching templates, named templates generally operate on
+  a single string or a flat list of lines, which are provided as
+  parameters.
+Some of these are small utility templates, whereas others provide the
+  bulk of the block‐level processing.
+
+✠ `LesML:do-split´
+
+The `LesML:do-split´ template provides the internal implementation for
+  the `LesML:split´ function.
+</!-->
+
        <template name="LesML:do-split">
                <param name="source"/>
                <param name="separator" select="'&#xA;'"/>
@@ -77,6 +139,20 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        </otherwise>
                </choose>
        </template>
+
+<!-->
+✠ `LesML:comment-out´
+
+The `LesML:comment-out´ template simply produces an X·M·L comment
+  containing the provided text.
+This is only nontrivial because it has to escape any `{U+2D}{U+2D}´
+  sequences, which are not permitted in X·M·L.
+
+The escaping simply inserts `U+034F COMBINING GRAPHEME JOINER´, which
+  is (per Unicode) intended to be used when disambiguating digraphs
+  from two independent characters in sequence.
+</!-->
+
        <template name="LesML:comment-out">
                <param name="source"/>
                <comment>
@@ -96,6 +172,20 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        </for-each>
                </comment>
        </template>
+
+<!-->
+✠ `LesML:unescape´
+
+The `LesML:unescape´ template takes the provided source text and
+  processes the Unicode character escapes within it.
+The result of this template is raw, unescaped X·M·L, because the only
+  way to implement Unicode character escapes is to convert them to
+  X·M·L entities, which do not have a representation in the X·S·L·T
+  data model.
+Consequently, this template must be among the last ones called and its
+  output cannot be fed back into X·S·L·T safely.
+</!-->
+
        <template name="LesML:unescape">
                <param name="source"/>
                <choose>
@@ -141,6 +231,41 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        </otherwise>
                </choose>
        </template>
+
+<!-->
+✠ `LesML:expand-sigils´
+
+The `LesML:expand-sigils´ template converts a list of block sigils into
+  the resulting H·T·M·L structure.
+It requires a number of parameters :⁠—
+
+• The sigils themselves, as a flat list of nodes.
+
+• The level of the resulting (outermost) block.
+
+• The type of paragraph to be created within the (innermost) block.
+
+• For preformatted code paragraphs, the associated syntax name.
+
+• The lines of content within the paragraph, as a flat list of nodes.
+
+For as long as the sigils are nonempty, the template is simply called
+  again with the level incremented and the outer sigil removed, with
+  the result wrapped appropriately.
+When no sigils remain, an appropriate paragraph element is created,
+  processing as necessary a leading `¶´ only if it appears on the first
+  line.
+
+Paragraphs and comments are “wrapped” in a `<div>´ to enable them to
+  participate in level nesting, and this `<div>´ is assigned their
+  `@id´ and `@lang´.
+They will later be “unwrapped”, and reclaim these attributes, if they
+  are not footnotes or comments and do not have any block children.
+If a block is nested within the wrapping `<div>´ of a paragraph, the
+  wrapping `<div>´ is preserved as the wrapper for both the paragraph
+  and its nested contents.
+</!-->
+
        <template name="LesML:expand-sigils">
                <param name="sigils" select="/.."/>
                <param name="level" select="0"/>
@@ -165,6 +290,12 @@ This forms the “canonical” definition of the Les·M·L syntax;
                                                                </attribute>
                                                        </element>
                                                </when>
+                                               <when test="$sigils[1]='℣'">
+                                                       <element name="dt" namespace="&xhtml;"/>
+                                               </when>
+                                               <when test="$sigils[1]='℟'">
+                                                       <element name="dd" namespace="&xhtml;"/>
+                                               </when>
                                                <when test="$sigils[1]='※'">
                                                        <element name="section" namespace="&xhtml;">
                                                                <attribute name="role">
@@ -424,6 +555,29 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        </otherwise>
                </choose>
        </template>
+
+<!-->
+✠ `LesML:chunk´
+
+The `LesML:chunk´ template converts the provided list of lines into the
+  list of block elements that they represent.
+This comprises the following steps :⁠—
+
+• Discovering the “last” lines of each block, which are nonempty lines
+  that are not followed by a nonempty line.
+
+• Collecting all of the lines in the block; i·e all lines since the
+  previous “last” line that are not empty.
+
+• Determining the level of the block and its sigils.
+
+• Passing the lines of the block, with any leading sigils removed, to
+  `LesML:expand-sigils´ to generate the actual block element.
+This also requires processing any paragraph‐type indicators and
+  stripping them if necessary to get the type of the underlying
+  paragraph.
+</!-->
+
        <template name="LesML:chunk">
                <param name="lines" select="/.."/>
                <variable name="last-lines" select="$lines[normalize-space()!='' and (normalize-space(following-sibling::*[1])='' or position()=last())]"/>
@@ -477,7 +631,7 @@ This forms the “canonical” definition of the Les·M·L syntax;
                                        </otherwise>
                                </choose>
                        </variable>
-                       <variable name="firstnosigil" select="substring(translate($nobullet, '•№※⯑∫☡⚠🛈💡»∎', ''), 1, 1)"/>
+                       <variable name="firstnosigil" select="substring(translate($nobullet, '•№※⯑∫☡⚠🛈💡»∎℣℟', ''), 1, 1)"/>
                        <variable name="sigilling">
                                <choose>
                                        <when test="$firstnosigil!=''">
@@ -572,6 +726,19 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        </call-template>
                </for-each>
        </template>
+
+<!-->
+✠ `LesML:paragraphize´
+
+The `LesML:paragraphize´ template first turns the provided lines into
+  blocks via `LesML:chunk´, arranges those blocks by applying templates
+  in the `LesML:blockify´ mode, then processes the inline contents of
+  those blocks by applying templates in the `LesML:inlinify´ mode.
+This does much of the processing for the body of a Les·M·L document,
+  altho some things, like footnote handling, can¦t be fully finalized
+  until later.
+</!-->
+
        <template name="LesML:paragraphize">
                <param name="lines" select="/.."/>
                <variable name="chunked-fragment">
@@ -581,6 +748,18 @@ This forms the “canonical” definition of the Les·M·L syntax;
                </variable>
                <apply-templates select="exsl:node-set($chunked-fragment)" mode="LesML:blockify"/>
        </template>
+
+<!-->
+✠ `LesML:parse´
+
+The `LesML:parse´ template is the main entrypoint for Les·M·L handling,
+  taking a list of input lines and returning a series of resulting
+  Les·M·L documents.
+This template operates by outputting the result of the first document
+  in the provided lines, and then calling itself recursively with any
+  remaining documents until the lines are exhausted.
+</!-->
+
        <template name="LesML:parse">
                <param name="lines" select="/.."/>
                <param name="parent-params" select="/.."/>
@@ -817,6 +996,15 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        </call-template>
                </if>
        </template>
+
+<!-->
+❦ Templates in the default mode
+
+This file provides a single template in the default mode: one which
+  matches H·T·M·L `<script>´ elements with a `@type="text/lesml"´ and
+  passes the contents thru to `LesML:parse´.
+</!-->
+
        <template match="html:script[@type='text/lesml']">
                <variable name="source">
                        <for-each select=".//text()">
@@ -829,6 +1017,17 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        </call-template>
                </element>
        </template>
+
+<!-->
+❦ Templates in the `LesML:blockify´ mode
+
+There is only one template in the `LesML:blockify´ mode and it can only
+  process an entire document at a time.
+It successively applies templates in the `LesML:nesting-blocks´ and
+  `LesML:inlinify´ modes to convert the relatively flat structure
+  produced by chunking into approximately the final tree.
+</!-->
+
        <template match="/" mode="LesML:blockify">
                <variable name="nested-fragment">
                        <apply-templates mode="LesML:nesting-blocks"/>
@@ -838,6 +1037,19 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        <with-param name="footnote-ids" select="$nested-document/*/processing-instruction()[local-name()='LesML-Footnote']"/>
                </apply-templates>
        </template>
+
+<!-->
+❦ Templates in the `LesML:nesting-blocks´ mode
+
+For any given block element, this template moves any following elements
+  with a higher level inside and then reprocesses the resulting
+  children.
+Elements which are preceded by elements of a lower level are assumed to
+  be nested and are not rendered.
+(At the time that these templates run, block elements can only have
+  block siblings.)
+</!-->
+
        <template match="*[&level-pi;]" mode="LesML:nesting-blocks">
                <variable name="current-level" select="number(&level-pi;)"/>
                <if test="not(preceding-sibling::*[&level-pi; and $current-level>&level-pi;])">
@@ -858,6 +1070,14 @@ This forms the “canonical” definition of the Les·M·L syntax;
                        <apply-templates select="node()" mode="LesML:nesting-blocks"/>
                </copy>
        </template>
+
+<!-->
+❦ Templates in the `LesML:inlinify´ and related modes
+
+These modes break up the lines of a paragraph into constituent inline
+  nodes.
+</!-->
+
        <template match="*[&level-pi;]" mode="LesML:inlinify">
                <param name="footnote-ids" select="/.."/>
                <copy>
@@ -1620,6 +1840,40 @@ These templates finalize the resulting tree.
                        </attribute>
                </if>
        </template>
+       <template match="html:dt|html:dd" mode="LesML:finalize-list">
+               <param name="used-footnotes" select="/.."/>
+               <variable name="current-level" select="number(&level-pi;)"/>
+               <variable name="notinlist" select="following-sibling::node()[not((self::html:dt or self::html:dd) and &level-pi;=$current-level)][1]"/>
+               <variable name="inlist" select=".|exslset:leading(following-sibling::node(), $notinlist)"/>
+               <variable name="lasts-in-pairing" select="$inlist[self::html:dd and not(exslset:intersection(following-sibling::node()[1]/self::html:dd, $inlist))]|$inlist[last()]"/>
+               <element name="dl" namespace="&xhtml;">
+                       <for-each select="$lasts-in-pairing">
+                               <variable name="prev-position" select="position()-1"/>
+                               <variable name="prev-last" select="$lasts-in-pairing[$prev-position]"/>
+                               <element name="div" namespace="&xhtml;">
+                                       <for-each select="exslset:leading(exslset:trailing($inlist, $prev-last), .)|.">
+                                               <if test="position()=1 and self::html:dd">
+                                                       <element name="dt" namespace="&xhtml;"/>
+                                               </if>
+                                               <copy>
+                                                       <apply-templates select="." mode="LesML:finalize-attributes"/>
+                                                       <apply-templates select="node()" mode="LesML:finalize">
+                                                               <with-param name="used-footnotes" select="$used-footnotes"/>
+                                                       </apply-templates>
+                                               </copy>
+                                               <if test="position()=last() and self::html:dt">
+                                                       <element name="dd" namespace="&xhtml;"/>
+                                               </if>
+                                       </for-each>
+                               </element>
+                       </for-each>
+               </element>
+               <if test="$notinlist/self::html:dt or $notinlist/self::html:dd">
+                       <apply-templates select="$notinlist" mode="LesML:finalize-list">
+                               <with-param name="used-footnotes" select="$used-footnotes"/>
+                       </apply-templates>
+               </if>
+       </template>
        <template match="html:li" mode="LesML:finalize-list">
                <param name="used-footnotes" select="/.."/>
                <variable name="current-class" select="string(@class)"/>
@@ -1729,6 +1983,14 @@ These templates finalize the resulting tree.
                        </apply-templates>
                </element>
        </template>
+       <template match="html:dt|html:dd" mode="LesML:finalize">
+               <param name="used-footnotes" select="/.."/>
+               <if test="not(preceding-sibling::node()) or preceding-sibling::node()[position()=1 and not(self::html:dt or self::html:dd)]">
+                       <apply-templates select="." mode="LesML:finalize-list">
+                               <with-param name="used-footnotes" select="$used-footnotes"/>
+                       </apply-templates>
+               </if>
+       </template>
        <template match="html:li" mode="LesML:finalize">
                <param name="used-footnotes" select="/.."/>
                <if test="not(preceding-sibling::node()) or preceding-sibling::node()[position()=1 and not(self::html:li)]">
This page took 0.311932 seconds and 4 git commands to generate.