From 909a952db02b6427ab71efaafcb1577a25c82539 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martynas=20Jusevi=C4=8Dius?= Date: Thu, 27 Aug 2026 15:33:13 +0200 Subject: [PATCH] Prefix the remaining template and mode names with their module namespaces, and drop the extractor's unnamed-mode match="/" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The extractor entry becomes rdfax:extract-rdfa (named only, per the composition contract already stated in CLAUDE.md — an unnamed-mode match="/" intercepts every default-mode dispatch against a document node in a host stylesheet; LinkedDataHub hit exactly that in its client.xsl integration). The canonicalization passes become cm:canonical, cm:normalize and cm:demote (the cm- local prefix is redundant once the namespace carries it; the cm:normalize function is the functional form of the mode), the entry cm:canonical-xhtml, and the standalone entry rdfae:main. Headless invocations move to Q{uri}local EQNames in run-tests.sh and initialTemplate in the HTML runners; docs updated to match. All 52 tests pass. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_014Tgf5DjbYiX3SPnCFSgCzd --- CLAUDE.md | 4 +- docs/ROADMAP.md | 6 +-- docs/ldh/MIGRATION.md | 8 ++-- index.html | 2 +- src/RDFa2RDFXML-v3.xsl | 3 +- src/annotate.xsl | 2 +- src/canonical-xhtml.xsl | 88 ++++++++++++++++++------------------- src/edit.xsl | 6 +-- src/index.xsl | 2 +- src/navigate.xsl | 2 +- src/select.xsl | 4 +- tests/fixture-blocks.html | 2 +- tests/fixture-dragnest.html | 2 +- tests/fixture-nesting.html | 2 +- tests/fixture.html | 2 +- tests/run-tests.sh | 4 +- 16 files changed, 70 insertions(+), 69 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4351540..05937cc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - **Undo is region-keyed**: one global stack, each stash entry carries `data-root` (region index) and restores only its region; caret data is region-relative. Stash ids: `rdfa-editor-undo-storage/-stack/-redo-stack`. - Blocks never move between regions (drop-target resolution is scoped to the dragged block's region). ToC and view-source follow the active region (`tocRoot` remembered at render); lint and find work across all regions. - **Editor state lives on one window container** `window.rdfaEditor` — a single object created with `Object()` in the `main` template (mirrors LinkedDataHub's `window.LinkedDataHub`), holding all mutable state under bare names (`range`, `activeBlock`, `editingSpan`, …). Every read/write goes through the `rdfae:editor-state()` accessor (`index.xsl`): reads `ixsl:get(rdfae:editor-state(), 'range')`, writes `object="rdfae:editor-state()"`. Browser globals (`scrollX`, `Date`, `location.href`, …) are read straight off `ixsl:window()` and are not editor state. Editor UI containers carry the `rdfa-editor-ui` class and all generic CSS selectors (`.btn-*`, `.modal-*`, `.crumb`, …) are scoped under it — LDH/Bootstrap pages stay unaffected. -- The extractor entry is a **named template only** (`-it:extract-rdfa`; no unnamed-mode `match="/"`) so it composes with host stylesheets' root templates. Integration with LinkedDataHub's client.xsl is compile-proven (see docs/ldh/MIGRATION.md §10). +- The extractor entry is a **named template only** (`rdfax:extract-rdfa`, invoked headless as `-it:"Q{https://w3id.org/atomgraph/rdfa-editor/rdfa#}extract-rdfa"`; no unnamed-mode `match="/"`) so it composes with host stylesheets' root templates. Integration with LinkedDataHub's client.xsl is compile-proven (see docs/ldh/MIGRATION.md §10). - Links in content: plain click places the caret (links render with a text I-beam, not a pointer, so the affordance matches — they are editable text); Ctrl/Cmd+Click opens the href. The annotation overlay paints the stored selection as `.rdfa-editor-selection-hint` boxes (no content mutation), cleared on hide. ## Overview @@ -61,7 +61,7 @@ Run `make sef` after any XSLT change; run the tests after any extractor change. - **No deviation from W3C specs.** The extractor follows the RDFa 1.1 processing rules exactly. Markup that relies on non-conformant readings (e.g. `about="#part" property="schema:hasPart"` expected to yield an edge) is a markup bug — the conformant containment idiom is `property="…hasPart" resource="#part" typeof="…"`. - **Content model: XHTML 1.0 Strict + HTML5 figure**, transcribed in `content-model.xsl` — nesting the DTD allows must round-trip intact; nesting it disallows is normalized at boundaries (canonical serialization, paste, load-init) and reported by lint. This changed the **storage contract for blockquote**: it is block-only, so stored bare-text quotes are rewritten to `blockquote > p` once at load/save (LDH content audit advised). - All `innerHTML` writes of serialized XDM use `serialize(…, map{ 'method': 'html' })` — XML's self-closing `

` reads as an *open* tag to the HTML fragment parser and swallows following siblings. -- The `$base-uri` global param is declared in `RDFa2RDFXML-v3.xsl` **only** — a second declaration in an including module is a static error (XTSE0630). The browser passes the page URI as a template param to `extract-rdfa` instead. +- The `$base-uri` global param is declared in `RDFa2RDFXML-v3.xsl` **only** — a second declaration in an including module is a static error (XTSE0630). The browser passes the page URI as a template param to `rdfax:extract-rdfa` instead. - The SEF is compiled with `-relocate:on`: relative `doc()`/`document()`/`@document` hrefs resolve against the SEF's **load location** (`dist/`), which is why `generate-sef.sh` copies `vocabs/` to `dist/vocabs/`. - Form state (`checked`/`value`/`disabled`) is read **and** written via `ixsl:get`/`ixsl:set-property` — the attributes never reflect user input. `ixsl:set-attribute` is used only for RDFa attributes that must serialize into content. - Vocabularies are plain ontology RDF/XML files in `vocabs/` — no custom manifest format. Adding a vocabulary = drop the file in `vocabs/` and add its href to `$vocab-hrefs`. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index a380ce8..ffa1655 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -11,7 +11,7 @@ The prototype (through commit `b8466cf`) is functionally rich: structured-block - Element allowlist question: canonicalization currently passes unknown elements through (shallow-copy) — decide pass-through vs allowlist for embedded content (iframe/object/embed/form should not survive). ### A2. Editing completeness -- **HTML paste** with sanitization: currently plain-text only. We already own the cleanup machinery — parse `clipboardData` `text/html` via `parse-xml`/fragment parsing and run it through `mode="canonical"` + the new sanitization rules; insert clean fragment. (Word/Google-Docs paste is the single biggest prod-usability item.) +- **HTML paste** with sanitization: currently plain-text only. We already own the cleanup machinery — parse `clipboardData` `text/html` via `parse-xml`/fragment parsing and run it through `mode="cm:canonical"` + the new sanitization rules; insert clean fragment. (Word/Google-Docs paste is the single biggest prod-usability item.) - Nested lists (indent/outdent via Tab/Shift+Tab in `li`), `ul`↔`ol` conversion of an existing list. - h4–h6 in the block-type select (cheap); `code`/`sub`/`sup` inline toggles (cheap — the `format-inline` machinery is generic). - Image upload (LDH has upload flows) — URL-only first; wire LDH upload later. @@ -47,7 +47,7 @@ Mapped integration surface (all in `LinkedDataHub/src/main/webapp/static/com/ato 6. **Vocabularies** — `/ns?uri=&accept=application/rdf+xml` endpoint (constructor.xsl:203-209 pattern) via `ixsl:promise`; typeahead control (LDH typeahead.xsl precedent) replaces the selects. 7. **i18n** — strings into `translations.rdf` (`key('resources', id, document(...))` + `ac:label`). 8. **Feature flag** — decision DEFERRED (user); document options: `lapp:Application` property / XSL param via web.xml / hard cutover. -9. **Build** — modules pulled into the LDH webapp tree at build time and `xsl:import`ed from `client.xsl`; existing pom `xslt3-he … -relocate:on` step compiles everything (pom.xml:39, 383-391). Conflict audit checklist: our unnamed-mode `match="/"` (extract-rdfa) vs LDH root templates, `body` keydown fallback, host-level event templates, `xsl:output`, `id('content')` assumptions. +9. **Build** — modules pulled into the LDH webapp tree at build time and `xsl:import`ed from `client.xsl`; existing pom `xslt3-he … -relocate:on` step compiles everything (pom.xml:39, 383-391). Conflict audit checklist: the extractor entry (named-only `rdfax:extract-rdfa` since the unnamed-mode `match="/"` was dropped) vs LDH root templates, `body` keydown fallback, host-level event templates, `xsl:output`, `id('content')` assumptions. 10. **v6 note** — view mode renders XMLLiteral via identity transform (`imports/default.xsl:1489-1503`); the eventual v6 in-place model (edit the view markup, PUT canonical doc) is the successor to this form-control integration. ## C. Milestones @@ -74,7 +74,7 @@ Mapped integration surface (all in `LinkedDataHub/src/main/webapp/static/com/ato ### D2. HTML paste (`src/edit.xsl` onpaste) - `text/html` non-empty → HTML path; else existing plain-text path. - Parse: detached `$carrier := createElement('div')` + `innerHTML :=` clipboard HTML (scripts inert when detached; XPath over detached nodes already proven). -- Sanitize: `$clean :=` apply-templates `$carrier/node()` in `mode="canonical"` (now incl. D1 rules). +- Sanitize: `$clean :=` apply-templates `$carrier/node()` in `mode="cm:canonical"` (now incl. D1 rules). - Wrap stray top-level inline runs: `for-each-group group-adjacent="boolean(self::p|self::h1|…block…)"` → non-block groups wrapped in `

`. - Re-materialize XDM → live DOM: `serialize()` the fragment → `$stage := createElement('div')` + `innerHTML` (safe post-sanitization). - Insert: diff --git a/docs/ldh/MIGRATION.md b/docs/ldh/MIGRATION.md index 88c5062..d484c76 100644 --- a/docs/ldh/MIGRATION.md +++ b/docs/ldh/MIGRATION.md @@ -56,7 +56,7 @@ kept the textarea in sync continuously; the replacement syncs once, declarativel ```xml - + @@ -65,7 +65,7 @@ kept the textarea in sync continuously; the replacement syncs once, declarativel ``` Key points: serialize the container's **children only** (no wrapper element — -parse-rdf-post adds the div); `mode="canonical"` guarantees the stored literal is +parse-rdf-post adds the div); `mode="cm:canonical"` guarantees the stored literal is sanitized and free of editing ephemera (chrome, contenteditable, classes, on* handlers, unsafe URLs). SaxonJS 3 applies `ixsl:set-property` immediately (verified in-browser), so the value is readable by `ldh:parse-rdf-post` within @@ -132,8 +132,8 @@ The full integration was proven by compiling LDH's `client.xsl` (from the built single SEF via `xslt3-he -nogo -ns:##html5 -relocate:on` — no errors, no conflicts. Audit results: -- Extractor entry is a **named template only** (`extract-rdfa`; the unnamed-mode - `match="/"` was removed; headless tests invoke with `-it:extract-rdfa`). All other +- Extractor entry is a **named template only** (`rdfax:extract-rdfa`; the unnamed-mode + `match="/"` was removed; headless tests invoke with `-it:"Q{https://w3id.org/atomgraph/rdfa-editor/rdfa#}extract-rdfa"`). All other editor matching lives in named modes (`rdfax:extract`, `canonical`) or `ixsl:*` event modes. - Event templates: LDH has no `contenteditable` usage and no `body` keydown template diff --git a/index.html b/index.html index 6dac44f..c45d8bb 100644 --- a/index.html +++ b/index.html @@ -21,7 +21,7 @@ return SaxonJS.transform({ documentPool: pool, stylesheetLocation: "dist/index.xsl.sef.json", - initialTemplate: "main", + initialTemplate: "Q{https://w3id.org/atomgraph/rdfa-editor#}main", logLevel: 10 }, "async"); }) diff --git a/src/RDFa2RDFXML-v3.xsl b/src/RDFa2RDFXML-v3.xsl index 7ff325d..cac9ec2 100644 --- a/src/RDFa2RDFXML-v3.xsl +++ b/src/RDFa2RDFXML-v3.xsl @@ -49,7 +49,8 @@ 'dct': 'http://purl.org/dc/terms/' }"/> - + + rdf:Description per subject (via rdfae:group-triples) for readability --> - + diff --git a/src/canonical-xhtml.xsl b/src/canonical-xhtml.xsl index 0aaac74..46453ed 100644 --- a/src/canonical-xhtml.xsl +++ b/src/canonical-xhtml.xsl @@ -15,11 +15,11 @@ version="3.0"> (index.xsl does; the test driver is tests/canonical-driver.xsl). Two passes in a fixed order: - 1. mode="canonical" strips editing ephemera per the LDH v6 convention + 1. mode="cm:canonical" strips editing ephemera per the LDH v6 convention (everything carrying @data-role is removable by construction), sanitizes, and normalizes browser mess. Nesting analysis must never see chrome, so this runs first. - 2. mode="cm-normalize" coerces the result to the XHTML Strict content model + 2. mode="cm:normalize" coerces the result to the XHTML Strict content model (blockquote is block-only, p is inline-only, ul holds only li, ...), always RDFa-preserving. The load-init path (edit.xsl) runs this pass ALONE on host content. @@ -32,13 +32,13 @@ version="3.0"> - - + + - + - + @@ -50,15 +50,15 @@ version="3.0"> - - + + - + + - + + | form | input | button | select | textarea | link | meta | base" mode="cm:canonical" priority="3"/> - + - + + mode="cm:canonical"/> + | @*[starts-with(name(), 'aria-')] | @*[starts-with(name(), 'data-')]" mode="cm:canonical"/> - + - + - + + or @datatype or @lang or @xml:lang)]" mode="cm:canonical"> + [empty(*[cm:block(local-name(.))])]" mode="cm:canonical">

@@ -179,7 +179,7 @@ version="3.0"> wrapper (p may not contain blocks) - unwrap to its children; stray inline residue is re-coerced by pass 2 in the parent's context --> + [exists(*[cm:block(local-name(.))])]" mode="cm:canonical"> @@ -189,7 +189,7 @@ version="3.0"> has become real content and stays a p --> + mode="cm:canonical" priority="1"> @@ -197,29 +197,29 @@ version="3.0"> clipboard/host wrappers, keep RDFa-bearing ones (dropping them would lose triples; lint reports them as unknown-element) --> + [not(@property or @about or @typeof or @resource)]" mode="cm:canonical"> + [not(@property or @about or @typeof or @resource or @content)]" mode="cm:canonical" priority="1"/> - + + mode="cm:canonical"/> - + - + @@ -227,13 +227,13 @@ version="3.0"> Matches EVERY inline-only element and decides on the PROCESSED children, so blocks surfaced by inner splits are handled in the same bottom-up pass (one invocation reaches the fixed point). An RDFa-bearing parent stays whole - - its block children demote to inline via mode="cm-demote" (recursive, all + its block children demote to inline via mode="cm:demote" (recursive, all attributes kept), so the extracted literal and triples are unchanged. A plain parent splits around its block children; inline runs keep a shell copying ALL attributes (safe: this branch is non-RDFa by construction, so nothing duplicates a triple - an split by a block keeps its target on both halves); whitespace-only residue between blocks drops --> - + @@ -248,7 +248,7 @@ version="3.0"> - + @@ -277,9 +277,9 @@ version="3.0"> kept), recursively - a demoted list becomes nested spans, never a bare li inside a span. Text, inline and unknown elements pass through with their children demoted likewise --> - + - + @@ -288,7 +288,7 @@ version="3.0"> - + @@ -299,7 +299,7 @@ version="3.0"> - + @@ -310,7 +310,7 @@ version="3.0"> - + @@ -321,7 +321,7 @@ version="3.0"> - + @@ -333,7 +333,7 @@ version="3.0"> - + @@ -370,7 +370,7 @@ version="3.0"> - + @@ -387,24 +387,24 @@ version="3.0"> - + - + - + - + diff --git a/src/edit.xsl b/src/edit.xsl index c845890..ae9879a 100644 --- a/src/edit.xsl +++ b/src/edit.xsl @@ -1601,7 +1601,7 @@ version="3.0"> - + diff --git a/src/navigate.xsl b/src/navigate.xsl index d2de4d2..90e6ddf 100644 --- a/src/navigate.xsl +++ b/src/navigate.xsl @@ -319,7 +319,7 @@ version="3.0"> - + diff --git a/src/select.xsl b/src/select.xsl index 291f79f..0b5e102 100644 --- a/src/select.xsl +++ b/src/select.xsl @@ -445,7 +445,7 @@ version="3.0"> @@ -485,7 +485,7 @@ version="3.0"> - + diff --git a/tests/fixture-blocks.html b/tests/fixture-blocks.html index 99f9d49..f3959ec 100644 --- a/tests/fixture-blocks.html +++ b/tests/fixture-blocks.html @@ -23,7 +23,7 @@ return SaxonJS.transform({ documentPool: pool, stylesheetLocation: "../dist/ldh-editor.xsl.sef.json", - initialTemplate: "main", + initialTemplate: "Q{https://w3id.org/atomgraph/rdfa-editor#}main", logLevel: 10 }, "async"); }) diff --git a/tests/fixture-dragnest.html b/tests/fixture-dragnest.html index 47fe5f7..339d2d9 100644 --- a/tests/fixture-dragnest.html +++ b/tests/fixture-dragnest.html @@ -18,7 +18,7 @@ return SaxonJS.transform({ documentPool: pool, stylesheetLocation: "../dist/index.xsl.sef.json", - initialTemplate: "main", + initialTemplate: "Q{https://w3id.org/atomgraph/rdfa-editor#}main", logLevel: 10 }, "async"); }) diff --git a/tests/fixture-nesting.html b/tests/fixture-nesting.html index 08e6de3..b9e1af8 100644 --- a/tests/fixture-nesting.html +++ b/tests/fixture-nesting.html @@ -18,7 +18,7 @@ return SaxonJS.transform({ documentPool: pool, stylesheetLocation: "../dist/ldh-editor.xsl.sef.json", - initialTemplate: "main", + initialTemplate: "Q{https://w3id.org/atomgraph/rdfa-editor#}main", logLevel: 10 }, "async"); }) diff --git a/tests/fixture.html b/tests/fixture.html index 1fcf2d4..bd12578 100644 --- a/tests/fixture.html +++ b/tests/fixture.html @@ -24,7 +24,7 @@ return SaxonJS.transform({ documentPool: pool, stylesheetLocation: "../dist/index.xsl.sef.json", - initialTemplate: "main", + initialTemplate: "Q{https://w3id.org/atomgraph/rdfa-editor#}main", logLevel: 10 }, "async"); }) diff --git a/tests/run-tests.sh b/tests/run-tests.sh index f6ee632..960ed7d 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -12,7 +12,7 @@ fail=0 for fixture in tests/fixtures/*.xhtml; do name=$(basename "$fixture" .xhtml) - if ! npx xslt3-he -xsl:src/RDFa2RDFXML-v3.xsl -s:"$fixture" -it:extract-rdfa -o:"$tmp/$name.rdf" base-uri="$BASE" 2>"$tmp/$name.err"; then + if ! npx xslt3-he -xsl:src/RDFa2RDFXML-v3.xsl -s:"$fixture" -it:"Q{https://w3id.org/atomgraph/rdfa-editor/rdfa#}extract-rdfa" -o:"$tmp/$name.rdf" base-uri="$BASE" 2>"$tmp/$name.err"; then echo "FAIL $name (extraction error)" cat "$tmp/$name.err" fail=1 @@ -48,7 +48,7 @@ done for fixture in tests/fixtures/canonical/*.xhtml; do name=$(basename "$fixture" .xhtml) - if ! npx xslt3-he -xsl:tests/canonical-driver.xsl -s:"$fixture" -it:canonical-xhtml -o:"$tmp/c-$name.xhtml" 2>"$tmp/c-$name.err"; then + if ! npx xslt3-he -xsl:tests/canonical-driver.xsl -s:"$fixture" -it:"Q{https://w3id.org/atomgraph/rdfa-editor/content-model#}canonical-xhtml" -o:"$tmp/c-$name.xhtml" 2>"$tmp/c-$name.err"; then echo "FAIL canonical/$name (transform error)" cat "$tmp/c-$name.err" fail=1