From: Lady <redacted>
Date: Sat, 19 Oct 2024 19:30:35 +0000 (-0400)
Subject: Support multiple documents per file
X-Git-Tag: 0.3.0~4
X-Git-Url: https://git.ladys.computer/LesML/commitdiff_plain/80c75b24c821e3d8a1d5d1a460304f8c8d35179c?ds=inline

Support multiple documents per file

Documents may begin with either `#!lesml` or `##`.
---

diff --git a/README.markdown b/README.markdown
index 7df7fde..406f611 100644
--- a/README.markdown
+++ b/README.markdown
@@ -43,6 +43,13 @@ Following the shebang line, document metadata may be provided in the
 The body of the document begins after the last line which begins with
   the string `%%`, or after the shebang line if none exists.
 
+Multiple documents can be catenated into a single file; a new document
+  is begun on any line which starts with `#!lesml` or `##`.
+Documents in the later case inherit the latest preceding `#!lesml`
+  declaration.
+`##` may be followed by other text; this is treated as an interdocument
+  comment.
+
 Documents are broken into paragraphs by blank lines.
 Empty paragraphs are ignored.
 Non·empty paragraphs are classified as follows :⁠—
@@ -55,7 +62,6 @@ Non·empty paragraphs are classified as follows :⁠—
 
   | Character | Codepoint | Unicode Name |
   | --------- | --------- | ------------ |
-  | `#` | `U+0023` | `NUMBER SIGN` |
   | `*` | `U+002A` | `ASTERISK` |
   | `-` | `U+002D` | `HYPHEN-MINUS` |
   | `.` | `U+002E` | `FULL STOP` |
diff --git a/parser.xslt b/parser.xslt
index f1ade04..d35d53b 100644
--- a/parser.xslt
+++ b/parser.xslt
@@ -12,7 +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 section-break '#*-.=_~·․‥…⁂⋯─━┄┅┈┉╌╍═╴╶╸╺☙❧ ・*-.=_~'>
+	<!ENTITY section-break '*-.=_~·․‥…⁂⋯─━┄┅┈┉╌╍═╴╶╸╺☙❧ ・*-.=_~'>
 ]>
 <transform
 	xmlns="http://www.w3.org/1999/XSL/Transform"
@@ -129,7 +129,6 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one
 				<value-of select="$first-line"/>
 			</if>
 		</variable>
-		<variable name="noshebang" select="$lines[position()>1 or not(starts-with(., '#!lesml') or starts-with(., '##'))]"/>
 		<variable name="params-string">
 			<choose>
 				<when test="starts-with($shebang, '#!lesml@')">
@@ -141,120 +140,141 @@ If a copy of the M·P·L was not distributed with this file, You can obtain one
 			</choose>
 		</variable>
 		<variable name="params-fragment">
-			<html:dl>
-				<if test="starts-with($shebang, '#!lesml@') and contains($shebang, '$')">
-					<html:div>
-						<html:dt>
-							<text> LANG </text>
-						</html:dt>
-						<html:dd>
-							<value-of select="substring-before(substring-after($shebang, '#!lesml@'), '$')"/>
-						</html:dd>
-					</html:div>
-				</if>
-				<for-each select="exslstr:tokenize($params-string)">
-					<choose>
-						<when test="contains(., '=')">
+			<choose>
+				<when test="$shebang!=''">
+					<html:dl>
+						<if test="starts-with($shebang, '#!lesml@') and contains($shebang, '$')">
 							<html:div>
 								<html:dt>
-									<value-of select="substring-before(., '=')"/>
+									<text> LANG </text>
 								</html:dt>
 								<html:dd>
-									<value-of select="substring-after(., '=')"/>
+									<value-of select="substring-before(substring-after($shebang, '#!lesml@'), '$')"/>
 								</html:dd>
 							</html:div>
-						</when>
-						<otherwise>
-							<html:div>
-								<html:dt>
-									<value-of select="."/>
-								</html:dt>
-								<html:dd/>
-							</html:div>
-						</otherwise>
-					</choose>
-				</for-each>
-			</html:dl>
+						</if>
+						<for-each select="exslstr:tokenize($params-string)">
+							<choose>
+								<when test="contains(., '=')">
+									<html:div>
+										<html:dt>
+											<value-of select="substring-before(., '=')"/>
+										</html:dt>
+										<html:dd>
+											<value-of select="substring-after(., '=')"/>
+										</html:dd>
+									</html:div>
+								</when>
+								<otherwise>
+									<html:div>
+										<html:dt>
+											<value-of select="."/>
+										</html:dt>
+										<html:dd/>
+									</html:div>
+								</otherwise>
+							</choose>
+						</for-each>
+					</html:dl>
+				</when>
+				<when test="$parent-params">
+					<copy-of select="$parent-params"/>
+				</when>
+				<otherwise>
+					<html:dl/>
+				</otherwise>
+			</choose>
 		</variable>
 		<variable name="params" select="exsl:node-set($params-fragment)/*"/>
-		<variable name="record-separators" select="$noshebang[starts-with(., '%%')]"/>
-		<html:article>
-			<for-each select="$params/html:div/html:dt[string()=' LANG ']">
-				<attribute name="lang">
-					<value-of select="following-sibling::html:dd"/>
-				</attribute>
-				<attribute name="xml:lang">
-					<value-of select="following-sibling::html:dd"/>
-				</attribute>
-			</for-each>
-			<for-each select="$params/html:div/html:dt[string()='profile']">
-				<attribute name="data-lesml-profile">
-					<value-of select="following-sibling::html:dd"/>
-				</attribute>
-			</for-each>
-			<if test="count($record-separators)>1">
-				<html:footer class="head">
-					<for-each select="$record-separators">
-						<variable name="position" select="position()"/>
-						<variable name="prev-separator" select="$record-separators[($position)-1]"/>
-						<variable name="fields" select="$noshebang[following-sibling::*[generate-id()=generate-id(current())] and (not($prev-separator) or preceding-sibling::*[generate-id()=generate-id($prev-separator)])]"/>
-						<if test="$fields">
-							<html:dl>
-								<for-each select="$fields">
-									<choose>
-										<when test="starts-with(., ' ') and $fields[generate-id()=generate-id(current()/preceding-sibling::*[1])]"/>
-										<otherwise>
-											<variable name="next" select="following-sibling::*[not(starts-with(., ' '))]"/>
-											<html:div>
-												<html:dt>
-													<value-of select="normalize-space(substring-before(., ':'))"/>
-												</html:dt>
-												<html:dd>
-													<variable name="firstline">
-														<choose>
-															<when test="contains(., ':')">
-																<value-of select="normalize-space(substring-after(., ':'))"/>
-															</when>
-															<otherwise>
-																<value-of select="normalize-space(.)"/>
-															</otherwise>
-														</choose>
-													</variable>
-													<choose>
-														<when test="substring($firstline, string-length($firstline))='\' and following-sibling::*[position()=1 and starts-with(., ' ')]">
-															<value-of select="substring($firstline, 1, string-length($firstline)-1)"/>
-														</when>
-														<otherwise>
-															<value-of select="$firstline"/>
-														</otherwise>
-													</choose>
-													<for-each select="following-sibling::*[starts-with(., ' ') and not(preceding-sibling::*[generate-id()=generate-id($next)])]">
-														<variable name="nextline" select="normalize-space(.)"/>
+		<variable name="noshebang" select="$lines[position()>1 or not(starts-with(., '#!lesml') or starts-with(., '##'))]"/>
+		<variable name="docsep" select="$noshebang[starts-with(., '#!lesml') or starts-with(., '##')][1]"/>
+		<variable name="doclines" select="$noshebang[not($docsep) or following-sibling::*[generate-id()=generate-id($docsep)]]"/>
+		<if test="$shebang!='' or $doclines[normalize-space()!='']">
+			<variable name="record-separators" select="$doclines[starts-with(., '%%')]"/>
+			<html:article>
+				<for-each select="$params/html:div/html:dt[string()=' LANG ']">
+					<attribute name="lang">
+						<value-of select="following-sibling::html:dd"/>
+					</attribute>
+					<attribute name="xml:lang">
+						<value-of select="following-sibling::html:dd"/>
+					</attribute>
+				</for-each>
+				<for-each select="$params/html:div/html:dt[string()='profile']">
+					<attribute name="data-lesml-profile">
+						<value-of select="following-sibling::html:dd"/>
+					</attribute>
+				</for-each>
+				<if test="$record-separators[preceding-sibling::*[normalize-space()!='']]">
+					<html:footer class="head">
+						<for-each select="$record-separators">
+							<variable name="position" select="position()"/>
+							<variable name="prev-separator" select="$record-separators[($position)-1]"/>
+							<variable name="fields" select="$noshebang[following-sibling::*[generate-id()=generate-id(current())] and (not($prev-separator) or preceding-sibling::*[generate-id()=generate-id($prev-separator)])]"/>
+							<if test="$fields">
+								<html:dl>
+									<for-each select="$fields">
+										<choose>
+											<when test="starts-with(., ' ') and $fields[generate-id()=generate-id(current()/preceding-sibling::*[1])]"/>
+											<otherwise>
+												<variable name="next" select="following-sibling::*[not(starts-with(., ' '))]"/>
+												<html:div>
+													<html:dt>
+														<value-of select="normalize-space(substring-before(., ':'))"/>
+													</html:dt>
+													<html:dd>
+														<variable name="firstline">
+															<choose>
+																<when test="contains(., ':')">
+																	<value-of select="normalize-space(substring-after(., ':'))"/>
+																</when>
+																<otherwise>
+																	<value-of select="normalize-space(.)"/>
+																</otherwise>
+															</choose>
+														</variable>
 														<choose>
-															<when test="substring($nextline, string-length($nextline))='\' and following-sibling::*[position()=1 and starts-with(., ' ')]">
-																<value-of select="substring($nextline, 1, string-length($nextline)-1)"/>
+															<when test="substring($firstline, string-length($firstline))='\' and following-sibling::*[position()=1 and starts-with(., ' ')]">
+																<value-of select="substring($firstline, 1, string-length($firstline)-1)"/>
 															</when>
 															<otherwise>
-																<value-of select="$nextline"/>
+																<value-of select="$firstline"/>
 															</otherwise>
 														</choose>
-													</for-each>
-												</html:dd>
-											</html:div>
-										</otherwise>
-									</choose>
-								</for-each>
-							</html:dl>
-						</if>
-					</for-each>
-				</html:footer>
-			</if>
-			<html:div class="body">
-				<call-template name="LesML:paragraphize">
-					<with-param name="lines" select="$noshebang[not($record-separators) or preceding-sibling::*[generate-id()=generate-id($record-separators[last()])]]"/>
-				</call-template>
-			</html:div>
-		</html:article>
+														<for-each select="following-sibling::*[starts-with(., ' ') and not(preceding-sibling::*[generate-id()=generate-id($next)])]">
+															<variable name="nextline" select="normalize-space(.)"/>
+															<choose>
+																<when test="substring($nextline, string-length($nextline))='\' and following-sibling::*[position()=1 and starts-with(., ' ')]">
+																	<value-of select="substring($nextline, 1, string-length($nextline)-1)"/>
+																</when>
+																<otherwise>
+																	<value-of select="$nextline"/>
+																</otherwise>
+															</choose>
+														</for-each>
+													</html:dd>
+												</html:div>
+											</otherwise>
+										</choose>
+									</for-each>
+								</html:dl>
+							</if>
+						</for-each>
+					</html:footer>
+				</if>
+				<html:div class="body">
+					<call-template name="LesML:paragraphize">
+						<with-param name="lines" select="$doclines[not($record-separators) or preceding-sibling::*[generate-id()=generate-id($record-separators[last()])]]"/>
+					</call-template>
+				</html:div>
+			</html:article>
+		</if>
+		<if test="$docsep">
+			<call-template name="LesML:parse">
+				<with-param name="lines" select="$docsep|$lines[preceding-sibling::*[generate-id()=generate-id($docsep)]]"/>
+				<with-param name="parent-params" select="$params"/>
+			</call-template>
+		</if>
 	</template>
 	<template name="LesML:paragraphize">
 		<param name="lines" select="/.."/>