{"id":17723569,"url":"https://github.com/doriantaylor/xslt-transclusion","last_synced_at":"2026-01-08T15:41:04.249Z","repository":{"id":142094189,"uuid":"156284173","full_name":"doriantaylor/xslt-transclusion","owner":"doriantaylor","description":"A Web-standards-compliant, deterministic, declarative, not to mention *JavaScript-free*, transclusion protocol, using XSLT 1.0.","archived":false,"fork":false,"pushed_at":"2024-01-03T01:11:20.000Z","size":78,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-01-03T03:05:29.249Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"XSLT","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/doriantaylor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-05T21:16:31.000Z","updated_at":"2023-08-26T21:29:39.000Z","dependencies_parsed_at":"2024-01-03T02:50:19.335Z","dependency_job_id":"fddd9dc1-51ca-42d1-aa24-1934786b36f8","html_url":"https://github.com/doriantaylor/xslt-transclusion","commit_stats":null,"previous_names":[],"tags_count":0,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Fxslt-transclusion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Fxslt-transclusion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Fxslt-transclusion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/doriantaylor%2Fxslt-transclusion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/doriantaylor","download_url":"https://codeload.github.com/doriantaylor/xslt-transclusion/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246475865,"owners_count":20783741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-25T15:43:23.437Z","updated_at":"2026-01-08T15:41:04.198Z","avatar_url":"https://github.com/doriantaylor.png","language":"XSLT","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Standard-Compliant Transclusion Protocol\n\nThis is an attempt to systematize a method of\n[transclusion](https://en.wikipedia.org/wiki/Transclusion) using only\ndeclarative Web standards, namely\n[(X)HTML5](https://www.w3.org/TR/html5/) and [XSLT\n1.0](https://www.w3.org/TR/1999/REC-xslt-19991116).\n\nWeb browsers have implemented the XSLT 1.0 specification for nearly\ntwo decades. This functionality continues to be supported, and is\nperfectly adequate, if not _ideal_, for performing straightforward\nmarkup manipulation tasks.\n\n## Rationale\n\nTransclusion is still a woefully underrepresented feature in\nWorld-Wide Web standards. Indeed there are many proprietary solutions\non both the server and the client side, but no _standards_ that enable\nthe seamless welding of resources together, thus enabling their\nre-use.\n\nWhile the now-defunct [XHTML2](https://www.w3.org/TR/xhtml2/) standard\nfeatured transclusion everywhere, HTML5 is considerably more\napprehensive. Aside from a provisional `\u003ciframe seamless\u003e` which was\neventually removed, the specification is silent on the subject.\n\n## Targets\n\n* Design patterns in XSLT 1.0 for performing reliable transclusions\n  within the scope of a given website,\n* XSLT 1.0 library encapsulating those patterns which can be encapsulated,\n* Design patterns in (X)HTML5 conducive to straightforward,\n  uncomplicated transclusion and content reuse.\n\n## Desiderata\n\nFor a transclusion reference, we want an element which is legal\nanywhere under `\u003cbody\u003e`, and already exhibits hypermedia semantics.\nHere are the candidates:\n\n### `\u003ciframe\u003e`\n\nThe most sensible initial choice, however `\u003ciframe\u003e` already _has_ a\nmeaning: make a new browser window inside a rectangle and put the\ndocument in it. Make it a sandbox. Do not integrate the embedded\ndocument into its caller; do not apply style rules. The proposed\n`seamless` attribute, which was intended to do this, has vanished.\n`\u003ciframe\u003e` is for banner ads.\n\n### `\u003cobject\u003e`\n\nAlso theoretically sensible, but sets off alarm bells for security\nplugins. `\u003cobject\u003e` is treated even more suspect than `\u003ciframe\u003e`\nis. Moreover, the hypermedia reference attribute is `data`, which is\ninvisible to [RDFa](https://www.w3.org/TR/rdfa-core/).\n\n### `\u003cembed\u003e`\n\nThe `\u003cembed\u003e` tag was deprecated in HTML 4.\n\n### `\u003cscript\u003e`\n\nThe current climate of HTML is very clement to `\u003cscript\u003e`. It can go\npretty much anywhere and contain pretty much anything. If we give it\ncertain attributes, we can make a pretty unambiguous statement:\n\n```xml\n\u003cscript type=\"application/xhtml+xml\" src=\"transcluded.xhtml\"/\u003e\n```\n\nThis declaration is effectively meaningless to an unadorned browser\ncontext. If we want, we can include the `defer` attribute to further\nsignal the inertness of the declaration, though in practice it does\nnot seem to make much of a difference. The only side effect is that\nthe URI in the `src` attribute seems to get unconditionally\ndereferenced, but this is fine considering we're planning on using it\nanyway.\n\n### General Pattern\n\nIndeed, one could activate the transclusion process for any of these\nelements, and others like `img`, as it is the `type` attribute that\nsignals that the referent in `src` is amenable to transclusion.\n\nThere is, for example, benefit in transcluding SVG images which are\nconnected via the `\u003cimg\u003e` tag. When referenced this way, SVG content\nis flattened and stripped of both its interactive content, as well as\ncallouts to external CSS resources. Invoking `\u003cimg\ntype=\"image/svg+xml\" src=\"...\"/\u003e` would plop the image markup directly\ninto the host document and circuvment that behaviour.\n\n## Transcluded Documents\n\nThe transcluded documents themselves are ordinary (X)HTML5 documents,\nor potentially other XML (SVG, MathML, Atom) vocabularies. The default\nbehaviour (e.g. SVG, MathML) will be to drop the document directly\ninto the spot from which it was referred, replacing the referring\nelement.\n\nIn the case of HTML, however, we invariably have to do surgery to the\ntranscluded document in order to obtain a conforming result. Namely,\nin all residual cases, we need to select the children of `\u003cbody\u003e`. In\nso doing, we have to consider the context into which these elements\nare being transcluded. The content models of certain elements prohibit\ncertain descendants. In general we can leave this problem to the\nimplementor, but in many cases we will still need to derive whether\nthe containing element is _block_ or _inline_, and likewise the\ntranscluded content. At that point we can determine whether we should\nwrap the transcluded content in a `\u003cdiv\u003e`, or in a `\u003cspan\u003e`, which\nwould be sensible defaults.\n\n### Tighter Integration\n\nIn the case that the transcluding element is the sole descendant of\nanother element, then wrapping the transcluded content in a `\u003cdiv\u003e` or\n`\u003cspan\u003e` will be redundant. Rather, the transcluded markup can be\nplaced directly under the transcluding element's parent.\n\n```xml\n\u003c!-- in referring document --\u003e\n\u003carticle\u003e\n  \u003cscript type=\"application/xhtml+xml\" src=\"/content\"/\u003e\n\u003c/article\u003e\n\n\u003c!-- in transcluded document at /content --\u003e\n\u003cbody\u003e\n  \u003cp\u003elol hi i got transcluded\u003c/p\u003e\n  \u003cp\u003eisn't life grand?\u003c/p\u003e\n\u003c/body\u003e\n```\n\nThis would replace the `\u003cscript\u003e` with everything under the `\u003cbody\u003e`\nof the transcluded document, directly under `\u003carticle\u003e` in the\ntranscluding document:\n\n```xml\n\u003carticle\u003e\n  \u003cp\u003elol hi i got transcluded\u003c/p\u003e\n  \u003cp\u003eisn't life grand?\u003c/p\u003e\n\u003c/article\u003e\n```\n\nTo summarize:\n\n* _Either_ the `\u003cbody\u003e` (or `\u003cmain\u003e`) of the transcluded document is\n  replaced by a suitable element in the referring document,\n  * (The replacement element _could_ be the parent of the special\n    `\u003cscript\u003e` element _if_ the latter had no siblings other than\n    whitespace)\n* _Or_ the transclusion content _is_ itself a single element.\n\n### Single-Element Documents\n\nWhen the `\u003cbody\u003e` (or when present, `\u003cmain\u003e`) of the transcluded HTML\ndocument only possesses a single child element (and no non-whitespace\ntext nodes as children), and, assuming the parent of the transclusion\nreference admits it, that single child element can directly replace\nthe transcluding element with no additional negotiation. Indeed, this\ncondition should supersede the behaviour described in the previous\nsection.\n\n### Other Vagaries of HTML 5\n\nThe introduction of the various sectioning elements in HTML5, in\nparticular `\u003cmain\u003e`, `\u003carticle\u003e`, and `\u003csection\u003e`, has complicated the\ntransclusion task.\n\nFor instance, it would be inappropriate to transclude the part of a\ndocument above `\u003cmain\u003e`, especially if the referring document had a\n`\u003cmain\u003e` of its own (`\u003cmain\u003e` cannot appear under another `\u003cmain\u003e`,\nand there can only be one `\u003cmain\u003e` that is not `hidden`). Therefore,\nwhen present, transclusion should begin beneath the first visible\n`\u003cmain\u003e` element in the transcluded document. This will also make it\neasier for transcluded resources to do double duty as standalone\ndocuments.\n\nThe presence of `\u003carticle\u003e`, irrespective of the presence of `\u003cmain\u003e`,\nis intended to denote a discrete body of text. It is not clear at this\ntime what the best policy is for transcluding around `\u003carticle\u003e` tags.\n\nFinally, the `\u003csection\u003e` element provides a more elegant method than\nthe general-purpose `\u003cdiv\u003e` for constructing document hierarchies,\nwhich can be given special consideration in the following section.\n\n### Heading levels\n\nA common pattern is to put an `\u003ch?\u003e` (where `?` is the number `1`\nthrough `6`) as the first child of a `\u003csection\u003e` element, containing\nthe title of the section. (There was briefly a recursive `\u003ch\u003e` tag in\nthe spec, but it was removed). When transcluding `\u003csection\u003e` content,\nit would be useful to automatically commute the `\u003ctitle\u003e` of the\ntranscluded document to an `\u003ch?\u003e` tag, notwithstanding there already\nbeing one present as the first child of the element to be transcluded.\n\nWhether we generate a heading or use an existing one, we will need to\nrecalculate the appropriate heading level for it, as well as all\n_descendant_ `\u003ch?\u003e` tags. This heading level must be propagated\nthrough subsequent templates until it stops at `6`, the smallest\nheading size.\n\n### Recursion\n\nA cycle in the transclusion path is bound to produce an infinite\nrecursion. Cycles can be detected through the use of a state\nmechanism. The parameter `$resource-path` is a space-separated list of\nURIs handed down through the templates. The efficacy of this mechanism\ndepends on the `\u003cbase href=\"...\"/\u003e` element to exactly match the\nRequest-URI of the transcluded document, or at the very least, a URI\nwhere the transcluded document can be accessed. It is up to the\nimplementor to make sure this condition remains satisfied.\n\n\u003e (Note that XSLT 1.0 has no mechanism for gleaning the Request-URI of\n\u003e the document it is currently processing. If the `\u003cbase\u003e` URI of the\n\u003e transcluded document is different from the URI in the transcluding\n\u003e element, we can simply append it to the `$resource-path`. I was\n\u003e originally planning on using the path length of `$resource-path` to\n\u003e compute the heading level, but it seems now like it would be more\n\u003e prudent to separate these concerns.)\n\nTemplate authors will have to manually propagate the `$resource-path`\nparameter through their work. Moreover, resource authors will have to\ntake into account the potential for acyclical, yet unsustainably long\ntransclusion paths. Parametric resources (i.e., those which can vary\nby URI query parameters) may also fall into this category.\n\n### Links, Fragments, and IDs\n\nLinks to transcluded documents should be rewritten into fragments of\nthe topmost document. This will entail coming up with both a way to\nprefetch the transcluded documents, and a method of generating\nidentifiers which match the\n[NCName](https://www.w3.org/TR/xml-names/#NT-NCName) grammar _and_ are\nguaranteed to be unique at the level of the site. (Browser\nimplementations have historically not permitted XSLT 1.0\nrequests—including subrequests—to cross origins; this may be different\nnow with [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS).)\n\n\u003e See [`uuid-ncname`](https://www.rubydoc.info/gems/uuid-ncname) for a\n\u003e candidate. For now we will assume that all `id` attributes are\n\u003e unique across the entire site.\n\nTransclusion references to URI fragments should select the element\nbearing the `id` that matches the fragment (the content after the\n`#`). This mode of operation should supersede all other behaviour\ndescribed above, unless there is no such `id`, in which case the\ntemplate should behave as normal (but perhaps signal a warning).\n\n## Procedure\n\nThe following parameters must be present in every template:\n\n* `$base`, which is the normalized content of `\u003cbase href=\"...\"/\u003e`.\n* `$resource-path`, which is a space-separated list of URIs that gets\n  added to with every transclusion, beginning with `$base`.\n* `$rewrite`, another space-separated list of URIs similar to\n  `$resource-path`, though collected all at once. Represents the set\n  of (transcluded) URIs to be rewritten into fragment identifiers, and\n  should therefore _not_ contain `$base`.\n* `$main`, a flag indicating the current node in the result tree is\n  beneath a `\u003cmain\u003e` element.\n* `$heading`, a non-negative integer, denoting the adjustment to the\n  heading level.\n  \nNow for the main procedure:\n\n* Set `$base` to `normalize-space((ancestor-or-self::html:html[html:head/html:base[@href]][1]/html:head/html:base[@href])[1]/@href)`.\n* Set `$resource-path` to the same value as `$base`.\n* Set `$rewrite` to the list of all URIs to be transcluded.\n* set `$main` to `boolean(ancestor-or-self::html:main)`.\n* Set the initial heading (`$heading`) level to **0**.\n* Traverse the document as normal, ensuring the aforementioned\n  parameters are conveyed through each template:\n  * If the processor encounters a `\u003csection\u003e`, increment `$heading`.\n  * If the processor encounters an `\u003ch1\u003e` through `\u003ch6\u003e` element,\n    extract the integer from the element name and increment it by\n    `$heading` (clipping at **6**), then use the resulting number to\n    generate a new heading element.\n  * If the processor encounters an `\u003ca\u003e` or `\u003carea\u003e`, rewrite `href`.\n  * If the processor encounters a `\u003cscript\u003e` (or other designated\n    element) with an `src` attribute and a `type` attribute containing\n    the string `xml`, perform the transclusion process.\n\n### Transclusion Process\n\nIn addition to the parameters already described, transclusion will also need:\n\n* `$uri` which is the content of e.g. the `src` attribute of the\n  appropriate `\u003cscript\u003e` element, turned into an absolute URI.\n* `$caller` which is the element in the referring document which\n  contains the `src` attribute we followed.\n\n\u003e No-ops may optionally issue warnings. It would be useful to create a\n\u003e mechanism such that users of the library can override the no-op\n\u003e behaviour.\n\n* Resolve the assumed-to-be relative URI in the `src` attribute.\n* Separate the fragment if it exists.\n* If the document (i.e. sans-fragment) URI is contained in\n  `$resource-path`, treat this element as a no-op.\n* Add the document URI to `$resource-path`.\n* Dereference the document URI using `document()`.\n* If the dereference fails to produce an element tree, this is a no-op\n* Set `$base` to the content of `\u003cbase href=\"...\"/\u003e`.\n* If the `$base` of the newly-dereferenced document is different from\n  the document URI, check it against `$resource-path`, bailing out\n  once again with a no-op if there is a match.\n  * Otherwise, add the new `$base` to `$resource-path`.\n* If `$uri` has a fragment, locate the element with the matching `id`.\n  * If an element is found, continue as if this is a single-element document.\n  * If no such element is found, continue as if `$uri` had no fragment.\n* If the document has a `\u003cmain\u003e` element, select it as our new root.\n  * Otherwise, select `\u003cbody\u003e`.\n* We generate an enclosing element if and only if:\n  * there are multiple child nodes under the new root **AND**\n  * `$caller` has sibling nodes other than whitespace/comments/PIs **OR**\n* Determine what the new enclosing element will be (or whether there\n  will be one):\n  * If the new root has multiple \n* If `$caller` has a parent element\n* If `$caller`'s parent element is a `\u003csection\u003e`:\n  * If there is no existing header element immediately under the root,\n  generate an `\u003ch?\u003e` (1 + `$heading`) element and populate it with the\n  contents of `\u003ctitle\u003e`.\n\n\n**TODO**\n\n### Rewriting `href`\n\n**TODO**\n\n* The first URI in `$resource-path` is always the topmost document.\n\n## Contributing\n\nBug reports and pull requests are welcome at\n[the GitHub repository](https://github.com/doriantaylor/xslt-transclusion).\n\n## Copyright \u0026 License\n\n©2018-2021 [Dorian Taylor](https://doriantaylor.com/)\n\nThis software is provided under\nthe [Apache License, 2.0](https://www.apache.org/licenses/LICENSE-2.0).\n\nThe file [xsltsl.xsl](xsltsl.xsl) contains some (modified)\ncode from [XSLTSL](http://xsltsl.sourceforge.net/). Since it is a\nderivative work, this code is licensed under the\n[LGPL](https://www.gnu.org/copyleft/lesser.html), in compliance with\nthe original.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoriantaylor%2Fxslt-transclusion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdoriantaylor%2Fxslt-transclusion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdoriantaylor%2Fxslt-transclusion/lists"}