]> Lady’s Gitweb - LesML/commitdiff
Support attributes
authorLady <redacted>
Sun, 27 Apr 2025 04:31:53 +0000 (00:31 -0400)
committerLady <redacted>
Tue, 31 Mar 2026 02:05:35 +0000 (22:05 -0400)
This required a reworking of the link parsing code to enable it to also
  be used for attribute parsing (more‐or‐less), and maybe also fixed a
  bug where, if there was an end token with no start token, no further
  end tokens would be processed (even later ones they did have start
  tokens).

README.markdown
parser.xslt

index 4c69cdce981e185b8732ef6894d9f1304abbbbcc447b8ad4fafdbf8a0bbb5cfb..00d077bd6934fd3a9384729b073d49c978083c3f972fc10734e1f4d0950ec644 100644 (file)
@@ -193,6 +193,16 @@ Markup within paragraphs is delimited with·out exception by pairs of
     (consisting of `U+034F COMBINING GRAPHEME JOINER` for X·M·L
     compatibility).
 
+- The characters `{@` and `"}` indicate attribute specifications.
+  The attribute specification must contain at least one `="` which
+    separates the key of the attribute from the value.
+  Attributes attach to the previous element or text node, with
+    white·space‐only text nodes after elements ignored; if there is no
+    such previous element or text node, an empty text node is used
+    instead.
+  Multiple attributes can be given in sequence.
+  Text nodes with attributes are wrapped in `<html:span>`.
+
 - The characters `{🔗` and `>}` indicate a hyperlink to a U·R·L
     (`<html:a>`).
   The hyperlink must contain at least one `<`; the content before the
@@ -251,7 +261,9 @@ Finally, any character can be escaped by instead providing its Unicode
   codepoint in the form `{U+NNNN}`, where `NNNN` is one or more
   hexadecimal digits.
 Multiple codepoints may be provided separated by periods, as in
-  `{U+WWWW.ZZZZ}`
+  `{U+WWWW.ZZZZ}`.
+Due to limitations in X·S·L·T, characters cannot be escaped in
+  attributes (including link targets).
 
 ## Usage
 
index 0221e4cfe59419f10491c83a2c180e6baf3423ea2dbde071b39d95ca5889f5f5..fbcfb15ae71ce551504b6d0dcfa2b6208801de37b3d20c06014f197d3bab6378 100644 (file)
@@ -12,6 +12,7 @@ This Source Code Form is subject to the terms of the Mozilla Public License, v 2
 If a copy of the M·P·L was not distributed with this file, You can obtain one at <https://mozilla.org/MPL/2.0/>.
 -->
 <!DOCTYPE transform [
+       <!ENTITY LesML "urn:fdc:ladys.computer:20240512:LesML">
        <!ENTITY section-break "*-.=_~·․‥…⁂⋯─━┄┅┈┉╌╍═╴╶╸╺☙❧ ・*-.=_~">
        <!ENTITY sigiled-text "(string-length($text)=1 or substring($text, 2, 1)=' ')">
        <!ENTITY unsigiled-text "substring($text, 3, string-length($text)-2)">
@@ -21,12 +22,13 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one
        xmlns="http://www.w3.org/1999/XSL/Transform"
        xmlns:LesML="urn:fdc:ladys.computer:20240512:LesML"
        xmlns:exsl="http://exslt.org/common"
+       xmlns:exsldyn="http://exslt.org/dynamic"
        xmlns:exslset="http://exslt.org/sets"
        xmlns:exslstr="http://exslt.org/strings"
        xmlns:html="http://www.w3.org/1999/xhtml"
        xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
        exclude-result-prefixes="LesML"
-       extension-element-prefixes="exsl exslstr"
+       extension-element-prefixes="exsl exsldyn exslset exslstr"
        version="1.0"
 >
        <书社:id>urn:fdc:ladys.computer:20240512:LesML:parser.xslt</书社:id>
@@ -645,6 +647,28 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one
                        </call-template>
                </element>
        </template>
+       <template match="node()" mode="LesML:finalize-attributes">
+               <variable name="notattr" select="following-sibling::node()[not(self::text() and translate(., ' &#x9;', '')='' or self::LesML:attribute)]"/>
+               <for-each select="(.|exslset:leading(following-sibling::node(), $notattr)[self::LesML:attribute])/@*">
+                       <copy-of select="."/>
+                       <if test="local-name()='lang' and namespace-uri()=''">
+                               <attribute name="xml:lang">
+                                       <value-of select="."/>
+                               </attribute>
+                       </if>
+               </for-each>
+       </template>
+       <template match="LesML:attribute[preceding-sibling::node()[position()=1 and (self::text() or self::*)]]|text()[preceding-sibling::node()[position()=1 and self::*] and following-sibling::node()[position()=1 and self::LesML:attribute] and translate(., ' &#x9;', '')='']" mode="LesML:finalize-tree" priority="2"/>
+       <template match="LesML:attribute|text()[following-sibling::node()[position()=1 and self::LesML:attribute]]" mode="LesML:finalize-tree" priority="1">
+               <element name="span" namespace="&xhtml;">
+                       <apply-templates select="." mode="LesML:finalize-attributes"/>
+                       <if test="self::text()">
+                               <call-template name="LesML:break-and-unescape">
+                                       <with-param name="source" select="string(.)"/>
+                               </call-template>
+                       </if>
+               </element>
+       </template>
        <template match="html:blockquote" mode="LesML:finalize-tree">
                <if test="not(preceding-sibling::node()) or preceding-sibling::node()[position()=1 and not(self::html:blockquote)]">
                        <variable name="notquote" select="following-sibling::node()[not(self::html:blockquote)][1]"/>
@@ -737,8 +761,8 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one
                        </apply-templates>
                </if>
        </template>
-       <template match="processing-instruction()[local-name()='LesML-Link-Escape']" mode="LesML:finalize-tree">
-               <text>🔗</text>
+       <template match="processing-instruction()[local-name()='LesML-Token-Escape']" mode="LesML:finalize-tree">
+               <value-of select="."/>
        </template>
        <template match="text()" mode="LesML:finalize-tree">
                <call-template name="LesML:break-and-unescape">
@@ -747,7 +771,8 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one
        </template>
        <template match="@*|node()" mode="LesML:finalize-tree" priority="-1">
                <copy>
-                       <apply-templates select="@*|node()" mode="LesML:finalize-tree"/>
+                       <apply-templates select="." mode="LesML:finalize-attributes"/>
+                       <apply-templates select="node()" mode="LesML:finalize-tree"/>
                </copy>
        </template>
        <template match="node()" mode="LesML:comment">
@@ -841,7 +866,7 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one
                                </otherwise>
                        </choose>
                </variable>
-               <apply-templates select="exsl:node-set($result)/node()" mode="LesML:linkify"/>
+               <apply-templates select="exsl:node-set($result)/node()" mode="LesML:attrify"/>
        </template>
        <template match="node()" mode="LesML:inline">
                <param name="element-name"/>
@@ -954,110 +979,261 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one
                        </otherwise>
                </choose>
        </template>
-       <template match="node()" mode="LesML:linkify">
+       <template match="*" mode="LesML:partition">
+               <param name="start-sigil"/>
+               <param name="end-sigil"/>
+               <param name="separator"/>
+               <param name="ncname-keys" select="false()"/>
+               <variable name="end-node" select="text()[contains(., $end-sigil)][1]"/>
+               <variable name="has-start-node" select="$end-node/preceding-sibling::text()[contains(., $start-sigil) and not(following-sibling::* or following-sibling::comment())] or string-length(substring-after($end-node, $start-sigil))>string-length(substring-after($end-node, $end-sigil))"/>
+               <choose>
+                       <when test="$end-node and $has-start-node">
+                               <variable name="preceding">
+                                       <copy-of select="$end-node/preceding-sibling::node()"/>
+                                       <value-of select="substring-before($end-node, $end-sigil)"/>
+                               </variable>
+                               <variable name="start-node" select="exsl:node-set($preceding)/text()[contains(., $start-sigil) and not(following-sibling::*)][last()]"/>
+                               <variable name="start-tokens-fragment">
+                                       <call-template name="LesML:split">
+                                               <with-param name="source" select="string($start-node)"/>
+                                               <with-param name="separator" select="$start-sigil"/>
+                                       </call-template>
+                               </variable>
+                               <variable name="start-tokens" select="exsl:node-set($start-tokens-fragment)/*"/>
+                               <variable name="innards">
+                                       <value-of select="$start-tokens[last()]"/>
+                                       <for-each select="$start-node/following-sibling::node()">
+                                               <value-of select="."/>
+                                       </for-each>
+                               </variable>
+                               <choose>
+                                       <when test="contains($innards, $separator)">
+                                               <variable name="keyval-fragment">
+                                                       <call-template name="LesML:split">
+                                                               <with-param name="source" select="$innards"/>
+                                                               <with-param name="separator" select="$separator"/>
+                                                       </call-template>
+                                               </variable>
+                                               <variable name="keyval" select="exsl:node-set($keyval-fragment)/*"/>
+                                               <variable name="key">
+                                                       <choose>
+                                                               <when test="$ncname-keys">
+                                                                       <value-of select="$keyval[1]"/>
+                                                               </when>
+                                                               <otherwise>
+                                                                       <for-each select="$keyval[position()!=last()]">
+                                                                               <value-of select="."/>
+                                                                               <if test="position()!=last()">
+                                                                                       <value-of select="$separator"/>
+                                                                               </if>
+                                                                       </for-each>
+                                                               </otherwise>
+                                                       </choose>
+                                               </variable>
+                                               <variable name="value">
+                                                       <choose>
+                                                               <when test="$ncname-keys">
+                                                                       <for-each select="$keyval[position()!=1]">
+                                                                               <value-of select="."/>
+                                                                               <if test="position()!=last()">
+                                                                                       <value-of select="$separator"/>
+                                                                               </if>
+                                                                       </for-each>
+                                                               </when>
+                                                               <otherwise>
+                                                                       <value-of select="$keyval[last()]"/>
+                                                               </otherwise>
+                                                       </choose>
+                                               </variable>
+                                               <choose>
+                                                       <when test="not($ncname-keys) or /self::node()[translate(normalize-space($key), ' /([,*', '')=string($key) and exsldyn:evaluate(concat('not(self::html:', $key, ')'))]">
+                                                               <element name="span" namespace="&xhtml;">
+                                                                       <copy-of select="$start-node/preceding-sibling::node()"/>
+                                                                       <for-each select="$start-tokens[position()!=last()]">
+                                                                               <value-of select="."/>
+                                                                               <if test="position()!=last()">
+                                                                                       <value-of select="$start-sigil"/>
+                                                                               </if>
+                                                                       </for-each>
+                                                               </element>
+                                                               <element name="span" namespace="&xhtml;">
+                                                                       <value-of select="$key"/>
+                                                               </element>
+                                                               <element name="span" namespace="&xhtml;">
+                                                                       <value-of select="$value"/>
+                                                               </element>
+                                                               <element name="span" namespace="&xhtml;">
+                                                                       <value-of select="substring-after($end-node, $end-sigil)"/>
+                                                                       <copy-of select="$end-node/following-sibling::node()"/>
+                                                               </element>
+                                                       </when>
+                                                       <otherwise>
+                                                               <element name="span" namespace="&xhtml;">
+                                                                       <copy-of select="$start-node/preceding-sibling::node()"/>
+                                                                       <for-each select="$start-tokens[position()!=last()]">
+                                                                               <value-of select="."/>
+                                                                               <if test="position()!=last()">
+                                                                                       <value-of select="$start-sigil"/>
+                                                                               </if>
+                                                                       </for-each>
+                                                                       <processing-instruction name="LesML-Token-Escape">
+                                                                               <value-of select="$start-sigil"/>
+                                                                       </processing-instruction>
+                                                                       <value-of select="$start-tokens[last()]"/>
+                                                                       <value-of select="$start-node/following-sibling::node()"/>
+                                                                       <value-of select="$end-sigil"/>
+                                                                       <value-of select="substring-after($end-node, $end-sigil)"/>
+                                                                       <copy-of select="$end-node/following-sibling::node()"/>
+                                                               </element>
+                                                       </otherwise>
+                                               </choose>
+                                       </when>
+                                       <otherwise>
+                                               <element name="span" namespace="&xhtml;">
+                                                       <copy-of select="$start-node/preceding-sibling::node()"/>
+                                                       <for-each select="$start-tokens[position()!=last()]">
+                                                               <value-of select="."/>
+                                                               <if test="position()!=last()">
+                                                                       <value-of select="$start-sigil"/>
+                                                               </if>
+                                                       </for-each>
+                                                       <processing-instruction name="LesML-Token-Escape">
+                                                               <value-of select="$start-sigil"/>
+                                                       </processing-instruction>
+                                                       <value-of select="$start-tokens[last()]"/>
+                                                       <value-of select="$start-node/following-sibling::node()"/>
+                                                       <value-of select="$end-sigil"/>
+                                                       <value-of select="substring-after($end-node, $end-sigil)"/>
+                                                       <copy-of select="$end-node/following-sibling::node()"/>
+                                               </element>
+                                       </otherwise>
+                               </choose>
+                       </when>
+                       <when test="$end-node">
+                               <element name="span" namespace="&xhtml;">
+                                       <copy-of select="$end-node/preceding-sibling::node()"/>
+                                       <value-of select="substring-before($end-node, $end-sigil)"/>
+                                       <processing-instruction name="LesML-Token-Escape">
+                                               <value-of select="$end-sigil"/>
+                                       </processing-instruction>
+                                       <value-of select="substring-after($end-node, $end-sigil)"/>
+                                       <copy-of select="$end-node/following-sibling::node()"/>
+                               </element>
+                       </when>
+                       <otherwise>
+                               <processing-instruction name="LesML-All-Done"/>
+                       </otherwise>
+               </choose>
+       </template>
+       <template match="node()" mode="LesML:attrify">
                <variable name="result">
                        <choose>
                                <when test="self::*">
-                                       <variable name="end-node" select="text()[contains(., '>}')][1]"/>
-                                       <variable name="has-start-node" select="$end-node/preceding-sibling::text()[contains(., '{🔗') and not(following-sibling::*)] or string-length(substring-after($end-node, '{🔗'))>string-length(substring-after($end-node, '>}'))"/>
+                                       <variable name="partitioned-fragment">
+                                               <apply-templates mode="LesML:partition" select=".">
+                                                       <with-param name="start-sigil" select="'{@'"/>
+                                                       <with-param name="end-sigil" select="'&quot;}'"/>
+                                                       <with-param name="separator" select="'=&quot;'"/>
+                                                       <with-param name="ncname-keys" select="true()"/>
+                                               </apply-templates>
+                                       </variable>
+                                       <variable name="partitioned" select="exsl:node-set($partitioned-fragment)/node()"/>
                                        <choose>
-                                               <when test="$end-node and $has-start-node">
-                                                       <variable name="preceding">
-                                                               <copy-of select="$end-node/preceding-sibling::node()"/>
-                                                               <value-of select="substring-before($end-node, '>}')"/>
+                                               <when test="count($partitioned)>1">
+                                                       <variable name="processed">
+                                                               <copy>
+                                                                       <copy-of select="@*"/>
+                                                                       <copy-of select="$partitioned[1]/node()"/>
+                                                                       <element name="LesML:attribute" namespace="&LesML;">
+                                                                               <attribute name="{$partitioned[2]}">
+                                                                                       <value-of select="$partitioned[3]"/>
+                                                                               </attribute>
+                                                                       </element>
+                                                                       <copy-of select="$partitioned[4]/node()"/>
+                                                               </copy>
                                                        </variable>
-                                                       <variable name="start-node" select="exsl:node-set($preceding)/text()[contains(., '{🔗') and not(following-sibling::*)][last()]"/>
-                                                       <variable name="start-tokens-fragment">
-                                                               <call-template name="LesML:split">
-                                                                       <with-param name="source" select="string($start-node)"/>
-                                                                       <with-param name="separator" select="'{🔗'"/>
-                                                               </call-template>
+                                                       <apply-templates select="exsl:node-set($processed)/node()" mode="LesML:attrify"/>
+                                               </when>
+                                               <when test="$partitioned[self::processing-instruction() and local-name()='LesML-All-Done']">
+                                                       <copy>
+                                                               <copy-of select="@*"/>
+                                                               <apply-templates select="node()" mode="LesML:attrify"/>
+                                                       </copy>
+                                               </when>
+                                               <otherwise>
+                                                       <variable name="processed">
+                                                               <copy>
+                                                                       <copy-of select="@*"/>
+                                                                       <copy-of select="$partitioned/node()"/>
+                                                               </copy>
                                                        </variable>
-                                                       <variable name="start-tokens" select="exsl:node-set($start-tokens-fragment)/*"/>
-                                                       <variable name="hyperlink">
-                                                               <value-of select="$start-tokens[last()]"/>
-                                                               <for-each select="$start-node/following-sibling::node()">
-                                                                       <choose>
-                                                                               <when test="self::text()">
-                                                                                       <value-of select="."/>
-                                                                               </when>
-                                                                               <when test="self::processing-instruction()[local-name()='LesML-Link-Escape']">
-                                                                                       <text>🔗</text>
-                                                                               </when>
-                                                                       </choose>
-                                                               </for-each>
+                                                       <apply-templates select="exsl:node-set($processed)/node()" mode="LesML:attrify"/>
+                                               </otherwise>
+                                       </choose>
+                               </when>
+                               <otherwise>
+                                       <copy-of select="."/>
+                               </otherwise>
+                       </choose>
+               </variable>
+               <apply-templates select="exsl:node-set($result)/node()" mode="LesML:linkify"/>
+       </template>
+       <template match="node()" mode="LesML:linkify">
+               <variable name="result">
+                       <choose>
+                               <when test="processing-instruction()[local-name()='LesML-All-Done']">
+                                       <copy>
+                                               <copy-of select="@*|node()[not(self::processing-instruction() and local-name()='LesML-All-Done')]"/>
+                                       </copy>
+                               </when>
+                               <when test="self::*">
+                                       <variable name="partitioned-fragment">
+                                               <apply-templates mode="LesML:partition" select=".">
+                                                       <with-param name="start-sigil" select="'{🔗'"/>
+                                                       <with-param name="end-sigil" select="'>}'"/>
+                                                       <with-param name="separator" select="'&lt;'"/>
+                                               </apply-templates>
+                                       </variable>
+                                       <variable name="partitioned" select="exsl:node-set($partitioned-fragment)/node()"/>
+                                       <choose>
+                                               <when test="count($partitioned)>1">
+                                                       <variable name="processed">
+                                                               <copy>
+                                                                       <copy-of select="@*"/>
+                                                                       <copy-of select="$partitioned[1]/node()"/>
+                                                                       <element name="a" namespace="&xhtml;">
+                                                                               <attribute name="href">
+                                                                                       <value-of select="$partitioned[3]"/>
+                                                                               </attribute>
+                                                                               <processing-instruction name="LesML-All-Done"/>
+                                                                               <choose>
+                                                                                       <when test="string($partitioned[2])=''">
+                                                                                               <value-of select="$partitioned[3]"/>
+                                                                                       </when>
+                                                                                       <otherwise>
+                                                                                               <value-of select="$partitioned[2]"/>
+                                                                                       </otherwise>
+                                                                               </choose>
+                                                                       </element>
+                                                                       <copy-of select="$partitioned[4]/node()"/>
+                                                               </copy>
                                                        </variable>
-                                                       <choose>
-                                                               <when test="contains($hyperlink, '&lt;')">
-                                                                       <variable name="ltcomponents-fragment">
-                                                                               <call-template name="LesML:split">
-                                                                                       <with-param name="source" select="$hyperlink"/>
-                                                                                       <with-param name="separator" select="'&lt;'"/>
-                                                                               </call-template>
-                                                                       </variable>
-                                                                       <variable name="ltcomponents" select="exsl:node-set($ltcomponents-fragment)/*"/>
-                                                                       <variable name="wrapped">
-                                                                               <copy>
-                                                                                       <copy-of select="@*"/>
-                                                                                       <copy-of select="$start-node/preceding-sibling::node()"/>
-                                                                                       <for-each select="$start-tokens[position()!=last()]">
-                                                                                               <value-of select="."/>
-                                                                                               <if test="position()!=last()">
-                                                                                                       <text>{🔗</text>
-                                                                                               </if>
-                                                                                       </for-each>
-                                                                                       <element name="a" namespace="&xhtml;">
-                                                                                               <attribute name="href">
-                                                                                                       <value-of select="$ltcomponents[last()]"/>
-                                                                                               </attribute>
-                                                                                               <choose>
-                                                                                                       <when test="count($ltcomponents)>2 or normalize-space($ltcomponents[1])!=''">
-                                                                                                               <for-each select="$ltcomponents[position()!=last()]">
-                                                                                                                       <value-of select="."/>
-                                                                                                                       <if test="position()!=last()">
-                                                                                                                               <text>&lt;</text>
-                                                                                                                       </if>
-                                                                                                               </for-each>
-                                                                                                       </when>
-                                                                                                       <otherwise>
-                                                                                                               <value-of select="$ltcomponents[last()]"/>
-                                                                                                       </otherwise>
-                                                                                               </choose>
-                                                                                       </element>
-                                                                                       <value-of select="substring-after($end-node, '>}')"/>
-                                                                                       <copy-of select="$end-node/following-sibling::node()"/>
-                                                                               </copy>
-                                                                       </variable>
-                                                                       <apply-templates select="exsl:node-set($wrapped)/*" mode="LesML:linkify"/>
-                                                               </when>
-                                                               <otherwise>
-                                                                       <variable name="escaped">
-                                                                               <copy>
-                                                                                       <copy-of select="@*"/>
-                                                                                       <copy-of select="$start-node/preceding-sibling::node()"/>
-                                                                                       <for-each select="$start-tokens[position()!=last()]">
-                                                                                               <value-of select="."/>
-                                                                                               <if test="position()!=last()">
-                                                                                                       <text>{🔗</text>
-                                                                                               </if>
-                                                                                       </for-each>
-                                                                                       <text>{</text>
-                                                                                       <processing-instruction name="LesML-Link-Escape"/>
-                                                                                       <copy-of select="$hyperlink"/>
-                                                                                       <text>>}</text>
-                                                                                       <value-of select="substring-after($end-node, '>}')"/>
-                                                                                       <copy-of select="$end-node/following-sibling::node()"/>
-                                                                               </copy>
-                                                                       </variable>
-                                                                       <apply-templates select="exsl:node-set($escaped)/*" mode="LesML:linkify"/>
-                                                               </otherwise>
-                                                       </choose>
+                                                       <apply-templates select="exsl:node-set($processed)/node()" mode="LesML:linkify"/>
                                                </when>
-                                               <otherwise>
+                                               <when test="$partitioned[self::processing-instruction() and local-name()='LesML-All-Done']">
                                                        <copy>
                                                                <copy-of select="@*"/>
                                                                <apply-templates select="node()" mode="LesML:linkify"/>
                                                        </copy>
+                                               </when>
+                                               <otherwise>
+                                                       <variable name="processed">
+                                                               <copy>
+                                                                       <copy-of select="@*"/>
+                                                                       <copy-of select="$partitioned/node()"/>
+                                                               </copy>
+                                                       </variable>
+                                                       <apply-templates select="exsl:node-set($processed)/node()" mode="LesML:linkify"/>
                                                </otherwise>
                                        </choose>
                                </when>
This page took 0.120877 seconds and 4 git commands to generate.