]> Lady’s Gitweb - Shushe/commitdiff
Initial commit; minimal working implementation 0.1.0
authorLady <redacted>
Mon, 1 Jan 2024 04:07:41 +0000 (23:07 -0500)
committerLady <redacted>
Tue, 2 Jan 2024 18:26:12 +0000 (13:26 -0500)
16 files changed:
.gitignore [new file with mode: 0644]
COPYING [new file with mode: 0644]
GNUmakefile [new file with mode: 0644]
README.markdown [new file with mode: 0644]
lib/catalog2dependencies.xslt [new file with mode: 0644]
lib/catalog2parser.xslt [new file with mode: 0644]
lib/catalog2transform.xslt [new file with mode: 0644]
lib/parser2types.xslt [new file with mode: 0644]
magic/css [new file with mode: 0644]
magic/js [new file with mode: 0644]
magic/tsv [new file with mode: 0644]
magic/xml [new file with mode: 0644]
parsers/plain.xslt [new file with mode: 0644]
parsers/tsv.xslt [new file with mode: 0644]
transforms/asset.xslt [new file with mode: 0644]
transforms/metadata.xslt [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..33687b1
--- /dev/null
@@ -0,0 +1,2 @@
+/build
+/public
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..a612ad9
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/GNUmakefile b/GNUmakefile
new file mode 100644 (file)
index 0000000..fe0cbd5
--- /dev/null
@@ -0,0 +1,434 @@
+SHELL = /bin/sh
+
+# ━ § BASIC INFORMATION ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+override define makefileinfo
+ ╭────────────────────────────╮
+╔╡ ⁌ ⛩️📰 书社 ∷ GNUmakefile ╞════════════════════════════════╗
+║╰────────────────────────────╯                                ║
+╟┬ ¶ Prerequisites ───────────────────────────────────────────┬╢
+║│                                                            │║
+║│ Requires G·N·U Make, at least version 3.81, a version of   │║
+║│ uuencode with base64 support, and the various programs     │║
+║│ offered by libxml2 and libxslt. Beyond this, only programs │║
+║│ required by Posix are used, altho there is a chance of     │║
+║│ version incompatibilities. The full list of program        │║
+║│ requirements is as follows :—                             │║
+║│                                                            │║
+║│ • cat                                                      │║
+║│ • cp                                                       │║
+║│ • echo                                                     │║
+║│ • file                                                     │║
+║│ • find                                                     │║
+║│ • mkdir (requires support for `-p´)                        │║
+║│ • mv                                                       │║
+║│ • printf                                                   │║
+║│ • sed                                                      │║
+║│ • test                                                     │║
+║│ • touch                                                    │║
+║│ • tr (requires support for `-d´)                           │║
+║│ • uuencode (requires support for `-m´ and `-r´)            │║
+║│ • xmlcatalog (provided by libxml2)                         │║
+║│ • xmllint (provided by libxml2)                            │║
+║│ • xsltproc (provided by libxslt)                           │║
+║│                                                            │║
+║│ In all cases, you can supply your own version of any       │║
+║│ program `program´ by overriding the corresponding variable │║
+║│ `PROGRAM´ when calling Make.                               │║
+║╰────────────────────────────────────────────────────────────╯║
+╟┬ ¶ Usage ───────────────────────────────────────────────────┬╢
+║│                                                            │║
+║│ • `make all´: Compile, but do not install, all files.      │║
+║│                                                            │║
+║│ • `make clean´: Remove `BUILDDIR´.                         │║
+║│                                                            │║
+║│ • `make gone´: Remove installed files.                     │║
+║│                                                            │║
+║│ • `make help´ (default): Print this message.               │║
+║│                                                            │║
+║│ • `make install´: Compile all files and install in         │║
+║│   `DESTDIR´.                                               │║
+║│                                                            │║
+║│ • `make list´: List all recognized source files and their  │║
+║│   classification (including media type and dependencies).  │║
+║│                                                            │║
+║│ Set `VERBOSE=1´ to see the text of commands as they are    │║
+║│ executed.                                                  │║
+║│                                                            │║
+║│ See `README.markdown´ for a more involved description of   │║
+║│ the capabilities and configuration of this program.        │║
+║╰────────────────────────────────────────────────────────────╯║
+╟┬ ¶ License ─────────────────────────────────────────────────┬╢
+║│ This Source Code Form is subject to the terms of the       │║
+║│ Mozilla Public License, v 2.0. If a copy of the M·P·L was  │║
+║│ not distributed with this file, You can obtain one at      │║
+║│ <http://mozilla.org/MPL/2.0/>.                             │║
+║╰────────────────────────────────────────────────────────────╯║
+╚══════════════════════════════════════════════════════════════╝
+endef
+
+# ━ § MAKE·FILE SETUP ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+# Programs needed to run this make·file.
+#
+# If these are not installed on your computer, or you need to use a
+# different implementation, you can override the appropriate variable.
+CAT := cat
+CP := cp
+ECHO := echo
+FILE := file
+FIND := find
+MKDIR := mkdir
+MV := mv
+PRINTF := printf
+RM := rm
+SED := sed
+TEST := test
+TOUCH := touch
+TR := tr
+UUENCODE := uuencode
+XMLCATALOG := xmlcatalog
+XMLLINT := xmllint
+XSLTPROC := xsltproc
+
+# The directory which contains the source files.
+SRCDIR := sources
+
+# The directory which contains “includes”: Files which may be included
+# in other files but for which no final output will be generated.
+#
+# This can be inside of `SOURCES_DIRECTORY´ if desired.
+INCLUDEDIR := sources/includes
+
+# The directory in which to generate temporary buildfiles.
+BUILDDIR := build
+
+# The directory into which to output files on `make install´.
+DESTDIR := public
+
+# The location of this Makefile (and related ⛩️📰 书社 files),
+# relative to the current working directory.
+#
+# By default, this is inferred from the variable `MAKEFILE_LIST´.
+THISDIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+
+# The location of the magic files to use when determining media types.
+#
+# One is provided as part of this repository, but you can override it
+# if you need different media type detection.
+#
+# Your computer probably has a more comprehensive one installed at
+# `/usr/share/file/magic´, but it is not recommended that you use this
+# directly. Instead, link or copy just the files you expect to need for
+# your project.
+MAGICDIR := $(patsubst ./%,%,$(THISDIR)/magic)
+
+# Configuration of `find´.
+#
+# By default, `find´ will follow symlinks and use extended regular
+# expressions, ignoring hidden files and those which begin with a
+# period.
+FINDOPTS := -LE
+FINDRULES := -flags -nohidden -and -not -name '.*'
+
+# The list of parsers for plaintext file types.
+#
+# Which parsers are provided will influence which kinds of files are
+# recognized as plaintext.
+#
+# Each parser ⁜must⁜ have a template which matches ⁜only⁜ X·H·T·M·L
+# `<script>´ elements that have a `@type´ of a plaintext type supported
+# by the parser. They may have multiple.
+PARSERS := $(patsubst ./%,%,$(wildcard $(THISDIR)/parsers/*.xslt))
+
+# The list of transforms.
+TRANSFORMS := $(patsubst ./%,%,$(wildcard $(THISDIR)/transforms/*.xslt))
+
+# List of types which should be treated as X·M·L.
+XMLTYPES := application/xml text/xml
+
+# Set to a non·empty value to print all commands as they run.
+VERBOSE :=
+
+# The default target for this makefile.
+#
+# Print help and exit.
+.DEFAULT_GOAL := help
+
+# ━ § BEGIN MAKE·FILE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+# ─ ¶ Non‐Recipe Variable Definitions ─────────────────────────────────
+
+# A variable with no value, usable when assigning values which contain
+# whitespace.
+override empty :=
+
+# A variable which contains a newline, to allow the generation of
+# multiline strings in function calls.
+override define newline
+
+
+endef
+
+# A variable which contains a single space.
+override space := $(empty) $(empty)
+
+# A variable which contains a single comma.
+override comma := ,
+
+# (callable) Quote the given string for use within shell calls.
+override quote = '$(subst ','"'"',$1)'
+
+# Outputs an `@´ to silence rules, unless `VERBOSE´ is nonempty.
+override silent := $(if $(VERBOSE),,@)
+
+# (callable) Escape special characters for use in sed regular
+# expressions.
+override sedesc = $(subst $$,\$$,$(subst *,\*,$(subst .,\.,$(subst [,\[,$(subst ^,\^,$(subst \,\\,$1))))))
+
+# Collect all of the applicable includes from the includes directory.
+sourceincludes := $(shell $(FIND) $(FINDOPTS) $(INCLUDEDIR) -type f '(' $(FINDRULES) ')')
+
+# Collect all of the applicable source files from the source directory,
+# removing any which are also includes.
+sourcefiles := $(filter-out $(sourceincludes),$(shell $(FIND) $(FINDOPTS) $(SRCDIR) -type f '(' $(FINDRULES) ')'))
+
+# Figure out the file type of each source file and source include.
+ifneq ($(wildcard $(BUILDDIR)/magic.mgc),)
+override types := $(shell $(SED) 's/^ *//;s/ *$$//;s/ {2,}/ /g' <<< $(call quote,$(sourcefiles) $(sourceincludes)) | $(TR) ' ' '\n' | $(FILE) -m $(call quote,$(BUILDDIR)/magic.mgc) --mime-type --separator '`' --files-from - | $(SED) 's/` */?type=/g')
+endif
+
+# Get the list of supported plaintext file types from the parser.
+ifneq ($(wildcard $(BUILDDIR)/parser.xslt),)
+override plaintexttypes := $(shell $(XSLTPROC) $(call quote,$(THISDIR)/lib/parser2types.xslt) $(call quote,$(BUILDDIR)/parser.xslt))
+endif
+
+# Simplify the file type by only taking the first component (image,
+# text, ⁊·c).
+override simpletypes := $(shell $(TR) ' ' '\n' <<< $(call quote,$(types)) | $(SED) 's`/[^/]*$$``g')
+
+# (callable) Get all of the files (source and includes) which have the
+# given types.
+override filesoftype = $(foreach type,$1,$(patsubst %?type=$(type),%,$(filter %?type=$(type),$(types))))
+
+# Build up collections of various file types.
+override plaintextfiles := $(call filesoftype,$(plaintexttypes))
+override xmlfiles := $(filter-out $(plaintextfiles),$(call filesoftype,$(XMLTYPES)))
+override assetfiles := $(filter-out $(xmlfiles) $(plaintextfiles),$(sourcefiles) $(sourceincludes))
+
+# (callable) Get the types of the given files.
+override typeoffile = $(patsubst $(foreach file,$1,$(file)?type=%),%,$(filter $(foreach file,$1,$(file)?type=%),$(types)))
+
+# (callable) Get base64 data u·r·i’s for the given files.
+override datauri = $(foreach file,$1,data:$(call typeoffile,$(file));base64,$(shell $(UUENCODE) -m -r $(call quote,$(file)) _ | tr -d ' \n'))
+
+# (callable) Get local leiris for the given files.
+override localuri = $(foreach file,$1,$(if $(filter $(file),$(sourceincludes)),$(patsubst $(INCLUDEDIR)/%,about:shushe?include=%,$(file)),$(patsubst $(SRCDIR)/%,about:shushe?source=%,$(file))))
+
+# (callable) Get the source files for the given local leiris.
+override sourcefile = $(foreach file,$1,$(if $(filter about:shushe?include=%,$(file)),$(patsubst about:shushe?include=%,$(INCLUDEDIR)/%,$(file)),$(patsubst about:shushe?source=%,$(SRCDIR)/%,$(file))))
+
+# Adds a requirement on `$(BUILDDIR)/.update-types´ if the file is
+# present.
+#
+# This file is created after a reload due to type changes, and is
+# removed after. Requiring it ensures that file classifications are
+# up‐to‐date immediately after the reload.
+override typeupdates := $(if $(wildcard $(BUILDDIR)/.update-types),$(BUILDDIR)/.update-types,)
+
+# (callable) Get the location of the transformed X·M·L files for the
+# given source files.
+override parsed = $(foreach file,$1,$(if $(filter $(file),$(sourceincludes)),$(patsubst $(INCLUDEDIR)/%,$(BUILDDIR)/includes/%,$(file)),$(patsubst $(SRCDIR)/%,$(BUILDDIR)/sources/%,$(file))))
+
+ifneq ($(wildcard $(BUILDDIR)/dependencies),)
+# Pair each file with a list of dependencies for it.
+override dependenciesforfile := $(foreach file,$(sourcefiles),$(file)`$(subst $(space),`,$(shell $(CAT) $(call quote,$(BUILDDIR)/dependencies) | $(SED) $(call quote,/^$(call sedesc,$(call localuri,$(file)))$$/$(comma)/^[^  ]/!d;/^ /!d;s/^ //))))
+
+# (callable) Get the list of dependency leiris for the given source
+# files.
+#
+# Recursive dependencies are marked with a leading `!´.
+override dependencyuris = $(foreach file,$1,$(subst `, ,$(patsubst $(file)`%,%,$(filter $(file)`%,$(dependenciesforfile)))))
+
+# (callable) Get the list of recursive dependencies for the given
+# source files.
+override recursives = $(foreach uri,$(filter !%,$(call dependencyuris,$1)),$(call sourcefile,$(patsubst !%,%,$(uri))))
+
+# (callable) Get the list of (nonrecursive) dependencies for the given
+# source files.
+override dependencies = $(foreach uri,$(filter-out !%,$(call dependencyuris,$1)),$(call sourcefile,$(uri)))
+endif
+
+# Collect all files with recursive dependencies.
+override recursivefiles := $(foreach file,$(filter-out $(assetfiles),$(sourcefiles)),$(if $(call recursives,$(file)),$(file),))
+
+# Collect all files which should be compiled.
+#
+# This is all of the non·asset, nonrecursive files.
+override compilablefiles := $(filter-out $(assetfiles) $(recursivefiles),$(sourcefiles))
+
+# (callable) Get the compiled locations for the given source files.
+override compiled = $(patsubst $(SRCDIR)/%,$(BUILDDIR)/public/%,$(1))
+
+# (callable) Get the installed locations for the given source files.
+override installed = $(patsubst $(SRCDIR)/%,$(DESTDIR)/%,$(1))
+
+# ─ ¶ Recipe Variable Definitions ─────────────────────────────────────
+
+# (callable) Check to see if the given directory exists and
+# create it if not.
+override ensuredirectory = if $(TEST) ! -d $(call quote,$1); then $(MKDIR) -p $(call quote,$1); fi
+
+# (callable) Sanitize and wrap the provided plaintext file in
+#  X·M·L, printing to `stdout´.
+override wrapplaintext = $(PRINTF) '%s\n' "$$($(PRINTF) '%b' '<?xml version=\042\061.0\042?>\n<script xmlns=\042http://www.w3.org/1999/xhtml\042 type=\042$(patsubst $1?type=%,%,$(filter $1?type=%,$(types)))\042><![CDATA['; $(CAT) $(call quote,$1) | $(TR) '\000\013\014' '\032\011\012' | $(SED) $$($(PRINTF) '%s%b' 's/]]>/]]]]><!\[CDATA\[>/g;s/\xEF\xBF\xBE/�/g;s/\xEF\xBF\xBF/�/g;$$!s/\r$$//g;s/\r/\n/g;$$!s/\xC2\x85$$//g;s/\xC2\x85/\n/g;s/\xE2\x80\xA8/\n/g;' 's/[\0001-\0010]/�/g;s/[\0016-\0037]/�/g'); $(PRINTF) '%s' ']]></script>')"
+
+# ─ ¶ Phony Targets ───────────────────────────────────────────────────
+
+# Provide help.
+help:
+       $(silent)$(PRINTF) '%b' '$(subst $(newline),\n,$(makefileinfo))'
+
+# Compile all files, or error if any are recursive.
+all: $(call compiled,$(recursivefiles) $(compilablefiles) $(assetfiles)) ;
+
+# Destroy buildfiles.
+clean:
+       $(silent)$(RM) -rf $(BUILDDIR)/
+
+# Destroy buildfiles and the install directory.
+gone:
+       $(silent)$(RM) -rf $(BUILDDIR)/ $(call compiled,$(recursivefiles) $(compilablefiles))
+
+# Install the compiled files into `DESTDIR´.
+install: $(call installed,$(recursivefiles) $(compilablefiles) $(assetfiles)) ;
+
+# List all source files and includes and their computed types.
+list:
+       $(silent)$(PRINTF) '%b' $(call quote,$(foreach file,$(sort $(sourcefiles)) $(sort $(sourceincludes)),\0033[1m$(file)\0033[22m`$(call typeoffile,$(file))`[\0033[3m$(if $(filter $(file),$(xmlfiles)),xml,$(if $(filter $(file),$(plaintextfiles)),text,asset))$(if $(filter $(file),$(sourceincludes)), include,)\0033[23m]$(if $(call dependencies,$(file))$(call recursives,$(file)), $(strip $(foreach recursive,$(call recursives,$(file)),\0033[93;41m•`Recursive`Dependency:\0033[39;49m`$(recursive)) $(foreach dependency,$(call dependencies,$(file)),\0033[2m•`Dependency:\0033[22m`$(dependency)))) )) | $(TR) ' `' '\n '
+
+# Raise an error when attempting to build any files with recursive
+# dependencies.
+$(call compiled,$(recursivefiles)): $(BUILDDIR)/public/%:
+       @$(PRINTF) '%b\n' $(call quote,\0033[93;41mError:\0033[39;49m `$*´ has recursive dependencies:\n$(subst `, ,$(subst $(space),$(newline),$(foreach recursive,$(call recursives,$(SRCDIR)/$*),•`$(patsubst $(SRCDIR)/%,%,$(recursive)))))) && false
+
+# ─ ¶ Special Targets ─────────────────────────────────────────────────
+
+# Perform secondary expansion; this enables pattern rules to determine
+# their prerequisites based on the matched pattern.
+.SECONDEXPANSION: ;
+
+# Don’t use any implicit rules.
+.SUFFIXES: ;
+
+# Phony rules; always consider these out‐of‐date.
+.PHONY: all default clean gone info install list $(call compiled,$(recursivefiles));
+
+# Reload this make·file if any of the magic files or parsers have
+# changed.
+#
+# These are used to classify source files, so if they have changed then
+# the make·file must be reloaded.
+$(THISDIR)/GNUmakefile:: $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt $(THISDIR)/lib/parser2types.xslt
+       $(silent)$(TOUCH) $(THISDIR)/GNUmakefile
+       $(silent)$(TOUCH) $(call quote,$(BUILDDIR)/.update-types)
+       $(silent)$(RM) -f $(call quote,$(BUILDDIR)/dependencies)
+       @$(PRINTF) '%b\n' '\0033[1mMagic file or parsers have updated. Restarting…\0033[22m'
+
+ifneq ($(wildcard $(BUILDDIR)/.update-types)$(wildcard $(BUILDDIR)/dependencies),)
+# Reload this make·file if the dependency graph has changed.
+#
+# This graph is a dependency for some of the variables that this
+# make·file uses, so it’s important to ensure that they are actually
+# up‐to‐date prior to executing any later rules.
+#
+# This recipe only exists after types have been updated or when the
+# dependency graph already exists.
+$(THISDIR)/GNUmakefile:: $(BUILDDIR)/dependencies
+       $(silent)$(TOUCH) $(THISDIR)/GNUmakefile
+       $(silent)$(RM) -f $(BUILDDIR)/.update-types
+       @$(PRINTF) '%b\n' '\0033[1mDependency graph updated. Restarting…\0033[22m'
+endif
+
+# ─ ¶ Build Targets ───────────────────────────────────────────────────
+
+# Generate the compiled magic file from its sources.
+#
+# It must be updated if any of the files in the magic directory change.
+# It ⁜also⁜ should be updated if any of the files in the magic
+# directory are deleted, but this isn’t tracked presently.
+$(BUILDDIR)/magic.mgc: $(wildcard $(MAGICDIR)/*)
+       @$(ECHO) "Compiling new magic…"
+       $(silent)$(call ensuredirectory,$(dir $@))
+       $(silent)$(FILE) -C -m $(call quote,$(MAGICDIR))
+       $(silent)$(MV) $(call quote,$(MAGICDIR).mgc) $(call quote,$(BUILDDIR)/magic.mgc)
+
+# Generate the main parser.
+$(BUILDDIR)/parser.catalog: $(PARSERS)
+       @$(ECHO) "Generating catalog of parsers…"
+       $(silent)$(XMLCATALOG) --create --noout $(call quote,$@)
+       $(foreach parser,$(PARSERS),$(silent)$(XMLCATALOG) --add uri $(call quote,$(basename $(notdir $(parser)))) $(call quote,../$(parser)) --noout $(call quote,$@)$(newline))
+$(BUILDDIR)/parser.xslt: $(BUILDDIR)/parser.catalog $(THISDIR)/lib/catalog2parser.xslt
+       @$(ECHO) "Generating main parser…"
+       $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2parser.xslt) $(call quote,$<)
+
+# Note updates to parsers or magic.
+#
+# This file is actually generated as part of the make·file restart
+# process, so it doesn’t need any recipes.
+$(BUILDDIR)/.update-types: $(BUILDDIR)/magic.mgc $(BUILDDIR)/parser.xslt $(THISDIR)/lib/parser2types.xslt ;
+
+# Parse the files.
+#
+# Even plain X·M·L files are parsed, because they may contain X·H·T·M·L
+# `<script>´ elements which contain other kinds of data. Asset files
+# are turned into H·T·M·L embeds pointing to `data:´ U·R·I’s.
+$(call parsed,$(sourceincludes)): $(BUILDDIR)/includes/%: $(INCLUDEDIR)/% $(typeupdates)
+       @$(PRINTF) '%s\n' $(call quote,Processing `$<´…)
+       $(silent)$(call ensuredirectory,$(dir $@))
+       $(silent)$(if $(filter $<,$(assetfiles)),$(PRINTF) '%s\n' $(call quote,<object xmlns="http://www.w3.org/1999/xhtml" type="$(call typeoffile,$(INCLUDEDIR)/$*)" data="$(call datauri,$<)"/>) > $(call quote,$@),$(if $(filter $<,$(plaintextfiles)),$(call wrapplaintext,$<),$(CAT) $(call quote,$<)) | $(XSLTPROC) -o $(call quote,$@) $(call quote,$(BUILDDIR)/parser.xslt) -)
+$(call parsed,$(sourcefiles)): $(BUILDDIR)/sources/%: $(SRCDIR)/% $(typeupdates)
+       @$(PRINTF) '%s\n' $(call quote,Processing `$<´…)
+       $(silent)$(call ensuredirectory,$(dir $@))
+       $(silent)$(if $(filter $<,$(assetfiles)),$(PRINTF) '%s\n' $(call quote,<object xmlns="http://www.w3.org/1999/xhtml" type="$(call typeoffile,$(SRCDIR)/$*)" data="$(call datauri,$<)"/>) > $(call quote,$@),$(if $(filter $<,$(plaintextfiles)),$(call wrapplaintext,$<),$(CAT) $(call quote,$<)) | $(XSLTPROC) -o $(call quote,$@) $(call quote,$(BUILDDIR)/parser.xslt) -)
+
+# Generate a catalog of all transformed files, for use when processing
+# includes. This does not depend on actually transforming the files.
+$(BUILDDIR)/catalog: $(sourcefiles) $(sourceincludes) $(typeupdates)
+       @$(ECHO) "Generating catalog of parsed files…"
+       $(silent)$(XMLCATALOG) --create --noout $(call quote,$@)
+       $(foreach source,$(sourcefiles) $(sourceincludes),$(silent)$(XMLCATALOG) --add uri $(call quote,$(call localuri,$(source))) $(call quote,$(patsubst $(BUILDDIR)/%,%,$(call parsed,$(source)))#$(if $(filter $(source),$(assetfiles)),asset,xml)) --noout $(call quote,$@)$(newline))
+
+# Build a list of dependencies for each transformed file.
+#
+# This recipe also adjusts backwards the modification time of this
+# make·file by one second, to ensure that it is always more outdated
+# than the built dependencies (triggering a restart).
+$(BUILDDIR)/dependencies: $(BUILDDIR)/catalog $(call parsed,$(plaintextfiles) $(xmlfiles)) $(THISDIR)/lib/catalog2dependencies.xslt
+       @$(ECHO) "Identifying dependencies…"
+       $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2dependencies.xslt) $(call quote,$<)
+       $(silent)$(TOUCH) -A -000001 -am $(THISDIR)/GNUmakefile
+
+# Generate the main transform.
+$(BUILDDIR)/transform.catalog: $(TRANSFORMS)
+       @$(ECHO) "Generating catalog of transforms…"
+       $(silent)$(XMLCATALOG) --create --noout $(call quote,$@)
+       $(foreach transform,$(TRANSFORMS),$(silent)$(XMLCATALOG) --add uri $(call quote,$(basename $(notdir $(transform)))) $(call quote,../$(transform)) --noout $(call quote,$@)$(newline))
+$(BUILDDIR)/transform.xslt: $(BUILDDIR)/transform.catalog $(THISDIR)/lib/catalog2transform.xslt
+       @$(ECHO) "Generating main transform…"
+       $(silent)$(XSLTPROC) -o $(call quote,$@) $(call quote,$(THISDIR)/lib/catalog2transform.xslt) $(call quote,$<)
+
+# Generate the output files using the dependencies as necessary.
+$(call compiled,$(compilablefiles)): $(BUILDDIR)/public/%: $$(call parsed,$(SRCDIR)/%) $(BUILDDIR)/transform.xslt $$(call parsed,$$(call dependencies,$(SRCDIR)/%))
+       $(silent)$(call ensuredirectory,$(dir $@))
+       @$(PRINTF) '%s\n' $(call quote,Compiling `$*´…)
+       $(silent)$(XSLTPROC) -o $(call quote,$@) --stringparam catalog 'catalog' $(call quote,$(BUILDDIR)/transform.xslt) $(call quote,$<)
+$(call compiled,$(filter $(assetfiles),$(sourcefiles))): $(BUILDDIR)/public/%: $(SRCDIR)/%
+       @$(PRINTF) '%s\n' $(call quote,Compiling `$*´…)
+       $(silent)$(call ensuredirectory,$(dir $@))
+       $(silent)$(CP) $(call quote,$<) $(call quote,$@)
+
+# Install compiled files (or error in the case of recursive ones).
+$(call installed,$(filter $(assetfiles),$(sourcefiles)) $(recursivefiles) $(compilablefiles)): $(DESTDIR)/%: $(BUILDDIR)/public/%
+       @$(PRINTF) '%s\n' $(call quote,Installing `$*´…)
+       $(silent)$(call ensuredirectory,$(dir $@))
+       $(silent)$(CP) $(call quote,$<) $(call quote,$@)
diff --git a/README.markdown b/README.markdown
new file mode 100644 (file)
index 0000000..51aca8d
--- /dev/null
@@ -0,0 +1,323 @@
+# ⛩️📰 书社
+
+<b>An X·S·L·T‐based static site generator.</b>
+
+<dfn>⛩️📰 书社</dfn> aims to make it easy to generate websites with
+  X·S·L·T and G·N·U Make.
+It is consequently only a good choice for people who like X·S·L·T and
+  G·N·U Make and wish it were easier to make websites with them.
+
+It makes things easier by :⁠—
+
+- Automatically identifying source files and characterizing them by
+    type (X·M·L, text, or asset).
+
+- Parsing supported text types into X·M·L trees.
+
+- Enabling easy inclusion of source files within each other.
+
+It aims to do this with zero dependencies beyond the programs already
+  installed on your computer.
+
+## Nomenclature
+
+<i lang="cmn-Hans">书社</i> is a Chinese word meaning “publishing
+  house”.
+
+The first character, <i lang="cmn-Hans">书</i>, is the simplified form
+  of “document”.
+
+The second character, <i lang="cmn-Hans">社</i>, contemporarily means
+  “association”, but historically referred to the god of the soil and
+  related altars or festivities.
+In Japanese, it is an alternate spelling for <i lang="ja">やしろ</i>,
+  the word for “Shinto shrine”.
+
+The name <i lang="cmn-Hans">书社</i> was chosen to play on this pun, as
+  it is intended as a publishing program for webshrines.
+
+In Ascii environments, ⛩️📰 书社 should be written `Shushe`, following
+  the pinyin transliteration.
+
+## Basic Usage
+
+Place source files in `sources/` and run `make install` to compile
+  the result to `public/`.
+Compilation involves the following steps :⁠—
+
+1. ⛩️📰 书社 compiles all of the magic files in `magic/` into a single
+    file, `build/magic.mgc`.
+
+2. ⛩️📰 书社 processes all of the parsers in `parsers/` and determines
+    the list of supported plaintext types.
+
+3. ⛩️📰 书社 identifies all of the source files and includes and uses
+    `build/magic.mgc` to classify them by media type.
+
+4. ⛩️📰 书社 parses all plaintext and X·M·L source files and includes
+    and then builds a dependency tree between them.
+
+5. ⛩️📰 书社 uses the dependency tree to establish prerequisites for
+    each output file.
+
+6. ⛩️📰 书社 compiles each output file to `build/public`.
+
+7. ⛩️📰 书社 copies the output files to `public`.
+
+You can use `make list` to list each identified source file or include
+  alongside its computed type and dependencies.
+As this is a Make‐based program, steps will only be run if the
+  corresponding buildfile or output file is older than its
+  prerequisites.
+
+## Namespaces
+
+The ⛩️📰 书社 namespace is `urn:fdc:ladys.computer:20231231:Shu1She4`.
+
+This document uses a few namespace prefixes, with the following
+  meanings :⁠—
+
+|   Prefix | Expansion                                  |
+| -------: | :----------------------------------------- |
+|  `html:` | `http://www.w3.org/1999/xhtml`             |
+| `xlink:` | `http://www.w3.org/1999/xlink`             |
+|  `xslt:` | `http://www.w3.org/1999/XSL/Transform`     |
+|  `书社:` | `urn:fdc:ladys.computer:20231231:Shu1She4` |
+
+## Setup and Configuration
+
+⛩️📰 书社 depends on the following programs to run.
+In every case, you may supply your own implementation by overriding the
+  corresponding (allcaps) variable (e·g, set `MKDIR` to supply your own
+  `mkdir` implementation).
+
+- `cat`
+- `cp`
+- `echo`
+- `file`
+- `find`
+- `mkdir` (requires support for `-p`)
+- `mv`
+- `printf`
+- `sed`
+- `test`
+- `touch`
+- `tr` (requires support for `-d`)
+- `uuencode` (requires support for `-m` and `-r`)
+- `xmlcatalog` (provided by `libxml2`)
+- `xmllint` (provided by `libxml2`)
+- `xsltproc` (provided by `libxslt`)
+
+The following additional variables can be used to control the behaviour
+  of ⛩️📰 书社 :⁠—
+
+- **`SRCDIR`:**
+  The location of the source files (default: `sources`).
+
+- **`INCLUDEDIR`:**
+  The location of the source files (default: `sources/includes`).
+  This can be inside of `SRCDIR`, but needn’t be.
+
+- **`BUILDDIR`:**
+  The location of the (temporary) build directory (default: `build`).
+
+- **`DESTDIR`:**
+  The location of directory to output files to (default: `public`).
+
+- **`THISDIR`:**
+  The location of the ⛩️📰 书社 `GNUmakefile`.
+  This should be set automatically when calling Make and shouldn’t ever
+    need to be set manually.
+  This variable is used to find the ⛩️📰 书社 `lib/` folder, which is
+    expected to be in the same location.
+
+- **`MAGICDIR`:**
+  The location of the magic files to use (default: `$(THISDIR)/magic`).
+
+- **`FINDOPTS`:**
+  Options to pass to `find` when searching for source files (default:
+    `-LE`).
+
+- **`FINDRULES`:**
+  Rules to use with `find` when searching for source files (default:
+    `-flags -nohidden -and -not -name '.*'`).
+
+- **`PARSERS`:**
+  A white·space‐separated list of parsers to use (default:
+    `$(THISDIR)/parsers/*.xslt`).
+
+- **`TRANSFORMS`:**
+  A white·space‐separated list of transforms to use (default:
+    `$(THISDIR)/transforms/*.xslt`).
+
+- **`XMLTYPES`:**
+  A white·space‐separated list of media types to consider X·M·L
+    (default: `application/xml text/xml`).
+
+- **`VERBOSE`:**
+  If this variable has a value, every recipe instruction will be
+    printed when it runs (default: empty).
+  This is helpful for debugging, but typically too noisy for general
+    usage.
+
+## Source Files
+
+Source files may be placed in `SRCDIR` in any manner; the file
+  structure used there will match the output.
+The type of source files is *not* determined by file extension, but
+  rather by magic number; this means that files **must** begin with
+  something recognizable.
+Supported magic numbers include :⁠—
+
+- `<?xml` for `application/xml` files
+- `#!js` for `text/javascript` files
+- `@charset "` for `text/css` files
+- `#!tsv` for `text/tab-separated-values` files
+
+Text formats with associated X·S·L·T parsers are wrapped in a H·T·M·L
+  `<script>` element whose `@type` gives its media type, and then
+  passed to the parser to process.
+Source files whose media type does not have an associated X·S·L·T
+  parser are considered “assets” and will not be transformed.
+
+For compatibility with this program, source filenames should conform to
+  the following rules :⁠—
+
+- They should not start with a hyphen‐minus.
+  This is to prevent confusion between filenames and options on the
+    commandline.
+
+- They should not contain spaces, colons, percent signs, backticks,
+    question marks, hashes, or backslashes.
+
+In general, filenames should be such that they do not require
+  percent‐encoding in the path component of an i·r·i.
+
+## Parsers
+
+Parsers are used to convert plaintext files into X·M·L trees, as well
+  as convert plaintext formats which are already included inline in
+  existing source X·M·L documents.
+⛩️📰 书社 comes with some parsers; namely :⁠—
+
+- **`parsers/plain.xslt`:**
+  Wraps `text/plain` contents in a `<html:pre>` element.
+
+- **`parsers/tsv.xslt`:**
+  Converts `text/tab-separated-values` contents into an `<html:table>`
+    element.
+
+New ⛩️📰 书社 parsers should have a `<xslt:template>` element with no
+  `@name` or `@mode` and whose `@match` attribute…
+
+- Starts with an appropriately‐namespaced qualified name for a
+    `<html:script>` element.
+
+- Follows this with the string `[@type=`.
+
+- Follows this with a quoted string giving a media type supported by
+    the parser.
+  Media type parameters are *not* supported.
+
+- Follows this with the string `]`.
+
+For example, the trivial `text/plain` parser is defined as follows :⁠—
+
+```xml
+<?xml version="1.0"?>
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:html="http://www.w3.org/1999/xhtml"
+       version="1.0"
+>
+       <template match="html:script[@type='text/plain']">
+               <html:pre><value-of select="."/></html:pre>
+       </template>
+</transform>
+```
+
+⛩️📰 书社 will scan the provided parsers for this pattern to determine
+  the set of allowed plaintext file types.
+Multiple such `<xslt:template>` elements may be provided in a single
+  parser, for example if the parser supports multiple media types.
+
+It is **strongly recommended** that all templates in parsers other than
+  those described above be namespaced (by `@name` or `@mode`), to avoid
+  conflicts between templates in multiple parsers.
+
+## Embedding
+
+Documents can be embedded in other documents using a `<书社:link>`
+  element with `@xlink:show="embed"`.
+The `@xlink:href`s of these elements should have the format
+  `about:shushe?source=<path>`, where `<path>` provides the path to the
+  file within `SRCDIR`.
+Includes, which do not generate outputs of their own but may still be
+  freely embedded, instead use the format
+  `about:shushe?include=<path>`, where `<path>` provides the path
+  within `INCLUDEDIR`.
+
+Embeds are replaced with the parsed contents of a file, unless the file
+  is an asset, in which case an `<html:object>` element is produced
+  instead (with the contents of the asset file provided as a base64
+  `data:` u·r·i).
+
+Embedding takes place after parsing but before transformation, so
+  parsers are able to generate their own embeds.
+⛩️📰 书社 is able to detect the transitive embed dependencies of files
+  and update them accordingly; it will signal an error if the
+  dependencies are recursive.
+
+## Transforms
+
+Transforms are used to convert X·M·L files into their final output,
+  after all necessary parsing and embedding has taken place.
+⛩️📰 书社 comes with some transforms; namely :⁠—
+
+- **`transforms/asset.xslt`:**
+  Converts `<html:object type="text/css">` elements into corresponding
+    `<html:link rel="stylesheet">` elements and
+    `<html:object type="text/javascript">` elements into corresponding
+    `<html:script>` elements.
+  This transform enables embedding of `text/css` and `text/javascript`
+    files, which ordinarily are considered assets (as they lack
+    associated parsers).
+
+- **`transforms/metadata.xslt`:**
+  Provides basic `<html:head>` metadata.
+  This metadata is generated from `<html:meta>` descendants of the
+    first element with an `@itemscope` attribute (recommended to just
+    be the root element).
+  Such elements can provide metadata using the following `@itemprop`
+    attributes :⁠—
+
+  - **`urn:fdc:ladys.computer:20231231:Shu1She4:title`:**
+    Provides the title of the page.
+
+The following are recommendations on effective creation of
+  transforms :⁠—
+
+- Make template matchers as specific as possible.
+  It is likely an error if two transforms have templates which match
+    the same element (unless the templates have different priority).
+
+- Namespace templates (with `@name` or `@mode`) whenever possible.
+
+- Set `@exclude-result-prefixes` on the root `xslt:transform` element
+    to reduce the number of declared namespaces in the final result.
+
+## Output Wrapping
+
+⛩️📰 书社 will wrap the final output of the transforms in appropriate
+  `<html:html>` and `<html:body>` elements, so it is not necessary for
+  transforms to do this explicitly.
+The `<html:head>` of the output will contain the result tree generated
+  by matching the root node in the `书社:metadata` mode; the provided
+  `transforms/metadata.xslt` transform uses this mode to generate basic
+  metadata, but it is possible for other transforms to add their own.
+
+## License
+
+Source files are licensed under the terms of the <cite>Mozilla Public
+  License, version 2.0</cite>.
+For more information, see [LICENSE](./LICENSE).
diff --git a/lib/catalog2dependencies.xslt b/lib/catalog2dependencies.xslt
new file mode 100644 (file)
index 0000000..b1e54a1
--- /dev/null
@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+<!--
+⁌ ⛩️📰 书社 ∷ lib/catalog2dependencies.xslt
+
+© 2023 Lady [@ Lady’s Computer]
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v 2.0.
+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/>.
+-->
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:catalog="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+       xmlns:exsl="http://exslt.org/common"
+       xmlns:xlink="http://www.w3.org/1999/xlink"
+       xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
+       version="1.0"
+>
+       <variable name="uris" select="//catalog:uri"/>
+       <template name="书社:process-dependencies">
+               <param name="processed"/>
+               <param name="unprocessed"/>
+               <variable name="queue">
+                       <copy-of select="exsl:node-set($unprocessed)/书社:dependency-root"/>
+                       <for-each select="exsl:node-set($unprocessed)/书社:dependency">
+                               <if test="not((exsl:node-set($processed)/书社:*|preceding-sibling::书社:*|following-sibling::书社:recursive-dependency)[string()=string(current())])">
+                                       <copy-of select="."/>
+                               </if>
+                       </for-each>
+               </variable>
+               <variable name="old">
+                       <copy-of select="exsl:node-set($processed)/书社:dependency-root"/>
+                       <copy-of select="exsl:node-set($processed)/书社:recursive-dependency"/>
+                       <for-each select="exsl:node-set($unprocessed)/书社:recursive-dependency">
+                               <if test="not((exsl:node-set($processed)/书社:recursive-dependency|preceding-sibling::书社:recursive-dependency)[string()=string(current())])">
+                                       <copy-of select="."/>
+                               </if>
+                       </for-each>
+                       <for-each select="exsl:node-set($processed)/书社:dependency">
+                               <if test="not(exsl:node-set($unprocessed)/书社:recursive-dependency[string()=string(current())])">
+                                       <copy-of select="."/>
+                               </if>
+                       </for-each>
+                       <copy-of select="$queue"/>
+               </variable>
+               <variable name="new">
+                       <for-each select="exsl:node-set($queue)/书社:*">
+                               <for-each select="$uris[@name=string(current()) and substring-after(@uri, '#')!='asset']">
+                                       <variable name="parent" select="@name"/>
+                                       <for-each select="document(substring-before(@uri, '#'), .)//书社:link[@xlink:show='embed']">
+                                               <if test="exsl:node-set($old)/书社:dependency-root[string()=string(current()/@xlink:href)]">
+                                                       <书社:recursive-dependency>
+                                                               <value-of select="$parent"/>
+                                                       </书社:recursive-dependency>
+                                               </if>
+                                               <if test="$uris/@name[string()=string(current()/@xlink:href)]">
+                                                       <书社:dependency>
+                                                               <value-of select="@xlink:href"/>
+                                                       </书社:dependency>
+                                               </if>
+                                       </for-each>
+                               </for-each>
+                       </for-each>
+               </variable>
+               <choose>
+                       <when test="exsl:node-set($new)/书社:dependency">
+                               <call-template name="书社:process-dependencies">
+                                       <with-param name="processed">
+                                               <copy-of select="$old"/>
+                                       </with-param>
+                                       <with-param name="unprocessed">
+                                               <copy-of select="$new"/>
+                                       </with-param>
+                               </call-template>
+                       </when>
+                       <otherwise>
+                               <copy-of select="$old"/>
+                       </otherwise>
+               </choose>
+       </template>
+       <template match="catalog:uri" mode="书社:dependencies">
+               <if test="substring-after(@uri, '#')!='asset'">
+                       <call-template name="书社:process-dependencies">
+                               <with-param name="unprocessed">
+                                       <书社:dependency-root>
+                                               <value-of select="@name"/>
+                                       </书社:dependency-root>
+                               </with-param>
+                       </call-template>
+               </if>
+       </template>
+       <template match="/">
+               <for-each select="$uris[not(substring-after(@uri, '#')='asset')]">
+                       <variable name="parent" select="@name"/>
+                       <variable name="dependencies">
+                               <apply-templates select="." mode="书社:dependencies"/>
+                       </variable>
+                       <value-of select="@name"/>
+                       <text>&#xA;</text>
+                       <for-each select="exsl:node-set($dependencies)/书社:recursive-dependency">
+                               <text>&#x9;</text>
+                               <text>!</text>
+                               <value-of select="."/>
+                               <text>&#xA;</text>
+                       </for-each>
+                       <for-each select="exsl:node-set($dependencies)/书社:dependency">
+                               <text>&#x9;</text>
+                               <value-of select="."/>
+                               <text>&#xA;</text>
+                       </for-each>
+               </for-each>
+       </template>
+       <output method="text" encoding="UTF-8"/>
+</transform>
diff --git a/lib/catalog2parser.xslt b/lib/catalog2parser.xslt
new file mode 100644 (file)
index 0000000..d044503
--- /dev/null
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<!--
+⁌ ⛩️📰 书社 ∷ lib/catalog2parser.xslt
+
+© 2023 Lady [@ Lady’s Computer]
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v 2.0.
+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/>.
+-->
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:catalog="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+       xmlns:xsla="http://www.w3.org/1999/XSL/TransformAlias"
+       exclude-result-prefixes="catalog"
+       version="1.0"
+>
+       <namespace-alias stylesheet-prefix="xsla" result-prefix="#default"/>
+       <template match="/">
+               <xsla:transform version="1.0">
+                       <for-each select="//catalog:uri">
+                               <xsla:include href="{@uri}"/>
+                       </for-each>
+                       <xsla:template match="@*|node()" priority="-1">
+                               <xsla:copy>
+                                       <xsla:apply-templates select="@*|node()"/>
+                               </xsla:copy>
+                       </xsla:template>
+               </xsla:transform>
+       </template>
+</transform>
diff --git a/lib/catalog2transform.xslt b/lib/catalog2transform.xslt
new file mode 100644 (file)
index 0000000..4a09235
--- /dev/null
@@ -0,0 +1,84 @@
+<?xml version="1.0"?>
+<!--
+⁌ ⛩️📰 书社 ∷ lib/catalog2transform.xslt
+
+© 2023 Lady [@ Lady’s Computer]
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v 2.0.
+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/>.
+-->
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:html="http://www.w3.org/1999/xhtml"
+       xmlns:catalog="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+       xmlns:exsl="http://exslt.org/common"
+       xmlns:xlink="http://www.w3.org/1999/xlink"
+       xmlns:xslt="http://www.w3.org/1999/XSL/TransformAlias"
+       xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
+       version="1.0"
+>
+       <namespace-alias stylesheet-prefix="xslt" result-prefix="#default"/>
+       <template match="/">
+               <xslt:transform exclude-result-prefixes="catalog exsl" version="1.0">
+                       <xslt:param name="catalog" select="'catalog'"/>
+                       <for-each select="//catalog:uri">
+                               <xslt:include href="{@uri}"/>
+                       </for-each>
+                       <xslt:template match="/" priority="1">
+                               <xslt:variable name="expansion">
+                                       <xslt:apply-templates select="." mode="书社:expand"/>
+                               </xslt:variable>
+                               <xslt:variable name="result">
+                                       <xslt:apply-templates select="exsl:node-set($expansion)/*"/>
+                               </xslt:variable>
+                               <xslt:variable name="metadata">
+                                       <xslt:copy-of select="exsl:node-set($result)/html/head/*"/>
+                                       <xslt:apply-templates select="exsl:node-set($result)" mode="书社:metadata"/>
+                               </xslt:variable>
+                               <html:html>
+                                       <xslt:copy-of select="exsl:node-set($result)/html:html/@*"/>
+                                       <html:head>
+                                               <xslt:copy-of select="exsl:node-set($result)/html:html/html:head/@*"/>
+                                               <html:title>
+                                                       <xslt:for-each select="exsl:node-set($metadata)/html:title">
+                                                               <xslt:value-of select="."/>
+                                                       </xslt:for-each>
+                                               </html:title>
+                                               <xslt:copy-of select="exsl:node-set($metadata)/*[not(self::html:title)]"/>
+                                               <xslt:if test="not(exsl:node-set($metadata)/html:meta[@name='generator'])">
+                                                       <html:meta name="generator" content="⛩️📰 书社"/>
+                                               </xslt:if>
+                                       </html:head>
+                                       <html:body>
+                                               <xslt:copy-of select="exsl:node-set($result)/*[not(self::html:html or self::html:body)]|exsl:node-set($result)/html:html/*[not(self::html:head or self::html:body)]|exsl:node-set($result)/html:html/html:body/*|exsl:node-set($result)/html:body/*"/>
+                                       </html:body>
+                               </html:html>
+                       </xslt:template>
+                       <xslt:template match="@*|node()" priority="-1">
+                               <xslt:copy>
+                                       <xslt:apply-templates select="@*|node()"/>
+                               </xslt:copy>
+                       </xslt:template>
+                       <xslt:template match="书社:link[@xlink:show='embed']" mode="书社:expand">
+                               <xslt:variable name="uri" select="substring-before(document($catalog)//catalog:uri[@name=current()/@xlink:href]/@uri[1], '#')"/>
+                               <xslt:choose>
+                                       <xslt:when test="$uri">
+                                               <xslt:apply-templates select="document($uri)" mode="书社:expand"/>
+                                       </xslt:when>
+                                       <xslt:otherwise>
+                                               <xslt:copy>
+                                                       <xslt:apply-templates select="@*|node()" mode="书社:expand"/>
+                                               </xslt:copy>
+                                       </xslt:otherwise>
+                               </xslt:choose>
+                       </xslt:template>
+                       <xslt:template match="@*|text()|*[not(self::书社:link) or not(@xlink:show='embed')]" mode="书社:expand">
+                               <xslt:copy>
+                                       <xslt:apply-templates select="@*|node()" mode="书社:expand"/>
+                               </xslt:copy>
+                       </xslt:template>
+                       <xslt:template match="text()" mode="书社:metadata"/>
+                       <xslt:output method="xml" encoding="UTF-8" cdata-section-elements="html:script html:style html:textarea"/>
+               </xslt:transform>
+       </template>
+</transform>
diff --git a/lib/parser2types.xslt b/lib/parser2types.xslt
new file mode 100644 (file)
index 0000000..ee107e4
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0"?>
+<!--
+⁌ ⛩️📰 书社 ∷ lib/parser2types.xslt
+
+© 2023 Lady [@ Lady’s Computer]
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v 2.0.
+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/>.
+-->
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:catalog="urn:oasis:names:tc:entity:xmlns:xml:catalog"
+       xmlns:xslt="http://www.w3.org/1999/XSL/Transform"
+       xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
+       version="1.0"
+>
+       <template match="/">
+               <for-each select="//xslt:include">
+                       <for-each select="document(@href, .)//xslt:template[not(@name) and not(@mode)]">
+                               <variable name="match" select="@match"/>
+                               <for-each select="namespace::*[local-name() and string()='http://www.w3.org/1999/xhtml']">
+                                       <variable name="matchstart">
+                                               <value-of select="local-name()"/>
+                                               <text>:</text>
+                                               <text>script[@type=</text>
+                                       </variable>
+                                       <if test="starts-with($match, $matchstart) and substring($match, string-length($match))=']' and contains($match, '/')">
+                                               <variable name="inner" select="substring-before(substring-after($match, $matchstart), ']')"/>
+                                               <if test="starts-with($inner, '&#x22;') and substring($inner, string-length($inner))='&#x22;' or starts-with($inner, &#x22;'&#x22;) and substring($inner, string-length($inner))=&#x22;'&#x22;">
+                                                       <variable name="type" select="substring($inner, 2, string-length($inner)-2)"/>
+                                                       <if test="not(translate($type, '0123456789abcdefghijklmnopqrstuvwxyz!#$&#x26;-^_.+/', ''))">
+                                                               <value-of select="$type"/>
+                                                               <text>&#xA;</text>
+                                                       </if>
+                                               </if>
+                                       </if>
+                               </for-each>
+                       </for-each>
+               </for-each>
+       </template>
+       <output method="text" encoding="UTF-8"/>
+</transform>
diff --git a/magic/css b/magic/css
new file mode 100644 (file)
index 0000000..24bbf5a
--- /dev/null
+++ b/magic/css
@@ -0,0 +1,14 @@
+0     string  /*css        CSS text
+!:mime text/css
+!:strength + 255
+
+0     string  @charset\ "  CSS text
+!:mime text/css
+!:strength + 255
+
+0     byte    0xEF
+>1    byte    0xBB
+>>2   byte    0xBF
+>>>3  string  @charset\ "  CSS text
+!:mime text/css
+!:strength + 255
diff --git a/magic/js b/magic/js
new file mode 100644 (file)
index 0000000..971c95f
--- /dev/null
+++ b/magic/js
@@ -0,0 +1,7 @@
+0  string  #!js          Javascript text
+!:mime text/javascript
+!:strength + 255
+
+0  string  #!javascript  Javascript text
+!:mime text/javascript
+!:strength + 255
diff --git a/magic/tsv b/magic/tsv
new file mode 100644 (file)
index 0000000..de72924
--- /dev/null
+++ b/magic/tsv
@@ -0,0 +1,10 @@
+0  string  #!tsv     TSV text
+!:mime text/tab-separated-values
+!:strength + 255
+
+0     byte    0xEF
+>1    byte    0xBB
+>>2   byte    0xBF
+>>>3  string  #!tsv  TSV text
+!:mime text/tab-separated-values
+!:strength + 255
diff --git a/magic/xml b/magic/xml
new file mode 100644 (file)
index 0000000..d441701
--- /dev/null
+++ b/magic/xml
@@ -0,0 +1,10 @@
+0     string  \<?xml  XML data
+!:mime application/xml
+!:strength + 255
+
+0     byte    0xEF
+>1    byte    0xBB
+>>2   byte    0xBF
+>>>3  string  \<?xml  XML data
+!:mime application/xml
+!:strength + 255
diff --git a/parsers/plain.xslt b/parsers/plain.xslt
new file mode 100644 (file)
index 0000000..0f61bfe
--- /dev/null
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+<!--
+⁌ ⛩️📰 书社 ∷ parsers/plain.xslt
+
+© 2023 Lady [@ Lady’s Computer]
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v 2.0.
+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/>.
+-->
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:html="http://www.w3.org/1999/xhtml"
+       version="1.0"
+>
+       <template match="html:script[@type='text/plain']">
+               <html:pre><value-of select="."/></html:pre>
+       </template>
+</transform>
diff --git a/parsers/tsv.xslt b/parsers/tsv.xslt
new file mode 100644 (file)
index 0000000..4de3b98
--- /dev/null
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!--
+⁌ ⛩️📰 书社 ∷ parsers/tsv.xslt
+
+© 2023 Lady [@ Lady’s Computer]
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v 2.0.
+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/>.
+-->
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:exsl="http://exslt.org/common"
+       xmlns:exslstr="http://exslt.org/strings"
+       xmlns:html="http://www.w3.org/1999/xhtml"
+       exclude-result-prefixes="exsl exslstr"
+       version="1.0"
+>
+       <template match="html:script[@type='text/tab-separated-values']">
+               <variable name="rows" select="exslstr:tokenize(., '&#xA;')[normalize-space(.) and not(starts-with(., '#'))]"/>
+               <variable name="head" select="exsl:node-set($rows)[1]"/>
+               <variable name="body" select="exsl:node-set($rows)[not(position()=1)]"/>
+               <html:table>
+                       <html:thead>
+                               <html:tr>
+                                       <for-each select="exslstr:tokenize($head, '&#x9;')">
+                                               <html:th scope="row">
+                                                       <value-of select="."/>
+                                               </html:th>
+                                       </for-each>
+                               </html:tr>
+                       </html:thead>
+                       <html:tbody>
+                               <for-each select="exsl:node-set($body)">
+                                       <html:tr>
+                                               <for-each select="exslstr:tokenize(., '&#x9;')">
+                                                       <html:td>
+                                                               <value-of select="."/>
+                                                       </html:td>
+                                               </for-each>
+                                       </html:tr>
+                               </for-each>
+                       </html:tbody>
+               </html:table>
+       </template>
+</transform>
diff --git a/transforms/asset.xslt b/transforms/asset.xslt
new file mode 100644 (file)
index 0000000..a85c891
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<!--
+⁌ ⛩️📰 书社 ∷ transforms/asset.xslt
+
+© 2023 Lady [@ Lady’s Computer]
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v 2.0.
+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/>.
+-->
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:html="http://www.w3.org/1999/xhtml"
+       xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
+       exclude-result-prefixes="书社"
+       version="1.0"
+>
+       <template match="html:object[@type='text/css']">
+               <comment>
+                       <text>[书社:CSS] </text>
+                       <value-of select="@data"/>
+               </comment>
+       </template>
+       <template match="comment()[starts-with(., '[书社:CSS] ')]" mode="书社:metadata">
+               <html:link rel="stylesheet" type="text/css" href="{substring-after(., '[书社:CSS] ')}"/>
+       </template>
+       <template match="html:object[@type='text/javascript']">
+               <html:script type="{@type}" src="{@data}"/>
+       </template>
+</transform>
diff --git a/transforms/metadata.xslt b/transforms/metadata.xslt
new file mode 100644 (file)
index 0000000..d4be493
--- /dev/null
@@ -0,0 +1,36 @@
+<?xml version="1.0"?>
+<!--
+⁌ ⛩️📰 书社 ∷ transforms/metadata.xslt
+
+© 2023 Lady [@ Lady’s Computer]
+
+This Source Code Form is subject to the terms of the Mozilla Public License, v 2.0.
+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/>.
+-->
+<transform
+       xmlns="http://www.w3.org/1999/XSL/Transform"
+       xmlns:html="http://www.w3.org/1999/xhtml"
+       xmlns:书社="urn:fdc:ladys.computer:20231231:Shu1She4"
+       exclude-result-prefixes="书社"
+       version="1.0"
+>
+       <template match="html:*[@itemscope][1]//html:meta[not(@name) and starts-with(@itemprop, 'urn:fdc:ladys.computer:20231231:Shu1She4:')]">
+               <comment>
+                       <text>[书社:META] </text>
+                       <value-of select="substring-after(@itemprop, 'urn:fdc:ladys.computer:20231231:Shu1She4:')"/>
+                       <text>: </text>
+                       <value-of select="@content"/>
+               </comment>
+       </template>
+       <template match="comment()[starts-with(., '[书社:META] ')]" mode="书社:metadata">
+               <variable name="property" select="substring-before(substring-after(., '[书社:META] '), ': ')"/>
+               <variable name="value" select="substring-after(., concat('[书社:META] ', $property, ': '))"/>
+               <choose>
+                       <when test="$property='title'">
+                               <html:title>
+                                       <value-of select="$value"/>
+                               </html:title>
+                       </when>
+               </choose>
+       </template>
+</transform>
This page took 0.148857 seconds and 4 git commands to generate.