]> Lady’s Gitweb - LesML/commitdiff
Support attributes
authorLady <redacted>
Sun, 27 Apr 2025 04:31:53 +0000 (00:31 -0400)
committerLady <redacted>
Sun, 27 Apr 2025 05:07:28 +0000 (01:07 -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 327f253c5d3c63fdb24c0c14ba3e1607a4d43b1e..11c8c3723b7ca80497e6bff3ac194d8bc663e0ad 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 7e6f6a305701e724cabb2b3c15a231a39f0c06f6..f6b38231a9d7d7f0aac4e5739595ed2342fb3aa1 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.104961 seconds and 4 git commands to generate.