@uniweb/semantic-parser 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -222,8 +222,8 @@ parser builds — a `<` someone typed shows as `<`. Marks become tags:
222
222
  ```
223
223
 
224
224
  A link is drawn only when its URL may be followed — relative, or `http:`,
225
- `https:`, `mailto:`, `tel:`, `page:` or `topic:`. Any other, such as
226
- `javascript:`, leaves its text unlinked. A `button` mark is a link whose `role`
225
+ `https:`, `mailto:`, `tel:` or `page:`. Any other, such as `javascript:`,
226
+ leaves its text unlinked. A `button` mark is a link whose `role`
227
227
  is `button`.
228
228
 
229
229
  ### Span Marks
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/semantic-parser",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "description": "Semantic parser for ProseMirror/TipTap content structures",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/sanitize.js CHANGED
@@ -172,10 +172,9 @@ function cleanStyle(value) {
172
172
 
173
173
  /**
174
174
  * The schemes a link may use: the web, mail and phone, and the framework's own
175
- * references to a page (`page:`, and the older `topic:`), which kit resolves to
176
- * a route at render.
175
+ * reference to a page, `page:`, which kit resolves to a route at render.
177
176
  */
178
- const LINK_SCHEMES = new Set(["http", "https", "mailto", "tel", "page", "topic"]);
177
+ const LINK_SCHEMES = new Set(["http", "https", "mailto", "tel", "page"]);
179
178
 
180
179
  /** A link a document may follow: relative, or one of LINK_SCHEMES. */
181
180
  function isSafeUrl(value) {
@@ -347,9 +346,16 @@ function rebuild(input, policy) {
347
346
 
348
347
  /**
349
348
  * What a rebuild would leave out of `input`, item by item — the same walk as
350
- * `rebuild`, reporting instead of writing. Empty when the markup is within the
351
- * policy, which is when a rebuild changes nothing but spelling (quotes,
352
- * escapes, a style's spacing).
349
+ * `rebuild`, reporting instead of writing — and, when it leaves nothing out but
350
+ * writes the markup differently, one `spelling` item at the first difference.
351
+ * Empty when a rebuild writes `input` back exactly as given.
352
+ *
353
+ * ⭐ Exactly, not "up to spelling" (since 2026-10-18): every text run and
354
+ * attribute value a rebuild writes is escaped, so markup in that form reads the
355
+ * same to any reader. Markup spelled otherwise — a `<` left in text, a CDATA
356
+ * section, a reference the rebuild would write as its character — can read one
357
+ * way here and another way to a reader that parses it as written, which a store
358
+ * holding it for every reader cannot allow.
353
359
  */
354
360
  function inspect(input, policy) {
355
361
  const outside = [];
@@ -394,6 +400,14 @@ function inspect(input, policy) {
394
400
  }
395
401
  }
396
402
  }
403
+ if (!outside.length) {
404
+ const rebuilt = rebuild(input, policy);
405
+ if (rebuilt !== input) {
406
+ let at = 0;
407
+ while (input[at] === rebuilt[at]) at++;
408
+ outside.push({ reason: "spelling", value: input.slice(at, at + 40) });
409
+ }
410
+ }
397
411
  return outside;
398
412
  }
399
413
 
@@ -412,14 +426,16 @@ export function sanitizeMathML(mathml) {
412
426
  /**
413
427
  * What in a MathML string falls outside the allowlist `sanitizeMathML` rebuilds
414
428
  * from: each element and attribute a rebuild drops, each style declaration it
415
- * drops, and any comment, doctype or processing instruction. Empty when the
416
- * markup is within it.
429
+ * drops, and any comment, doctype or processing instruction — or, when it drops
430
+ * nothing, a `spelling` item if the rebuild writes the markup differently. Empty
431
+ * when `sanitizeMathML` writes the markup back exactly as given, so a writer
432
+ * that stores what `sanitizeMathML` returns stores markup within it.
417
433
  *
418
434
  * This is how content is checked rather than cleaned — a writer refused with
419
435
  * what it sent can fix it, where a cleaned value changes without a word.
420
436
  *
421
437
  * @param {string} mathml
422
- * @returns {Array<{reason: 'element'|'attribute'|'style'|'markup', element?: string, attribute?: string, value?: string}>}
438
+ * @returns {Array<{reason: 'element'|'attribute'|'style'|'markup'|'spelling', element?: string, attribute?: string, value?: string}>}
423
439
  */
424
440
  export function inspectMathML(mathml) {
425
441
  if (typeof mathml !== "string" || !mathml) return [];
@@ -443,11 +459,11 @@ export function sanitizeSVG(svg) {
443
459
 
444
460
  /**
445
461
  * What in an SVG string falls outside the allowlist `sanitizeSVG` rebuilds
446
- * from, reported as `inspectMathML` reports MathML. Empty when the markup is
447
- * within it.
462
+ * from, reported as `inspectMathML` reports MathML. Empty when `sanitizeSVG`
463
+ * writes the markup back exactly as given.
448
464
  *
449
465
  * @param {string} svg
450
- * @returns {Array<{reason: 'element'|'attribute'|'style'|'markup', element?: string, attribute?: string, value?: string}>}
466
+ * @returns {Array<{reason: 'element'|'attribute'|'style'|'markup'|'spelling', element?: string, attribute?: string, value?: string}>}
451
467
  */
452
468
  export function inspectSVG(svg) {
453
469
  if (typeof svg !== "string" || !svg) return [];
@@ -456,7 +472,7 @@ export function inspectSVG(svg) {
456
472
 
457
473
  /**
458
474
  * Whether a link may be followed: a relative URL, or `http:`, `https:`,
459
- * `mailto:`, `tel:`, or the framework's `page:` / `topic:` references. Control
475
+ * `mailto:`, `tel:`, or the framework's `page:` reference. Control
460
476
  * characters and spaces are ignored when reading the scheme, as a browser
461
477
  * ignores them, so `java\tscript:` is read as `javascript:`.
462
478
  *
@@ -480,8 +496,8 @@ export function isImageSource(url) {
480
496
 
481
497
  /**
482
498
  * Whether a `data:` URL holds an image a page may show: a PNG, JPEG, GIF, WebP
483
- * or AVIF in base64, or SVG — percent-encoded or base64 — whose markup is within
484
- * the allowlist `sanitizeSVG` rebuilds from. ⭐ Admitted [Diego, 2026-10-16]: a
499
+ * or AVIF in base64, or SVG — percent-encoded or base64 — whose markup is
500
+ * exactly what `sanitizeSVG` writes of it (`inspectSVG`). ⭐ Admitted [Diego, 2026-10-16]: a
485
501
  * picture an `<img>` loads runs no script, and a built-in starter image is one.
486
502
  * The URL is read as a browser reads it: tab and newline removed, leading and
487
503
  * trailing spaces and controls trimmed.
@@ -598,6 +614,14 @@ export function isCssColor(value) {
598
614
  return typeof value === "string" && CSS_COLOR.test(value) && !NOT_A_COLOR.test(value);
599
615
  }
600
616
 
617
+ /** How a rebuild writes markup — what `inspect` compares the input with. */
618
+ const WRITTEN =
619
+ "exactly as a rebuild writes it: a text run with `&`, `<` and `>` written `&amp;`, `&lt;` and `&gt;`, and every other character as itself; " +
620
+ "a start tag `<name>` or `<name/>` and an end tag `</name>`, the name as `elements` spells it, with no space before `>` or `/>`; " +
621
+ "each attribute once, as one space then `name=\"value\"` — the name as `attributes` spells it, or a common one in lower case — " +
622
+ "with `&`, `<`, `>` and `\"` in the value written `&amp;`, `&lt;`, `&gt;` and `&quot;`; " +
623
+ "a `style` value as its declarations, each `property:value;` with the property in lower case and the value trimmed";
624
+
601
625
  /**
602
626
  * The same rules, as data — for a reader that implements them in another
603
627
  * language, such as a backend checking content when it is written. Every list
@@ -632,7 +656,8 @@ export function allowlists() {
632
656
  unsafeValue: UNSAFE_VALUE.source,
633
657
  urlInAValue: "only url(#id), a reference within the document",
634
658
  style: "declaration by declaration: a property of letters and -, a value with none of \\ < > { } /* and no unsafe value",
635
- markup: "no comment, doctype or processing instruction; CDATA is text",
659
+ markup: "no comment, doctype, processing instruction or CDATA section",
660
+ written: WRITTEN,
636
661
  },
637
662
  imageData: {
638
663
  raster: RASTER_DATA.source,
@@ -649,7 +674,8 @@ export function allowlists() {
649
674
  unsafeValue: UNSAFE_VALUE.source,
650
675
  urlInAValue: "only url(#id), a reference within the document",
651
676
  style: "declaration by declaration: a property of letters and -, a value with none of \\ < > { } /* and no unsafe value",
652
- markup: "no comment, doctype or processing instruction; CDATA is text",
677
+ markup: "no comment, doctype, processing instruction or CDATA section",
678
+ written: `${WRITTEN}; an \`href\` or \`xlink:href\` value trimmed`,
653
679
  },
654
680
  },
655
681
  };